|
https://m.toutiao.com/is/i8UekRdk/
一 為什么會發(fā)明Bitmap在計算機科學中,處理大規(guī)模數(shù)據(jù)時,傳統(tǒng)的數(shù)據(jù)結構和算法往往會遇到性能瓶頸。例如,當需要在大量整數(shù)中查找、去重或進行其他操作時,常規(guī)方法可能需要大量的內存和計算資源。 為了解決這個問題,研究者們開始尋找更高效的數(shù)據(jù)結構。他們發(fā)現(xiàn),如果使用位(bit)來表示數(shù)據(jù),可以極大地節(jié)省內存空間。這是因為一個位只有0和1兩種狀態(tài),相比于使用字節(jié)或更大的單位來存儲數(shù)據(jù),位操作更加高效。 基于上述思路,John W. Tukey在1948年發(fā)明了Bitmap。Bitmap是一種特殊的數(shù)組,它的每個元素都可以表示為一個位。通過使用位運算,可以對Bitmap進行高效的操作,如設置、清除和檢測某個位的狀態(tài)。 二 Bitmap詳解Bitmap 的基本原理就是用一個 bit 來標記某個元素對應的 Value,而 Key 即是該元素。由于采用一 個bit 來存儲一個數(shù)據(jù),因此可以大大的節(jié)省空間。 我們通過一個具體的例子來說明 Bitmap 的原理,假設我們要對 0-31 內的 3 個元素 (10, 17,28) 排序,那么我們就可以采用 Bitmap 方法(假設這些元素沒有重復)。 如下圖,要表示 32 個數(shù),我們就只需要 32 個 bit(4Bytes),首先我們開辟 4Byte 的空間,將這些空間的所有 bit 位都置為 0。 ![]() 然后,我們要添加(10, 17,28) 這三個數(shù)到 BitMap 中,需要的操作就是在相應的位置上將0置為1即可。如下圖,比如現(xiàn)在要插入 10 這個元素,只需要將藍色的那一位變?yōu)?即可。 ![]() 將這些數(shù)據(jù)插入后,假設我們想對數(shù)據(jù)進行排序或者檢索數(shù)據(jù)是否存在,就可以依次遍歷這個數(shù)據(jù)結構,碰到位為 1 的情況,就當這個數(shù)據(jù)存在。 Bitmap 也可以用來表述字符串類型的數(shù)據(jù),但是需要有一層Hash映射,如下圖,通過一層映射關系,可以表述字符串是否存在。 ![]() 代碼實現(xiàn)方面,可陳述如下: 第一步:構建特定長度的byte數(shù)組(new byte[capacity/8 + 1]),其中capacity為整數(shù)數(shù)組長度(如:10億個數(shù)字等) byte[] bits = new byte[getIndex(n) + 1];第二步:計算數(shù)字num在byte[]中的位置(num/8和num >> 3一樣),也就是說num在byte[k],算這個k是幾 第三步:計算數(shù)字num在byte[index]中的位置,就是在byte[index]的第幾位,每個byte有8位(num % 8) public int getPosition(int num){ return num & 0x07;}第四步:將所在位置從0變成1,其它位置不變 第五步:判斷指定數(shù)字num是否存在 public boolean contains(byte[] bits, int num){ return (bits[getIndex(num)] & 1 << getPosition(num)) != 0;}第六步:重置某一數(shù)字對應在bitmap中的值 三 Bitmap優(yōu)缺點優(yōu)點
缺點
四 Bitmap的擴展--Roaring Bitmap針對Bitmap的缺點1(數(shù)據(jù)稀疏時占用內存空間大),S. Chambi、D. Lemire、O. Kaser等人在2016年發(fā)表的論文《Better bitmap performance with Roaring bitmaps》 《Consistently faster and smaller compressed bitmaps with Roaring》中提出了RoaringBitMap,一種高效壓縮位圖,簡稱RBM。 實現(xiàn)思路 RBM主要將32位的整型(int)分為高16位和低16位(兩個short),其中高16位對應的數(shù)字使用16位整型有序數(shù)組存儲,低16位根據(jù)不同的情況選擇三種不同的container來存儲,這三種container分別為: 1 Array Container 底層數(shù)據(jù)結構為short類型的數(shù)組,直接將數(shù)字低16位的值存儲到該數(shù)組中。short類型的數(shù)組始終保持有序,方便使用二分查找,且不會存儲重復數(shù)值。因為這種Container存儲數(shù)據(jù)沒有任何壓縮,因此只適合存儲少量數(shù)據(jù)。其內部數(shù)組容量是動態(tài)變化的,當容量不夠時會進行擴容,最大容量為4096。由于數(shù)組是有序的,存儲和查詢時都可以通過二分查找快速定位其在數(shù)組中的位置。 ArrayContainer占用的空間大小與存儲的數(shù)據(jù)量為線性關系,每個short為2字節(jié),因此存儲了N個數(shù)據(jù)的ArrayContainer占用空間大致為2N字節(jié)。存儲一個數(shù)據(jù)占用2字節(jié),存儲4096個數(shù)據(jù)占用8kb。 ![]() 2 Bitmap Container 底層實現(xiàn)為位圖。這種Container使用long[]存儲位圖數(shù)據(jù)。我們知道,每個Container處理16位整形的數(shù)據(jù),也就是0~65535,因此根據(jù)位圖的原理,需要65536個比特來存儲數(shù)據(jù),每個比特位用1來表示有,0來表示無。每個long有64位,因此需要1024個long來提供65536個比特。 因此,每個BitmapContainer在構建時就會初始化長度為1024的long[]。這就意味著,不管一個BitmapContainer中只存儲了1個數(shù)據(jù)還是存儲了65536個數(shù)據(jù),占用的空間都是同樣的8kb。 ![]() 3 Run Container RunContainer中的Run指的是行程長度壓縮算法(Run Length Encoding),對連續(xù)數(shù)據(jù)有比較好的壓縮效果。它的原理是,對于連續(xù)出現(xiàn)的數(shù)字,只記錄初始數(shù)字和后續(xù)數(shù)量。即:
源碼中的short[] valueslength中存儲的就是壓縮后的數(shù)據(jù)。 這種壓縮算法的性能和數(shù)據(jù)的連續(xù)性(緊湊性)關系極為密切,對于連續(xù)的100個short,它能從200字節(jié)壓縮為4字節(jié),但對于完全不連續(xù)的100個short,編碼完之后反而會從200字節(jié)變?yōu)?00字節(jié)。 也就RBM在存入一個32位的整形數(shù)字時,會先按照該數(shù)字的高16位進行分桶,以確定該數(shù)字要存入到哪個桶中。確定好分桶位置后,再將該數(shù)字對應的低16位放入到當前桶所對應的container中。 ![]() 舉個栗子 以十進制數(shù)字131122為例,現(xiàn)在我們要將該數(shù)字放入到RBM中。第一步,先將該數(shù)字轉換為16進制,131122對應的十六進制為0x00020032;其中,高十六位對應0x0002,首先我們找到0x0002所在的桶,再將131122的低16位存入到對應的container中,131122的低16位轉換為10進制就是50,沒有超過ArrayContainer的容量4096,所以將低16位直接放入到對應的ArrayContainer中。 ![]() 如果要插入的數(shù)字低16位超過了4096,RBM會將ArrayContainer轉換為BitMapContainer。反之,如果數(shù)據(jù)在刪除之后,數(shù)組中的最大數(shù)據(jù)小于4096,RBM會將BitMapContainer轉換回ArrayContainer。 ![]() RBM處理的是32位的數(shù)字,如果我們想處理Long類型的數(shù)字怎么辦呢?這個時候可以使用Roaring64NavigableMap。Roaring64NavigableMap也是使用拆分模式,將一個long類型數(shù)據(jù),拆分為高32位與低32位,高32位代表索引,低32位存儲到對應RoaringBitmap中,其內部是一個TreeMap類型的結構,會按照signed或者unsigned進行排序,key代表高32位,value代表對應的RoaringBitmap。 空間占用對比
五 Bitmap的擴展--Bloom Filter針對Bitmap的缺點2(數(shù)據(jù)不能重復),Burton Howard Bloom在1970年他發(fā)表的論文《Space/Time Trade-offs in Hash Coding with Allowable Errors》中提出了Bloom Filter。 Bloom Filter:由只存0或1的位數(shù)組和多個hash算法, 進行判斷數(shù)據(jù)一定不存在或者可能存在的算法;如果這些bit數(shù)組 有任何一個0,則被判定的元素一定不在; 如果都是1則被檢元素很可能在;對比bitmap位圖,布隆過濾器適合更多類型元素,通過hash值轉換。 ![]() 每次處理元素時,
優(yōu)點:占用空間小,查詢速度快,空間效率和查詢時間都遠遠超過一般的算法 缺點:有一定的誤識別率,有一定的誤識別率,即某個元素可能存在,但實際上并不存在;刪除困難,因為無法確定某個位置是由哪個元素映射而來的 ![]() ![]() 六 Bitmap的應用--Redis![]() 統(tǒng)計當日活躍用戶 每日活躍統(tǒng)計創(chuàng)建一個bitmap鍵,當用戶活躍了根據(jù)用戶id的偏移量來設置對應的位為1 用戶簽到 每個用戶創(chuàng)建一個位圖的鍵,以某一天為基礎,之后的天數(shù)距離這一天的天數(shù)為偏移量,如果用戶點擊了簽到,則設置對用的偏移位為1。 應對緩存穿透問題 舉例:比如爬蟲服務器在爬取電商網站的商品信息時,首先經過緩存,如果緩存查不到,再去數(shù)據(jù)庫獲取信息,因為爬蟲的效率很高,且sku很有可能是不存在或者已下架的,就會造成緩存穿透,大量請求被發(fā)送到數(shù)據(jù)庫,導致服務器受到影響。 此時,可以在緩存層之前,添加一個布隆過濾器,布隆 過濾器看作是一個bitmap,sku作為offset值,如果商品真實存在,bit值設為1。首先將商品數(shù)據(jù)初始化,當有請求時,通過getbit判斷sku是否有效。如果布隆過濾器認為商品不存在,就拒絕訪問,這樣就可以保護存儲層。 七 Bitmap的應用--阿里Hologres技術平臺基于RoaringBitmap的畫像分析使用實踐,技術用戶畫像分析是對一個或多個指定用戶群,通過可視化的方式,可以多維、立體的展示用戶群的畫像信息,讓運營更好的理解人群,實現(xiàn)人群細分及對個性化運營策略的制定提供能力支撐;在進行數(shù)據(jù)分析時,更加便捷的獲得人群的特征信息,從而更好的進行定量的分析。 業(yè)務畫像分析流程: 1、默認情況下平臺有業(yè)務的所有標簽在MaxCompute中存儲,同時也會存儲一份Bitmap的標簽索引數(shù)據(jù),再標簽數(shù)據(jù)更新時構建。 2、業(yè)務可以使用標簽和其他用戶相關的數(shù)據(jù)圈選人群,人群圈選完成后,也會對其構建一份Bitmap索引數(shù)據(jù)。 3、圈選完人群后,就會進行畫像分析,如果標簽和人群都有Bitmap索引數(shù)據(jù),就會通過Bitmap進行分析 ![]() 技術平臺早期畫像分析主要依賴于MaxCompute執(zhí)行SQL,但這種方式耗時較長,對于需要實時洞察分析的業(yè)務場景,分析需要更加快速看到結果,高延遲無法滿足業(yè)務需求。之前嘗試通過將數(shù)據(jù)導入Hologres進行加速,然而由于數(shù)據(jù)量龐大,Hologres的執(zhí)行耗時仍然超過3分鐘。 在前期,根據(jù)業(yè)務需求在技術上做了一些調研。從技術方案角度看,一些可以實現(xiàn)的畫像分析的方案和其缺點: ![]() 而Hologres原生支持RoaringBitmap數(shù)據(jù)結構,特別適合大數(shù)據(jù)量級下的交并差運算,并且在畫像分析的場景天然適合,因此我們最后選擇了RoaringBitmap方案。雖然RoaringBitmap會帶來額外的處理和優(yōu)化步驟、索引構建過程,這可能會帶來一定的初期成本上升。然而,在既定的場景中,數(shù)據(jù)更新不是那么頻繁,數(shù)據(jù)量級也在可控范圍內,因此這一成本上升在可接受范圍。而且使用RoaringBitmap帶來的效率提升非常明顯,最終還是選擇使用這一方案進行標簽和人群加速。 八 Bitmap的應用--美團DorisDoris是美團數(shù)據(jù)加工處理的產品,其在整個數(shù)倉的位置如下圖所示 ![]() 在24年約Q3前后,從位運算、內存拷貝、接口、并發(fā)等四類場景對Doris中使用的Bitmap進行優(yōu)化,如: ![]() ![]() ![]() ![]() 九 Bitmap其他應用場景
十 Bitmap經典面試題解析題目1:什么是Bitmap? Bitmap是一種數(shù)據(jù)結構,它使用二進制位來表示數(shù)據(jù),可以高效地處理大量數(shù)據(jù)。 題目2:Bitmap有什么優(yōu)點? Bitmap的優(yōu)點包括:
題目3:如何實現(xiàn)一個Bitmap? 實現(xiàn)一個Bitmap需要使用一個數(shù)組來存儲二進制位,每個位表示一個數(shù)據(jù)元素是否出現(xiàn)。可以使用位運算來設置、清除和檢查位的狀態(tài)。 題目4:如何使用Bitmap進行數(shù)據(jù)的查找? 使用Bitmap進行數(shù)據(jù)的查找時,首先需要將數(shù)據(jù)元素的數(shù)值轉換為對應的位索引,然后檢查該位的狀態(tài)。如果該位為1,則表示該數(shù)據(jù)元素出現(xiàn);如果該位為0,則表示該數(shù)據(jù)元素未出現(xiàn)。 題目5:如何使用Bitmap進行數(shù)據(jù)的計數(shù)? 使用Bitmap進行數(shù)據(jù)的計數(shù)時,需要遍歷所有的位,統(tǒng)計為1的位的數(shù)量即可。 題目6:如何使用Bitmap進行數(shù)據(jù)的刪除? 使用Bitmap進行數(shù)據(jù)的刪除時,需要將該數(shù)據(jù)元素對應的位設置為0,表示該數(shù)據(jù)元素不再出現(xiàn)。 題目7:如何優(yōu)化Bitmap的性能?
題目8:Bitmap適用于哪些場景?
題目9:如何理解Bitmap的位運算? 位運算是一種二進制運算,包括與、或、異或、非等操作。在Bitmap中,可以使用位運算來設置、清除和檢查位的值。例如,將第i位設置為1可以使用“1<<i”來表示;將第i位清除可以使用“~(1<<i)”來表示;檢查第i位的值可以使用“(value & (1<<i)) != 0”來判斷。 題目10:如何理解Bitmap的存儲結構? Bitmap的存儲結構通常是一個數(shù)組,每個元素是一個二進制位。可以使用無符號整數(shù)類型來存儲這個數(shù)組,也可以使用位向量或者位集等特殊的數(shù)據(jù)結構來存儲這個數(shù)組。具體的存儲結構取決于具體的應用場景和需求。 題目11:如何理解Bitmap的動態(tài)擴展? 當需要添加新的數(shù)據(jù)元素時,可能需要動態(tài)擴展Bitmap的大小。這可以通過創(chuàng)建一個新的更大的數(shù)組,并將原來的數(shù)據(jù)復制到新的數(shù)組中來實現(xiàn)。具體的擴展方式取決于具體的應用場景和需求。 題目12:如何理解Bitmap的內存優(yōu)化? 在處理海量數(shù)據(jù)時,需要盡可能地節(jié)省內存空間。可以使用壓縮技術來減少Bitmap所占用的內存空間。此外,還可以通過復用已經計算出來的結果來減少計算量,進一步提高效率。例如,可以將已經計算出來的結果緩存起來,避免重復計算。 題目13:如何理解Bitmap的并發(fā)操作? 在多線程環(huán)境下,需要對Bitmap進行并發(fā)操作。這需要使用線程安全的并發(fā)數(shù)據(jù)結構,例如ConcurrentHashMap或者AtomicInteger等。同時,還需要對并發(fā)操作進行合理的同步和調度,以避免數(shù)據(jù)不一致和競態(tài)條件等問題。 題目14:如何理解Bitmap的位反轉問題? 在Bitmap中,如果某個位被錯誤地設置或者清除,可能會導致位反轉問題。這通常是由于位運算的錯誤或者硬件故障等原因引起的。為了避免位反轉問題,可以使用校驗和等技術來檢測和修復錯誤。 題目15:如何理解Bitmap的索引問題? 在Bitmap中,每個位都對應一個數(shù)據(jù)元素。但是,如果需要查找某個具體的數(shù)據(jù)元素,需要遍歷所有的位,這可能會非常耗時。為了解決這個問題,可以使用索引技術來快速定位到需要查找的數(shù)據(jù)元素。例如,可以使用哈希表或者B樹等數(shù)據(jù)結構來建立索引。 題目16:如何理解Bitmap的動態(tài)擴容問題? 當Bitmap的大小不足以容納新的數(shù)據(jù)元素時,需要進行動態(tài)擴容。這需要重新分配內存空間,并將原來的數(shù)據(jù)元素復制到新的數(shù)組中。這個過程可能會非常耗時,并且可能會影響程序的性能。為了解決這個問題,可以使用預分配技術來預先分配足夠的內存空間,避免頻繁的動態(tài)擴容操作。 題目17:如何理解Bitmap的位壓縮問題? 在處理海量數(shù)據(jù)時,為了節(jié)省內存空間,需要對Bitmap進行位壓縮。但是,壓縮和解壓縮的過程可能會非常耗時,并且可能會影響程序的性能。為了解決這個問題,可以使用高效的壓縮和解壓縮算法,例如LZ77或者Huffman編碼等。 題目18:如何理解Bitmap的刪除操作? 在Bitmap中,刪除一個數(shù)據(jù)元素通常需要將該數(shù)據(jù)元素對應的位設置為0。但是,如果該數(shù)據(jù)元素對應的位已經被其他數(shù)據(jù)元素共享,那么刪除該數(shù)據(jù)元素可能會影響其他數(shù)據(jù)元素的表示。為了解決這個問題,可以使用位運算中的異或操作來刪除一個數(shù)據(jù)元素,而不會影響其他數(shù)據(jù)元素的表示。 題目19:如何理解Bitmap的位運算效率問題? 位運算是一種非常高效的運算方式,但是在進行復雜的位運算時,也可能會遇到效率問題。為了提高位運算的效率,可以使用一些優(yōu)化技巧,例如使用緩存、并行計算、硬件加速等。同時,也可以使用一些高效的位運算庫或者工具來提高效率。 題目20:如何理解Bitmap的適用場景和局限性? Bitmap是一種非常高效的數(shù)據(jù)結構,但是也有一些局限性。例如,它不適合處理包含大量重復元素的數(shù)據(jù)集,因為這會導致大量的位被占用。此外,Bitmap也不適合處理需要頻繁更新和查找的數(shù)據(jù)集,因為這可能會導致大量的磁盤I/O操作。因此,在使用Bitmap時需要考慮到它的適用場景和局限性。 十一 Bitmap算法題解析問題1:給定一個長度為n的整數(shù)數(shù)組,其中可能存在重復元素,要求使用Bitmap算法找出數(shù)組中只出現(xiàn)一次的元素。 問題2:有兩個非常大的整數(shù)集合A和B,要求使用Bitmap算法求出它們的交集。 問題3:給定一個長度為n的整數(shù)數(shù)組和一個目標整數(shù)target,要求使用Bitmap算法判斷數(shù)組中是否存在兩個數(shù)的和等于target。 問題4:給定一個長度為n的整數(shù)數(shù)組,要求使用Bitmap算法找出數(shù)組中出現(xiàn)次數(shù)最多的k個元素。 問題5:給定一個長度為n的整數(shù)數(shù)組和一個整數(shù)k,要求使用Bitmap算法找出數(shù)組中是否存在長度為k的連續(xù)子數(shù)組的和等于某個給定值。 問題6:給定兩個非常大的整數(shù)集合A和B,要求使用Bitmap算法求出它們的并集。 問題7:給定一個長度為n的整數(shù)數(shù)組和一個整數(shù)k,要求使用Bitmap算法找出數(shù)組中是否存在兩個數(shù)的差的絕對值等于k。 問題8:給定一個長度為n的整數(shù)數(shù)組和一個整數(shù)m,要求使用Bitmap算法判斷數(shù)組中是否存在一個子集的和等于m。 問題9:給定一個長度為n的整數(shù)數(shù)組和一個整數(shù)target,要求使用Bitmap算法找出數(shù)組中是否存在三個數(shù)的和等于target。 問題10:給定一個長度為n的整數(shù)數(shù)組和一個整數(shù)k,要求使用Bitmap算法找出數(shù)組中是否存在長度為k的子數(shù)組的異或和等于0。 十二 參考資料[1] BitMap 的基本原理和實現(xiàn): [2] Bitmap的原理和應用: [3] Bitmap、RoaringBitmap原理分析: [5] Redis之bitmap類型解讀: [6] Hologres RoaringBitmap實踐:千億級畫像數(shù)據(jù)秒級分析: [7] 美團 Doris Bitmap 精確去重優(yōu)化實踐: |
|
|
來自: 山峰云繞 > 《操作系統(tǒng)原理及內核源碼文件》