RenderingNG中关键数据结构和它们的角色
人的一切痛苦,本质上都是对自己无能的愤怒 – 王小波
前言
大家好,我是柒八九
。今天这篇文章是Chromium
最新渲染架构 RenderingNG
的译文系列文章的第二篇 – 在RenderingNG
渲染过程中关键数据结构和它们所担当的角色。
针对RenderingNG的介绍可以参考之前的文章。
时间不早了,干点正事哇。
简明扼要
- {帧树|Frame Tree}: 由本地和远程节点组成
- 每个渲染进程都有属于自己的对网页内容进行描述的
frame
树 - 一个渲染在不同进程的
frame
被称为远程帧 - {渲染管线|rendering pipeline}是以{本地帧树片段| local frame tree fragment}的粒度来操作的
- 像{设备比例因子| device scale factor}和{视口大小| viewport size}这样的视觉属性会影响到渲染输出,并且必须在本地帧树片段之间同步
- 不可变的片段树是渲染管道的布局阶段的输出
它表示页面上所有元素的位置和大小
每个{片段| fragment}代表一个DOM元素的一部分 - 内联片段信息列表中的每个_条目_都是一个存有(对象,后代数量)等特定信息的{元组| Tuple }
- 属性树是解释视觉和滚动效果如何应用于DOM元素的数据结构
- 每个Web文档都有四个独立的属性树:{变换| Transform }、{剪切| clip }、{视觉效果| effect }和{滚动| Scroll }
- 显示list中的显示项包含低级别的绘图命令,可以用
Skia
进行光栅化 - 显示项大致对应于CSS绘制顺序规范的原子步骤
- 绘画块的有序列表,即显示项目组和属性树状态,作为渲染管道{图层化|Layerize}步骤的输入数据
- 合成器帧是
RenderingNG
表示如何将栅格化的内容拼接在一起,并使用GPU
有效地绘制它的数据格式 - 视口被划分为{瓦片|Tile}>
- Quad描述纹理的输入信息,并指出如何对其进行转换和应用视觉效果
GPU纹理瓦片是一种特殊的Quad
,它只是一类纹理瓦片的别称 - 每个GPU纹理瓦片都有一个quad
文章概要
- {帧树|Frame Tree}
- {不可变的片段树|The immutable fragment tree}
- {属性树|Property trees}
- {显示列表和绘画块|Display lists and paint chunke}
- {合成器帧|Compositor frame}:{表面| surface}、{渲染表面| render surface}、{GPU 纹理瓦片| GPU texture tile}
前置知识简讲
在渲染流程中出现了大致五种比较重要的数据结构。
- {帧树|Frame Tree}: 由本地和远程节点组成,表示对应的文档信息应该被哪个渲染进程中的
Blink
渲染器所消费 - {不可变的片段树|Immutable Fragment Tree}:代表布局阶段的信息产生
- {属性树|Property Tree}:代表了针对文档进行{转换|transform}、{剪切|clip}、{视觉效果|effect}和{滚动|scroll}等操作后的数据格式,并为后续的渲染流程所使用。
- {显示列表和绘画块|Display lists and paint chunks}: 将被传入到合成线程中,并被光栅化和分层算法所_消费_
- {合成器帧|Compositor frame}:将渲染接口和GPU纹理瓦片封装到一起,并使用GPU进行绘制
我们通过一个例子,来解释刚才所说的数据结构。大致的文档结构如下:
// 主 frame 为foo.com
<html><div style="overflow: hidden; width: 100px; height: 100px;">// 子 frame (foo.com/etc)<iframe style="filter: blur(3px);transform: rotateZ(1deg);width: 100px; height: 300px"id="one" src="foo.com/etc"></iframe></div>// 子 frame (bar.com)<iframe style="top:200px;transform: scale(1.1);translateX(200px)"id="two" src="bar.com"></iframe>
</html>
1. {帧树|Frame Tree}
Chrome 有时候会选择一个与父框架不同的渲染进程来处理{跨域框架|cross-origin frame}。
在上面的提供文档结构中,一共出现了3个框架结构。
在{站点隔离|Site Isolation}机制的作用下,Chromium
将会启用两个渲染进程来渲染该页面结构。
每个渲染进程都有属于自己的对网页内容进行描述的
frame
树
一个渲染在不同进程的
frame
被称为{远程帧|Remote Frame }
远程帧在被引用的渲染进程像占位符一样,仅仅保存了用于标识该frame
最基础信息,例如:尺寸信息等。也就是说,远程帧中不包含对应帧在渲染过程中的需要任何有用信息。
与之相反,{本地帧| Local Frame }包含了对应frame
的所有数据(DOM树和样式数据)转化为可以渲染和显示的东西所需的所有信息。(这里有点绕口)
{渲染管线|rendering pipeline}是以{本地帧树片段| local frame tree fragment}的粒度来操作的
假如存在如下的文档结构:
// 主 frame 为foo.com
<html>// 子 frame (bar.com)<iframe src="bar.com">// 子 frame (foo.com/etc)<iframe src="foo.com/etc"></iframe></iframe>
</html>
foo.com
作为主frame
, bar.com
作为子frame
,而foo.com/etc
作为bar.com
的子frame
。
尽管,现在也和最上面的示例一样,也存在两个渲染进程,但是此时存在三个 局部frame
树片段,两个存在于与foo.com
所对应的渲染进程中,另外一个位于与bar.com
所对应的渲染进程中。
为了将多个本地帧树合成一个合成器帧, Viz会同时从三个本地帧的根节点请求对应的合成器帧,随后将其聚合到一起。
虽然,主帧foo.com
和子帧foo.com/other-page
位于同一个帧树上,并且同一个渲染进程中处理他们的渲染过程,但是,它们位于不同的{局部frame树片段| local frame tree fragments}所以存在不同的{文档生命周期| document lifecycles}。由于这个原因,不可能在一次更新中为两者生成一个合成器帧。渲染过程没有足够的信息来将foo.com/etc
生成的合成器帧直接合成到foo.com
主帧的合成器帧中。例如,在foo.com
进程外的bar.com
可能通过CSS或者其他方式改变foo.com/ect
对应的显隐。
视觉属性更新步骤
像{设备比例因子| device scale factor}和{视口大小| viewport size}这样的视觉属性会影响到渲染输出,并且必须在本地帧树片段之间同步。
每个本地框架树片段的根部都有一个与之相关的widget
对象。视觉属性的更新先到主frame的部件,然后再从上到下传播到其余部件。
这个过程不是即时的,所以复制的视觉属性也包括一个{同步令牌| sync token}。Viz合成器使用这个同步令牌来等待所有本地frame树片段提交一个具有当前同步令牌的合成器帧。这个过程避免了混合具有不同视觉属性的合成器frame。
2. {不可变的片段树|The immutable fragment tree}
不可变的片段树是渲染管道的布局阶段的输出
它表示页面上所有元素的位置和大小
每个{片段| fragment}代表一个DOM元素的一部分
通常情况下,每个元素只有一个片段,但如果在渲染管道中{绘制| Paint}阶段被{分割| Split}到不同的页面,则会有更多的片段。
在布局之后,每个片段都变得{不可改变| Immutable },不再被改变。
还设置了一些额外的限制。
- 一个_孩子节点_不能有指向其父辈的指针
- 数据是单向的(某个节点只能访问其子节点的数据信息,而不能从父级获取)
这些限制使我们能够在随后的布局中重新使用一个片段。
大多数布局都是典型的{增量更新| incremental updates},例如,一个网络应用在用户点击某个元素时更新一小部分用户界面。理想情况下,布局应该只做与屏幕上实际改变的内容相对应的工作。我们可以通过尽可能多地重复使用以前的树的部分来实现这一点。
{内联|Lnline}片段信息
内联内容使用一个稍微不同的表示方法。我们使用一个{扁平化| flat}的列表来表示内联内容。主要的好处是,内联内容的扁平化列表表示是_快速_的,对检查或查询内联数据结构很有用,而且缓存效率高。
扁平化的列表是按照其_内联布局子树_的{深度优先搜索| depth-first search}的顺序为每个{内联格式化上下文| lnline formatting context }创建的。
列表中的每个_条目_都是一个存有(对象,后代数量)等特定信息的{元组| Tuple }。
例如,考虑这个DOM。
<div style="width: 0;"><span style="color: blue; position: relative;">北宸</span> <b>南蓁</b>.
</div>
宽度属性被设置为0,以便在 "北宸 "和 "南蓁"之间进行换行。从而形成两个Line Box
这种情况的内联格式化上下文被表示为一棵树时,它看起来像下面这样。
{"Line box": {"Box <span>": {"Text": "北宸"}},"Line box": {"Box <b>": {"Text": "南蓁"}},{"Text": "."}
}
对应的扁平list 如下:每个条目都是(对象,后代数量)的元组信息
- (Line box, 2)
- (Box
<span>
, 1) - (Text “北宸”, 0)
- (Line box, 3)
- (Box
<b>
, 1) - (Text “南蓁”, 0)
- (Text “.”, 0)
这个数据结构有很多消费者:可访问性API_和_几何API,如getClientRects
,和contenteditable
。每个消费者都有不同的要求。这些组件通过一个{游标| cursor}访问扁平化数据结构。
游标有MoveToNext
, MoveToNextLine
, CursorForChildren
等API。
3. {属性树|Property trees}
众所周知,DOM是一棵由元素(加上文本节点)组成的树,而CSS可以对元素应用各种样式
属性对应四种类型的效果处理:
- {布局| Layout }:作为布局阶段的数据输入
- {绘制| Paint }:如何绘制和栅格化当前元素
- {视觉处理| Visual }:将{变换| transforms}、{过滤| filters}和{剪切| clipping}等产生的效果应用于DOM 子树
- {滚动| Scrolling }:包含子树的轴对齐和圆角剪切和滚动
属性树是解释视觉和滚动效果如何应用于DOM元素的数据结构
它们提供了回答问题的方法,例如:一个给定布局尺寸和位置的DOM元素,它应该被放置在相对于屏幕的哪个位置?以及:应该使用什么顺序的GPU操作来应用视觉和滚动效果?
网站中的视觉效果和滚动效果在它们的全貌中是非常复杂的。因此,_属性树_所做的最重要的事情是将这种复杂性转化为一个单一的数据结构,精确地表示它们的结构和意义,同时去除DOM和CSS的其余复杂性。
例如:
- 将潜在的容易出错的几何图形和其他计算可以集中到一个地方
- 将建立和更新属性树的繁琐操作隔离到一个渲染管道中
- 与完整的DOM状态相比,将属性树发送到不同的线程和进程中要容易得多,也快得多
- 更能合理利用缓存机制
RenderingNG
将属性树用于很多目的。
- 将合成与绘制分开,将合成与主线程分开
- 确定一个最佳的合成/绘制策略
- 避免为屏幕外元素和GPU纹理工作
- 有效而准确地使绘制和光栅失效
- 测量
Core Web Vitals
中的布局偏移和最大内容的绘制
每个Web文档都有四个独立的属性树:{变换| Transform }、{剪切| clip }、{视觉效果| effect }和{滚动| Scroll }
- 变换树表示CSS变换和滚动
- 剪切树表示表示溢出剪切
- 视觉效果树表示所有其他的视觉效果:{不透明度| opacity}、{过滤器| filters}、{遮罩| masks}、{混合模式| blend modes}
- 滚动树表示关于滚动的信息
属性树中的每个节点代表一个DOM元素应用的滚动或视觉效果
如果它恰好有多种效果,那么对于同一个元素,每棵树上可能有不止一个属性树节点。
每个属性树的{拓扑结构| topology}就像DOM树一样,分散排布。例如,如果有三个DOM元素有{溢出剪切| overflow clip},那么将有三个剪切树节点,剪切树的结构将遵循溢出剪切之间的包含块关系。
每个DOM元素都有一个属性树状态属性,它是一个4元组(
transform
,clip
,effect
,scroll
),表示该元素的最近的祖先如何剪切、变换和效果该元素节点。
这非常方便,因为有了这些信息,我们就能准确地知道适用于该元素的剪切、变换和效果的列表,以及它们的顺序。这告诉我们它在屏幕上的位置以及如何绘制它。
示例
// 主 frame 为foo.com
<html><div style="overflow: scroll; width: 100px; height: 100px;">// 子 frame (foo.com/etc)<iframe style="filter: blur(3px);transform: rotateZ(1deg);width: 100px; height: 300px"id="one" src="foo.com/etc"></iframe></div>// 子 frame (bar.com)<iframe style="top:200px;transform: scale(1.1);translateX(200px)"id="two" src="bar.com"></iframe>
</html>
这里根据一些属性生成了四类属性树。
4. {显示列表和绘画块|Display lists and paint chunke}
一个显示项包含低级别的绘图命令,可以用
Skia
进行光栅化
显示项通常很简单,只有几个_绘画命令_,比如画一个边框或背景。绘画操作在布局树和相关片段上按照CSS顺序进行迭代,产生一个显示项列表。
例如:
<div id="green" style="background:green; width:80px;height:18px">Hello world
</div>
<div id="blue" style="width:100px;height:100px; background:blue;position:absolute;top:0; left:0; z-index:-1;"/>
这个HTML和CSS将产生以下显示列表,其中每项是一个显示项目。(从上到下依次排列)
- 绘制{视图| view}背景 :
drawRect
命令绘制大小为800x600(视图大小),颜色为白色的区块 - 绘制#blue 背景:
drawRect
命令在以视图为参照物的位置为(0,0)处绘制大小为100x100,颜色为蓝色的区块 - 绘制#green 背景:
drawRect
命令在以视图为参照物的位置为(8,8)处绘制大小为80x18,颜色为绿色的区块 - 处理#green 行内文本:
drawTextBlob
命令在(8,8)处绘制Hello world
文本信息
在上面的例子中,绿色 div 在 DOM 顺序中位于蓝色 div 之前,但 CSS 绘制顺序要求负 z-index 的蓝色 div 在绿色 div 之前绘制。
显示项大致对应于CSS绘制顺序规范的原子步骤
一个DOM元素可能导致多个显示项,例如#green有一个背景显示项和另一个内联文本显示项。这种粒度对于表现CSS绘画顺序规范的复杂性是很重要的,例如由负边距产生的交错。
<div id="green" style="background:green; width:80px;height:18px;">Hello world
</div>
<div id="gray" style="width:35px; height:20px;background:gray;margin-top:-10px;">
</div>
这个HTML和CSS将产生以下显示列表,其中每项是一个显示项目。(从上到下依次排列)
- 绘制{视图| view}背景 :
drawRect
命令绘制大小为800x600,颜色为白色的区块 - 绘制#green 背景:
drawRect
命令在以视图为参照物的位置为(8,8)处绘制大小为80x18,颜色为绿色的区块 - 绘制#gray 背景:
drawRect
命令在以视图为参照物的位置为(8,16)处绘制大小为35x20,颜色为灰色的区块 - 处理#green 行内文本:
drawTextBlob
命令在(8,8)处绘制Hello world
文本信息
显示项目列表可以被后续更新复用。如果一个布局对象在绘制树的过程中没有改变,它的显示项目就会从以前的列表中复制出来。
有一个针对{层叠上下文| Stacking Context }的优化:如果在一个层叠上下文中没有布局对象的变更,那么绘制游标会直接跳过该上下文,并且从之前的显示列表中复制整个显示序列。
当前的属性树状态在绘制过程中被保持,显示项目列表被划分为拥有相同属性树状态的显示项目{块| Chunk }。
<div id="scroll" style="background:pink; width:100px;height:100px; overflow:scroll;position:absolute; top:0; left:0;">Hello world<div id="orange" style="width:75px; height:200px;background:orange; transform:rotateZ(25deg);">I'm falling</div>
</div>
这个HTML和CSS将产生以下显示列表,其中每项是一个显示项目。(从上到下依次排列)
- 绘制{视图| view}背景 :
drawRect
命令绘制大小为800x600,颜色为白色的区块 - 绘制#scrolll 背景:
drawRect
命令在以视图为参照物的位置为(0,0)处绘制大小为100x100,颜色为粉色的区块 - 绘制#scroll 行内文本:
drawTextBlob
命令在(0,0)处绘制Hello world
文本信息 - 处理#orange 背景:
drawRect
命令在以视图为参照物的位置为(0,0)处绘制大小为75x200,颜色为橘色的区块 - 绘制#orange 行内文本:
drawTextBlob
命令在(0,0)处绘制I'm falling
文本信息
属性树和绘制块关系如下:
绘画块的有序列表,即显示项目组和属性树状态,作为渲染管道{图层化|Layerize}步骤的输入数据
整个绘制块列表可以合并成一个合成层并一起栅格化,但这需要在用户每次滚动时进行昂贵的栅格化操作。作为优化处理,可以为每个绘制块创建一个合成层并单独光栅化,以避免所有的重新光栅化,但这将很快耗尽GPU内存。
所以,图层化步骤必须在GPU内存和减少事物变化时的成本之间做出权衡。一个好的方法是默认合并图块,也就是不对具有属性树状态的绘制块进行合并处理,这些属性树状态可能会在合成器线程上发生变化,比如合成器线程的滚动或合成器线程的变换动画。
前面的例子最好能产生两个合成的图层。
- 一个
800x600
的合成层(默认图块合并)drawRect
命令绘制尺寸为800x600,颜色为白色的图块drawRect
命令绘制位于相对于视图(0,0)位置,尺寸为100x100,且颜色为粉色的图块
- 一个
144x244
的合成层 (拥有属性树的图块)drawTextBlob
命令在(0,0)位置,绘制Hello world
文本信息- 平移(0,18)
- 围绕Z轴旋转顺时针旋转25度
drawRect
命令绘制位于相对于视图(0,0)位置,尺寸为75x200,且颜色为橘色的图块drawTextBlob
命令在(0,0)位置,绘制I'm falling
文本信息
如果用户滚动#scroll,第二个合成层会被移动,但不需要栅格化。
5. {合成器帧|Compositor frame}:{表面| surface}、{渲染表面| render surface}、{GPU 纹理瓦片| GPU texture tile}
在Chromium 最新渲染引擎–RenderingNG最后的示例中,我们得知,_浏览器_和_渲染进程_管理内容的光栅化,然后将合成器帧提交给Viz进程以呈现给屏幕。
合成器帧是
RenderingNG
表示如何将栅格化的内容拼接在一起,并使用GPU
有效地绘制它的数据格式
{瓦片|Tile}
理论上,渲染进程或浏览器进程中的{合成器| compositor}可以将像素栅格化为渲染器视口的单一纹理,并将该纹理提交给Viz。为了显示它,显示合成器只需将单个纹理中的像素复制到帧缓冲区的适当位置(例如,屏幕)。然而,如果该合成器想要更新哪怕是一个像素,它就需要对整个视口进行重新光栅化处理,并向Viz提交一个新的纹理。
相反,视口被划分为{瓦片|Tile}。
一个单独的GPU纹理瓦片为每个瓦片提供了视口部分的光栅化像素
然后,渲染器可以更新单个瓦片,甚至只是改变现有瓦片在屏幕上的位置。例如,当滚动一个网站时,现有瓦片的位置会向上移动,只是需要为更远的页面内容栅格化一个新瓦片。
上面的图片有四张瓦片。当滚动发生时,第五块瓦片开始出现。
{Quad and surfaces|Quad and Surfaces}
GPU纹理瓦片是一种特殊的Quad,它只是一类纹理瓦片的别称
Quad描述纹理的输入信息,并指出如何对其进行转换和应用视觉效果。
例如,内容瓦片有一个变换,表示它们在瓦片网格中的x、y位置。
这些栅格化的瓦片被包裹在一个渲染通道中,它是一个quad的列表。渲染通道不包含任何像素信息;相反,它有关于在哪里以及如何绘制每个quad
所需像素输出的指示。
每个GPU纹理瓦片都有一个quad
显示合成器只需要在quad
列表中进行迭代,用指定的视觉效果绘制每一个quad
,以产生渲染通道所需的像素输出。渲染通道的绘制quad
合成可以在GPU上有效地完成,因为允许的视觉效果是经过精心挑选的,可以直接映射到GPU的特性上。
除了光栅化瓦片之外,还有其他类型的quad
。例如,有一些完全不依赖纹理机制的纯色quad
,或者用于视频或画布等纹理绘制quad
。
一个合成器帧也有可能嵌入另一个合成器帧
例如,浏览器合成器会产生一个带有浏览器用户界面的合成器帧,以及一个空的区域以便于将渲染合成器的内容嵌入其中。另一个例子是存在站点隔离的多个iframe
之间。这种嵌入是{表面|Surface}通过完成的。
当一个合成器提交一个合成器帧时,它伴随着一个用于区分合成帧的标识符,即表面ID。最新提交的带有特定表面ID的合成器帧被Viz储存起来。另一个合成器帧随后可以通过表面quad来引用它,因此Viz知道要绘制什么。(注意,表面quad
只包含表面ID,而不是纹理。)
中间的渲染通道
一些视觉效果,如许多滤镜或高级混合模式,需要将两个或更多的quad
合并到一个中间纹理中。然后,中间纹理被绘制到GPU上的目标缓冲区(或者可能是另一个中间纹理),同时应用视觉效果。为了实现这一点,一个合成器帧实际上包含一个渲染通道的列表。并且总是有一个根渲染通道,它是最后绘制的。
每个通道必须在GPU上按顺序执行,分为多个 “阶段”,而单个阶段可以在单个大规模并行的GPU计算中完成。
{合成|Aggregation}
多个合成器帧被提交给Viz,它们需要被一起绘制到屏幕上。这是由一个{聚合阶段|Aggregation}完成的,该阶段将它们转换为一个单一的、聚合的合成器帧
聚合将表面quad替换成他们指定的合成器帧。
这也是一个优化不必要的中间纹理或屏幕外内容的机会。例如,在很多情况下,一个独立网站的iframe
的合成器帧不需要它自己的中间纹理,可以通过绘制quad
直接绘制到框架缓冲区。聚合阶段会找出这样的优化,并根据单个渲染合成器无法访问的全局来应用这些优化。
示例
以本文开头的例子做讲解
// 主 frame 为foo.com
<html><div style="overflow: hidden; width: 100px; height: 100px;">// 子 frame (foo.com/etc)<iframe style="filter: blur(3px);transform: rotateZ(1deg);width: 100px; height: 300px"id="one" src="foo.com/etc"></iframe></div>// 子 frame (bar.com)<iframe style="top:200px;transform: scale(1.1);translateX(200px)"id="two" src="bar.com"></iframe>
</html>
foo.com/index.html
surface: ID =0- 渲染通道 0 : 绘制到输出
- 绘制
quad
:以3px的模糊度绘制,并夹入渲染通道0- 渲染通道 1:
- 为#one的帧绘制带有x/y位置信息的
quad
- 为#one的帧绘制带有x/y位置信息的
- 渲染通道 1:
- 表面绘制
quad
:ID =2,用比例和平移变换绘制
- 绘制
- 渲染通道 0 : 绘制到输出
- 浏览器 UI surface: ID =1
- 渲染通道 0 : 绘制到输出
- 为 浏览器UI绘制
quad
- 为 浏览器UI绘制
- 渲染通道 0 : 绘制到输出
bar.com/index.html
surface: ID=2- 渲染通道 0 : 绘制到输出
- 为#two的帧绘制带有x/y位置信息的
quad
- 为#two的帧绘制带有x/y位置信息的
- 渲染通道 0 : 绘制到输出
后记
分享是一种态度,这篇文章,是一篇译文,算是一个自我学习过程中的一种记录和总结。主要是把自己认为重要的点,都罗列出来。同时,也是为大家节省一下排雷和踩坑的时间。当然,可能由于自己认知能力所限,有些点,没能表达很好。如果大家想看原文,“墙裂推荐”看原文。
参考资料:
- 原文地址 需要
RenderingNG中关键数据结构和它们的角色相关推荐
- Python中的数据结构
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:磐创AI 概述 在深入研究数据科学和模型构建之前,Pyt ...
- python数据结构包括什么_Python中的数据结构详解
概述 在深入研究数据科学和模型构建之前,Python中的数据结构是一个需要学习的关键内容 了解Python提供的不同数据结构,包括列表.元组等 介绍 数据结构听起来是一个非常直截了当的话题,但许多数据 ...
- python中定义数据结构_Python中的数据结构—简介
python中定义数据结构 You have multiples algorithms, the steps of which require fetching the smallest value ...
- 向内存中连续存入数据_内存节省到极致!Redis中这个数据结构,值得每个程序员了解...
在之前我们介绍了,Redis有五种基础数据类型,分别是String,Set,List,Hash与SortSet. 今天我们又学习了一个命令,我们可以使用DEBUG OBJECT key查询Redis中 ...
- java中的数据结构——树
树 树形结构是一种层级式的数据结构,由节点和连接它们的边组成, java语言编写的程序中常常用引用来表示边.根是树中顶端的节 点:它没有父节点.节点表示保存在树中的数据对象.非平衡树是 指根左边的后代 ...
- Python基石 | Python中的数据结构详解
概述 在深入研究数据科学和模型构建之前,Python中的数据结构是一个需要学习的关键内容 了解Python提供的不同数据结构,包括列表.元组等 介绍 数据结构听起来是一个非常直截了当的话题,但许多数据 ...
- java中的数据结构之HashMap学习
java中的数据结构之HashMap学习 equal与hashcode equals与hashcode的源码 为什么hashmap中作为键值的类要重写hashcode和equals方法 Integer ...
- Redis中五大数据结构的底层实现
来自:DBAplus社群 作者介绍 田兆壮,新炬网络开发工程师.具备扎实的Java.Scala开发经验,熟练使用Python和Shell等脚本语言:具备前后端开发能力,熟练使用关系型数据库和非关系型数 ...
- iOS标准库中常用数据结构和算法之内存池
上一篇:iOS标准库中常用数据结构和算法之位串 ⛲️内存池 内存池提供了内存的复用和持久的存储功能.设想一个场景,当你分配了一块大内存并且填写了内容,但是你又不是经常去访问这块内存.这样的内存利用率将 ...
- 动图 + 源码,演示 Java 中常用数据结构执行过程及原理
最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于jdk8, 可能会有些特性与jdk7之前不相同, 例如LinkedList Linke ...
最新文章
- R语言dataframe计算满足筛选条件的行的个数(筛选满足条件的数据行并计数):类似于excel的countif函数
- 深入浅出 - Android系统移植与平台开发(十)- Android编译系统与定制Android平台系统(瘋耔修改篇二)...
- UA MATH563 概率论的数学基础 中心极限定理3 推导一元随机变量独立性的判断方法
- 加密锁 vs. 云授权
- Entity Framework 4.3.1 Code First 连接 PostgreSQL 9.2.3 小结
- 计算机狐狸标志的程序,小狐狸等分线计算工具
- CSS基础(part11)--盒子模型之内边距
- log4j无厘头异常
- csgo客户文件与服务器,csgo与远程服务器
- 数据库之MySQL ERROR 1698 (28000) 错误:Access denied for user 'root'@'localhost' error【摘抄】...
- 自定义request链路跟踪
- 腾讯暑期日常实习前端面试
- AcWing 898. 数字三角形(线性DP)
- 练习: 将一个int[] 中元素,转成字符串格式
- C语言全局变量,局部变量,静态局部变量的区分
- __stdcall的作用及今天的坑
- python爬虫爬取视频
- 网络上的计算机找不到打印机,网络打印机找不到,详细教您网络打印机找不到怎么办...
- python假设税前工资和税率如下_计算税后收入_税前税后工资计算公式,软件和手动计算哪个更有优势?...
- php redis 防超卖,redis防止抢购商品超卖
热门文章
- linux命令--netstat
- 备案域名基础知识,网站备案新政策
- Python与数据库之学员管理系统
- 【VOLTE】【SRVCC】 SRVCC TO 3GPP
- 359860-27-8,Biotin-PEG3-Amine增加了与生物素化合物共轭的分子的水溶性
- 《Spring Boot极简教程》附录1 计算机简史
- c语言put()用法,C++ get()和put()读写文件详解
- 双重for循环 语法结构
- 计算机科学与技术的培养方案,计算机科学与技术专业培养方案2017版.PDF
- mc服务器文件夹改皮肤,我的世界皮肤替换教程 老司机教你更换皮肤
- Python中的数据结构