]>
git.saurik.com Git - redis.git/blob - ziplist.c
1 /* Memory layout of a ziplist, containing "foo", "bar", "quux":
2 * <zlbytes><zllen><len>"foo"<len>"bar"<len>"quux"
4 * <zlbytes> is an unsigned integer to hold the number of bytes that
5 * the ziplist occupies. This is stored to not have to traverse the ziplist
6 * to know the new length when pushing.
8 * <zllen> is the number of items in the ziplist. When this value is
9 * greater than 254, we need to traverse the entire list to know
10 * how many items it holds.
12 * <len> is the number of bytes occupied by a single entry. When this
13 * number is greater than 253, the length will occupy 5 bytes, where
14 * the extra bytes contain an unsigned integer to hold the length.
27 #define ZIP_BIGLEN 254
31 #define ZIP_ENC_SHORT 1
33 #define ZIP_ENC_LLONG 3
34 #define ZIP_ENCODING(p) ((p)[0] >> 6)
36 /* Length encoding for raw entries */
37 #define ZIP_LEN_INLINE 0
38 #define ZIP_LEN_UINT16 1
39 #define ZIP_LEN_UINT32 2
42 #define ZIPLIST_BYTES(zl) (*((unsigned int*)(zl)))
43 #define ZIPLIST_LENGTH(zl) (*((zl)+sizeof(unsigned int)))
44 #define ZIPLIST_HEADER_SIZE (sizeof(unsigned int)+1)
45 #define ZIPLIST_INCR_LENGTH(zl,incr) { \
46 if (ZIPLIST_LENGTH(zl) < ZIP_BIGLEN) ZIPLIST_LENGTH(zl)+=incr; }
48 /* Return bytes needed to store integer encoded by 'encoding' */
49 static unsigned int zipEncodingSize(char encoding
) {
50 if (encoding
== ZIP_ENC_SHORT
) {
51 return sizeof(short int);
52 } else if (encoding
== ZIP_ENC_INT
) {
54 } else if (encoding
== ZIP_ENC_LLONG
) {
55 return sizeof(long long);
60 /* Decode the encoded length pointed by 'p'. If a pointer to 'lensize' is
61 * provided, it is set to the number of bytes required to encode the length. */
62 static unsigned int zipDecodeLength(unsigned char *p
, unsigned int *lensize
) {
63 unsigned char encoding
= ZIP_ENCODING(p
), lenenc
;
66 if (encoding
== ZIP_ENC_RAW
) {
67 lenenc
= (p
[0] >> 4) & 0x3;
68 if (lenenc
== ZIP_LEN_INLINE
) {
70 if (lensize
) *lensize
= 1;
71 } else if (lenenc
== ZIP_LEN_UINT16
) {
72 len
= p
[1] | (p
[2] << 8);
73 if (lensize
) *lensize
= 3;
75 len
= p
[1] | (p
[2] << 8) | (p
[3] << 16) | (p
[4] << 24);
76 if (lensize
) *lensize
= 5;
79 len
= zipEncodingSize(encoding
);
80 if (lensize
) *lensize
= 1;
85 /* Encode the length 'l' writing it in 'p'. If p is NULL it just returns
86 * the amount of bytes required to encode such a length. */
87 static unsigned int zipEncodeLength(unsigned char *p
, char encoding
, unsigned int rawlen
) {
88 unsigned char len
= 1, lenenc
, buf
[5];
89 if (encoding
== ZIP_ENC_RAW
) {
92 lenenc
= ZIP_LEN_INLINE
;
94 } else if (rawlen
<= 0xffff) {
97 lenenc
= ZIP_LEN_UINT16
;
98 buf
[1] = (rawlen
) & 0xff;
99 buf
[2] = (rawlen
>> 8) & 0xff;
103 lenenc
= ZIP_LEN_UINT32
;
104 buf
[1] = (rawlen
) & 0xff;
105 buf
[2] = (rawlen
>> 8) & 0xff;
106 buf
[3] = (rawlen
>> 16) & 0xff;
107 buf
[4] = (rawlen
>> 24) & 0xff;
109 buf
[0] = (lenenc
<< 4) | (buf
[0] & 0xf);
113 /* Apparently we need to store the length in 'p' */
114 buf
[0] = (encoding
<< 6) | (buf
[0] & 0x3f);
119 /* Check if string pointed to by 'entry' can be encoded as an integer.
120 * Stores the integer value in 'v' and its encoding in 'encoding'.
121 * Warning: this function requires a NULL-terminated string! */
122 static int zipTryEncoding(unsigned char *entry
, long long *v
, char *encoding
) {
126 if (entry
[0] == '-' || (entry
[0] >= '0' && entry
[0] <= '9')) {
127 value
= strtoll(entry
,&eptr
,10);
128 if (eptr
[0] != '\0') return 0;
129 if (value
>= SHRT_MIN
&& value
<= SHRT_MAX
) {
130 *encoding
= ZIP_ENC_SHORT
;
131 } else if (value
>= INT_MIN
&& value
<= INT_MAX
) {
132 *encoding
= ZIP_ENC_INT
;
134 *encoding
= ZIP_ENC_LLONG
;
142 /* Store integer 'value' at 'p', encoded as 'encoding' */
143 static void zipSaveInteger(unsigned char *p
, long long value
, char encoding
) {
147 if (encoding
== ZIP_ENC_SHORT
) {
149 memcpy(p
,&s
,sizeof(s
));
150 } else if (encoding
== ZIP_ENC_INT
) {
152 memcpy(p
,&i
,sizeof(i
));
153 } else if (encoding
== ZIP_ENC_LLONG
) {
155 memcpy(p
,&l
,sizeof(l
));
161 /* Read integer encoded as 'encoding' from 'p' */
162 static long long zipLoadInteger(unsigned char *p
, char encoding
) {
166 if (encoding
== ZIP_ENC_SHORT
) {
167 memcpy(&s
,p
,sizeof(s
));
169 } else if (encoding
== ZIP_ENC_INT
) {
170 memcpy(&i
,p
,sizeof(i
));
172 } else if (encoding
== ZIP_ENC_LLONG
) {
173 memcpy(&l
,p
,sizeof(l
));
181 /* Return the total amount used by an entry (encoded length + payload). */
182 static unsigned int zipRawEntryLength(unsigned char *p
) {
183 unsigned int lensize
, len
;
184 len
= zipDecodeLength(p
, &lensize
);
185 return lensize
+ len
;
188 /* Create a new empty ziplist. */
189 unsigned char *ziplistNew(void) {
190 unsigned int bytes
= ZIPLIST_HEADER_SIZE
+1;
191 unsigned char *zl
= zmalloc(bytes
);
192 ZIPLIST_BYTES(zl
) = bytes
;
193 ZIPLIST_LENGTH(zl
) = 0;
194 zl
[bytes
-1] = ZIP_END
;
198 /* Resize the ziplist. */
199 static unsigned char *ziplistResize(unsigned char *zl
, unsigned int len
) {
200 zl
= zrealloc(zl
,len
);
201 ZIPLIST_BYTES(zl
) = len
;
206 static unsigned char *ziplistHead(unsigned char *zl
) {
207 return zl
+ZIPLIST_HEADER_SIZE
;
210 static unsigned char *ziplistTail(unsigned char *zl
) {
211 unsigned char *p
, *q
;
212 p
= q
= ziplistHead(zl
);
213 while (*p
!= ZIP_END
) {
215 p
+= zipRawEntryLength(p
);
220 unsigned char *ziplistPush(unsigned char *zl
, unsigned char *entry
, unsigned int elen
, int where
) {
221 unsigned int curlen
= ZIPLIST_BYTES(zl
), reqlen
;
223 char encoding
= ZIP_ENC_RAW
;
226 /* See if the entry can be encoded */
227 if (zipTryEncoding(entry
,&value
,&encoding
)) {
228 reqlen
= zipEncodingSize(encoding
);
232 reqlen
+= zipEncodeLength(NULL
,encoding
,elen
);
234 /* Resize the ziplist and move if needed */
235 zl
= ziplistResize(zl
,curlen
+reqlen
);
236 if (where
== ZIPLIST_HEAD
) {
237 p
= zl
+ZIPLIST_HEADER_SIZE
;
239 /* Subtract one because of the ZIP_END bytes */
240 memmove(p
+reqlen
,p
,curlen
-ZIPLIST_HEADER_SIZE
-1);
246 /* Write the entry */
247 p
+= zipEncodeLength(p
,encoding
,elen
);
248 if (encoding
!= ZIP_ENC_RAW
) {
249 zipSaveInteger(p
,value
,encoding
);
251 memcpy(p
,entry
,elen
);
253 ZIPLIST_INCR_LENGTH(zl
,1);
257 unsigned char *ziplistPop(unsigned char *zl
, sds
*target
, int where
) {
258 unsigned int curlen
= ZIPLIST_BYTES(zl
), rawlen
;
259 unsigned int len
, lensize
;
262 if (target
) *target
= NULL
;
264 /* Get pointer to element to remove */
265 p
= (where
== ZIPLIST_HEAD
) ? ziplistHead(zl
) : ziplistTail(zl
);
266 if (*p
== ZIP_END
) return zl
;
267 len
= zipDecodeLength(p
,&lensize
);
269 if (ZIP_ENCODING(p
) == ZIP_ENC_RAW
) {
270 *target
= sdsnewlen(p
+lensize
,len
);
272 value
= zipLoadInteger(p
+lensize
,ZIP_ENCODING(p
));
273 *target
= sdscatprintf(sdsempty(), "%lld", value
);
277 /* Move list to front when popping from the head */
278 rawlen
= lensize
+len
;
279 if (where
== ZIPLIST_HEAD
) {
280 memmove(p
,p
+rawlen
,curlen
-ZIPLIST_HEADER_SIZE
-len
);
283 /* Resize and update length */
284 zl
= ziplistResize(zl
,curlen
-rawlen
);
285 ZIPLIST_INCR_LENGTH(zl
,-1);
289 /* Returns an offset to use for iterating with ziplistNext. */
290 unsigned char *ziplistIndex(unsigned char *zl
, unsigned int index
) {
291 unsigned char *p
= zl
+ZIPLIST_HEADER_SIZE
;
293 for (; i
< index
; i
++) {
294 if (*p
== ZIP_END
) break;
295 p
+= zipRawEntryLength(p
);
300 /* Return pointer to next entry in ziplist. */
301 unsigned char *ziplistNext(unsigned char *p
) {
302 return *p
== ZIP_END
? p
: p
+zipRawEntryLength(p
);
305 /* Get entry pointer to by 'p' and store in either 'e' or 'v' depending
306 * on the encoding of the entry. 'e' is always set to NULL to be able
307 * to find out whether the string pointer or the integer value was set.
308 * Return 0 if 'p' points to the end of the zipmap, 1 otherwise. */
309 unsigned int ziplistGet(unsigned char *p
, unsigned char **e
, unsigned int *elen
, long long *v
) {
310 unsigned int len
, lensize
;
311 if (*p
== ZIP_END
) return 0;
313 len
= zipDecodeLength(p
,&lensize
);
314 if (ZIP_ENCODING(p
) == ZIP_ENC_RAW
) {
321 *v
= zipLoadInteger(p
+lensize
,ZIP_ENCODING(p
));
327 /* Delete a range of entries from the ziplist. */
328 unsigned char *ziplistDeleteRange(unsigned char *zl
, unsigned int index
, unsigned int num
) {
329 unsigned char *p
, *first
= ziplistIndex(zl
, index
);
330 unsigned int i
, deleted
= 0, totlen
, newlen
;
331 for (p
= first
, i
= 0; *p
!= ZIP_END
&& i
< num
; i
++) {
332 p
+= zipRawEntryLength(p
);
338 /* Move current tail to the new tail when there *is* a tail */
339 if (*p
!= ZIP_END
) memmove(first
,p
,ZIPLIST_BYTES(zl
)-(p
-zl
)-1);
341 /* Resize and update length */
342 zl
= ziplistResize(zl
, ZIPLIST_BYTES(zl
)-totlen
);
343 ZIPLIST_INCR_LENGTH(zl
,-deleted
);
348 /* Delete a single entry from the ziplist, pointed to by *p.
349 * Also update *p in place, to be able to iterate over the
350 * ziplist, while deleting entries. */
351 unsigned char *ziplistDelete(unsigned char *zl
, unsigned char **p
) {
352 unsigned int offset
= *p
-zl
, tail
, len
;
353 len
= zipRawEntryLength(*p
);
354 tail
= ZIPLIST_BYTES(zl
)-offset
-len
-1;
356 /* Move current tail to the new tail when there *is* a tail */
357 if (tail
> 0) memmove(*p
,*p
+len
,tail
);
359 /* Resize and update length */
360 zl
= ziplistResize(zl
, ZIPLIST_BYTES(zl
)-len
);
361 ZIPLIST_INCR_LENGTH(zl
,-1);
363 /* Store new pointer to current element in p.
364 * This needs to be done because zl can change on realloc. */
369 /* Compare entry pointer to by 'p' with 'entry'. Return 1 if equal. */
370 unsigned int ziplistCompare(unsigned char *p
, unsigned char *entry
, unsigned int elen
) {
371 unsigned int zlen
, lensize
;
373 long long zval
, eval
;
374 if (*p
== ZIP_END
) return 0;
376 zlen
= zipDecodeLength(p
,&lensize
);
377 if (ZIP_ENCODING(p
) == ZIP_ENC_RAW
) {
380 return memcmp(p
+lensize
,entry
,elen
) == 0;
385 if (zipTryEncoding(entry
,&eval
,&encoding
)) {
386 /* Do integer compare */
387 zval
= zipLoadInteger(p
+lensize
,ZIP_ENCODING(p
));
390 /* Ziplist entry is integer encoded, but given entry is not. */
396 /* Return length of ziplist. */
397 unsigned int ziplistLen(unsigned char *zl
) {
398 unsigned int len
= 0;
399 if (ZIPLIST_LENGTH(zl
) < ZIP_BIGLEN
) {
400 len
= ZIPLIST_LENGTH(zl
);
402 unsigned char *p
= zl
+ZIPLIST_HEADER_SIZE
;
403 while (*p
!= ZIP_END
) {
404 p
+= zipRawEntryLength(p
);
408 /* Re-store length if small enough */
409 if (len
< ZIP_BIGLEN
) ZIPLIST_LENGTH(zl
) = len
;
414 /* Return size in bytes of ziplist. */
415 unsigned int ziplistSize(unsigned char *zl
) {
416 return ZIPLIST_BYTES(zl
);
419 void ziplistRepr(unsigned char *zl
) {
420 unsigned char *p
, encoding
;
421 unsigned int l
, lsize
;
424 printf("{total bytes %d} {length %u}\n",ZIPLIST_BYTES(zl
), ZIPLIST_LENGTH(zl
));
426 while(*p
!= ZIP_END
) {
427 l
= zipDecodeLength(p
,&lsize
);
428 printf("{header %u, payload %u} ",lsize
,l
);
429 encoding
= ZIP_ENCODING(p
);
431 if (encoding
== ZIP_ENC_RAW
) {
432 fwrite(p
,l
,1,stdout
);
434 printf("%lld", zipLoadInteger(p
,encoding
));
442 #ifdef ZIPLIST_TEST_MAIN
444 unsigned char *createList() {
445 unsigned char *zl
= ziplistNew();
446 zl
= ziplistPush(zl
, (unsigned char*)"foo", 3, ZIPLIST_TAIL
);
447 zl
= ziplistPush(zl
, (unsigned char*)"quux", 4, ZIPLIST_TAIL
);
448 zl
= ziplistPush(zl
, (unsigned char*)"hello", 5, ZIPLIST_HEAD
);
449 zl
= ziplistPush(zl
, (unsigned char*)"1024", 4, ZIPLIST_TAIL
);
453 unsigned char *createIntList() {
454 unsigned char *zl
= ziplistNew();
458 zl
= ziplistPush(zl
, buf
, strlen(buf
), ZIPLIST_TAIL
);
459 sprintf(buf
, "128000");
460 zl
= ziplistPush(zl
, buf
, strlen(buf
), ZIPLIST_TAIL
);
461 sprintf(buf
, "-100");
462 zl
= ziplistPush(zl
, buf
, strlen(buf
), ZIPLIST_HEAD
);
463 sprintf(buf
, "4294967296");
464 zl
= ziplistPush(zl
, buf
, strlen(buf
), ZIPLIST_HEAD
);
465 sprintf(buf
, "non integer");
466 zl
= ziplistPush(zl
, buf
, strlen(buf
), ZIPLIST_TAIL
);
467 sprintf(buf
, "much much longer non integer");
468 zl
= ziplistPush(zl
, buf
, strlen(buf
), ZIPLIST_TAIL
);
472 int main(int argc
, char **argv
) {
473 unsigned char *zl
, *p
, *q
, *entry
;
478 zl
= createIntList();
484 zl
= ziplistPop(zl
, &s
, ZIPLIST_TAIL
);
485 printf("Pop tail: %s (length %ld)\n", s
, sdslen(s
));
488 zl
= ziplistPop(zl
, &s
, ZIPLIST_HEAD
);
489 printf("Pop head: %s (length %ld)\n", s
, sdslen(s
));
492 printf("Iterate list from 0 to end:\n");
495 p
= ziplistIndex(zl
, 0);
496 while (ziplistGet(p
, &entry
, &elen
, &value
)) {
499 fwrite(entry
,elen
,1,stdout
);
501 printf("%lld", value
);
509 printf("Iterate list from 1 to end:\n");
512 p
= ziplistIndex(zl
, 1);
513 while (ziplistGet(p
, &entry
, &elen
, &value
)) {
516 fwrite(entry
,elen
,1,stdout
);
518 printf("%lld", value
);
526 printf("Iterate list from 2 to end:\n");
529 p
= ziplistIndex(zl
, 2);
530 while (ziplistGet(p
, &entry
, &elen
, &value
)) {
533 fwrite(entry
,elen
,1,stdout
);
535 printf("%lld", value
);
543 printf("Iterate starting out of range:\n");
546 p
= ziplistIndex(zl
, 4);
547 if (!ziplistGet(p
, &entry
, &elen
, &value
)) {
548 printf("No entry\n");
555 printf("Delete inclusive range 0,0:\n");
558 zl
= ziplistDeleteRange(zl
, 0, 1);
562 printf("Delete inclusive range 0,1:\n");
565 zl
= ziplistDeleteRange(zl
, 0, 2);
569 printf("Delete inclusive range 1,2:\n");
572 zl
= ziplistDeleteRange(zl
, 1, 2);
576 printf("Delete with start index out of range:\n");
579 zl
= ziplistDeleteRange(zl
, 5, 1);
583 printf("Delete with num overflow:\n");
586 zl
= ziplistDeleteRange(zl
, 1, 5);
590 printf("Delete foo while iterating:\n");
593 p
= ziplistIndex(zl
, 0);
594 while (ziplistGet(p
, &entry
, &elen
, &value
)) {
595 if (entry
&& strncmp("foo", entry
, elen
) == 0) {
596 printf("Delete foo\n");
597 zl
= ziplistDelete(zl
, &p
);
601 fwrite(entry
,elen
,1,stdout
);
603 printf("%lld", value
);
613 printf("Compare strings with ziplist entries:\n");
616 p
= ziplistIndex(zl
, 0);
617 if (!ziplistCompare(p
,"hello",5)) {
621 if (ziplistCompare(p
,"hella",5)) {
626 p
= ziplistIndex(zl
, 3);
627 if (!ziplistCompare(p
,"1024",4)) {
631 if (ziplistCompare(p
,"1025",4)) {