piece table 的C语言简单实现
piece table的介绍
piece table是文本编辑器领域的一个很重要的数据结构
能实现文本编辑器的数据结构有很多,例如vi编辑器远古版本使用的一整块数组、块状链表、行链表、数组的改进GAP BUFFER等,还有本文要介绍的piece table。
关于文本编辑器的各种实现方式,这篇论文给出了很详细的介绍
https://pan.baidu.com/s/1tNuJ6trAStnr52z1QZDR4A
关于piece table,这篇文章给出了很详细的说明
https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation
这里简要说明什么是piece table
主要参考了这篇文章:
https://zhuanlan.zhihu.com/p/259387658
piece table 由三部分构成:
源文本、输入缓存、pieces
源文本记录源文件的内容,它是一个只读字符串
输入缓存记录每一次输入的内容。它是一个只增字符串(append only)
pieces是piece table的精华
它的作用是指示哪些是应当出现在渲染界面的内容
举个例子,源文件内容为:
hello
01234
最开始的时候pieces记录的内容如下:
type |start |len
SOURCE |0 |5
此时渲染的内容为hello
我们往输入缓存中添加, world
, world
0123456
要使渲染层输出内容为hello, world
,需要将pieces记录的内容改为:
type |start |len
SOURCE |0 |5
ADDBUF |0 |7
此时要删除llo,
,需要将pieces记录的内容更改为
type |start |len
SOURCE |0 |2
ADDBUF |1 |7
此时的输出为he world
pieces就像是手电筒一样,照亮我们需要的字符,如要删除某些字符,只要简单地将它的范围缩小即可。
piece table比起其他数据结构,优越的地方在于它可以迅速实现撤回和重做功能,无需复制粘贴大量数据
使用一个栈来存储每次的操作,只需要对table链表执行对应的操作即可迅速实现撤回与重做功能。
另外,可以使用另一个结构来存储每一行开始的位置等等。不在本文的考虑范围内。
C语言的简单实现
本次主要实现的功能为:在piecetable数据结构中添加一段字符、删除一段字符
涉及到的数据结构操作有:
- 可扩容数组
- 单链表的搜索、增添、删除、合并操作
设计如下数据结构:
typedef struct buf_t
{
char *data;
size_t curSize;
size_t maxSize;
}buf_t; // 变长缓存
typedef enum
{
SOURCE = 0,
ADDBUF,
UNKNOW
}buf_e;
typedef struct table_t
{
buf_e type;
size_t start;
size_t len;
struct table_t *next;
}table_t; // 单链表结构
typedef struct piecetable_t
{
buf_t addbuf; // append only
char *source; // read only
table_t *table; // linked list
}piecetable_t;
将输入缓存设置为可扩容类型的
注意其中的table_t类型,我将其设置为无表头的单向链表
实际上有表头的链表更好写一点
在实际使用中,可以使用红黑数来重写table_t类型的所有操作,将时间复杂度从O(n)
提升到O(ln n)
创建与销毁
设计如下函数
// 创建新的piecetable结构
piecetable_t *piecetable_new(char *srcTxt);
// 销毁该piecetable
bool piecetable_free(piecetable_t *pt);
/**
* 创建新的piecetable类型
* @param srcTxt 源文件指针
*/
piecetable_t *piecetable_new(char *srcTxt)
{
piecetable_t *pt = (piecetable_t *)
malloc(sizeof(piecetable_t));
if (pt == NULL)
return NULL;
pt->source = srcTxt; // 只读
pt->table = table_init(srcTxt); // 无表头的单链表结构
buf_init(&(pt->addbuf)); // 可扩容的缓存数组
return pt;
}
/**
* 销毁piecetable
* @param pt 要销毁的对象
* @return 是否销毁成功
*/
bool piecetable_free(piecetable_t *pt)
{
free(pt->source);
buf_free(&(pt->addbuf));
table_free(pt->table);
free(pt);
return true;
}
具体函数实现细节请看等一下贴出来的源码
添加一段字符串
设置如下函数来添加字符串
// 往pt的pos处后面插入大小为size的字符串
bool piecetable_ins(
piecetable_t *pt, size_t pos, char *s, size_t size);