redis6.0.5之Rax阅读笔记3-删除和释放数据节点

 

************************************************************************************
/* Remove the specified item. Returns 1 if the item was found and
 * deleted, 0 otherwise. */
移除特定的项,返回1如果这个项被找到和删除,否则返回0
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--;  元素减少1

    /* If this node has no children, the deletion needs to reclaim the
     * no longer used nodes. This is an iterative process that needs to
     * walk the three upward, deleting all the nodes with just one child
     * that are not keys, until the head of the rax is reached or the first
     * node with more than one child is found. */
如果这个节点没有子节点,则删除操作需要回收不再使用的节点。
这是一个迭代的过程,需要向上遍历3个节点(和新增是相反操作,一个新增最多分成相连3部分,即3个节点),
删除所有只有一个孩子而且不是键的节点,直到基树的头节点或者第一个拥有超过一个孩子的节点。
    int trycompress = 0; /* Will be set to 1 if we should try to optimize the
                            tree resulting from the deletion. */
如果我们尝试优化删除后的树,那么就会设置为1
    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);  把父节点弹出(就是这个节点的上一层节点)
             /* If this node has more then one child, or actually holds
              * a key, stop here. */
              如果这个节点是一个键 或者  这个节点不是一个压缩节点并且不止一个子节点, 结束
              如果不是的话,就与可能需要压缩的节点合并
            if (h->iskey || (!h->iscompr && h->size != 1)) break;  
        }
        if (child) {
            debugf("Unlinking child %p from parent %p\n",
                (void*)child, (void*)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)); 将新的地址赋值给父节点的子指针
            }
            /* If after the removal the node has just a single child
             * and is not a key, we need to try to compress it. */
             如果移除的节点只有一个子节点并且不是一个键,我们需要尝试去压缩它
            if (new->size == 1 && new->iskey == 0) {
                trycompress = 1;
                h = new;
            }
        }
    } else if (h->size == 1) {
        /* If the node had just one child, after the removal of the key
         * further compression with adjacent nodes is pontentially possible. */
         如果这个节点只有一个孩子,经过键值的移除,进一步和相邻节点压缩是可能的
        trycompress = 1;
    }
    /* Don't try node compression if our nodes pointers stack is not
     * complete because of OOM while executing raxLowWalk() */
     如果用函数raxLowWalk在遍历基树的过程中发生了OOM,那么不用压缩
    if (trycompress && ts.oom) trycompress = 0;

    /* Recompression: if trycompress is true, 'h' points to a radix tree node
     * that changed in a way that could allow to compress nodes in this
     * sub-branch. Compressed nodes represent chains of nodes that are not
     * keys and have a single child, so there are two deletion events that
     * may alter the tree so that further compression is needed:
再次压缩:如果压缩表示为真,h指向一个基树节点,该店经过变化之后,在这个分支上允许压缩节点
压缩节点表示一个节点链,不是键并且只有一个孩子,所以两个删除事件可能会导致树需要进一步压缩
     * 1) A node with a single child was a key and now no longer is a key.
第一个事件是 一个节点只有一个孩子并且这个孩子是键,但是现在不是键了
     * 2) A node with two children now has just one child.
第二个是 一个节点有两个孩子,但是现在只有一个孩子了
     * We try to navigate upward till there are other nodes that can be
     * compressed, when we reach the upper node which is not a key and has
     * a single child, we scan the chain of children to collect the
     * compressable part of the tree, and replace the current node with the
     * new one, fixing the child pointer to reference the first non
     * compressable node.
我们尝试向上浏览基树直到没有其它的节点可以被压缩,
当我们到达一个不是键而且只有一个孩子的上层节点时,
我们扫描孩子链来搜集树的压缩部分,然后用新的压缩节点替换当前节点,
修补第一个非压缩节点的子指针(就是指向这个新的压缩节点)
     * Example of case "1". A tree stores the keys "FOO" = 1 and
     * "FOOBAR" = 2:
     情况1,拥有FOO 和 FOOBAR 两个元素
     * "FOO" -> "BAR" -> [] (2)
     *           (1)
     * After the removal of "FOO" the tree can be compressed as:
     * 删除元素FOO,节点可以压缩成如下情况
     * "FOOBAR" -> [] (2)

     * Example of case "2". A tree stores the keys "FOOBAR" = 1 and
     * "FOOTER" = 2:
     *
     *          |B| -> "AR" -> [] (1)
     * "FOO" -> |-|
     *          |T| -> "ER" -> [] (2)
     *
     * After the removal of "FOOTER" the resulting tree is:
     * "FOO" -> |B| -> "AR" -> [] (1)
     * That can be compressed into:
     * "FOOBAR" -> [] (1)
     */
    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. */
