链表是Redis中一个常用的结构,可以存储多个字符串,而且它是有序的。Redis链表是双向的,可以从左到右,也可以从右到左遍历它存储的节点
由于是双向链表,所以只能从左到右,或者从右到左访问和操作链表里面的数据节点。但是使用链表结构就意味着读性能的丧失,所以要在大量数据中找到一个节点的操作性能是不佳的,因为链表只能从一个方向中去遍历所要节点,比如从查找节点10000开始查询,它需要按照节点1、节点2、节点3……直至节点10000,这样的顺序查找,然后把一个个节点和你给出的值对比,才能确定节点所在。如果这个链表很大,如有上百万个节点,可能需要遍历几十万次才能找到所需的节点,显然查找性能是不佳的
链表的优势在于插入和删除的便利,因为链表的数据节点是分配在不同的内存区域的,并不连续,只是根据上一个节点保存下一个节点的顺序来索引而已,无需移动元素
因为是双向链表结构,所以Redis链表命令分为左操作和右操作两种命令,左操作就意味着从左到右,右操作就意味着从右到左
命令说明备注LPUSH key value1 [value2]把节点node1加入到链表最左边如果是node1、node2….noden这样加入,那么链表开头从左到右的顺序是noden…node2、node1RPUSH key value1 [value2]把节点node1加入到链表最右边如果是node1、node2….noden这样加入,那么链表开头从左到右的顺序是node1、node2…nodenLINDEX key index读取下标为index的节点返回节点字符串,从0开始算LLEN key链表的长度返回链表节点数LPOP key删除左边第一个节点,并将其返回RPOP key删除右边第一个节点,并将其返回LINSERT key BEFOREAFTER pivot value插入一个节点node,并且可以指定在值为pivot的节点的前面或者后面LPUSHX list node如果存在key未list的链表,则插入节点node,并且作为从左到右的第一个节点如果list不存在,则失败RPUSHX list node如果存在key未list的链表,则插入节点node,并且作为从左到右的最后一个节点如果list不存在,则失败LRANGE key start stop获取list从start下标到end下标的节点值包含start和end下标的值LREM key count value如果count为0,则删除所有值等于value的节点;如果count不是0,则先对count取绝对值,假设记为abs,然后从左到右删除不大于abs个等于value的节点注意,count为整数,如果是负数,则redis会先取其绝对值,然后传递到后台操作LSET key index value设置列表下标为index的节点的值为nodeLTRIM key start stop对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除包含start和end的下标的节点会保留如下的例子:
public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class); try { // 删除链表 redisTemplate.delete("list"); // 把node3插入链表list redisTemplate.opsForList().leftPush("list", "node3"); printList(redisTemplate, "list");//[node3] // 相当于lpush把多个值从左插入链表 List<String> nodeList = new ArrayList<String>(); for (int i = 2; i >= 1; i--) { nodeList.add("node" + i); } redisTemplate.opsForList().leftPushAll("list", nodeList); printList(redisTemplate, "list");//[node1, node2, node3] // 从右边插入一个节点 redisTemplate.opsForList().rightPush("list", "node4"); printList(redisTemplate, "list");//[node1, node2, node3, node4] // 获取下标为0的节点 String node1 = (String) redisTemplate.opsForList().index("list", 0); System.out.println(node1); //node1 // 获取链表的长度 long size = redisTemplate.opsForList().size("list"); System.out.println(size); //4 // 从左边弹出一个节点 String lpop = (String) redisTemplate.opsForList().leftPop("list"); System.out.println(lpop);//node1 printList(redisTemplate, "list");//[node2, node3, node4] // 从右边弹出一个节点 String rpop = (String) redisTemplate.opsForList().rightPop("list"); System.out.println(rpop);//node4 printList(redisTemplate, "list");//[node2, node3] // 使用linsert命令在node2前插入一个节点 redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"), RedisListCommands.Position.BEFORE, "node2".getBytes("utf-8"), "before_node".getBytes("utf-8")); printList(redisTemplate, "list");//[before_node, node2, node3] // 使用linsert命令在node2后插入一个节点 redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"), RedisListCommands.Position.AFTER, "node2".getBytes("utf-8"), "after_node".getBytes("utf-8")); printList(redisTemplate, "list");//[before_node, node2, after_node, node3] // 判断list是否存在,如果存在则在左边插入head节点 redisTemplate.opsForList().leftPushIfPresent("list", "head"); printList(redisTemplate, "list");//[head, before_node, node2, after_node, node3] // 判断list是否存在,如果存在则在右边插入end节点 redisTemplate.opsForList().rightPushIfPresent("list", "end"); printList(redisTemplate, "list");//[head, before_node, node2, after_node, node3, end] // 从左到右,获取下标从0到10的节点元素 List valueList = redisTemplate.opsForList().range("list", 0, 10); System.out.println(valueList);//[head, before_node, node2, after_node, node3, end] nodeList.clear(); // 在链表的左边插入三个值为node的节点 for (int i = 1; i <= 3; i++) { nodeList.add("node"); } redisTemplate.opsForList().leftPushAll("list", nodeList); printList(redisTemplate, "list");//[node, node, node, head, before_node, node2, after_node, node3, end] // 从左到右删除至多三个node节点 redisTemplate.opsForList().remove("list", 3, "node"); printList(redisTemplate, "list");//[head, before_node, node2, after_node, node3, end] // 给下标为0的节点设置新值 redisTemplate.opsForList().set("list", 0, "new_head_value"); printList(redisTemplate, "list");//[new_head_value, before_node, node2, after_node, node3, end] } catch (UnsupportedEncodingException ex) { ex.printStackTrace(); } } public static void printList(RedisTemplate redisTemplate, String key) { // 链表长度 Long size = redisTemplate.opsForList().size(key); // 获取整个链表的值 List valueList = redisTemplate.opsForList().range(key, 0, size); // 打印 System.out.println(valueList); }需要指出的是,之前的这些操作都是进程不安全的,因为当我们操作这些命令的时候,其他Redis的客户端也可能操作同一个链表,这样就造成并发数据和一致性的问题,尤其是当你操作一个数据量不小的链表结构时,常常会遇到这样的问题。为了克服这些问题,Redis提供了链表的阻塞命令,它们在运行的时候,会给链表加锁,以保证操作链表的命令安全性
命令说明备注BLPOP key1 [key2 ] timeout移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止相对于lpop命令,它的操作是进程安全的BRPOP key1 [key2 ] timeout移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止相对于rpop命令,它的操作是进程安全的BRPOPLPUSH source destination timeout从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止可设置超时时间RPOPLPUSH source destination从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止不可设置超时时间 public static void testBList() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class); // 清空数据,可以重复测试 redisTemplate.delete("list1"); redisTemplate.delete("list2"); // 初始化链表list1 List<String> nodeList = new ArrayList<String>(); for (int i = 1; i <= 5; i++) { nodeList.add("node" + i); } redisTemplate.opsForList().leftPushAll("list1", nodeList); // Spring使用参数超时时间作为阻塞命令区分,等价于blpop命令,并且可以设置时间参数 redisTemplate.opsForList().leftPop("list1", 1, TimeUnit.SECONDS); // Spring使用参数超时时间作为阻塞命令区分,等价于brpop命令,并且可以设置时间参数 redisTemplate.opsForList().rightPop("list1", 1, TimeUnit.SECONDS); nodeList.clear(); // 初始化链表list2 for (int i = 1; i <= 3; i++) { nodeList.add("data" + i); } redisTemplate.opsForList().leftPushAll("list2", nodeList); // 相当于rpoplpush命令,弹出list1最右边的节点,插入到list2最左边 redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2"); // 相当于brpoplpush命令,弹出list1最右边的节点,插入到list2最左边 redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2", 1, TimeUnit.SECONDS); // 打印链表数据 printList(redisTemplate, "list1"); printList(redisTemplate, "list2"); }