通過BitSet源碼來理解BitMap算法

BitMap是一種很常用的數(shù)據(jù)結(jié)構(gòu)鼠哥,它的思想的和原理是很多算法的基礎(chǔ)驻售,當(dāng)然露久,并且在索引,數(shù)據(jù)壓縮欺栗,海量數(shù)據(jù)處理等方面有廣泛應(yīng)用毫痕。


一、簡(jiǎn)介

BitMap 是一種很常用的數(shù)據(jù)結(jié)構(gòu)迟几,它的思想和原理是很多算法的基礎(chǔ)消请,比如Bloom Filter 。

BitMap 的基本原理就是用一個(gè) bit 位來存放某種狀態(tài)(如果理解不了类腮,看完下文再回頭來看即可)臊泰,適用于擁有大規(guī)模數(shù)據(jù),但數(shù)據(jù)狀態(tài)又不是很多的情況蚜枢。通常是用來判斷某個(gè)數(shù)據(jù)存不存在的缸逃。
它最大的一個(gè)特點(diǎn)就是對(duì)內(nèi)存的占用極小,所以經(jīng)常在大數(shù)據(jù)中被優(yōu)化使用厂抽。

為什么說占用內(nèi)存小呢需频?其實(shí)從名字就可以看出端倪,直譯過來叫位圖筷凤,但不是圖形學(xué)里面的位圖哦昭殉,關(guān)鍵單詞是Bit。比如通過某種方法用一個(gè) bit 來表示一個(gè) int,這樣的話內(nèi)存足足壓縮至 1/32(1 int = 4 byte = 32 bit挪丢,PS:理論計(jì)算而已莽鸭,實(shí)操時(shí)并不會(huì)有 1/32 這么夸張,下文會(huì)解釋)吃靠,所以原先需要8G內(nèi)存的數(shù)據(jù)硫眨,現(xiàn)在只需要256M,豈不樂哉巢块?當(dāng)然了礁阁,其中算法的一些概念在下文會(huì)詳解。

二族奢、初窺 BitMap

1姥闭、概念理解

所謂的 BitMap 就是用一個(gè) Bit 位來標(biāo)記某個(gè)元素對(duì)應(yīng)的 Value, 而 Key 即是該元素越走。由于采用了 Bit 為單位來存儲(chǔ)數(shù)據(jù)棚品,因此在存儲(chǔ)空間方面,可以大大節(jié)省廊敌。

比如有個(gè) int 數(shù)組 [2,6,1,7,3]铜跑,內(nèi)含5個(gè)元素,存儲(chǔ)的空間大小為 5 * 32 = 160 bit骡澈,取的時(shí)候锅纺,使用元素的下標(biāo)來獲取對(duì)應(yīng)位上的元素。

但是如果換種思路肋殴,把元素的值作為下標(biāo)囤锉,每個(gè)下標(biāo)位使用 bit 來標(biāo)記,有值則為1护锤,否則為0官地,此時(shí)我們只需要在內(nèi)存上開辟一個(gè)連續(xù)的二進(jìn)制位空間,長度為8(因?yàn)樯厦鏀?shù)據(jù)最大的元素是7烙懦,但是需要考慮下標(biāo)起點(diǎn)為0)驱入,則可以表示成:

BitMap 最簡(jiǎn)單的說明圖,藍(lán)色代表有值

說明:初始化一個(gè)長度是8的 BitMap修陡,初始值均為0沧侥,然后將[2,6,1,7,3]填入對(duì)應(yīng)的下標(biāo)處,上圖中藍(lán)色域魄鸦,即將這幾個(gè)下標(biāo)處的值設(shè)置為1宴杀,所以表示為:1 1 0 0 1 1 1 0。此時(shí)占用的內(nèi)存空間為 8 bit拾因,而原來是 160 bit(順便解釋下上文提到的 1/32旺罢,因?yàn)槲覀冮_辟的是連續(xù)的內(nèi)容空間旷余,所以會(huì)有冗余)。

2扁达、案例說明

① 案例一:還是上文的數(shù)組正卧,需求是查詢?cè)?是否在數(shù)組中。
原先我們需要遍歷整個(gè)數(shù)組跪解,時(shí)間復(fù)雜度為 O(n);
而現(xiàn)在我們只需要查驗(yàn)下標(biāo)為6的字節(jié)是0還是1即可炉旷,如果是1,則代表存在叉讥,時(shí)間復(fù)雜度直接降為 O(1)窘行。
所以,最直接的應(yīng)用場(chǎng)景便是:數(shù)據(jù)的查重图仓。

