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

数据结构


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

  • 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

  • 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; //当前遍历到的key

void 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 not

iterate again (since i == len) set the split

position to 0 to signal this node represents

the 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 compressed

node, the index ‘j’ represents the char we stopped within the

compressed node, that is, the position where to split the

node 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" -> []
    
    1. Inserting “ANNIBALI”
  •              |E| -> "SCO" -> []
    
  • "ANNIBAL" -> |-|
    
  •              |I| -> (... continue algo ...) []
    
    1. Inserting “AGO” (Like case 1, but set iscompr = 0 into original node)
  •        |N| -> "NIBALE" -> "SCO" -> []
    
  • |A| -> |-|
    
  •        |G| -> (... continue algo ...) |O| -> []
    
    1. Inserting “CIAO”
  • |A| -> "NNIBALE" -> "SCO" -> []
    
  • |-|
    
  • |C| -> (... continue algo ...) "IAO" -> []
    
    1. 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).

    1. 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.

    1. Set child[0] of split node to postfix node.
    1. 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).

    1. 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.

    1. 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.

    1. 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 the

tree 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;

面试结束复盘查漏补缺

每次面试都是检验自己知识与技术实力的一次机会,面试结束后建议大家及时总结复盘,查漏补缺,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。

以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~

重要的事说三遍,关注+关注+关注!

历经30天,说说我的支付宝4面+美团4面+拼多多四面,侥幸全获Offer

image.png

更多笔记分享

历经30天,说说我的支付宝4面+美团4面+拼多多四面,侥幸全获Offer

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;

面试结束复盘查漏补缺

每次面试都是检验自己知识与技术实力的一次机会,面试结束后建议大家及时总结复盘,查漏补缺,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。

以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~

重要的事说三遍,关注+关注+关注!

[外链图片转存中…(img-6vjp8pwo-1714518695697)]

[外链图片转存中…(img-ItoVYgGN-1714518695698)]

更多笔记分享

[外链图片转存中…(img-ZP3HeEBz-1714518695698)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值