Redis链表

Redis链表

链表是Redis中一个常用的结构,可以存储多个字符串,而且它是有序的。Redis链表是双向的,可以从左到右,也可以从右到左遍历它存储的节点

由于是双向链表,所以只能从左到右,或者从右到左访问和操作链表里面的数据节点。但是使用链表结构就意味着读性能的丧失,所以要在大量数据中找到一个节点的操作性能是不佳的,因为链表只能从一个方向中去遍历所要节点,比如从查找节点10000开始查询,它需要按照节点1、节点2、节点3……直至节点10000,这样的顺序查找,然后把一个个节点和你给出的值对比,才能确定节点所在。如果这个链表很大,如有上百万个节点,可能需要遍历几十万次才能找到所需的节点,显然查找性能是不佳的

链表的优势在于插入和删除的便利,因为链表的数据节点是分配在不同的内存区域的,并不连续,只是根据上一个节点保存下一个节点的顺序来索引而已,无需移动元素

因为是双向链表结构,所以Redis链表命令分为左操作和右操作两种命令,左操作就意味着从左到右,右操作就意味着从右到左

命令 说明 备注
LPUSH key value1 [value2] 把节点node1加入到链表最左边 如果是node1、node2….noden这样加入,那么链表开头从左到右的顺序是noden…node2、node1
RPUSH key value1 [value2] 把节点node1加入到链表最右边 如果是node1、node2….noden这样加入,那么链表开头从左到右的顺序是node1、node2…noden
LINDEX key index 读取下标为index的节点 返回节点字符串,从0开始算
LLEN key 链表的长度 返回链表节点数
LPOP key 删除左边第一个节点,并将其返回
RPOP key 删除右边第一个节点,并将其返回
LINSERT key BEFORE AFTER 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的节点的值为node
LTRIM 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]

            // 从左到右,获取下标从010的节点元素
            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");
    }

猜你喜欢

转载自blog.csdn.net/winfredzen/article/details/80362440
今日推荐