認真寫文章,用心作分享。java
我的網站:yasinshaw.com面試
公衆號:xy的技術圈算法
前面的文章介紹了Redis的五種最經常使用的對象及其底層的數據結構。這篇文章主要介紹一下一個不那麼經常使用,卻很是適用於一些特殊場景的對象:bitmaps。數據庫
先考慮幾個常見的場景:編程
這些需求可使用數據庫來實現,使用一些日誌表,再經過SQL查詢出來。但這樣會浪費大量的磁盤空間,並且性能還很低。在數據量比較大的狀況下,使用數據庫來查詢分析會很是慢。數組
這裏要感謝咱們計算機的科學家大佬們,發明了一種數據結構能夠用來解決這類問題。它就是BitMap。也就是這篇文章主要介紹的Redis bitmaps底層使用的數據結構。數據結構
《編程珠璣》中第一篇講的就是使用BitMap來排序大文件裏面的數據。在一些面試裏也會有相似的題目。性能
面試題目:一個10G的文件,裏面所有是天然數,一行一個,亂序排列,對其排序。在32位機器上面完成,內存限制爲2G。網站
LeetCode上有一個算法題,也是可使用BitMap來解決:日誌
算法題目:從0到n之間取出n個不一樣的數,找出漏掉的那個。注意:你的算法應當具備線性的時間複雜度。你能實現只佔用常數額外空間複雜度的算法嗎?
BitMap實際上是一個數據結構,它是利用了位運算的高性能,來存儲和計算一些信息。接下來咱們來介紹一下BitMap的原理。
BitMap是基於bit位的位置來記錄信息的。好比咱們如今有一個8位的BitMap。最開始時,全部位上都是0。
0, 0, 0, 0, 0, 0, 0, 0
而後有這麼一個需求:假設今天有id爲1,2,4,7這四個用戶登陸了咱們的系統,咱們想要把這個登陸信息記錄下來,就只須要在相應的位置標記爲1就好了:
1, 1, 0, 1, 0, 0, 1, 0
這個時候,你想知道id爲7的用戶今天是否登陸過系統,就只須要去看這個BitMap裏面第7位是否是1就能夠了。
能夠看到,若是你使用一個list或者set來存的話,若是一個id是一個byte,佔8位(真實狀況多是long類型,佔64位),那8個id就要佔64位,而使用BitMap只須要佔用8位。同理,若是咱們的id字段佔了64位,那就能夠節省64倍的空間。並且,能夠利用位運算的特性,來快速實現統計、並集、交集等操做。
看過文章A的人有:1, 2, 4, 7:
1, 1, 0, 1, 0, 0, 1, 0
看過文章B的人有:1, 3, 4, 8:
1, 0, 1, 1, 0, 0, 0, 1
看過文章A且看過文章B的人有:
1, 1, 0, 1, 0, 0, 1, 0
&
1, 0, 1, 1, 0, 0, 0, 1
=
1, 0, 0, 1, 0, 0, 0, 0
獲得看過文章A且看過文章B的人有:1, 4
上面的例子只能放8位,若是個人id是9怎麼辦?
BitMap是一個一個上面這樣的數據來組成的。你可使用一個能夠擴容的整數數組來作,好比long數組。因此能夠無限擴展。
若是id比較稀疏怎麼辦?
好比我要存入的id多是1,101,1001這種比較稀疏的,若是用BitMap就會浪費一些空間。雖然如今開源的一些實現能夠經過記錄偏移量來解決這個問題,但也會由於頻繁的分裂影響性能。這種場景下,其實不建議使用BitMap。
有興趣的同窗能夠了解一下谷歌開源的EWAHCompressedBitMap,它解決了輸入稀疏的問題。
由於筆者主要熟悉Java語言,因此介紹一下Java語言對BitMap的實現。
JDK提供了一個BitMap的實現,叫BitSet
,位於java.util
包下。其底層使用的是一個long
類型的數組,一個long表明一個word。但BitSet沒有解決上面提到的輸入稀疏的問題。谷歌開源的EWAHCompressedBitMap解決了輸入稀疏的問題。
Redis提供了bitmaps對象。實際上是使用的string對象,底層使用了咱們上篇文章提到的SDS。因此會有512M的最大限制,即最多能存2^32
個數據。若是你的id是long類型的,佔64位,那可使用兩個bitmaps來存。
由SDS的底層實現可知,它是能夠擴容和縮容的,可是若是輸入比較稀疏,仍然會浪費大量的內存。因此若是輸入很稀疏,也不建議使用Redis的bitmaps。
Redis提供了bitmaps對象。它在某些場景下能夠節省空間,並顯著提高性能。但若是輸入比較稀疏(好比網站註冊用戶有1億,但天天只有10萬用戶登陸),那還不如使用set。
另一方面,輸入只能是整形。若是是字符串類型的,就無法使用這個數據結構了。
關注公衆號:xy的技術圈