② 案例二:有兩個(gè)數(shù)組罐盔,判斷這兩個(gè)數(shù)組中的重復(fù)元素。
原先的最淺顯的做法是雙層for循環(huán)進(jìn)行判斷比較救崔。
而現(xiàn)在惶看,只需要將轉(zhuǎn)換完成的兩個(gè)BirMap進(jìn)行與運(yùn)算即可,如:11001110B & 10100000B = 10000000B六孵,所有得出結(jié)果纬黎,只有元素 7 重復(fù)。
當(dāng)然狸臣,最直接的應(yīng)用場(chǎng)景是:每個(gè)客戶都有不同的標(biāo)簽莹桅,當(dāng)需要查找同時(shí)符合標(biāo)簽a和標(biāo)簽b的客戶的時(shí)候昌执,只需要將標(biāo)簽a和標(biāo)簽b的客戶查出來進(jìn)行如上的與運(yùn)算即可烛亦。

3、補(bǔ)充說明

① 實(shí)際使用的時(shí)候懂拾,并不會(huì)向上面一樣很隨意地將長度設(shè)置為8煤禽,一般會(huì)設(shè)置為32(int型)或64(long型),理由見下文 BitSet 源碼即可岖赋。
② 除了上文提到的與運(yùn)算檬果,當(dāng)然了,邏輯或和邏輯異或操作都是OK的唐断。
③ 每個(gè)Bit位只能是0或1选脊,所以只能代表true or false,當(dāng)我們要進(jìn)行少量統(tǒng)計(jì)的時(shí)候脸甘,可以使用2-BitMap恳啥,即每個(gè)位上可以使用 00、01丹诀、10钝的、11來分別表示數(shù)量為 0翁垂、1、2硝桩,此時(shí)的 11 一般無意義沿猜。

三、BitSet 源碼

1碗脊、簡(jiǎn)述

對(duì)于 BitMap 這種經(jīng)典的數(shù)據(jù)結(jié)構(gòu)啼肩,在 Java 語言里面,其實(shí)已經(jīng)有對(duì)應(yīng)實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)類 java.util.BitSet 了(**@since ****JDK1.0)衙伶,而 BitSet 的底層原理疟游,其實(shí)就是用 long 類型的數(shù)組來存儲(chǔ)元素,所以回過頭來看上文提到的為什么實(shí)際使用的時(shí)候痕支,長度一般會(huì)是有規(guī)則的颁虐,因?yàn)榇颂幨褂玫氖莑ong類型的數(shù)組,而 1 long = 64 bit卧须,所以數(shù)據(jù)大小會(huì)是64的整數(shù)倍另绩。

/**
* The internal field corresponding to the serialField "bits".
*/
private  long[]  words;

至于 Java 中的 BitSet 為什么使用 long 數(shù)組而不使用 int 數(shù)組,我覺得應(yīng)該是出于 Java 語言的性能考慮的花嘶,因?yàn)樵谶M(jìn)行邏輯與等一系列位運(yùn)算的時(shí)候笋籽,是需要將兩個(gè)數(shù)組中的元素一一進(jìn)行位運(yùn)算的,而使用 long 的一個(gè)好處是數(shù)組的長度減少了椭员,從而遍歷的次數(shù)也就減少了车海。

總之就是和場(chǎng)景有關(guān)系,抽象概念上就有點(diǎn)類似 Java 中字符串的匹配算法(indexOf)使用的是 BF(暴力檢索)算法一樣隘击,為什么不用更優(yōu)解呢侍芝?還不是因?yàn)楦鼉?yōu)解在少量數(shù)據(jù)的情況下反而是拖后腿的那一位。

2埋同、成員變量

BitSet 成員變量

3州叠、構(gòu)造方法

有參構(gòu)造的參數(shù)代表的是元素的長度,不是數(shù)組的大小凶赁,比如傳參1和64咧栗,數(shù)組的長度均為1,整個(gè)size均為64虱肄,但是傳參65的時(shí)候致板,數(shù)組長度為2,size為128咏窿,因?yàn)閿?shù)組是long類型斟或,而一個(gè)long可以存儲(chǔ)64個(gè)bit元素。

BitSet 構(gòu)造函數(shù)

4翰灾、 initWords 函數(shù)

該函數(shù)只在兩個(gè)構(gòu)造方法中調(diào)用缕粹,作用是初始化數(shù)組稚茅,而數(shù)組的長度則會(huì)通過 workIndex(nbits-1) + 1 來獲取。

5平斩、 wordIndex 函數(shù)

