人的一切痛苦,本质上都是对自己无能的愤怒 – 王小波

前言

大家好,我是柒八九。今天这篇文章是Chromium最新渲染架构 RenderingNG的译文系列文章的第二篇 – 在RenderingNG渲染过程中关键数据结构和它们所担当的角色。

针对RenderingNG的介绍可以参考之前的文章。

时间不早了,干点正事哇

简明扼要

  1. {帧树|Frame Tree}: 由本地远程节点组成
  2. 每个渲染进程都有属于自己的对网页内容进行描述的frame
  3. 一个渲染在不同进程的frame被称为远程帧
  4. {渲染管线|rendering pipeline}是以{本地帧树片段| local frame tree fragment}的粒度来操作的
  5. 像{设备比例因子| device scale factor}和{视口大小| viewport size}这样的视觉属性会影响到渲染输出,并且必须在本地帧树片段之间同步
  6. 不可变的片段树是渲染管道的布局阶段的输出
    它表示页面上所有元素的位置和大小
    每个{片段| fragment}代表一个DOM元素的一部分
  7. 内联片段信息列表中的每个_条目_都是一个存有(对象,后代数量)等特定信息的{元组| Tuple }
  8. 属性树是解释视觉和滚动效果如何应用于DOM元素的数据结构
  9. 每个Web文档都有四个独立的属性树:{变换| Transform }、{剪切| clip }、{视觉效果| effect }和{滚动| Scroll }
  10. 显示list中的显示项包含低级别的绘图命令,可以用Skia进行光栅化
  11. 显示项大致对应于CSS绘制顺序规范的原子步骤
  12. 绘画块的有序列表,即显示项目组和属性树状态,作为渲染管道{图层化|Layerize}步骤的输入数据
  13. 合成器帧是RenderingNG表示如何将栅格化的内容拼接在一起,并使用GPU有效地绘制它的数据格式
  14. 视口被划分为{瓦片|Tile}>
  15. Quad描述纹理的输入信息,并指出如何对其进行转换应用视觉效果
    GPU纹理瓦片是一种特殊的Quad,它只是一类纹理瓦片的别称
  16. 每个GPU纹理瓦片都有一个quad

文章概要

  1. {帧树|Frame Tree}
  2. {不可变的片段树|The immutable fragment tree}
  3. {属性树|Property trees}
  4. {显示列表和绘画块|Display lists and paint chunke}
  5. {合成器帧|Compositor frame}:{表面| surface}、{渲染表面| render surface}、{GPU 纹理瓦片| GPU texture tile}

前置知识简讲

