您的位置:首页 > 理论基础 > 数据结构算法

2.Redis数据结构常用命令

2018-10-25 14:17 495 查看

文章目录

  • 2.6 基数——HyperLogLog
  • 2.Redis数据结构常用命令

    本文所有内容为《Java EE互联网轻量级框架整合开发》此书相应章节笔记

    • 本小节内容都使用Spring进行操作,它可能来自于同一个 Redis 连接池的不同连接。这样做是因为大部分工作情况下,我们使用 Redis 只是执行一个简单的命令往往就结束了,因此没有必要区分连接的不同;如果要对Redis同时执行多个命令,需采用SessionCallback接口进行操作,从而保证多个命令在同一个Redis连接操作中。

    2.1Redis数据结构-字符串

    • 字符串是Redis最基本的数据结构,它将以一个键和一个值存储于Redis内部,它犹如Java的Map结构,让Redis通过键去找到值。

    • 在Spring中测试这些命令,首先配置Spring关于Redis字符串的运行环境,如以下代码所示
    <?xml version='1.0' encoding='UTF-8' ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    ">
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxIdle" value="50" />
    <property name="maxTotal" value="100" />
    <property name="maxWaitMillis" value="20000" />
    </bean>
    
    <bean id="stringRedisSerializer"
    class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    
    <bean id="connectionFactory"
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value="127.0.0.1" />
    <property name="port" value="6379" />
    <property name="poolConfig" ref="poolConfig" />
    </bean>
    <!--Spring的RedisTemplate的键值序列化器设置为了String类型,所以它就是一种字符串的操作-->
    <!--这里把 Spring 提供的RedisTemplate的默认序列化器(defaultSerializer)修改为了字符串序列     化器。
    因为在Spring对hash结构的操作中会涉及map等其他类的操作,所以需要明确它的规则。
    这里只是指定默认的序列化器,如果想为hash结构指定序列化器,
    可以使用RedisTemplate提供的两个属性hashKeySerializer和hashValueSerializer,
    来为hash结构的field和value指定序列化器-->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="connectionFactory" />
    <property name="defaultSerializer" ref="stringRedisSerializer" />
    <property name="keySerializer" ref="stringRedisSerializer" />
    <property name="valueSerializer" ref="stringRedisSerializer" />
    </bean>
    </beans>
    • 在 Spring 中,redisTemplate.opsForValue()所返回的对象ValueOperations可以操作简单的键值对,可以是字符串,也可以是对象,具体依据你所配置的序列化方案。
    • 使用Spring测试Redis字符串操作
    public static void testString() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
    // 设值
    redisTemplate.opsForValue().set("key1", "value1");
    redisTemplate.opsForValue().set("key2", "value2");
    // 通过key获取值
    String value1 = (String) redisTemplate.opsForValue().get("key1");
    System.out.println(value1);//value1
    // 通过key删除值
    redisTemplate.delete("key1");
    // 求长度
    Long length = redisTemplate.opsForValue().size("key2");
    System.out.println(length);//6
    // 设值新值并返回旧值
    String oldValue2 = (String) redisTemplate.opsForValue().getAndSet("key2", "new_value2");
    System.out.println(oldValue2);//value2
    // 通过key获取值.
    String value2 = (String) redisTemplate.opsForValue().get("key2");
    System.out.println(value2);//new_value2
    // 求子串
    String rangeValue2 = redisTemplate.opsForValue().get("key2", 0, 3);
    System.out.println(rangeValue2);//new_
    // 追加字符串到末尾,返回新串长度
    int newLen = redisTemplate.opsForValue().append("key2", "_app");
    System.out.println(newLen);//14
    String appendValue2 = (String) redisTemplate.opsForValue().get("key2");
    System.out.println(appendValue2);//new_value2_app
    }
    • Redis 提供了对整数和浮点型数字的功能,如果字符串是数字(整数或者浮点数),支持简单的运算。不过运算能力比较弱,目前版本只能支持简单的加减法运算

    • 测试过程中,如果开始把val设置为浮点数,那么incr、decr、incrby、decrby的命令都会失败。

    • 由于Redis的功能比较弱,所以经常会在Java程序中读取它们,并设置它们的值。这里使用Spring提供的RedisTemplate测试一下它们,值得注意的是,这里使用的是字符串序列化器,所以Redis保存的还是字符串,如果采用其他的序列化器,比如JDK序列化器,那么Redis保存的将不会是数字而是产生异常。
    • 因为Redis 的版本在更替,支持的命令会有所不一,而 Spring 提供的RedisTemplate方法不足以支撑Redis的所有命令,所以redis的减法还使用原生的jedis进行。而使用纯粹的Java Redis的最新API则可以看到这些命令对应的方法
    /**
    * 测试Redis运算.
    */
    public static void testCal() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
    redisTemplate.opsForValue().set("i", "9");
    printCurrValue(redisTemplate, "i");
    redisTemplate.opsForValue().increment("i", 1);
    printCurrValue(redisTemplate, "i");
    
    //注意,Spring已经优化了代码,所以加粗的increment方法可以支持长整形(long)
    //和双精度(double)的加法,而对于减法而言,RedisTemplate 并没有进行支持
    redisTemplate.getConnectionFactory().getConnection().decr(redisTemplate.getKeySerializer().serialize("i"));
    printCurrValue(redisTemplate, "i");
    redisTemplate.getConnectionFactory().getConnection().decrBy(redisTemplate.getKeySerializer().serialize("i"), 6);
    printCurrValue(redisTemplate, "i");
    
    //通过获得连接工厂再获得连接从而得到底层的Redis连接对象。
    // 为了和RedisTemplate的配置保持一致,所以先获取了其keySerializer属性,对键进行了序列化,如果获取结果也可以进行同样的转换。
    // 当然getConnection()只是获取一个spring data redis项目中封装的底层对象	//RedisConnection,甚至可以获取原始的链接对象——Jedis对象
    Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection();
    
    //所有关于减法的方法,原有值都必须是整数,否则就会引发异常
    // 可以编译通过,但是运行之后产生了异常,这是因为对浮点数使用了Redis的命令
    redisTemplate.opsForValue().set("i", "8.3");
    redisTemplate.getConnectionFactory().getConnection().decr(redisTemplate.getKeySerializer().serialize("i"));
    printCurrValue(redisTemplate, "i");
    
    redisTemplate.opsForValue().increment("i", 2.3);
    printCurrValue(redisTemplate, "i");
    }
    
    /**
    * 打印当前key的值
    *
    * @param redisTemplate
    *            spring RedisTemplate
    * @param key
    */
    public static void printCurrValue(RedisTemplate redisTemplate, String key) {
    String i = (String) redisTemplate.opsForValue().get(key);
    System.err.println(i);
    }

    2.2 Redis数据结构-哈希

    • 哈希结构就如同Java中的对象,对象里面有很多成员变量(field),成员变量对应的有值(value)。在Redis中,hash是一个String类型的field和value的映射表,因此我们存储的数据实际在Redis内存中都是一个个字符串而已。类比,hash作为一个redis的value,对应的key为对象名。
    • hash的键值对在内存中是一种无序的状态,我们可以通过键(field)找到对应的值(value)
    • 假设角色有3个字段:编号(id)、角色名称(roleName)和备注(note),用一个hash结构保存,内存结果如图示,role_1代表的是这个hash结构在Redis内存的key,通过key找到这个hash结构,而hash结构由一系列的field和value组成。

    • Redis hash结构命令

    • 大多数命令多了一个层级field,这是hash结构的一个内部键,也就是说Redis需要通过key索引到对应的hash结构,再通过field来确定使用hash结构的哪个键值对。关于哈希结构的相关命令,需注意:
        哈希结构的大小。如果哈希结构是个很大的键值对,尤其是关于 hkeys、hgetall、hvals 等返回所有哈希结构数据的命令,会造成大量数据的读取。这需要考虑性能和读取数据大小对JVM内存的影响。
      1. 对于数字的操作命令 hincrby 而言,要求存储的也是整数型的字符串;对于hincrbyfloat而言,则要求使用浮点数或者整数,否则命令会失败。
    public static void testRedisHash() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
    String key = "hash";
    Map<String, String> map = new HashMap<String, String>();
    map.put("f1", "val1");
    map.put("f2", "val2");
    // 相当于hmset命令
    redisTemplate.opsForHash().putAll(key, map);
    // 相当于hset命令
    redisTemplate.opsForHash().put(key, "f3", "6");
    printValueForhash(redisTemplate, key, "f3");
    // 相当于 hexists key filed命令
    boolean exists = redisTemplate.opsForHash().hasKey(key, "f3");
    System.out.println(exists);
    // 相当于hgetall命令
    Map keyValMap = redisTemplate.opsForHash().entries(key);
    // 相当于hincrby命令
    redisTemplate.opsForHash().increment(key, "f3", 2);
    printValueForhash(redisTemplate, key, "f3");
    // 相当于hincrbyfloat命令
    redisTemplate.opsForHash().increment(key, "f3", 0.88);
    printValueForhash(redisTemplate, key, "f3");
    // 相当于hvals命令
    List valueList = redisTemplate.opsForHash().values(key);
    // 相当于hkeys命令
    Set keyList = redisTemplate.opsForHash().keys(key);
    List<String> fieldList = new ArrayList<String>();
    fieldList.add("f1");
    fieldList.add("f2");
    // 相当于hmget命令
    List valueList2 = redisTemplate.opsForHash().multiGet(key, keyList);
    // 相当于hsetnx命令
    boolean success = redisTemplate.opsForHash().putIfAbsent(key, "f4", "val4");
    System.out.println(success);
    // 相当于hdel命令
    Long result = redisTemplate.opsForHash().delete(key, "f1", "f2");
    System.out.println(result);
    }
    
    private static void printValueForhash(RedisTemplate redisTemplate, String key, String field) {
    // 相当于hget命令
    Object value = redisTemplate.opsForHash().get(key, field);
    System.out.println(value);
    }
    • hmset命令,在Java的API中,是使用map保存多个键值对在先的。
    • hgetall命令会返回所有的键值对,并保存到一个map对象中,如果hash结构很大,那么要考虑它对JVM的内存影响。
    • hincrby和hincrbyFloat命令都采用increment方法,Spring会识别它具体使用何种方法。
    • redisTemplate.opsForHash().values(key)方法相当于hvals命令,它会返回所有的值,并保存到一个List对象中;而redisTemplate.opsForHash().keys(key)方法相当于hkeys命令,它会获取所有的键,保存到一个Set对象 中。
    • 在 Spring 中使用 redisTemplate.opsForHash().putAll(key,map)方法相当于执行了hmset 命令,使用了 map,由于配置了默认的序列化器为字符串,所以它也只会用字符串进行转化,这样才能执行对应的数值加法,如果使用其他序列化器,则后面的命令可能会抛出异常。

    2.3 Redis数据结构-链表(linked-list)

    • Redis链表是双向的,因此即可以从左到右,也可以从右到左遍历它存储的节点,链表结构如图所示

    • 由于是双向链表,只能从左到右,或者从右到左地访问和操作链表里面的数据节点。在链表中查找数据性能不佳,原因是必须从头至到查找到节点,每个节点数据必须比对才能确定节点数据。若数据比较大,性能更弱。

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

    • 新增节点。对插入图中的节点4而言,先看从左到右的指向,先让节点4指向节点1原来的下一个节点,也就是节点2,然后让节点1指向节点4,这样就完成了从右到左的指向修改;再看从右到左,先让节点4指向节点1,然后节点2指向节点4,这个时候就完成了从右到左的指向,那么节点1和节点2之间的原有关联关系都已经失效,这样就完成了在链表中新增节点4的功能。
    • 删除节点。对删除图中的节点3而言,首先让节点2从左到右指向后续节点,然后让后续节点指向节点2,这样节点3就脱离了链表,也就是断绝了与节点2和后继节点的关联关系,然后对节点3进行内存回收,无须移动任何节点,就完成了删除。
    • Redis 链表命令因为是双向链表结构,分为左操作和右操作两种命令。以“l”开头的代表左操作,以“r”开头的代表右操作。

    • 对于大量数据操作,需考虑插入和删除内容的大小,否则会十分消耗性能,导致Redis服务器的卡顿。对于不允许卡顿的一些服务器,可以进行分批次操作

    • 以上链表的命令为进程不安全,会有并发数据的安全和一致性的问题,以下命令为链表的阻塞命令,在运行的时候,会给链表加锁,以保证操作链表的命令安全性。但是会导致其它进程不能再读取或者写入该链表,只能等待命令结束。加锁的好处可以保证在多线程并发环境中数据的一致性,保证一些重要数据的一致性。但是也导致其他线程等待、线程环境切换等代价。更多的时候并不是阻塞的处理请求,因此阻塞命令在实际中使用不多。

    • Spring操作Redis链表命令,在多值操作的时候,会使用list进行封装,比如leftPushAll方法,对于很大的list的操作需要注意性能,比如remove这样的操作,在大的链表中会消耗Redis系统很多的性能
    public static void testList() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
    try {
    // 删除链表,以便我们可以反复测试
    redisTemplate.delete("list");
    // 把node3插入链表list
    redisTemplate.opsForList().leftPush("list", "node3");
    List<String> nodeList = new ArrayList<String>();
    for (int i = 2; i >= 1; i--) {
    nodeList.add("node" + i);
    }
    // 相当于lpush把多个价值从左插入链表
    redisTemplate.opsForList().leftPushAll("list", nodeList);
    // 从右边插入一个节点
    redisTemplate.opsForList().rightPush("list", "node4");
    // 获取下标为0的节点
    String node1 = (String) redisTemplate.opsForList().index("list", 0);
    // 获取链表长度
    long size = redisTemplate.opsForList().size("list");
    // 从左边弹出一个节点
    String lpop = (String) redisTemplate.opsForList().leftPop("list");
    // 从右边弹出一个节点
    String rpop = (String) redisTemplate.opsForList().rightPop("list");
    // 注意,需要使用更为底层的命令才能操作linsert命令
    // 使用linsert命令在node2前插入一个节点
    redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"),
    RedisListCommands.Position.BEFORE, "node2".getBytes("utf-8"), "before_node".getBytes("utf-8"));
    // 使用linsert命令在node2后插入一个节点
    redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"),
    RedisListCommands.Position.AFTER, "node2".getBytes("utf-8"), "after_node".getBytes("utf-8"));
    // 判断list是否存在,如果存在则从左边插入head节点
    redisTemplate.opsForList().leftPushIfPresent("list", "head");
    // 判断list是否存在,如果存在则从右边插入end节点
    redisTemplate.opsForList().rightPushIfPresent("list", "end");
    // 从左到右,或者下标从0到10的节点元素
    List valueList = redisTemplate.opsForList().range("list", 0, 10);
    nodeList.clear();
    for (int i = 1; i <= 3; i++) {
    nodeList.add("node");
    }
    // 在链表左边插入三个值为node的节点
    redisTemplate.opsForList().leftPushAll("list", nodeList);
    // 从左到右删除至多三个node节点
    redisTemplate.opsForList().remove("list", 3, "node");
    // 给链表下标为0的节点设置新值
    redisTemplate.opsForList().set("list", 0, "new_head_value");
    } catch (UnsupportedEncodingException ex) {
    ex.printStackTrace();
    }
    // 打印链表数据
    printList(redisTemplate, "list");
    }
    
    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);
    }
    • Spring操作Redis链表阻塞命令
    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命令,注意在Spring中使用超时参数区分
    redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2", 1, TimeUnit.SECONDS);
    // 打印链表数据
    printList(redisTemplate, "list1");
    printList(redisTemplate, "list2");
    }

    2.4 Redis数据结构-集合

    • Redis的集合不是一个线性结构,而是一个哈希表结构,它的内部会根据hash 分子来存储和查找数据,因为采用哈希表结构,所以对于Redis集合的插入、删除和查找的复杂度都是O(1),只是需要注意3点。

        对于集合而言,它的每一个元素都是不能重复的,当插入相同记录的时候都会失败。
      1. 集合是无序的。
      2. 集合的每一个元素都是String数据结构类型。
    • 以下为集合的操作命令,前缀都包含了一个s,表示是集合的命令,集合是无序的,并且支持并集、交集和差集的运算

    • 用spring操作集合
    public static void testSet() {
    // 请把RedisTemplate值序列化器设置为StringRedisSerializer测试该代码片段
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
    Set set = null;
    // 将元素加入列表
    redisTemplate.boundSetOps("set1").add("v1", "v2", "v3", "v4", "v5", "v6");
    redisTemplate.boundSetOps("set2").add("v0", "v2", "v4", "v6", "v8");
    // 求集合长度
    redisTemplate.opsForSet().size("set1");
    // 求差集
    set = redisTemplate.opsForSet().difference("set1", "set2");
    // 求并集
    set = redisTemplate.opsForSet().intersect("set1", "set2");
    // 判断是否集合中的元素
    boolean exists = redisTemplate.opsForSet().isMember("set1", "v1");
    // 获取集合所有元素
    set = redisTemplate.opsForSet().members("set1");
    // 从集合中随机弹出一个元素
    String val = (String) redisTemplate.opsForSet().pop("set1");
    // 随机获取一个集合的元素
    val = (String) redisTemplate.opsForSet().randomMember("set1");
    // 随机获取2个集合的元素
    List list = redisTemplate.opsForSet().randomMembers("set1", 2L);
    // 删除一个集合的元素,参数可以是多个
    redisTemplate.opsForSet().remove("set1", "v1");
    // 求两个集合的并集
    redisTemplate.opsForSet().union("set1", "set2");
    // 求两个集合的差集,并保存到集合diff_set中
    redisTemplate.opsForSet().differenceAndStore("set1", "set2", "diff_set");
    // 求两个集合的交集,并保存到集合inter_set中
    redisTemplate.opsForSet().intersectAndStore("set1", "set2", "inter_set");
    // 求两个集合的并集,并保存到集合union_set中
    redisTemplate.opsForSet().unionAndStore("set1", "set2", "union_set");
    }

    2.5 Redis数据结构-有序集合

    • 有序集合和集合类似,和无序集合的主要区别在于每一个元素除了值之外,它还会多一个分数。
    • 分数是一个浮点数,在Java中是使用双精度表示的,根据分数可双向排序。
    • 和无序集合一样,对于每一个元素都是唯一的,但是对于不同元素而言,它的分数可以一样。元素也是 String 数据类型,也是一种基于hash的存储结构。有序集合的数据结构如图示。

    • 有序集合依赖 key 标示它是属于哪个集合,依赖分数进行排序,值和分数是必须的,而实际上不仅可以对分数进行排序,在满足一定的条件下,也可以对值进行排序。

    2.5.1 Redis有序集合基础命令

    • 有序集合和无序集合的命令接近,有序集合会增加对于排序的操作

    2.5.2 spring-data-redis对有序集合的封装

    • spring将有序集合的值value和分数score封装到TypedTuple接口的默认实现类中。这样通过ZSetOperations接口操作有序集合。
    package org.springframework.data.redis.core;
    
    import java.util.Collection;
    import java.util.Set;
    
    import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
    import org.springframework.data.redis.connection.RedisZSetCommands.Range;
    
    /**
    * Redis ZSet/sorted set specific operations.
    *
    * @author Costin Leau
    * @author Christoph Strobl
    * @author Mark Paluch
    */
    public interface ZSetOperations<K, V> {
    
    /**
    * Typed ZSet tuple.
    */
    public interface TypedTuple<V> extends Comparable<TypedTuple<V>> {
    V getValue();//获取值
    
    Double getScore();//获取分数
    }
    //省略
    }
    • TypedTuple的默认实现类
    package org.springframework.data.redis.core;
    
    import java.util.Arrays;
    
    import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
    
    /**
    * Default implementation of TypedTuple.
    *
    * @author Costin Leau
    */
    public class DefaultTypedTuple<V> implements TypedTuple<V> {
    
    private final Double score;
    private final V value;
    
    /**
    * Constructs a new <code>DefaultTypedTuple</code> instance.
    *
    * @param value
    * @param score
    */
    public DefaultTypedTuple(V value, Double score) {
    this.score = score;
    this.value = value;
    }
    
    public Double getScore() {
    return score;
    }
    
    public V getValue() {
    return value;
    }
    //省略
    }
    • spring也对排序中经常使用的范围Range及偏移量也进行了包装。详见org.springframework.data.redis.connection.RedisZSetCommands接口中的Range和 Limit两个内部类。

    2.5.3 使用Spring操作有序集合

    public static void testZset() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
    // Spring提供接口TypedTuple操作有序集合
    Set<TypedTuple> set1 = new HashSet<TypedTuple>();
    Set<TypedTuple> set2 = new HashSet<TypedTuple>();
    int j = 9;
    for (int i = 1; i <= 9; i++) {
    j--;
    // 计算分数和值
    Double score1 = Double.valueOf(i);
    String value1 = "x" + i;
    Double score2 = Double.valueOf(j);
    String value2 = j % 2 == 1 ? "y" + j : "x" + j;
    // 使用Spring提供的默认TypedTuple――DefaultTypedTuple
    TypedTuple typedTuple1 = new DefaultTypedTuple(value1, score1);
    set1.add(typedTuple1);
    TypedTuple typedTuple2 = new DefaultTypedTuple(value2, score2);
    set2.add(typedTuple2);
    }
    // 将元素插入有序集合zset1
    redisTemplate.opsForZSet().add("zset1", set1);
    redisTemplate.opsForZSet().add("zset2", set2);
    // 统计总数
    Long size = null;
    size = redisTemplate.opsForZSet().zCard("zset1");
    // 计分数为score,那么下面的方法就是求3<=score<=6的元素
    size = redisTemplate.opsForZSet().count("zset1", 3, 6);
    Set set = null;
    // 从下标一开始截取5个元素,但是不返回分数,每一个元素是String
    set = redisTemplate.opsForZSet().range("zset1", 1, 5);
    printSet(set);
    // 截取集合所有元素,并且对集合按分数排序,并返回分数,每一个元素是TypedTuple
    set = redisTemplate.opsForZSet().rangeWithScores("zset1", 0, -1);
    printTypedTuple(set);
    // 将zset1和zset2两个集合的交集放入集合inter_zset
    size = redisTemplate.opsForZSet().intersectAndStore("zset1", "zset2", "inter_zset");
    // 区间
    Range range = Range.range();
    range.lt("x8");// 小于
    range.gt("x1");// 大于
    set = redisTemplate.opsForZSet().rangeByLex("zset1", range);
    printSet(set);
    range.lte("x8");// 小于等于
    range.gte("x1");// 大于等于
    set = redisTemplate.opsForZSet().rangeByLex("zset1", range);
    printSet(set);
    // 限制返回个数
    Limit limit = Limit.limit();
    // 限制返回个数
    limit.count(4);
    // 限制从第五个开始截取
    limit.offset(5);
    // 求区间内的元素,并限制返回4条
    set = redisTemplate.opsForZSet().rangeByLex("zset1", range, limit);
    printSet(set);
    // 求排行,排名第1返回0,第2返回1
    Long rank = redisTemplate.opsForZSet().rank("zset1", "x4");
    System.err.println("rank = " + rank);
    // 删除元素,返回删除个数
    size = redisTemplate.opsForZSet().remove("zset1", "x5", "x6");
    System.err.println("delete = " + size);
    // 按照排行删除从0开始算起,这里将删除第排名第2和第3的元素
    size = redisTemplate.opsForZSet().removeRange("zset2", 1, 2);
    // 获取所有集合的元素和分数,以-1代表全部元素
    set = redisTemplate.opsForZSet().rangeWithScores("zset2", 0, -1);
    printTypedTuple(set);
    // 删除指定的元素
    size = redisTemplate.opsForZSet().remove("zset2", "y5", "y3");
    System.err.println(size);
    // 给集合中的一个元素的分数加上11
    Double dbl = redisTemplate.opsForZSet().incrementScore("zset1", "x1", 11);
    redisTemplate.opsForZSet().removeRangeByScore("zset1", 1, 2);
    set = redisTemplate.opsForZSet().reverseRangeWithScores("zset2", 1, 10);
    printTypedTuple(set);
    }
    
    /**
    * 打印TypedTuple集合
    *
    * @param set
    *            -- Set<TypedTuple>
    */
    public static void printTypedTuple(Set<TypedTuple> set) {
    if (set != null && set.isEmpty()) {
    return;
    }
    Iterator iterator = set.iterator();
    while (iterator.hasNext()) {
    TypedTuple val = (TypedTuple) iterator.next();
    System.err.print("{value = " + val.getValue() + ", score = " + val.getScore() + "}\n");
    }
    }

    2.6 基数——HyperLogLog

    • 基数是一种算法。举个例子,一本英文著作由数百万个单词组成,你的内存却不足以存储它们,那么我们先分析一下业务。英文单词本身是有限的,在这本书的几百万个单词中有许许多多重复单词,扣去重复的单词,这本书中也就是几千到一万多个单词而已,那么内存就足够存储它们了。比如数字集合{1,2,5,7,9,1,5,9}的基数集合为{1,2,5,7,9}那么基数(不重复元素)就是5,基数的作用是评估大约需要准备多少个存储单元去存储数据,但是基数的算法一般会存在一定的误差(一般是可控的)。Redis 对基数数据结构的支持是从版本2.8.9开始的。

    • 基数并不是存储元素,存储元素消耗内存空间比较大,而是给某一个有重复元素的数据集合(一般是很大的数据集合)评估需要的空间单元数,所以它没有办法进行存储。

    public static void testHyperLogLog() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
    
    redisTemplate.opsForHyperLogLog().add("HyperLogLog", "a", "b", "c", "d", "a");
    redisTemplate.opsForHyperLogLog().add("HyperLogLog2", "a");
    redisTemplate.opsForHyperLogLog().add("HyperLogLog2", "z");
    Long size = redisTemplate.opsForHyperLogLog().size("HyperLogLog");
    System.err.println(size);
    size = redisTemplate.opsForHyperLogLog().size("HyperLogLog2");
    System.err.println(size);
    redisTemplate.opsForHyperLogLog().union("des_key", "HyperLogLog", "HyperLogLog2");
    size = redisTemplate.opsForHyperLogLog().size("des_key");
    System.err.println(size);
    }
    阅读更多
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: