文章目录

  • rax VS trie
  • 数据结构
  • 查找函数
  • 插入元素
  • 删除元素
  • 迭代
    • 向后迭代
    • 向前迭代
  • 附上copyright

rax VS trie

对于这个rax,那我们可更不陌生了,我觉得它就是把 trie 进行一个变种、压缩、强化。

关于 trie,字典树,咱也是手写过的:数据结构(12)-- 前缀树(字典树、Trie)

看一下前缀树和基数树的差别哈,以后有空了自己再手写一个基数树。

 * 假设要存三个字符串:foo, footer, foobar* 这是一个没有压缩的结构**              (f) ""*                \*                (o) "f"*                  \*                  (o) "fo"*                    \*                  [t   b] "foo"*                  /     \*         "foot" (e)     (a) "foob"*                /         \*      "foote" (r)         (r) "fooba"*              /             \*    "footer" []             [] "foobar"*  我们进行一下压缩:*                  ["foo"] ""*                     |*                  [t   b] "foo"*                  /     \*        "foot" ("er")    ("ar") "foob"*                 /          \*       "footer" []          [] "foobar"* redis中基本就是这个样子* 如果我们再插入一个first:**                    (f) ""*                    /*                 (i o) "f"*                 /   \*    "firs"  ("rst")  (o) "fo"*              /        \*    "first" []       [t   b] "foo"*                     /     \*           "foot" ("er")    ("ar") "foob"*                    /          \*          "footer" []          [] "foobar"* 那就变成了这个样子

RAX不仅可以存储字符串,同时还可以为这个字符串设置一个值,也就是 key-value。


数据结构

typedef struct rax {raxNode *head;   //指向头结点的指针uint64_t numele;  //元素个数(key的个数)uint64_t numnodes;  //节点个数
} rax;  //rax代表一个Rax树

typedef struct raxNode {uint32_t iskey:1;  //当前节点是否包含一个key,占用一个字节uint32_t isnull:1; //当前key对应的value是否为空,占用一个字节uint32_t iscompr:1;//当前节点是否为压缩节点,占用一个字节uint32_t size:29;  //压缩节点压缩的字符串长度 或 非压缩节点的子节点个数,占用29个字节//真抠,不错/* Data layout is as follows:** If node is not compressed we have 'size' bytes, one for each children* character, and 'size' raxNode pointers, point to each child node.* Note how the character is not stored in the children but in the* edge of the parents:** [header iscompr=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)** if node is compressed (iscompr bit is 1) the node has 1 children.* In that case the 'size' bytes of the string stored immediately at* the start of the data section, represent a sequence of successive* nodes linked one after the other, for which only the last one in* the sequence is actually represented as a node, and pointed to by* the current compressed node.** [header iscompr=1][xyz][z-ptr](value-ptr?)** Both compressed and not compressed nodes can represent a key* with associated data in the radix tree at any level (not just terminal* nodes).** If the node has an associated key (iskey=1) and is not NULL* (isnull=0), then after the raxNode pointers poiting to the* children, an additional value pointer is present (as you can see* in the representation above as "value-ptr" field).*/unsigned char data[];//data中包含:填充字段、当前节点包含的字符串以及子节点的指针、key对应的value指针
} raxNode;  //raxNode代表Rax中的一个节点

为了实现Rax树的遍历,redis提供了 RaxStack、raxIterator 两种结构。

