前言
上篇文章介绍了rax树的插入操作原理,这篇文章看看删除代码。插入涉及到了压缩节点的分裂,那么删除则是会导致节点的合并。
正文
在看源码之前在脑海中试想以下删除流程,首先删除的条件是得完全匹配字符串,并且匹配只有两种情况:要么完全匹配一个压缩节点,要么匹配一个普通节点。 看一下源码是怎么处理的
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
raxNode *h;
raxStack ts;
debugf("### Delete: %.*s\n", (int)len, s);
//用一个栈来记录访问过的节点
raxStackInit(&ts);
int splitpos = 0;
size_t i = raxLowWalk(rax,s,len,&h,NULL,&splitpos,&ts);
//表示没有匹配到节点
if (i != len || (h->iscompr && splitpos != 0) || !h->iskey) {
raxStackFree(&ts);
return 0;
}
if (old) *old = raxGetData(h);
h->iskey = 0;
rax->numele--;
还是使用了raxLowWalk代码来匹配节点并且获取信息,这里比Insert多了一步就是创建了一个Stack,将查找过程中访问节点给记录下来
if (ts) raxStackPush(ts,h);
raxLowWalk中记录栈的代码
能够达成删除条件表示这个节点必须是以前Insert过的真实节点,具有以下三个条件:
- 必须是一个key
- 如果是压缩节点,那么匹配到分割位置必须是0
- 字符串得被完全匹配
然后继续执行
int trycompress = 0;
if (h->size == 0) {
debugf("Key deleted in node without children. Cleanup needed.\n");
raxNode *child = NULL;
while(h != rax->head) {
child = h;
debugf("Freeing child %p [%.*s] key:%d\n", (void*)child,
(int)child->size, (char*)child->data, child->iskey);
rax_free(child);
rax->numnodes--;
h = raxStackPop(&ts);
/* 不断获取已访问过的节点进行释放
* 直到是一个key节点 或者非压缩节点的子节点大于1
*/
if (h->iskey || (!h->iscompr && h->size != 1)) break;
}
trycompress表示能否尝试压缩节点
首先开始从匹配到的节点向上遍历,通过Stack来访问。因为一个字符串是分散到各个节点上的,所以要不断删除直到不满足条件为止,一个key节点或者多个子节点的普通节点是不能够直接删除的。
if (child) {
debugf("Unlinking child %p from parent %p\n",
(void*)child, (void*)h);
//这里返回的是断开child后的h
raxNode *new = raxRemoveChild(h,child);
if (new != h) {
raxNode *parent = raxStackPeek(&ts);
raxNode **parentlink;
if (parent == NULL) {
parentlink = &rax->head;
} else {
parentlink = raxFindParentLink(parent,h);
}
memcpy(parentlink,&new,sizeof(new));
}
//如果删除后节点只有一个child 而且不是key的话 可以变成一个压缩节点
if (new->size == 1 && new->iskey == 0) {
trycompress = 1;
h = new;
}
}
}else if (h->size == 1) {
//如果这个节点有child 删除后可能会压缩
trycompress = 1;
}
//OOM后不压缩
if (trycompress && ts.oom) trycompress = 0;
这段代码将上面访问到不符合条件的节点与父节点断开,同同时保存父节点的指针,接下来就要开始对这个节点动刀了,会用全新的节点来替换,先看一下raxRemoveChild断开父子节点的代码
raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
debugnode("raxRemoveChild before", parent);
//paren节点如果是压缩的 则可以直接删除子节点
if (parent->iscompr) {
void *data = NULL;
if (parent->iskey) data = raxGetData(parent);
parent->isnull = 0;
parent->iscompr = 0;
parent->size = 0;
if (parent->iskey) raxSetData(parent,data);
debugnode("raxRemoveChild after", parent);
return parent;
}
//获取第一个字节点指针 然后不断循环到等于child节点的指针位置 就能获取到计数
raxNode **cp = raxNodeFirstChildPtr(parent);
raxNode **c = cp;
unsigned char *e = parent->data;
while(1) {
raxNode *aux;
memcpy(&aux,c,sizeof(aux));
if (aux == child) break;
c++;
e++;
}
//e是child位置 将e+1后面剩余的数据复制到e的位置 等于删除了e
int taillen = parent->size - (e - parent->data) - 1;
debugf("raxRemoveChild tail len: %d\n", taillen);
memmove(e,e+1,taillen);
//这里是重新对齐
size_t shift = ((parent->size+4) % sizeof(void*)) == 1 ? sizeof(void*) : 0;
if (shift)
memmove(((char*)cp)-shift,cp,(parent->size-taillen-1)*sizeof(raxNode**));
/* Move the remaining "tail" pointers at the right position as well. */
size_t valuelen = (parent->iskey && !parent->isnull) ? sizeof(void*) : 0;
memmove(((char*)c)-shift,c+1,taillen*sizeof(raxNode**)+valuelen);
//减少计数
parent->size--;
raxNode *newnode = rax_realloc(parent,raxNodeCurrentLength(parent));
if (newnode) {
debugnode("raxRemoveChild after", newnode);
}
return newnode ? newnode : parent;
}
看注释
* "FOO" -> "BAR" -> [] (2)
*删除FOO
* "FOOBAR" -> [] (2)
* |B| -> "AR" -> [] (1)
* "FOO" -> |-|
* |T| -> "ER" -> [] (2)
*
* 删除FOOTER
*
* "FOO" -> |B| -> "AR" -> [] (1)
*
* 压缩
*
* "FOOBAR" -> [] (1)
redis注释给了两种情况,意思是,删除了节点后会出现多个非key的压缩节点,可以合并为一个节点。
if (trycompress) {
debugf("After removing %.*s:\n", (int)len, s);
debugnode("Compression may be needed",h);
debugf("Seek start node\n");
/* Try to reach the upper node that is compressible.
* At the end of the loop 'h' will point to the first node we
* can try to compress and 'parent' to its parent. */
raxNode *parent;
//从stack获取节点 直到以下条件满足为止
while(1) {
parent = raxStackPop(&ts);
//压缩的条件是 非key节点 或者 size为1的压缩节点
if (!parent || parent->iskey ||
(!parent->iscompr && parent->size != 1)) break;
h = parent;
debugnode("Going up to",h);
}
raxNode *start = h;
/* Scan chain of nodes we can compress. */
size_t comprsize = h->size;
int nodes = 1;
//从h开始计算可以合并的节点数量
while(h->size != 0) {
//不断获取child 直到不满足合并条件
raxNode **cp = raxNodeLastChildPtr(h);
memcpy(&h,cp,sizeof(h));
if (h->iskey || (!h->iscompr && h->size != 1)) break;
if (comprsize + h->size > RAX_NODE_MAX_SIZE) break;
nodes++;
comprsize += h->size;
}
if (nodes > 1) {
//可以压缩nodes个节点 创建新的节点来保存
size_t nodesize =
sizeof(raxNode)+comprsize+raxPadding(comprsize)+sizeof(raxNode*);
raxNode *new = rax_malloc(nodesize);
//创建不了节点不能调整树 但是要保留树以便后面查询
if (new == NULL) {
raxStackFree(&ts);
return 1;
}
new->iskey = 0;
new->isnull = 0;
new->iscompr = 1;
new->size = comprsize;
rax->numnodes++;
//重新再遍历一遍
comprsize = 0;
h = start;
while(h->size != 0) {
//拷贝节点数据
memcpy(new->data+comprsize,h->data,h->size);
comprsize += h->size;
raxNode **cp = raxNodeLastChildPtr(h);
raxNode *tofree = h;
memcpy(&h,cp,sizeof(h));
//释放原节点内存
rax_free(tofree); rax->numnodes--;
if (h->iskey || (!h->iscompr && h->size != 1)) break;
}
debugnode("New node",new);
//修改指针 指向新节点
raxNode **cp = raxNodeLastChildPtr(new);
memcpy(cp,&h,sizeof(h));
//修改父节点
if (parent) {
raxNode **parentlink = raxFindParentLink(parent,start);
memcpy(parentlink,&new,sizeof(new));
} else {
rax->head = new;
}
debugf("Compressed %d nodes, %d total bytes\n",
nodes, (int)comprsize);
}
}
raxStackFree(&ts);
return 1;
步骤如下:
- 从Stack取出节点,不断向上获取直到不满足压缩条件
- 从这个节点不断向下获取,统计可以被压缩的节点数量
- 创建新节点,再次向下遍历将节点数据添加进新节点,并释放旧节点
- 新节点连接进rax树
通过以上步骤,可以将多个压缩节点合并成一个压缩节点,裁剪了树完成了节点的删除。
遍历节点
由于rax内部的key是按照字典序排序的,某个节点是key节点,左右两边都是按照字典序排列的。遍历方法如下:
void raxStart(raxIterator *it, rax *rt);
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len);
int raxNext(raxIterator *it);
int raxPrev(raxIterator *it);
通过raxStart来创建迭代器,然后调用raxSeek来初始化迭代器位置
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
int eq = 0, lt = 0, gt = 0, first = 0, last = 0;
it->stack.items = 0;
it->flags |= RAX_ITER_JUST_SEEKED;
it->flags &= ~RAX_ITER_EOF;
it->key_len = 0;
it->node = NULL;
/* Set flags according to the operator used to perform the seek. */
if (op[0] == '>') {
gt = 1;
if (op[1] == '=') eq = 1;
} else if (op[0] == '<') {
lt = 1;
if (op[1] == '=') eq = 1;
} else if (op[0] == '=') {
eq = 1;
} else if (op[0] == '^') {
first = 1;
} else if (op[0] == '$') {
last = 1;
} else {
errno = 0;
return 0; /* Error. */
}
ele表示元素,op为查找操作,支持:>、>=、<、<=、=、^(查找首个元素)、$(查找尾部元素)
上述代码解析op命令,并创建标志符
if (it->rt->numele == 0) {
it->flags |= RAX_ITER_EOF;
return 1;
}
if (first) {
return raxSeek(it,">=",NULL,0);
}
如果numele为0,表示没有元素并且设置EOF返回,如果查找首个元素,改写为大于等于NULL的元素
if (last) {
it->node = it->rt->head;
if (!raxSeekGreatest(it)) return 0;
assert(it->node->iskey);
it->data = raxGetData(it->node);
return 1;
}
int raxSeekGreatest(raxIterator *it) {
while(it->node->size) {
if (it->node->iscompr) {
if (!raxIteratorAddChars(it,it->node->data,
it->node->size)) return 0;
} else {
if (!raxIteratorAddChars(it,it->node->data+it->node->size-1,1))
return 0;
}
raxNode **cp = raxNodeLastChildPtr(it->node);
if (!raxStackPush(&it->stack,it->node)) return 0;
memcpy(&it->node,cp,sizeof(it->node));
}
return 1;
}
如果是last获取最后一个元素,那么只需要一直遍历每个元素的最右节点即可。
int splitpos = 0;
size_t i = raxLowWalk(it->rt,ele,len,&it->node,NULL,&splitpos,&it->stack);
/* Return OOM on incomplete stack info. */
if (it->stack.oom) return 0;
//设置了"="符号 后面的条件表示找到了对应的值 可以获取
if (eq && i == len && (!it->node->iscompr || splitpos == 0) &&
it->node->iskey)
{
if (!raxIteratorAddChars(it,ele,len)) return 0;
it->data = raxGetData(it->node);
然后通过raxLowWalk寻找节点,找到即可返回,如果没有找到节点,但是设置了<或者>,就要借用stack来进行辅助,核心是利用了raxIteratorPrevStep和raxIteratorNextStep函数,raxNext和raxPrev方法也是用到这两个。
int raxIteratorNextStep(raxIterator *it, int noup) {
if (it->flags & RAX_ITER_EOF) {
return 1;
} else if (it->flags & RAX_ITER_JUST_SEEKED) {
it->flags &= ~RAX_ITER_JUST_SEEKED;
return 1;
}
size_t orig_key_len = it->key_len;
size_t orig_stack_items = it->stack.items;
raxNode *orig_node = it->node;
raxIteratorNextStep首先处理flag情况比如到底了,然后保存原始key_len和item
while(1) {
int children = it->node->iscompr ? 1 : it->node->size;
//当前节点有字节点 返回
if (!noup && children) {
debugf("GO DEEPER\n");
if (!raxStackPush(&it->stack,it->node)) return 0;
raxNode **cp = raxNodeFirstChildPtr(it->node);
if (!raxIteratorAddChars(it,it->node->data,
it->node->iscompr ? it->node->size : 1)) return 0;
memcpy(&it->node,cp,sizeof(it->node));
if (it->node_cb && it->node_cb(&it->node))
memcpy(cp,&it->node,sizeof(it->node));
if (it->node->iskey) {
it->data = raxGetData(it->node);
return 1;
}
} else {
//找父节点
while(1) {
int old_noup = noup;
//已经循环到头部 无法找了 返回
if (!noup && it->node == it->rt->head) {
it->flags |= RAX_ITER_EOF;
it->stack.items = orig_stack_items;
it->key_len = orig_key_len;
it->node = orig_node;
return 1;
}
//如果当前节点没有字节点 寻找父节点的下一个子节点
unsigned char prevchild = it->key[it->key_len-1];
if (!noup) {
it->node = raxStackPop(&it->stack);
} else {
noup = 0;
}
/* Adjust the current key to represent the node we are
* at. */
int todel = it->node->iscompr ? it->node->size : 1;
raxIteratorDelChars(it,todel);
//至少又一个额外的子节点 尝试
if (!it->node->iscompr && it->node->size > (old_noup ? 0 : 1)) {
raxNode **cp = raxNodeFirstChildPtr(it->node);
int i = 0;
while (i < it->node->size) {
//遍历所有子节点 找到比当前key大的第一个
debugf("SCAN NEXT %c\n", it->node->data[i]);
if (it->node->data[i] > prevchild) break;
i++;
cp++;
}
//如果找到了子节点比当前大
if (i != it->node->size) {
debugf("SCAN found a new node\n");
raxIteratorAddChars(it,it->node->data+i,1);
if (!raxStackPush(&it->stack,it->node)) return 0;
memcpy(&it->node,cp,sizeof(it->node));
if (it->node_cb && it->node_cb(&it->node))
memcpy(cp,&it->node,sizeof(it->node));
//如果当前节点是key节点 就可以获取值返回 不然则终止内循环
if (it->node->iskey) {
it->data = raxGetData(it->node);
return 1;
}
break;
}
}
}
}
}
详细细节见注释
由于raxIteratorPrevStep是next的反方向,所以就不分析了,逻辑都是差不多的。
总结
rax的删除以及遍历分析完了,stream是基于rax编写的,后续会分析stream源码