萬丈高樓平地起 ——Redis 基礎數據結構

Redis 有 5 種基礎數據結構,分別爲:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。java

1、string (字符串)

字符串內部結構

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);

過時和 set 命令擴展

能夠對 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

2、list (列表)

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());

3、hash (字典)

Redis 的字典至關於 Java 語言裏面的 HashMap,它是無序字典。內部實現結構上同Java 的 HashMap 也是一致的,一樣的數組 + 鏈表二維結構。第一維 hash 的數組位置碰撞時,就會將碰撞的元素使用鏈表串接起來。

hash字典數據結構

不一樣的是,Redis 的字典的值只能是字符串,另外它們 rehash 的方式不同,由於Java 的 HashMap 在字典很大時,rehash 是個耗時的操做,須要一次性所有 rehash。Redis 爲了高性能,不能堵塞服務,因此採用了漸進式 rehash 策略。

漸進式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");

3、set (集合)

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());

4、zset (有序列表)

zset 多是 Redis 提供的最爲特點的數據結構,它相似於 Java 的 SortedSetHashMap 的結合體,一方面它是一個 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)

相關文章
相關標籤/搜索