/* Stack data structure used by raxLowWalk() in order to, optionally, return* a list of parent nodes to the caller. The nodes do not have a "parent"* field for space concerns, so we use the auxiliary stack when needed. */
#define RAX_STACK_STATIC_ITEMS 32
typedef struct raxStack {void **stack; /* Points to static_items or an heap allocated array. */size_t items, maxitems; /* Number of items contained and total space. *//* Up to RAXSTACK_STACK_ITEMS items we avoid to allocate on the heap* and use this static array of pointers instead. */void *static_items[RAX_STACK_STATIC_ITEMS];   //这是一个数组,其中的每一个元素都指向一个存储路径int oom; /* True if pushing into this stack failed for OOM at some point. */
} raxStack; //用于存储从根节点到当前节点的路径
typedef struct raxIterator {int flags;               //选择范围在下面rax *rt;                /* Radix tree we are iterating. */unsigned char *key;     //当前遍历到的keyvoid *data;             /* Data associated to this key. */size_t key_len;         /* Current key length. */size_t key_max;         /* Max key len the current key buffer can hold. */unsigned char key_static_string[RAX_ITER_STATIC_LEN];    //当key较大时,会从堆空间申请内存raxNode *node;          /* Current node. Only for unsafe iteration. */raxStack stack;         /* Stack used for unsafe iteration. */raxNodeCallback node_cb; /* Optional node callback. Normally set to NULL. */
} raxIterator;  //用于遍历Rax树中所有的key
#define RAX_ITER_JUST_SEEKED (1<<0)
/* Iterator was just seeked. Return current element for the first iteration and clear the flag. */
#define RAX_ITER_EOF (1<<1)    /* End of iteration reached. */
#define RAX_ITER_SAFE (1<<2)   /* Safe iterator, allows operations while iterating. But it is slower. */

查找函数

这里可以稍微先瞄一眼,反正后面都是要去手写一下基数树的,先偷瞄一眼。

绕过重重包围,我们发现其实最终调用的是下面这个函数进行查找:

(不做解释,英文注释已经够清楚了)

/* Low level function that walks the tree looking for the string* 's' of 'len' bytes. The function returns the number of characters* of the key that was possible to process: if the returned integer* is the same as 'len', then it means that the node corresponding to the* string was found (however it may not be a key in case the node->iskey is* zero or if simply we stopped in the middle of a compressed node, so that* 'splitpos' is non zero).** Otherwise if the returned integer is not the same as 'len', there was an* early stop during the tree walk because of a character mismatch.** The node where the search ended (because the full string was processed* or because there was an early stop) is returned by reference as* '*stopnode' if the passed pointer is not NULL. This node link in the* parent's node is returned as '*plink' if not NULL. Finally, if the* search stopped in a compressed node, '*splitpos' returns the index* inside the compressed node where the search ended. This is useful to* know where to split the node for insertion.** Note that when we stop in the middle of a compressed node with* a perfect match, this function will return a length equal to the* 'len' argument (all the key matched), and will return a *splitpos which is* always positive (that will represent the index of the character immediately* *after* the last match in the current compressed node).** When instead we stop at a compressed node and *splitpos is zero, it* means that the current node represents the key (that is, none of the* compressed node characters are needed to represent the key, just all* its parents nodes). */
static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode **stopnode, raxNode ***plink, int *splitpos, raxStack *ts) {raxNode *h = rax->head;raxNode **parentlink = &rax->head;size_t i = 0; /* Position in the string. */size_t j = 0; /* Position in the node children (or bytes if compressed).*/while(h->size && i < len) {debugnode("Lookup current node",h);unsigned char *v = h->data;if (h->iscompr) {for (j = 0; j < h->size && i < len; j++, i++) {if (v[j] != s[i]) break;}if (j != h->size) break;} else {/* Even when h->size is large, linear scan provides good* performances compared to other approaches that are in theory* more sounding, like performing a binary search. */for (j = 0; j < h->size; j++) {if (v[j] == s[i]) break;}if (j == h->size) break;i++;}if (ts) raxStackPush(ts,h); /* Save stack of parent nodes. */raxNode **children = raxNodeFirstChildPtr(h);if (h->iscompr) j = 0; /* Compressed node only child is at index 0. */memcpy(&h,children+j,sizeof(h));parentlink = children+j;j = 0; /* If the new node is non compressed and we do notiterate again (since i == len) set the splitposition to 0 to signal this node representsthe searched key. */}debugnode("Lookup stop node is",h);if (stopnode) *stopnode = h;if (plink) *plink = parentlink;if (splitpos && h->iscompr) *splitpos = j;return i;
}