在渲染流程中出现了大致五种比较重要的数据结构。

  1. {帧树|Frame Tree}: 由本地远程节点组成,表示对应的文档信息应该被哪个渲染进程中的Blink渲染器所消费
  2. {不可变的片段树|Immutable Fragment Tree}:代表布局阶段的信息产生
  3. {属性树|Property Tree}:代表了针对文档进行{转换|transform}、{剪切|clip}、{视觉效果|effect}和{滚动|scroll}等操作后的数据格式,并为后续的渲染流程所使用。
  4. {显示列表和绘画块|Display lists and paint chunks}: 将被传入到合成线程中,并被光栅化和分层算法所_消费_
  5. {合成器帧|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将产生以下显示列表,其中每项是一个显示项目。(从上到下依次排列)

  1. 绘制{视图| view}背景 :drawRect命令绘制大小为800x600(视图大小),颜色为白色的区块
  2. 绘制#blue 背景: drawRect命令在以视图为参照物的位置为(0,0)处绘制大小为100x100,颜色为蓝色的区块
  3. 绘制#green 背景:drawRect命令在以视图为参照物的位置为(8,8)处绘制大小为80x18,颜色为绿色的区块
  4. 处理#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将产生以下显示列表,其中每项是一个显示项目。(从上到下依次排列)

  1. 绘制{视图| view}背景 :drawRect命令绘制大小为800x600,颜色为白色的区块
  2. 绘制#green 背景:drawRect命令在以视图为参照物的位置为(8,8)处绘制大小为80x18,颜色为绿色的区块
  3. 绘制#gray 背景:drawRect命令在以视图为参照物的位置为(8,16)处绘制大小为35x20,颜色为灰色的区块
  4. 处理#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将产生以下显示列表,其中每项是一个显示项目。(从上到下依次排列)

  1. 绘制{视图| view}背景 :drawRect命令绘制大小为800x600,颜色为白色的区块
  2. 绘制#scrolll 背景:drawRect命令在以视图为参照物的位置为(0,0)处绘制大小为100x100,颜色为粉色的区块
  3. 绘制#scroll 行内文本:drawTextBlob命令在(0,0)处绘制Hello world文本信息
  4. 处理#orange 背景:drawRect命令在以视图为参照物的位置为(0,0)处绘制大小为75x200,颜色为橘色的区块
  5. 绘制#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
      • 表面绘制quad:ID =2,用比例和平移变换绘制
  • 浏览器 UI surface: ID =1
    • 渲染通道 0 : 绘制到输出

      • 为 浏览器UI绘制quad
  • bar.com/index.htmlsurface: ID=2
    • 渲染通道 0 : 绘制到输出

      • 为#two的帧绘制带有x/y位置信息的quad

后记

分享是一种态度,这篇文章,是一篇译文,算是一个自我学习过程中的一种记录和总结。主要是把自己认为重要的点,都罗列出来。同时,也是为大家节省一下排雷和踩坑的时间。当然,可能由于自己认知能力所限,有些点,没能表达很好。如果大家想看原文,“墙裂推荐”看原文。

参考资料:

  1. 原文地址 需要

    RenderingNG中关键数据结构和它们的角色相关推荐

    1. Python中的数据结构

      点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:磐创AI 概述 在深入研究数据科学和模型构建之前,Pyt ...

    2. python数据结构包括什么_Python中的数据结构详解

      概述 在深入研究数据科学和模型构建之前,Python中的数据结构是一个需要学习的关键内容 了解Python提供的不同数据结构,包括列表.元组等 介绍 数据结构听起来是一个非常直截了当的话题,但许多数据 ...

    3. python中定义数据结构_Python中的数据结构—简介

      python中定义数据结构 You have multiples algorithms, the steps of which require fetching the smallest value ...

    4. 向内存中连续存入数据_内存节省到极致!Redis中这个数据结构,值得每个程序员了解...

      在之前我们介绍了,Redis有五种基础数据类型,分别是String,Set,List,Hash与SortSet. 今天我们又学习了一个命令,我们可以使用DEBUG OBJECT key查询Redis中 ...

    5. java中的数据结构——树

      树 树形结构是一种层级式的数据结构,由节点和连接它们的边组成, java语言编写的程序中常常用引用来表示边.根是树中顶端的节 点:它没有父节点.节点表示保存在树中的数据对象.非平衡树是 指根左边的后代 ...

    6. Python基石 | Python中的数据结构详解

      概述 在深入研究数据科学和模型构建之前,Python中的数据结构是一个需要学习的关键内容 了解Python提供的不同数据结构,包括列表.元组等 介绍 数据结构听起来是一个非常直截了当的话题,但许多数据 ...

    7. java中的数据结构之HashMap学习

      java中的数据结构之HashMap学习 equal与hashcode equals与hashcode的源码 为什么hashmap中作为键值的类要重写hashcode和equals方法 Integer ...

    8. Redis中五大数据结构的底层实现

      来自:DBAplus社群 作者介绍 田兆壮,新炬网络开发工程师.具备扎实的Java.Scala开发经验,熟练使用Python和Shell等脚本语言:具备前后端开发能力,熟练使用关系型数据库和非关系型数 ...

    9. iOS标准库中常用数据结构和算法之内存池

      上一篇:iOS标准库中常用数据结构和算法之位串 ⛲️内存池 内存池提供了内存的复用和持久的存储功能.设想一个场景,当你分配了一块大内存并且填写了内容,但是你又不是经常去访问这块内存.这样的内存利用率将 ...

    10. 动图 + 源码,演示 Java 中常用数据结构执行过程及原理

      最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于jdk8, 可能会有些特性与jdk7之前不相同, 例如LinkedList Linke ...

    最新文章

    1. R语言dataframe计算满足筛选条件的行的个数(筛选满足条件的数据行并计数):类似于excel的countif函数
    2. 深入浅出 - Android系统移植与平台开发(十)- Android编译系统与定制Android平台系统(瘋耔修改篇二)...
    3. UA MATH563 概率论的数学基础 中心极限定理3 推导一元随机变量独立性的判断方法
    4. 加密锁 vs. 云授权
    5. Entity Framework 4.3.1 Code First 连接 PostgreSQL 9.2.3 小结
    6. 计算机狐狸标志的程序,小狐狸等分线计算工具
    7. CSS基础(part11)--盒子模型之内边距
    8. log4j无厘头异常
    9. csgo客户文件与服务器,csgo与远程服务器
    10. 数据库之MySQL ERROR 1698 (28000) 错误:Access denied for user 'root'@'localhost' error【摘抄】...
    11. 自定义request链路跟踪
    12. 腾讯暑期日常实习前端面试
    13. AcWing 898. 数字三角形(线性DP)
    14. 练习: 将一个int[] 中元素,转成字符串格式
    15. C语言全局变量,局部变量,静态局部变量的区分
    16. __stdcall的作用及今天的坑
    17. python爬虫爬取视频
    18. 网络上的计算机找不到打印机,网络打印机找不到,详细教您网络打印机找不到怎么办...
    19. python假设税前工资和税率如下_计算税后收入_税前税后工资计算公式,软件和手动计算哪个更有优势?...
    20. php redis 防超卖,redis防止抢购商品超卖

    热门文章

    1. linux命令--netstat
    2. 备案域名基础知识,网站备案新政策
    3. Python与数据库之学员管理系统
    4. 【VOLTE】【SRVCC】 SRVCC TO 3GPP
    5. 359860-27-8,Biotin-PEG3-Amine增加了与生物素化合物共轭的分子的水溶性
    6. 《Spring Boot极简教程》附录1 计算机简史
    7. c语言put()用法,C++ get()和put()读写文件详解
    8. 双重for循环 语法结构
    9. 计算机科学与技术的培养方案,计算机科学与技术专业培养方案2017版.PDF
    10. mc服务器文件夹改皮肤,我的世界皮肤替换教程 老司机教你更换皮肤