尝试到达压缩的上层节点。在循环之后,h指向第一个我们可以压缩的节点并且parent指向父节点
        raxNode *parent;
        while(1) {
            parent = raxStackPop(&ts);
            if (!parent || parent->iskey ||
                (!parent->iscompr && parent->size != 1)) break;
            h = parent;
            debugnode("Going up to",h);
        }
        raxNode *start = h; /* Compression starting node. */ 压缩开始节点

        /* Scan chain of nodes we can compress. */
        size_t comprsize = h->size;
        int nodes = 1;
        while(h->size != 0) {
            raxNode **cp = raxNodeLastChildPtr(h);
            memcpy(&h,cp,sizeof(h));
            if (h->iskey || (!h->iscompr && h->size != 1)) break;
            /* Stop here if going to the next node would result into
             * a compressed node larger than h->size can hold. */
             超过了压缩节点最大的限额,也必须停止
            if (comprsize + h->size > RAX_NODE_MAX_SIZE) break;
            nodes++;  可压缩节点计数
            comprsize += h->size;  可压缩字符计数
        }
        if (nodes > 1) {
            /* If we can compress, create the new node and populate it. */
            超过一个节点,意味着需要压缩,创建新节点,并且填充它
            size_t nodesize =
                sizeof(raxNode)+comprsize+raxPadding(comprsize)+sizeof(raxNode*);
            raxNode *new = rax_malloc(nodesize);
            /* An out of memory here just means we cannot optimize this
             * node, but the tree is left in a consistent state. */
             如果创建新节点内存分配不足,那么就不能优化这个节点,但是留下的树状态是一致完整的
            if (new == NULL) {
                raxStackFree(&ts);
                return 1;
            }
            new->iskey = 0;
            new->isnull = 0;
            new->iscompr = 1;
            new->size = comprsize;
            rax->numnodes++;

            /* Scan again, this time to populate the new node content and
             * to fix the new node child pointer. At the same time we free
             * all the nodes that we'll no longer use. */
        再次扫描可压缩节点,这次我们填充新节点并且填补新节点的子指针。
        同时我们释放所有不再使用的节点(就是被合并掉的节点)
            comprsize = 0;  注意到这里的值重新置为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--;  释放旧节点,节点数减1
                if (h->iskey || (!h->iscompr && h->size != 1)) break;
            }
            debugnode("New node",new);

            /* Now 'h' points to the first node that we still need to use,
             * so our new node child pointer will point to it. */
             现在h指向了第一个我们还需要使用的节点,所以我们的新节点也要指向这个节点
            raxNode **cp = raxNodeLastChildPtr(new); 让新节点的子指针指向原来后面的节点
            memcpy(cp,&h,sizeof(h));

            /* Fix parent link. */  填充父节点的链接
            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;
}
************************************************************************************
/* Low level child removal from node. The new node pointer (after the child
 * removal) is returned. Note that this function does not fix the pointer
 * of the parent node in its parent, so this task is up to the caller.
 * The function never fails for out of memory. */
底层的从节点移除子子节点。返回新的节点指针(在子节点被移除情况下)。
注意到这函数不会修复父节点中的指向子节点的指针,所以这个工作需要调用者处理
raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
    debugnode("raxRemoveChild before", parent);
    /* If parent is a compressed node (having a single child, as for definition
     * of the data structure), the removal of the child consists into turning
     * it into a normal node without children. */
如果父节点是一个压缩节点(从数据结构的定义上来说,只有一个子节点),
移除子节点包括转变它为一个没有孩子的普通节点
    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;
    }
    /* Otherwise we need to scan for the child pointer and memmove()
     * accordingly.
否则我们需要去扫描子指针和用函数memmove移动数据
     * 1. To start we seek the first element in both the children
     *    pointers and edge bytes in the node. */
1首先我们需要查找第一个元素,在节点的子指针和边缘字节中都需要找到
    raxNode **cp = raxNodeFirstChildPtr(parent);  这里是第一个子指针
    raxNode **c = cp;
    unsigned char *e = parent->data;  边缘数据

    /* 2. Search the child pointer to remove inside the array of children
     *    pointers. */
在子指针数组中搜寻待删除子指针
    while(1) {  一直循环直到内部中断为止
        raxNode *aux;
        memcpy(&aux,c,sizeof(aux));  从第一个子指针开始查找
        if (aux == child) break; 找到就停止
        c++; 按节点指针的大小递增
        e++; 字符的位置和子节点指针的顺序位置是一样的,但是e的指针大小是一个字节
    }

    /* 3. Remove the edge and the pointer by memmoving the remaining children
     *    pointer and edge bytes one position before. */

    int taillen = parent->size - (e - parent->data) - 1;
      尾部的长度 = 总长度      -   字符所在的偏移量 - 字符本身的长度一个1字符
    debugf("raxRemoveChild tail len: %d\n", taillen);
    memmove(e,e+1,taillen); 对边缘字符数组进行移动,去除掉待查找字符

    /* Compute the shift, that is the amount of bytes we should move our
     * child pointers to the left, since the removal of one edge character
     * and the corresponding padding change, may change the layout.
     * We just check if in the old version of the node there was at the
     * end just a single byte and all padding: in that case removing one char
     * will remove a whole sizeof(void*) word. */