插入元素

我们发现真正插入的函数是这个(有点长哈)

int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old, int overwrite) {size_t i;int j = 0; /* Split position. If raxLowWalk() stops in a compressednode, the index 'j' represents the char we stopped within thecompressed node, that is, the position where to split thenode for insertion. */raxNode *h, **parentlink;debugf("### Insert %.*s with value %p\n", (int)len, s, data);i = raxLowWalk(rax,s,len,&h,&parentlink,&j,NULL);/* If i == len we walked following the whole string. If we are not* in the middle of a compressed node, the string is either already* inserted or this middle node is currently not a key, but can represent* our key. We have just to reallocate the node and make space for the* data pointer. */if (i == len && (!h->iscompr || j == 0 /* not in the middle if j is 0 */)) {debugf("### Insert: node representing key exists\n");/* Make space for the value pointer if needed. */if (!h->iskey || (h->isnull && overwrite)) {h = raxReallocForData(h,data);if (h) memcpy(parentlink,&h,sizeof(h));}if (h == NULL) {errno = ENOMEM;return 0;}/* Update the existing key if there is already one. */if (h->iskey) {if (old) *old = raxGetData(h);if (overwrite) raxSetData(h,data);errno = 0;return 0; /* Element already exists. */}/* Otherwise set the node as a key. Note that raxSetData()* will set h->iskey. */raxSetData(h,data);rax->numele++;return 1; /* Element inserted. */}/* If the node we stopped at is a compressed node, we need to* split it before to continue.** Splitting a compressed node have a few possible cases.* Imagine that the node 'h' we are currently at is a compressed* node contaning the string "ANNIBALE" (it means that it represents* nodes A -> N -> N -> I -> B -> A -> L -> E with the only child* pointer of this node pointing at the 'E' node, because remember that* we have characters at the edges of the graph, not inside the nodes* themselves.** In order to show a real case imagine our node to also point to* another compressed node, that finally points at the node without* children, representing 'O':**     "ANNIBALE" -> "SCO" -> []** When inserting we may face the following cases. Note that all the cases* require the insertion of a non compressed node with exactly two* children, except for the last case which just requires splitting a* compressed node.** 1) Inserting "ANNIENTARE"**               |B| -> "ALE" -> "SCO" -> []*     "ANNI" -> |-|*               |E| -> (... continue algo ...) "NTARE" -> []** 2) Inserting "ANNIBALI"**                  |E| -> "SCO" -> []*     "ANNIBAL" -> |-|*                  |I| -> (... continue algo ...) []** 3) Inserting "AGO" (Like case 1, but set iscompr = 0 into original node)**            |N| -> "NIBALE" -> "SCO" -> []*     |A| -> |-|*            |G| -> (... continue algo ...) |O| -> []** 4) Inserting "CIAO"**     |A| -> "NNIBALE" -> "SCO" -> []*     |-|*     |C| -> (... continue algo ...) "IAO" -> []** 5) Inserting "ANNI"**     "ANNI" -> "BALE" -> "SCO" -> []** The final algorithm for insertion covering all the above cases is as* follows.** ============================= ALGO 1 =============================** For the above cases 1 to 4, that is, all cases where we stopped in* the middle of a compressed node for a character mismatch, do:** Let $SPLITPOS be the zero-based index at which, in the* compressed node array of characters, we found the mismatching* character. For example if the node contains "ANNIBALE" and we add* "ANNIENTARE" the $SPLITPOS is 4, that is, the index at which the* mismatching character is found.** 1. Save the current compressed node $NEXT pointer (the pointer to the*    child element, that is always present in compressed nodes).** 2. Create "split node" having as child the non common letter*    at the compressed node. The other non common letter (at the key)*    will be added later as we continue the normal insertion algorithm*    at step "6".** 3a. IF $SPLITPOS == 0:*     Replace the old node with the split node, by copying the auxiliary*     data if any. Fix parent's reference. Free old node eventually*     (we still need its data for the next steps of the algorithm).** 3b. IF $SPLITPOS != 0:*     Trim the compressed node (reallocating it as well) in order to*     contain $splitpos characters. Change chilid pointer in order to link*     to the split node. If new compressed node len is just 1, set*     iscompr to 0 (layout is the same). Fix parent's reference.** 4a. IF the postfix len (the length of the remaining string of the*     original compressed node after the split character) is non zero,*     create a "postfix node". If the postfix node has just one character*     set iscompr to 0, otherwise iscompr to 1. Set the postfix node*     child pointer to $NEXT.** 4b. IF the postfix len is zero, just use $NEXT as postfix pointer.** 5. Set child[0] of split node to postfix node.** 6. Set the split node as the current node, set current index at child[1]*    and continue insertion algorithm as usually.** ============================= ALGO 2 =============================** For case 5, that is, if we stopped in the middle of a compressed* node but no mismatch was found, do:** Let $SPLITPOS be the zero-based index at which, in the* compressed node array of characters, we stopped iterating because* there were no more keys character to match. So in the example of* the node "ANNIBALE", addig the string "ANNI", the $SPLITPOS is 4.** 1. Save the current compressed node $NEXT pointer (the pointer to the*    child element, that is always present in compressed nodes).** 2. Create a "postfix node" containing all the characters from $SPLITPOS*    to the end. Use $NEXT as the postfix node child pointer.*    If the postfix node length is 1, set iscompr to 0.*    Set the node as a key with the associated value of the new*    inserted key.** 3. Trim the current node to contain the first $SPLITPOS characters.*    As usually if the new node length is just 1, set iscompr to 0.*    Take the iskey / associated value as it was in the orignal node.*    Fix the parent's reference.** 4. Set the postfix node as the only child pointer of the trimmed*    node created at step 1.*//* ------------------------- ALGORITHM 1 --------------------------- */if (h->iscompr && i != len) {debugf("ALGO 1: Stopped at compressed node %.*s (%p)\n",h->size, h->data, (void*)h);debugf("Still to insert: %.*s\n", (int)(len-i), s+i);debugf("Splitting at %d: '%c'\n", j, ((char*)h->data)[j]);debugf("Other (key) letter is '%c'\n", s[i]);/* 1: Save next pointer. */raxNode **childfield = raxNodeLastChildPtr(h);raxNode *next;memcpy(&next,childfield,sizeof(next));debugf("Next is %p\n", (void*)next);debugf("iskey %d\n", h->iskey);if (h->iskey) {debugf("key value is %p\n", raxGetData(h));}/* Set the length of the additional nodes we will need. */size_t trimmedlen = j;size_t postfixlen = h->size - j - 1;int split_node_is_key = !trimmedlen && h->iskey && !h->isnull;size_t nodesize;/* 2: Create the split node. Also allocate the other nodes we'll need*    ASAP, so that it will be simpler to handle OOM. */raxNode *splitnode = raxNewNode(1, split_node_is_key);raxNode *trimmed = NULL;raxNode *postfix = NULL;if (trimmedlen) {nodesize = sizeof(raxNode)+trimmedlen+raxPadding(trimmedlen)+sizeof(raxNode*);if (h->iskey && !h->isnull) nodesize += sizeof(void*);trimmed = rax_malloc(nodesize);}if (postfixlen) {nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+sizeof(raxNode*);postfix = rax_malloc(nodesize);}/* OOM? Abort now that the tree is untouched. */if (splitnode == NULL ||(trimmedlen && trimmed == NULL) ||(postfixlen && postfix == NULL)){rax_free(splitnode);rax_free(trimmed);rax_free(postfix);errno = ENOMEM;return 0;}splitnode->data[0] = h->data[j];if (j == 0) {/* 3a: Replace the old node with the split node. */if (h->iskey) {void *ndata = raxGetData(h);raxSetData(splitnode,ndata);}memcpy(parentlink,&splitnode,sizeof(splitnode));} else {/* 3b: Trim the compressed node. */trimmed->size = j;memcpy(trimmed->data,h->data,j);trimmed->iscompr = j > 1 ? 1 : 0;trimmed->iskey = h->iskey;trimmed->isnull = h->isnull;if (h->iskey && !h->isnull) {void *ndata = raxGetData(h);raxSetData(trimmed,ndata);}raxNode **cp = raxNodeLastChildPtr(trimmed);memcpy(cp,&splitnode,sizeof(splitnode));memcpy(parentlink,&trimmed,sizeof(trimmed));parentlink = cp; /* Set parentlink to splitnode parent. */rax->numnodes++;}/* 4: Create the postfix node: what remains of the original* compressed node after the split. */if (postfixlen) {/* 4a: create a postfix node. */postfix->iskey = 0;postfix->isnull = 0;postfix->size = postfixlen;postfix->iscompr = postfixlen > 1;memcpy(postfix->data,h->data+j+1,postfixlen);raxNode **cp = raxNodeLastChildPtr(postfix);memcpy(cp,&next,sizeof(next));rax->numnodes++;} else {/* 4b: just use next as postfix node. */postfix = next;}/* 5: Set splitnode first child as the postfix node. */raxNode **splitchild = raxNodeLastChildPtr(splitnode);memcpy(splitchild,&postfix,sizeof(postfix));/* 6. Continue insertion: this will cause the splitnode to* get a new child (the non common character at the currently* inserted key). */rax_free(h);h = splitnode;} else if (h->iscompr && i == len) {/* ------------------------- ALGORITHM 2 --------------------------- */debugf("ALGO 2: Stopped at compressed node %.*s (%p) j = %d\n",h->size, h->data, (void*)h, j);/* Allocate postfix & trimmed nodes ASAP to fail for OOM gracefully. */size_t postfixlen = h->size - j;size_t nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+sizeof(raxNode*);if (data != NULL) nodesize += sizeof(void*);raxNode *postfix = rax_malloc(nodesize);nodesize = sizeof(raxNode)+j+raxPadding(j)+sizeof(raxNode*);if (h->iskey && !h->isnull) nodesize += sizeof(void*);raxNode *trimmed = rax_malloc(nodesize);if (postfix == NULL || trimmed == NULL) {rax_free(postfix);rax_free(trimmed);errno = ENOMEM;return 0;}/* 1: Save next pointer. */raxNode **childfield = raxNodeLastChildPtr(h);raxNode *next;memcpy(&next,childfield,sizeof(next));/* 2: Create the postfix node. */postfix->size = postfixlen;postfix->iscompr = postfixlen > 1;postfix->iskey = 1;postfix->isnull = 0;memcpy(postfix->data,h->data+j,postfixlen);raxSetData(postfix,data);raxNode **cp = raxNodeLastChildPtr(postfix);memcpy(cp,&next,sizeof(next));rax->numnodes++;/* 3: Trim the compressed node. */trimmed->size = j;trimmed->iscompr = j > 1;trimmed->iskey = 0;trimmed->isnull = 0;memcpy(trimmed->data,h->data,j);memcpy(parentlink,&trimmed,sizeof(trimmed));if (h->iskey) {void *aux = raxGetData(h);raxSetData(trimmed,aux);}/* Fix the trimmed node child pointer to point to* the postfix node. */cp = raxNodeLastChildPtr(trimmed);memcpy(cp,&postfix,sizeof(postfix));/* Finish! We don't need to continue with the insertion* algorithm for ALGO 2. The key is already inserted. */rax->numele++;rax_free(h);return 1; /* Key inserted. */}/* We walked the radix tree as far as we could, but still there are left* chars in our string. We need to insert the missing nodes. */while(i < len) {raxNode *child;/* If this node is going to have a single child, and there* are other characters, so that that would result in a chain* of single-childed nodes, turn it into a compressed node. */if (h->size == 0 && len-i > 1) {debugf("Inserting compressed node\n");size_t comprsize = len-i;if (comprsize > RAX_NODE_MAX_SIZE)comprsize = RAX_NODE_MAX_SIZE;raxNode *newh = raxCompressNode(h,s+i,comprsize,&child);if (newh == NULL) goto oom;h = newh;memcpy(parentlink,&h,sizeof(h));parentlink = raxNodeLastChildPtr(h);i += comprsize;} else {debugf("Inserting normal node\n");raxNode **new_parentlink;raxNode *newh = raxAddChild(h,s[i],&child,&new_parentlink);if (newh == NULL) goto oom;h = newh;memcpy(parentlink,&h,sizeof(h));parentlink = new_parentlink;i++;}rax->numnodes++;h = child;}raxNode *newh = raxReallocForData(h,data);if (newh == NULL) goto oom;h = newh;if (!h->iskey) rax->numele++;raxSetData(h,data);memcpy(parentlink,&h,sizeof(h));return 1; /* Element inserted. */oom:/* This code path handles out of memory after part of the sub-tree was* already modified. Set the node as a key, and then remove it. However we* do that only if the node is a terminal node, otherwise if the OOM* happened reallocating a node in the middle, we don't need to free* anything. */if (h->size == 0) {h->isnull = 1;h->iskey = 1;rax->numele++; /* Compensate the next remove. */assert(raxRemove(rax,s,i,NULL) != 0);}errno = ENOMEM;return 0;
}

