Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。java
Redis 的字符串是动态字符串,是能够修改的字符串,内部结构实现上相似于 Java 的ArrayList
,采用预分配冗余空间的方式来减小内存的频繁分配,如图中所示,内部为当前字符串实际分配的空间 capacity 通常要高于实际字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,若是超过 1M,扩容时一次只会多扩 1M 的空间。须要注意的是字符串最大长度为 512M。python
jedis.set(STR,STR); Assert.assertTrue(jedis.exists(STR)); Assert.assertEquals(STR,jedis.get(STR)); jedis.del(STR);
能够批量对多个字符串进行读写,节省网络耗时开销golang
jedis.mset(STR+1,STR+1,STR+2,STR+2); String[] mKey = new String[]{STR + 1, STR + 2}; List<String> mStr = jedis.mget(mKey); Assert.assertEquals(2,mStr.size()); Assert.assertEquals(CollectionUtil.newArrayList(mKey),mStr); jedis.del(mKey);
能够对 key 设置过时时间,到点自动删除,这个功能经常使用来控制缓存的失效时间。数组
jedis.set(STR,STR); jedis.expire(STR,2); // 2s 后过时 Thread.sleep(2000); Assert.assertFalse(jedis.exists(STR)); jedis.setex(STR,2,STR); // 2s 后过时 Thread.sleep(2000); Assert.assertFalse(jedis.exists(STR)); Long setnx = jedis.setnx(STR, STR);// 若是 key 不存在就执行 set 建立 Assert.assertEquals(1,setnx.longValue()); setnx = jedis.setnx(STR,STR+1); // 由于 key 已经存在,因此 set 建立不成功 Assert.assertEquals(0,setnx.longValue()); jedis.del(STR);
若是 value 值是一个整数,还能够对它进行自增操做。自增是有范围的,它的范围是signed long 的最大最小值,超过了这个值,Redis 会报错缓存
jedis.set(STR,"0"); jedis.incr(STR); Assert.assertEquals("1",jedis.get(STR)); jedis.incrBy(STR,5); Assert.assertEquals("6",jedis.get(STR)); jedis.set(STR,Long.MAX_VALUE + StrUtil.EMPTY); try { jedis.incr(STR); }catch (Exception e){ Assert.assertEquals(JedisDataException.class,e.getClass()); } jedis.del(STR);
字符串是由多个字节组成,每一个字节又是由 8 个 bit 组成,如此即可以将一个字符串当作不少 bit 的组合,这即是 bitmap「位图」数据结构。ruby
Redis 的列表至关于 Java 语言里面的 LinkedList
,注意它是链表而不是数组。这意味着list 的插入和删除操做很是快,时间复杂度为 O(1),可是索引定位很慢,时间复杂度为O(n),这点让人很是意外。网络
当列表弹出了最后一个元素以后,该数据结构自动被删除,内存被回收。数据结构
Redis 的列表结构经常使用来作异步队列使用。将须要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另外一个线程从这个列表中轮询数据进行处理。异步
jedis.rpush(BOOKS,"python","java","golang"); Assert.assertEquals(3,jedis.llen(BOOKS).longValue()); Assert.assertEquals("python",jedis.lpop(BOOKS)); Assert.assertEquals("java",jedis.lpop(BOOKS)); Assert.assertEquals("golang",jedis.lpop(BOOKS)); Assert.assertEquals(null,jedis.lpop(BOOKS));
Assert.assertEquals(3,jedis.rpush(BOOKS,"python","java","golang").longValue()); Assert.assertEquals("golang",jedis.rpop(BOOKS)); Assert.assertEquals("java",jedis.rpop(BOOKS)); Assert.assertEquals("python",jedis.rpop(BOOKS)); Assert.assertEquals(null,jedis.rpop(BOOKS));
lindex
至关于 Java 链表的 get(int index)
方法,它须要对链表进行遍历,性能随着参数index 增大而变差。 ltrim
跟的两个参数 start_index 和 end_index 定义了一个区间,在这个区间内的值要保留,区间以外通通砍掉。咱们能够经过 ltrim 来实现一个定长的链表,这一点很是有用。index 能够为负数,index=-1 表示倒数第一个元素,一样 index=-2 表示倒数第二个元素性能
Assert.assertEquals(3,jedis.rpush(BOOKS, "python", "java", "golang").longValue()); Assert.assertEquals("java",jedis.lindex(BOOKS,1)); // O(n) 慎用 Assert.assertEquals(CollectionUtil.newArrayList("python", "java", "golang"), jedis.lrange(BOOKS,0,-1)); // O(n) 慎用 jedis.ltrim(BOOKS,1,-1); Assert.assertEquals(CollectionUtil.newArrayList( "java", "golang"), jedis.lrange(BOOKS,0,-1)); // O(n) 慎用 jedis.ltrim(BOOKS,1,0); // 是清空了整个列表,由于区间范围长度为负 Assert.assertEquals(0,jedis.llen(BOOKS).longValue());
Redis 的字典至关于 Java 语言里面的 HashMap
,它是无序字典。内部实现结构上同Java 的 HashMap 也是一致的,一样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
不一样的是,Redis 的字典的值只能是字符串,另外它们 rehash 的方式不同,由于Java 的 HashMap 在字典很大时,rehash 是个耗时的操做,须要一次性所有 rehash。Redis 为了高性能,不能堵塞服务,因此采用了渐进式 rehash 策略。
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个hash 结构,而后在后续的定时任务中以及 hash 的子指令中,按部就班地将旧 hash 的内容一点点迁移到新的 hash 结构中。当 hash 移除了最后一个元素以后,该数据结构自动被删除,内存被回收。
hash 结构也能够用来存储用户信息,不一样于字符串一次性须要所有序列化整个对象,hash 能够对用户结构中的每一个字段单独存储。这样当咱们须要获取用户信息时能够进行部分获取。而以整个字符串的形式去保存用户信息的话就只能一次性所有读取,这样就会比较浪费网络流量。
hash 也有缺点,hash 结构的存储消耗要高于单个字符串,到底该使用 hash 仍是字符串,须要根据实际状况再三权衡。
jedis.hset(BOOKS,"java","think in java"); jedis.hset(BOOKS,"golang","concurrency in go"); jedis.hset(BOOKS,"python","python cookbook"); Assert.assertEquals(3,jedis.hlen(BOOKS).longValue()); Map<String, String> books = jedis.hgetAll(BOOKS); Assert.assertEquals(3,books.size()); Assert.assertEquals(jedis.hget(BOOKS,"java"),books.get("java")); jedis.hset(BOOKS,"golang","learning go programming" ); Assert.assertEquals("learning go programming",jedis.hget(BOOKS,"golang")); jedis.hdel(BOOKS,"java","golang","python"); jedis.hmset( BOOKS,books); // 批量 set Assert.assertEquals("concurrency in go",jedis.hget(BOOKS,"golang")); jedis.hdel(BOOKS,ArrayUtil.toArray(books.keySet(),String.class));
同字符串同样,hash 结构中的单个子 key 也能够进行计数,它对应的指令是 hincrby,和 incr 使用基本同样。
jedis.hincrBy(USER_LAOQIAN, "age", 1 ); jedis.hincrBy(USER_LAOQIAN, "age", 1 ); // 老钱又老了一岁 Assert.assertEquals("2",jedis.hget(USER_LAOQIAN,"age")); jedis.hdel(USER_LAOQIAN,"age");
Redis 的集合至关于 Java 语言里面的 HashSet
,它内部的键值对是无序的惟一的。它的内部实现至关于一个特殊的字典,字典中全部的 value 都是一个值 NULL。
当集合中最后一个元素移除以后,数据结构自动删除,内存被回收。 set 结构能够用来存储活动中奖的用户 ID,由于有去重功能,能够保证同一个用户不会中奖两次。
Assert.assertEquals(1,jedis.sadd(BOOKS,"python").longValue()); Assert.assertEquals(0,jedis.sadd(BOOKS,"python").longValue()); // 重复 Assert.assertEquals(2,jedis.sadd(BOOKS,"java","golang").longValue()); Set<String> smembers = jedis.smembers(BOOKS); Console.log(JSONUtil.toJsonPrettyStr(smembers)); // 注意顺序,和插入的并不一致,由于 set 是无序的 Assert.assertTrue(jedis.sismember(BOOKS,"java")); // 查询某个 value 是否存在,至关于 contains(o) Assert.assertFalse(jedis.sismember(BOOKS,"ruby")); Assert.assertEquals(3,jedis.scard(BOOKS).longValue()); // 获取长度至关于 count() Console.log(jedis.spop(BOOKS)); // 弹出一个 Assert.assertEquals(2,jedis.scard(BOOKS).longValue()); jedis.srem(BOOKS,ArrayUtil.toArray(smembers,String.class)); Assert.assertEquals(0,jedis.scard(BOOKS).longValue());
zset 多是 Redis 提供的最为特点的数据结构,它相似于 Java 的 SortedSet
和 HashMap
的结合体,一方面它是一个 set,保证了内部value 的惟一性,另外一方面它能够给每一个 value 赋予一个 score,表明这个 value 的排序权重。它的内部实现用的是一种叫着「跳跃列表」的数据结构。zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。
zset 能够用来存粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。咱们能够对粉丝列表按关注时间进行排序。
zset 还能够用来存储学生的成绩,value 值是学生的 ID,score 是他的考试成绩。咱们能够对成绩按分数进行排序就能够获得他的名次。
String book1 = "think in java"; String book2 = "java concurrency"; String book3 = "java cookbook"; jedis.zadd(BOOKS,9.0,book1); jedis.zadd(BOOKS,8.9,book2); jedis.zadd(BOOKS,8.6,book3); Assert.assertEquals(CollectionUtil.newArrayList(book3,book2,book1), CollectionUtil.newArrayList(jedis.zrange(BOOKS,0,-1))); // 按 score 排序列出,参数区间为排名范围 Assert.assertEquals(CollectionUtil.newArrayList(book1,book2,book3), CollectionUtil.newArrayList(jedis.zrevrange(BOOKS,0,-1))); // 按 score 逆序列出,参数区间为排名范围 Assert.assertEquals(3,jedis.zcard(BOOKS).longValue()); // 至关于 count() Assert.assertEquals("8.9",jedis.zscore(BOOKS, "java concurrency").toString()); // 获取指定 value 的 score Assert.assertEquals(1,jedis.zrank(BOOKS,"java concurrency").longValue() ); // 排名 Assert.assertEquals(0,jedis.zrank(BOOKS,"java cookbook").longValue() ); // 排名,从0开始 Assert.assertEquals(CollectionUtil.newArrayList(book3,book2), CollectionUtil.newArrayList(jedis.zrangeByScore(BOOKS, 0, 8.91))); // 根据分值区间遍历 zset // 根据分值区间 (-∞, 8.91] 遍历 zset,同时返回分值。inf 表明 infinite,无穷大的意思。 Assert.assertEquals(CollectionUtil.newArrayList(new Tuple(book3,8.6),new Tuple(book2,8.9)), CollectionUtil.newArrayList(jedis.zrangeByScoreWithScores(BOOKS, "-inf","8.91"))); jedis.zrem(BOOKS,book1,book2,book3); Assert.assertEquals(0,jedis.zcard(BOOKS).longValue());
本文基于《Redis深度历险:核心原理和应用实践》一文的JAVA实践。更多文章请参考:高性能缓存中间件Redis应用实战(JAVA)