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)