删除元素

删除要注意,删除之后可能要压缩一下哦!!!


/* Remove the specified item. Returns 1 if the item was found and* deleted, 0 otherwise. */
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--;/* 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. */int trycompress = 0; /* Will be set to 1 if we should try to optimize thetree resulting from the deletion. */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() */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:** 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:*** "FOO" -> "BAR" -> [] (2)*           (1)** After the removal of "FOO" the tree can be compressed as:** "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. */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;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);/* Now 'h' points to the first node that we still need to use,* so our new node child pointer will point to it. */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;
}

迭代

向后迭代

真的,到这里我已经要看吐了。。。

/* Do an iteration step towards the next element. At the end of the step the* iterator key will represent the (new) current key. If it is not possible* to step in the specified direction since there are no longer elements, the* iterator is flagged with RAX_ITER_EOF.** If 'noup' is true the function starts directly scanning for the next* lexicographically smaller children, and the current node is already assumed* to be the parent of the last key node, so the first operation to go back to* the parent will be skipped. This option is used by raxSeek() when* implementing seeking a non existing element with the ">" or "<" options:* the starting node is not a key in that particular case, so we start the scan* from a node that does not represent the key set.** The function returns 1 on success or 0 on out of memory. */
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;}/* Save key len, stack items and the node where we are currently* so that on iterator EOF we can restore the current key and state. */size_t orig_key_len = it->key_len;size_t orig_stack_items = it->stack.items;raxNode *orig_node = it->node;while(1) {int children = it->node->iscompr ? 1 : it->node->size;if (!noup && children) {debugf("GO DEEPER\n");/* Seek the lexicographically smaller key in this subtree, which* is the first one found always going torwards the first child* of every successive node. */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));/* Call the node callback if any, and replace the node pointer* if the callback returns true. */if (it->node_cb && it->node_cb(&it->node))memcpy(cp,&it->node,sizeof(it->node));/* For "next" step, stop every time we find a key along the* way, since the key is lexicograhically smaller compared to* what follows in the sub-children. */if (it->node->iskey) {it->data = raxGetData(it->node);return 1;}} else {/* If we finished exporing the previous sub-tree, switch to the* new one: go upper until a node is found where there are* children representing keys lexicographically greater than the* current key. */while(1) {int old_noup = noup;/* Already on head? Can't go up, iteration finished. */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;}/* If there are no children at the current node, try parent's* next child. */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);/* Try visiting the next child if there was at least one* additional child. */if (!it->node->iscompr && it->node->size > (old_noup ? 0 : 1)) {raxNode **cp = raxNodeFirstChildPtr(it->node);int i = 0;while (i < it->node->size) {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));/* Call the node callback if any, and replace the node* pointer if the callback returns true. */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;}break;}}}}}
}