计算偏移量, 就是我们需要往左边移动我们子节点指针的总字节数,因为移动一个边缘字符导致填充的改变,
可能会导致格式的改变。我们检查如果在老版本的节点中,如果只有一个字节和所有的填充(就是字符数组只有一个字节是单的),
:在这种案例下,移除一个字符就可以减少一个完整的sizeof(void*)大小的空间(其它情况下因为填充字符的存在,无需改变)
    size_t shift = ((parent->size+4) % sizeof(void*)) == 1 ? sizeof(void*) : 0;
                     (字符数组大小+ 头的4个字节 对 指针的大小取模 )  
                     如果恰好多1,那么在移除1个字符的情况下就可以去掉一个指针大小的空间了
    /* Move the children pointers before the deletion point. */
    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);
    将下一个子节点指针往前移动一个节点指针位置,
    (char*)c)-shift 这里的指针是按照字节计算, c+1 这里的指针是按照节点指针来计算

    /* 4. Update size. */
    parent->size--;  少了一个字符

    /* realloc the node according to the theoretical memory usage, to free
     * data if we are over-allocating right now. */
根据理论内存使用情况重新分配内存,以便在当前过渡分配时候释放数据
    raxNode *newnode = rax_realloc(parent,raxNodeCurrentLength(parent));
    if (newnode) {
        debugnode("raxRemoveChild after", newnode);
    }
    /* Note: if rax_realloc() fails we just return the old address, which
     * is valid. */
如果重新分配内存失败,我们返回有效的旧节点地址
    return newnode ? newnode : parent;
}
************************************************************************************
/* Find a key in the rax, returns raxNotFound special void pointer value
 * if the item was not found, otherwise the value associated with the
 * item is returned. */
在基树中查找一个键,如果找到返回键相关的值,找不到返回一个特殊的raxNotFound指针
void *raxFind(rax *rax, unsigned char *s, size_t len) {
    raxNode *h;

    debugf("### Lookup: %.*s\n", (int)len, s);
    int splitpos = 0;
    size_t i = raxLowWalk(rax,s,len,&h,NULL,&splitpos,NULL);  查找
    if (i != len || (h->iscompr && splitpos != 0) || !h->iskey) 没有找到
    第一种情况, 长度都不能匹配,肯定找不到
    第二种情况,长度够了,但是是在压缩节点内部,显然不是一个键
    第三种情况,找到的节点不是键
    这三种情况表示该键在基树中不存在
        return raxNotFound;
    return raxGetData(h); 返回相关联数据
}
************************************************************************************
/* Return the memory address where the 'parent' node stores the specified
 * 'child' pointer, so that the caller can update the pointer with another
 * one if needed. The function assumes it will find a match, otherwise the
 * operation is an undefined behavior (it will continue scanning the
 * memory without any bound checking). */
返回父节点保存的特定子节点指针的内存地址,这样调用者在有需求的情况下可以用另外的指针代替它。
这个函数假设会找到一个匹配项,否则这个操作就是一个没有定义的表现(会持续的没有边界的扫描内存)
这个函数好奇怪,为什么不做个边界检查呢 ? 万一传错了,程序就挂了。。。
raxNode **raxFindParentLink(raxNode *parent, raxNode *child) {
    raxNode **cp = raxNodeFirstChildPtr(parent);  第一个子节点的指针位置
    raxNode *c;
    while(1) {
        memcpy(&c,cp,sizeof(c)); 把地址赋值给c
        if (c == child) break;  用指针地址比较
        cp++; 不等继续下一个
    }
    return cp;
}
************************************************************************************
/* This is the core of raxFree(): performs a depth-first scan of the
 * tree and releases all the nodes found. */
这个函数是raxFree的核心,对基树执行了一个深度优先的扫描,释放所有找到的节点
void raxRecursiveFree(rax *rax, raxNode *n, void (*free_callback)(void*)) {
    debugnode("free traversing",n);
    int numchildren = n->iscompr ? 1 : n->size;  是否是压缩节点,判断子指针的个数
    raxNode **cp = raxNodeLastChildPtr(n);  最后一个子指针
    while(numchildren--) {  从后往前
        raxNode *child;
        memcpy(&child,cp,sizeof(child));  赋值给新创建的节点,做递归用
        raxRecursiveFree(rax,child,free_callback);  递归子节点
        cp--;  往前移
    }
    debugnode("free depth-first",n);
    if (free_callback && n->iskey && !n->isnull)  有回调函数,是一个键,有值
        free_callback(raxGetData(n)); 通过回调函数释放键值
    rax_free(n); 释放节点
    rax->numnodes--;  节点数减1
}
************************************************************************************
/* Free a whole radix tree, calling the specified callback in order to
 * free the auxiliary data. */
释放整颗基树,调用特定的回调函数释放键值
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)) {
    raxRecursiveFree(rax,rax->head,free_callback);  释放每个节点
    assert(rax->numnodes == 0);
    rax_free(rax); 释放树本身
}
************************************************************************************
/* Free a whole radix tree. */ 释放整棵树
void raxFree(rax *rax) {
    raxFreeWithCallback(rax,NULL);
}
************************************************************************************

 

posted on 2021-01-14 11:36  子虚乌有  阅读(207)  评论(0)    收藏  举报