操作list类型value的redis命令主要包括rpush,lpush,llen,lrange,ltrim,lindex,lpop,和rpop.
redis rpush命令
redis rpush命令的格式为rpush key value
. 该命令将value添加到key对应的链表尾部.telnet模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
rpush mykey 8
myvalue1
+OK
rpush mykey 8
myvalue2
+OK
redis的rpush命令对应的处理函数为rpushCommand
,该函数的实现为(redis.c):
1663 static void rpushCommand(redisClient *c) {
1664 pushGenericCommand(c,REDIS_TAIL);
1665 }
函数pushGenericCommand
的实现为(redis.c):
1620 static void pushGenericCommand(redisClient *c, int where) {
1621 robj *ele, *lobj;
1622 dictEntry *de;
1623 list *list;
1624
1625 ele = createObject(REDIS_STRING,c->argv[2]);
1626 c->argv[2] = NULL;
1627
1628 de = dictFind(c->dict,c->argv[1]);
1629 if (de == NULL) {
1630 lobj = createListObject();
1631 list = lobj->ptr;
1632 if (where == REDIS_HEAD) {
1633 if (!listAddNodeHead(list,ele)) oom("listAddNodeHead");
1634 } else {
1635 if (!listAddNodeTail(list,ele)) oom("listAddNodeTail");
1636 }
1637 dictAdd(c->dict,c->argv[1],lobj);
1638
1639 /* Now the key is in the hash entry, don't free it */
1640 c->argv[1] = NULL;
1641 } else {
1642 lobj = dictGetEntryVal(de);
1643 if (lobj->type != REDIS_LIST) {
1644 decrRefCount(ele);
1645 addReplySds(c,sdsnew("-ERR push against existing key not holding a list\r\n"));
1646 return;
1647 }
1648 list = lobj->ptr;
1649 if (where == REDIS_HEAD) {
1650 if (!listAddNodeHead(list,ele)) oom("listAddNodeHead");
1651 } else {
1652 if (!listAddNodeTail(list,ele)) oom("listAddNodeTail");
1653 }
1654 }
1655 server.dirty++;
1656 addReply(c,shared.ok);
1657 }
Line1625:1626创建一个robj
对象来封装value值,同时将c->argv[2]设置为NULL, 即不再指向value.Line1628在数据库中查找包含该key的节点.Line1629:1641如果没有找到该节点,调用函数createListObject
创建一个类型REDIS_LIST
的robj
对象,该函数的实现为(redis.c):
1009 static robj *createListObject(void) {
1010 list *l = listCreate();
1011
1012 if (!l) oom("createListObject");
1013 listSetFreeMethod(l,decrRefCount);
1014 return createObject(REDIS_LIST,l);
1015 }
首先创建一个类型list
对象,然后再创建一个类型robj
对象,封装list
对象.
回到函数pushGenericCommand
, Line1635将封装value的robj
对象添加到链表尾部,因为链表是新创建的,所以这个添加的链接节点,既是尾部节点又是头部节点.Line1637调用函数dictAdd
将key和类型list的robj
对象存入数据库.Line1640将指向key的sds
指针设置为NULL, 因为key已经由哈希节点对象的管理.Line1642:1653说明该key对应的哈希节点已经存在,判断哈希节点中的value是否是REDIS_LIST
类型,如果不是,释放要添加的value,并向客户端发送错误提示响应.如果类型正确,将要添加的value插入到链表的尾部.rpush命令导致的数据库更新操作结束,Line1656向客户端发送成功字符串.
redis lpush命令
redis lpush命令的格式为lpush key value
. 该命令将value添加到key对应的链表首部.telnet模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush lkey 6
value1
+OK
lpush lkey 6
value2
+OK
lpush命令对应的处理函数为lpushCommand
, 其实现为(redis.c):
1659 static void lpushCommand(redisClient *c) {
1660 pushGenericCommand(c,REDIS_HEAD);
1661 }
和rpush命令的唯一区别在于,函数pushGenericCommand
调用listAddNodeHead
将value添加到key对应的链表首部.
redis lpop命令
redis lpop命令的格式为lpop key
, 该命令从key对应的链表首部移除一个value节点,并返回该value.telnet的模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lpush mykey 8
myvalue3
+OK
lpop mykey
8
myvalue3
lpop命令对应的处理函数为lpopCommand
,其实现为(redis.c):
1785 static void lpopCommand(redisClient *c) {
1786 popGenericCommand(c,REDIS_HEAD);
1787 }
函数popGenericCommand
的实现为(redis.c):
1749 static void popGenericCommand(redisClient *c, int where) {
1750 dictEntry *de;
1751
1752 de = dictFind(c->dict,c->argv[1]);
1753 if (de == NULL) {
1754 addReply(c,shared.nil);
1755 } else {
1756 robj *o = dictGetEntryVal(de);
1757
1758 if (o->type != REDIS_LIST) {
1759 char *err = "POP against key not holding a list value";
1760 addReplySds(c,
1761 sdscatprintf(sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
1762 } else {
1763 list *list = o->ptr;
1764 listNode *ln;
1765
1766 if (where == REDIS_HEAD)
1767 ln = listFirst(list);
1768 else
1769 ln = listLast(list);
1770
1771 if (ln == NULL) {
1772 addReply(c,shared.nil);
1773 } else {
1774 robj *ele = listNodeValue(ln);
1775 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr)));
1776 addReply(c,ele);
1777 addReply(c,shared.crlf);
1778 listDelNode(list,ln);
1779 server.dirty++;
1780 }
1781 }
1782 }
1783 }
Line1752在数据库中查找包含该key的哈希节点.Line1753:1755如果没有找到该哈希节点,则向客户端返回空结果字符串.
如果找到对应的哈希节点,获得其类型robj
对象,如果对象类型错误,向客户端返回错误字符串.Line1766:1767获得value链表的头节点,Line1771:1772如果value链表为空(可能已经pop操作了全部节点),则向客户端返回空结果字符串.Line1774:1777向客户端发送value信息(起始信息是value字节数).Line1778:1779将该value从所在链表删除,并记录数据库更新次数.
redis rpop命令
redis rpop命令的格式为rpop key
, 该命令从key对应的链表尾部移除一个value节点,并返回该value.telnet的模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lpush mykey 8
myvalue3
+OK
rpop mykey
8
myvalue1
rpop命令对应的处理函数为rpopCommand
.其实现为(redis.c):
1789 static void rpopCommand(redisClient *c) {
1790 popGenericCommand(c,REDIS_TAIL);
1791 }
rpop和lpop命令的唯一区别在于,函数popGenericCommand
中, Line1769调用函数listLast从链表尾部获得要返回的value节点.
redis llen命令
redis llen命令的格式为llen key
, 该命令返回key对应的链表中value节点数.telnet的模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
llen mykey
2
llen命令对应的处理函数为llenCommand
, 其实现为(redis.c):
1667 static void llenCommand(redisClient *c) {
1668 dictEntry *de;
1669 list *l;
1670
1671 de = dictFind(c->dict,c->argv[1]);
1672 if (de == NULL) {
1673 addReply(c,shared.zero);
1674 return;
1675 } else {
1676 robj *o = dictGetEntryVal(de);
1677 if (o->type != REDIS_LIST) {
1678 addReplySds(c,sdsnew("-1\r\n"));
1679 } else {
1680 l = o->ptr;
1681 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",listLength(l)));
1682 }
1683 }
1684 }
Line1671在数据库中查找包括该key的哈希节点.Line1672:1674如果没有指定的哈希节点,向客户端返回0.Line1677:1678如果哈希节点包含的robj
对象类型错误,向客户端返回-1,否则,Line1680:1681向客户端返回value链表节点数.
redis lrange命令
redis lrange命令的格式为lrange key start end
, 该命令返回key对应的链表中指定的value节点,是一个子序列, 从链表索引start开始到索引end结束,注意是包含索引end的value.索引可以为负数, -1代表最后一个value节点,-2代表-1前面的value节点,依次类推. telnet的模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lpush mykey 8
myvalue3
+OK
lpush mykey 8
myvalue4
+OK
lrange mykey 1 2
2
8
myvalue3
8
myvalue2
lrange命令对应的处理函数为lrangeCommand
,其实现为(redis.c):
1793 static void lrangeCommand(redisClient *c) {
1794 dictEntry *de;
1795 int start = atoi(c->argv[2]);
1796 int end = atoi(c->argv[3]);
1797
1798 de = dictFind(c->dict,c->argv[1]);
1799 if (de == NULL) {
1800 addReply(c,shared.nil);
1801 } else {
1802 robj *o = dictGetEntryVal(de);
1803
1804 if (o->type != REDIS_LIST) {
1805 char *err = "LRANGE against key not holding a list value";
1806 addReplySds(c,
1807 sdscatprintf(sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
1808 } else {
1809 list *list = o->ptr;
1810 listNode *ln;
1811 int llen = listLength(list);
1812 int rangelen, j;
1813 robj *ele;
1814
1815 /* convert negative indexes */
1816 if (start < 0) start = llen+start;
1817 if (end < 0) end = llen+end;
1818 if (start < 0) start = 0;
1819 if (end < 0) end = 0;
1820
1821 /* indexes sanity checks */
1822 if (start > end || start >= llen) {
1823 /* Out of range start or start > end result in empty list */
1824 addReply(c,shared.zero);
1825 return;
1826 }
1827 if (end >= llen) end = llen-1;
1828 rangelen = (end-start)+1;
1829
1830 /* Return the result in form of a multi-bulk reply */
1831 ln = listIndex(list, start);
1832 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",rangelen));
1833 for (j = 0; j < rangelen; j++) {
1834 ele = listNodeValue(ln);
1835 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr)));
1836 addReply(c,ele);
1837 addReply(c,shared.crlf);
1838 ln = ln->next;
1839 }
1840 }
1841 }
1842 }
Line1798在数据库中查找包含该key的哈希节点. Line1799:1800如果没有指定的节点,向客户端返回空字符串提示.Line1804:1807如果哈希节点包含的robj
对象类型错误,向客户端返回错误提示字符串.Line1811获得value链表中节点数.Line1815:1828根据传入的开始索引和结束索引,计算要返回的子序列节点数.Line1838调用函数listIndex
返回value链表中指定索引的value节点.函数listIndex
的实现为(adlist.c):
240 /* Return the element at the specified zero-based index
241 * where 0 is the head, 1 is the element next to head
242 * and so on. Negative integers are used in order to count
243 * from the tail, -1 is the last element, -2 the penultimante
244 * and so on. If the index is out of range NULL is returned. */
245 listNode *listIndex(list *list, int index) {
246 listNode *n;
247
248 if (index < 0) {
249 index = (-index)-1;
250 n = list->tail;
251 while(index-- && n) n = n->prev;
252 } else {
253 n = list->head;
254 while(index-- && n) n = n->next;
255 }
256 return n;
257 }
索引值为负数时,从链表尾部遍历,索引值为正数时,从链表头部遍历.
回到函数lrangeCommand
,Line1832:1839向客户端返回结果,包含子序列的节点个数,及其value节点.
redis lindex命令
redis lindex命令的格式为lindex key index
, 该命令返回key对应的链表中指定的value节点, 索引可以为负数, -1代表最后一个value节点,-2代表-1前面的value节点,依次类推. telnet的模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lindex mykey 1
8
myvalue1
lindex命令对应的处理函数为lindexCommand
,其实现为(redis.c):
1686 static void lindexCommand(redisClient *c) {
1687 dictEntry *de;
1688 int index = atoi(c->argv[2]);
1689
1690 de = dictFind(c->dict,c->argv[1]);
1691 if (de == NULL) {
1692 addReply(c,shared.nil);
1693 } else {
1694 robj *o = dictGetEntryVal(de);
1695
1696 if (o->type != REDIS_LIST) {
1697 char *err = "LINDEX against key not holding a list value";
1698 addReplySds(c,
1699 sdscatprintf(sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
1700 } else {
1701 list *list = o->ptr;
1702 listNode *ln;
1703
1704 ln = listIndex(list, index);
1705 if (ln == NULL) {
1706 addReply(c,shared.nil);
1707 } else {
1708 robj *ele = listNodeValue(ln);
1709 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr)));
1710 addReply(c,ele);
1711 addReply(c,shared.crlf);
1712 }
1713 }
1714 }
1715 }
Line1690在数据库中查找包含该key的哈希节点.Line1691:1692如果没有指定的哈希节点,则向客户端返回空字符串.Line1696:1699如果哈希节点的robj对象的类型错误,则向客户端返回错误提示字符串.Line1704:1712返回value链表中指定索引的value节点,如果索引超出链表长度,向客户端返回空字符串,否则想客户端返回该索引对应的value.
redis ltrim命令
redis ltrim命令的格式为ltrim key start end
, 该命令返回key对应的链表进行trim操作,之保留指定索引表示的子序列. 索引可以为负数, -1代表最后一个value节点,-2代表-1前面的value节点,依次类推. telnet的模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lpush mykey 8
myvalue3
+OK
lpush mykey 8
myvalue4
+OK
ltrim mykey 1 2
+OK
lrange mykey 0 -1
2
8
myvalue3
8
myvalue2
ltrim命令对应的处理函数为ltrimCommand
,其实现为(redis.c):
1844 static void ltrimCommand(redisClient *c) {
1845 dictEntry *de;
1846 int start = atoi(c->argv[2]);
1847 int end = atoi(c->argv[3]);
1848
1849 de = dictFind(c->dict,c->argv[1]);
1850 if (de == NULL) {
1851 addReplySds(c,sdsnew("-ERR no such key\r\n"));
1852 } else {
1853 robj *o = dictGetEntryVal(de);
1854
1855 if (o->type != REDIS_LIST) {
1856 addReplySds(c,
1857 sdsnew("-ERR LTRIM against key not holding a list value"));
1858 } else {
1859 list *list = o->ptr;
1860 listNode *ln;
1861 int llen = listLength(list);
1862 int j, ltrim, rtrim;
1863
1864 /* convert negative indexes */
1865 if (start < 0) start = llen+start;
1866 if (end < 0) end = llen+end;
1867 if (start < 0) start = 0;
1868 if (end < 0) end = 0;
1869
1870 /* indexes sanity checks */
1871 if (start > end || start >= llen) {
1872 /* Out of range start or start > end result in empty list */
1873 ltrim = llen;
1874 rtrim = 0;
1875 } else {
1876 if (end >= llen) end = llen-1;
1877 ltrim = start;
1878 rtrim = llen-end-1;
1879 }
1880
1881 /* Remove list elements to perform the trim */
1882 for (j = 0; j < ltrim; j++) {
1883 ln = listFirst(list);
1884 listDelNode(list,ln);
1885 }
1886 for (j = 0; j < rtrim; j++) {
1887 ln = listLast(list);
1888 listDelNode(list,ln);
1889 }
1890 addReply(c,shared.ok);
1891 server.dirty++;
1892 }
1893 }
1894 }
Line1849在数据库中查找包含该key的哈希节点.Line1850:1851如果没有指定的节点,向客户端返回错误提示字符串.Line1855:1857如果哈希节点包含的robj
对象的类型错误,向客户端返回错误提示字符串.
Line1861获得value链表中节点个数.trim操作的思想是只保留指定索引区间表示的子序列,所以原来链表中该子序列前面部分和后面部分的value节点将不再需要.Line1864:1879计算子序列前面部分和后面部分分别要删除的节点数.Line1881:1889分别从value链表头部和尾部删除指定数目的节点.Line1890:1891标记内存数据库更新次数,并向客户端发送操作成功提示字符串.对于trim操作后的结果,可以使用lrange命令进行验证.