向前迭代

/* Like raxIteratorNextStep() but implements an iteration step moving* to the lexicographically previous element. The 'noup' option has a similar* effect to the one of raxIteratorNextStep(). */
int raxIteratorPrevStep(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;}/* Save key len, stack items and the node where we are currently* so that on iterator EOF we can restore the current key and state. */size_t orig_key_len = it->key_len;size_t orig_stack_items = it->stack.items;raxNode *orig_node = it->node;while(1) {int old_noup = noup;/* Already on head? Can't go up, iteration finished. */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);/* Try visiting the prev child if there is at least one* child. */if (!it->node->iscompr && it->node->size > (old_noup ? 0 : 1)) {raxNode **cp = raxNodeLastChildPtr(it->node);int i = it->node->size-1;while (i >= 0) {debugf("SCAN PREV %c\n", it->node->data[i]);if (it->node->data[i] < prevchild) break;i--;cp--;}/* If we found a new subtree to explore in this node,* go deeper following all the last children in order to* find the key lexicographically greater. */if (i != -1) {debugf("SCAN found a new node\n");/* Enter the node we just found. */if (!raxIteratorAddChars(it,it->node->data+i,1)) return 0;if (!raxStackPush(&it->stack,it->node)) return 0;memcpy(&it->node,cp,sizeof(it->node));/* Seek sub-tree max. */if (!raxSeekGreatest(it)) return 0;}}/* Return the key: this could be the key we found scanning a new* subtree, or if we did not find a new subtree to explore here,* before giving up with this node, check if it's a key itself. */if (it->node->iskey) {it->data = raxGetData(it->node);return 1;}}
}

