深入理解 Piece Table

604 阅读5分钟

【数据结构】深入理解Piece Table数据结构

引言

当在vscode中键入文字的时候,这些文字会缓存在内存中的数据结构(piecetable)中。此外另有Abiword,atom,bravo,某些版本的microsoft word都使用了piecetable这种数据结构来表示文本文档的内容。

什么是 “Piece Table”

维基百科中有如下定义:

A Piece Table is a data structure typically used to represent a series of edits on a text document Piece Table是一种数据结构,通常用来表示文本文档中一系列的编辑操作。

概念

image.png

在Piece Table中,真实的文本内容都被分类两类:

  • 原始文本内容(original) :初始化加载的文本内容,可以为空
  • 新增文本内容(append | add) :指的是编辑者通过编辑新增的文本内容

这两类文本内容存储在两个分离的缓存中(buffer),通常最简单的方式是用两个String Buffer来存储。当以某种顺序的获取 原始文本内容新增文本内容 中的部分文本内容(Piece)的时候,就是组成了最终的文档的内容。代指部分文本内容的结构,称之为Piece。

通常,Piece 的基本结构如下:

  • buffer type: buffer类型,是original还是append
  • start: 在该buffer中所在的开始的index
  • length: 内容的长度。

简单例子

原始文本(Original): 这是一段文字

新增文本(Append): 美好,这是另一段文字

buffer typestartlength表示内容
original04这是一段
append02美好
original42文字

所有的piece连起来就是:**这是一段美好的文字,**这样一篇在编辑中的文档内容就这样被一连串的piece所指代。

操作

Piece Table有以下基本的操作:

  • insert(p, str) : 在某个位置p插入(输入)一段文本
  • itemAt(p1, p2) : 获取 p1 到 p2 位置的文本内容
  • delete(p1, len) : 删除 p1 位置开始 长度为 len 的文本内容,删除其实并不删除真实的文本内容,而是更改piece的索引指向

特点

其特点简单总结为:

  1. 只增不减(Append-only),初次加载的原始文本和新增文本会被缓存,并不被删除
  2. 每个piece存储的是对于文本的指针,所以对于任何插入、删除操作其实就是一个index的变更而已,任何真实的文本内容是不变的。这样的特性,可以为无限撤销、重放提供了可能性
  3. 由于原始文本是不会被改变的,因此也为懒加载、分块加载、大文件加载等提供了可能性

用例子来理解

下面用一个稍微复杂的例子来看PieceTable的各种操作是如何达到编辑的效果的,也能让我们更加直观的理解这个数据结构。

buffer TypeContent
original这是一段文字
append(空的)
buffer typestartlength表示内容
original06这是一段文字

编辑后文档内容:这是一段文字

 // 执行操作
 insert(4, '美好的')
buffer TypeContent
original这是一段文字
append优美的
buffer typestartlength表示内容
original04这是一段
append03优美的
original42文字

编辑后文档内容:这是一段优美的文字

// 执行操作:位置2,长度1
delete(2, 1)
buffer TypeContent
original这是一段文字
append优美的
buffer typestartlength表示内容
original02这是
original31
append03优美的
original42文字

编辑后文档内容:这是段优美的文字

Piece的管理

image (1).png

Piece Table 本身的实现,核心是 Piece的管理(增删改查) ,这里就需要另外的数据结构来实现之,下面分析列举一些数据结构:

  • 数组:最常用的数据结构之一,优势在于实现简单直接,劣势在于随着piece的增多,插入删除数组中的某个值,性能是比较差的。

  • 链表:也是最常用的数据结构之一,插入、删除的性能较好[O(1)],但是获取的性能就不稳定了[搜索时间 + O(1)],搜索时间最好是 O(1),最差是O(n),非常不稳定。

  • 平衡二叉树:各个操作都在 O(logn),非常的稳定

    • 红黑树(red-black tree)
    • Splay Tree

从这些常用的数据结构来说,使用平衡二叉树是比较好的选择,即使在piece非常膨胀的时候,也能有稳定的性能表现。

从现实产品的例子可以参考,vscode是使用了红黑树,atom使用的是splay tree。

下面是自己写的一个实验性的piece table实现,用的是红黑树来管理piece。还有添加了一些额外的功能,比如undo/redo,富文本支持等

总结

Piece Table **本身的概念是非常易于理解的,实现层面的难点在于用什么方式来管理piece,兼顾性能,内存开销,undo\redo,树快照等等功能。

Piece Table **给我的另一个启发就是,“真实内容存储和逻辑的分离” ,这个理念隐藏在其背后,真实的文本存储是连续的,而Piece通过索引来指向具体的内容,具体的逻辑(piece管理的数据结构实现)则与具体内容完全隔离开了。