這個(gè)方法很重要亚享, 它是用來獲取某個(gè)數(shù)在 words 數(shù)組中的索引的,采用的算法是將這個(gè)數(shù)右移6位绘面,why欺税?因?yàn)?bitIndex >> 6 == bitIndex / (2^6) == bitIndex /64,而long就是64個(gè)字節(jié)揭璃。

獲取元素在 work 數(shù)組中的下標(biāo)

6晚凿、ensureCapacity 函數(shù)

又是一個(gè)很重要的方法,作用是動(dòng)態(tài)擴(kuò)容瘦馍,因?yàn)樵诔跏蓟臅r(shí)候歼秽,我們并不知道將來會(huì)需要存儲(chǔ)多大的數(shù)據(jù)。

數(shù)組動(dòng)態(tài)擴(kuò)容

7情组、size 和 length 函數(shù)

size 方法很好理解燥筷,返回的其實(shí)就是數(shù)組的空間大小,即數(shù)組長度64院崇。
而 length 方法肆氓,看源碼其實(shí)有點(diǎn)晦(qu)澀(qiao),簡(jiǎn)言之底瓣,返回的是 BitSet 的“邏輯大小”谢揪,即
BitSet 中最高設(shè)置位的索引加 1* 。

舉個(gè)栗子捐凭,一個(gè) BitSet 中存儲(chǔ)了兩個(gè)元素拨扶,10和50,那么柑营,此時(shí)這個(gè) BitMap 的:size = 64屈雄;length = 51。

計(jì)算 length

8官套、題外話

其余的 set、get等方法暫不贅述蚁孔,總之一句話奶赔,想要深刻理解 BitSet 的源碼,對(duì)于二進(jìn)制的計(jì)算需要有一定的掌握水準(zhǔn)杠氢。不得不承認(rèn)站刑,BitSet 的源碼,很多細(xì)節(jié)的設(shè)計(jì)太精妙了鼻百。

四绞旅、拓展

如要論述拓展摆尝,要么就是論述場(chǎng)景的高層次應(yīng)用,要么就是論述此算法的不足之處因悲,此處各提一個(gè)點(diǎn):

不足:數(shù)據(jù)稀疏問題堕汞,比如三個(gè)元素(1,100,10000000),則需要初始化的長度為 10000000晃琳,很不合理讯检,此時(shí)可以使用 Roaring BitMap 算法來解決,而 Java 程序可以使用goolge的 **EWAHCompressedBitmap **來解決卫旱。

拓展:數(shù)據(jù)碰撞問題人灼,比如上文提到的爬蟲應(yīng)用場(chǎng)景是將URL進(jìn)行哈希運(yùn)算,然后將hash值存入BitMap之中顾翼,但是不得不面臨一個(gè)尷尬的情況投放,那就是哈希碰撞,而布隆算法Bloom Filter)就可以解決這個(gè)問題适贸,為什么是拓展呢跪呈?因?yàn)樗且?BitMap 為基礎(chǔ)的排重算法。

原文地址:http://www.jetchen.cn/algorithm-bitmap-bitset/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末取逾,一起剝皮案震驚了整個(gè)濱河市耗绿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砾隅,老刑警劉巖误阻,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異晴埂,居然都是意外死亡究反,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門儒洛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來精耐,“玉大人,你說我怎么就攤上這事琅锻∝酝#” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵恼蓬,是天一觀的道長惊完。 經(jīng)常有香客問我,道長处硬,這世上最難降的妖魔是什么小槐? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮荷辕,結(jié)果婚禮上凿跳,老公的妹妹穿的比我還像新娘件豌。我一直安慰自己,他們只是感情好控嗜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布茧彤。 她就那樣靜靜地躺著,像睡著了一般躬审。 火紅的嫁衣襯著肌膚如雪棘街。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天承边,我揣著相機(jī)與錄音遭殉,去河邊找鬼。 笑死博助,一個(gè)胖子當(dāng)著我的面吹牛险污,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播富岳,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蛔糯,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了窖式?” 一聲冷哼從身側(cè)響起蚁飒,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萝喘,沒想到半個(gè)月后淮逻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阁簸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年爬早,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片启妹。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筛严,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饶米,到底是詐尸還是另有隱情桨啃,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布咙崎,位于F島的核電站优幸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏褪猛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一羹饰、第九天 我趴在偏房一處隱蔽的房頂上張望伊滋。 院中可真熱鬧碳却,春花似錦、人聲如沸笑旺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筒主。三九已至关噪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乌妙,已是汗流浹背使兔。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藤韵,地道東北人虐沥。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像泽艘,于是被迫代替她去往敵國和親欲险。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容