附上copyright

/* Rax -- A radix tree implementation.** Version 1.2 -- 7 February 2019** Copyright (c) 2017-2019, Salvatore Sanfilippo <antirez at gmail dot com>* All rights reserved.** Redistribution and use in source and binary forms, with or without* modification, are permitted provided that the following conditions are met:**   * Redistributions of source code must retain the above copyright notice,*     this list of conditions and the following disclaimer.*   * Redistributions in binary form must reproduce the above copyright*     notice, this list of conditions and the following disclaimer in the*     documentation and/or other materials provided with the distribution.*   * Neither the name of Redis nor the names of its contributors may be used*     to endorse or promote products derived from this software without*     specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE* POSSIBILITY OF SUCH DAMAGE.*/

【redis源码学习】rax,我愿称之为“升级版字典树”相关推荐

  1. 【Redis学习笔记】2018-05-30 Redis源码学习之Ziplist、Server

    作者:施洪宝 顺风车运营研发团队 一. 压缩列表 压缩列表是Redis的关键数据结构之一.目前已经有大量的相关资料,下面几个链接都已经对Ziplist进行了详细的介绍. http://origin.r ...

  2. redis源码学习笔记目录

    Redis源码分析(零)学习路径笔记 Redis源码分析(一)redis.c //redis-server.c Redis源码分析(二)redis-cli.c Redis源码剖析(三)--基础数据结构 ...

  3. Redis源码学习(20),学习感悟

      最近学习Redis源码也有半个月的时间了,有不少收获也有不少感悟,今天来好好聊聊我学习的感悟. 1 发现问题   人非圣贤孰能无过,只要是人难免会犯错,回顾我之前的学习历程,其实是可以发现不少的问 ...

  4. 【redis源码学习】simple dynamic strings(简单动态字符串 sds)

    文章目录 接 化 sds 结构分析 基本操作 创建字符串 释放字符串 sdsMakeRoomFor 扩容 小tip:`__attribute__ ((__packed__))` 发 接 阅读源码之前, ...

  5. 【Redis学习笔记】2018-06-14 Redis源码学习之sentinel

    顺风车运营研发团队 方波 sentinel是redis的高可用解决方案,由一个或多个sentinel实例组成的系统可以同时监听多组master-slave实例(后面简称一组),当发现master进入下 ...

  6. Redis源码学习(13),t_set.c 学习(一),sadd,srem 命令学习

      学习完 t_string.c.t_list.c.t_hash.c文件后,现在开始学习 t_set.c 的代码,从文件名可以看到是相关集合相关命令的代码文件.总共5种数据结构,我们已经学习到第4个了 ...

  7. redis源码学习-03_动态字符串SDS

    概述 简单动态字符串(SDS, Simple Dynamic String)是 Redis 底层所使用的的字符串表示(而不是使用传统的 C 字符串). redis需要的不仅仅是一个字符串变量,而是一个 ...

  8. 结合redis设计与实现的redis源码学习-2-SDS(简单动态字符串)

    上一次我们学习了redis的内存分配方式,今天我们来学习redis最基本的数据结构SDS,在redis的数据库里,包含字符产值的简直对在底层都是由SDS实现的. SDS的基本数据结构是sdshdr结构 ...

  9. 【redis源码学习】redis 中的“消息队列” Stream

    文章目录 关于redis Stream Stream 结构 Stream 操作 添加消息 新增消费组 删除消息 裁剪信息流 释放消费组 查找元素 关于redis Stream redis stream ...

  10. Redis源码学习(6),t_list.c 学习(一),rpush、lpush命令实现学习

    前言   大体学习完t_string.c的代码,我们正式进入下一个文件的学习,这一次我们学习的是t_list.c文件,从文件名我们可以知道这是一个关于列表的相关命令的源代码.   在学习列表命令之前, ...

