《核心方向》 参考别人代码,纵向是一个列表使用层索引推动,横向用各自元素的next指针往右侧推动。 《问题1:如果后来的节点随机的层数比最左侧高该怎么弄?》 目前看来,在初始化的时候就限定了最高层,所以随机不让超过此层。 《问题2:假如第二个节点很矮,是不是这样,在后边就回不到高处了》 不是必须经过矮点,可以直接 next 到后面高点。 《问题3:底层节点是有序的,比如最开始空数据时,先来一个8节点,又来个2节点,2怎么插到8前面去》 个人觉得就是找节点找到空的就往前面插入。 《atomic 相关》 1.std::memory_order_releaxed 这种语义表明加载操作并不引入额外的同步或顺序性,因此不会有额外的同步开销。 2.std::memory_order_acquire 这种语义确保了对被加载数据的后续操作符合所加载的数据顺序,即在加载数据后, 后续对返回的节点数据操作会被保证在加载操作之后执行。 3.std::memory_order_release 这种语义通常用于写操作,以确保在写入某个变量后,所有之前的写入操作在此之前都已完成。 《Leveldb中遇到的一个可变长结构体》 如 struct A { int a; char b[1];} 其中b其实是可变长的结构体,也叫柔性数组(注意b必须在结构体最后面), 实际分配内存时,给b动态分配额外的内存空间,可以使用[i]形式访问。 如果定义成 char* 那么额外的空间将不能被访问到,因为malloc的数据内容是未定义的, 为什么 char* data = (char *)malloc(4); 可以使用data[i]访问,是因为data存的是已经分配好的空间的地址, 而直接给 A malloc内存,如果定义成 char* b就如上面所说。 一.与传统指针的区别 1.b[1]的形式在编译器就预留了一定的空间,在简单足够使用时可以避免new free的额外管理操作。 2.这样写能一眼看出这个变量是一个数组。 《new 的用法》 new 可以在已经分配的空间上调用构造: new (memory_alloc_addr) A(1); 《思路的改进点》 我默认的思路是同一个值的一个链条,结构体这样定义: struct N { T* k; N* next; }; k指向一个有真正内存的首个节点,然后再定义个下一个位置指针, github 源码中的思路是一个结构体 struct N { T k; N* next; // 这个next是一个数组 } 这样一来,同一个值的数据,一个 N 就可以了,剩余的都是指针。相比较默认思路的优势是 可以直接使用下表 [i] 访问而无需一个一个next,速度更快,且在后续直接取N某一层节点数 据的逻辑中直接用,用默认next的方法,多很多不必要的逻辑。 《leveldb中跳表的实现逻辑》 一、这里说一下插入整体逻辑,这个逻辑通了,别的都OK。 1.一个数据一个 Node,该 Node 中含有一个 Node* 列表用于延伸高度。 2.在查找某个数据时,定义了一个高度的 Node* 临时列表 pre,用于记录每一层的插入点在哪个 Node。 3.pre 找好之后,new 一个保存该值的 Node x, 4.将 pre 待插入节点的下一级节点放到 x 下一级,pre 下一级改到 x。 二、这里说一下插入的细节逻辑 1.默认无数据时候,有个 Head 作用的 Node。 2.随机的高度比当前数据中的最高要高时,要将 pre 在该层的记录点指向 head