最新文章

  1. php视频录制插件,Chrome浏览器录屏扩展插件
  2. mysql的编译_Mysql编译安装
  3. vue 新窗口打开外链接
  4. java servlet 部署到tomcat_如何把spring boot项目部署到tomcat容器中
  5. 英特尔杀入游戏显卡市场:支持光追和AI超分辨率,挑战AMD英伟达
  6. Linux(CentOS6.4、CentOS6.3)下安装、配置PostgreSQL9.2
  7. 【优化算法】多目标水母搜索优化算法 (MOJS) 【含Matlab源码 248期】
  8. 数据结构与算法概念与理解
  9. Informatic中如何设置每月初,和每周五、周六跑批
  10. 第四届中国区块链开发大赛初评公布 超级链战队多个作品进入复赛
  11. Python Revisited Day 08 (高级程序设计技术)
  12. 主流浏览器发展史及其内核初探
  13. VMware下安装虚拟机windows server 2016
  14. python使用matplotlib 画柱状图代码_Python 使用 matplotlib 画柱状图教程
  15. RK3308开启UAC1功能及相关参数设置
  16. Android原生OS风格ROM包,小米5S 的LineageOS14.1刷机包 安卓7.1.1原生风格 20180203更新...
  17. 歪门邪道 Leetcode 463 Island Perimeter
  18. Qt + libVlc
  19. 千里走单骑:06-北京到上海骑记--Day5.风雨回家路
  20. BZOJ 4355: Play with sequence

热门文章

  1. 新浪OAuth客户端登陆另辟蹊径
  2. Azure Az-900认证 04——-考取AZ900所有知识点总结--获取证书!
  3. 《曾文正公家书》摘录一
  4. 微信公众平台消息接口开发(8)小黄鸡(小贱鸡)机器人
  5. js 判断图片和视频是否加载成功
  6. mysql sphinx windows安装_Sphinx在windows下如何安装使用
  7. CVAL,PVAL,SVAL宏定义
  8. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java基于框架的动漫网站设计与实现q6dcx
  9. 下载好的IDEA双击打不开,解决方法
  10. 【推荐】精选行政文书模板大全(调查报告+会议纪要+通知+通告+总结+规定等模板,共177份)