首先
每個RoaringBitmap(GitHub鏈接)中都包含一個RoaringArray,名字叫highLowContainer。
highLowContainer存儲了RoaringBitmap中的全部數(shù)據(jù)。
RoaringArray highLowContainer;
這個名字意味著,會將32位的整形(int)拆分成高16位和低16位兩部分(兩個short)來處理。
RoaringArray的數(shù)據(jù)結(jié)構(gòu)很簡單,核心為以下三個成員:
#short[] keys;
Container[] values;
int size;
每個32位的整形,高16位會被作為key存儲到short[] keys中,低16位則被看做value,存儲到Container[] values中的某個Container中。keys和values通過下標一一對應(yīng)。size則標示了當前包含的key-value pair的數(shù)量,即keys和values中有效數(shù)據(jù)的數(shù)量。
keys數(shù)組永遠保持有序,方便二分查找。
三種Container
下面介紹到的是RoaringBitmap的核心,三種Container。
通過上面的介紹我們知道,每個32位整形的高16位已經(jīng)作為key存儲在RoaringArray中了,那么Container只需要處理低16位的數(shù)據(jù)。
ArrayContainer
static final int DEFAULT_MAX_SIZE = 4096
short[] content;
結(jié)構(gòu)很簡單,只有一個short[] content,將16位value直接存儲。
short[] content始終保持有序,方便使用二分查找,且不會存儲重復數(shù)值。
因為這種Container存儲數(shù)據(jù)沒有任何壓縮,因此只適合存儲少量數(shù)據(jù)。
ArrayContainer占用的空間大小與存儲的數(shù)據(jù)量為線性關(guān)系,每個short為2字節(jié),因此存儲了N個數(shù)據(jù)的ArrayContainer占用空間大致為2N字節(jié)。存儲一個數(shù)據(jù)占用2字節(jié),存儲4096個數(shù)據(jù)占用8kb。
根據(jù)源碼可以看出,常量DEFAULT_MAX_SIZE值為4096,當容量超過這個值的時候會將當前Container替換為BitmapContainer。
BitmapContainer
final long[] bitmap;
這種Container使用long[]存儲位圖數(shù)據(jù)。我們知道,每個Container處理16位整形的數(shù)據(jù),也就是0~65535,因此根據(jù)位圖的原理,需要65536個比特來存儲數(shù)據(jù),每個比特位用1來表示有,0來表示無。每個long有64位,因此需要1024個long來提供65536個比特。
因此,每個BitmapContainer在構(gòu)建時就會初始化長度為1024的long[]。這就意味著,不管一個BitmapContainer中只存儲了1個數(shù)據(jù)還是存儲了65536個數(shù)據(jù),占用的空間都是同樣的8kb。
RunContainer
private short[] valueslength;
int nbrruns = 0;
RunContainer中的Run指的是行程長度壓縮算法(Run Length Encoding),對連續(xù)數(shù)據(jù)有比較好的壓縮效果。
它的原理是,對于連續(xù)出現(xiàn)的數(shù)字,只記錄初始數(shù)字和后續(xù)數(shù)量。即:
對于數(shù)列11,它會壓縮為11,0;
對于數(shù)列11,12,13,14,15,它會壓縮為11,4;
對于數(shù)列11,12,13,14,15,21,22,它會壓縮為11,4,21,1;
源碼中的short[] valueslength中存儲的就是壓縮后的數(shù)據(jù)。
這種壓縮算法的性能和數(shù)據(jù)的連續(xù)性(緊湊性)關(guān)系極為密切,對于連續(xù)的100個short,它能從200字節(jié)壓縮為4字節(jié),但對于完全不連續(xù)的100個short,編碼完之后反而會從200字節(jié)變?yōu)?00字節(jié)。
如果要分析RunContainer的容量,我們可以做下面兩種極端的假設(shè):
最好情況,即只存在一個數(shù)據(jù)或只存在一串連續(xù)數(shù)字,那么只會存儲2個short,占用4字節(jié)
最壞情況,0~65535的范圍內(nèi)填充所有的奇數(shù)位(或所有偶數(shù)位),需要存儲65536個short,128kb
Container性能總結(jié)
讀取時間
只有BitmapContainer可根據(jù)下標直接尋址,復雜度為O(1),ArrayContainer和RunContainer都需要二分查找,復雜度O(log n)
內(nèi)存占用
這是我畫的一張圖,大致描繪了各Container占用空間隨數(shù)據(jù)量的趨勢。
其中,
ArrayContainer一直線性增長,在達到4096后就完全比不上BitmapContainer了
BitmapContainer是一條橫線,始終占用8kb
RunContainer比較奇葩,因為和數(shù)據(jù)的連續(xù)性關(guān)系太大,因此只能畫出一個上下限范圍。不管數(shù)據(jù)量多少,下限始終是4字節(jié);上限在最極端的情況下可以達到128kb。
RoaringBitmap針對Container的優(yōu)化策略
創(chuàng)建時:
創(chuàng)建包含單個值的Container時,選用ArrayContainer
創(chuàng)建包含一串連續(xù)值的Container時,比較ArrayContainer和RunContainer,選取空間占用較少的
轉(zhuǎn)換:
針對ArrayContainer:
如果插入值后容量超過4096,則自動轉(zhuǎn)換為BitmapContainer。因此正常使用的情況下不會出現(xiàn)容量超過4096的ArrayContainer。
調(diào)用runOptimize()方法時,會比較和RunContainer的空間占用大小,選擇是否轉(zhuǎn)換為RunContainer。
針對BitmapContainer:
如果刪除某值后容量低至4096,則會自動轉(zhuǎn)換為ArrayContainer。因此正常使用的情況下不會出現(xiàn)容量小于4096的BitmapContainer。
調(diào)用runOptimize()方法時,會比較和RunContainer的空間占用大小,選擇是否轉(zhuǎn)換為RunContainer。
針對RunContainer:
只有在調(diào)用runOptimize()方法才會發(fā)生轉(zhuǎn)換,會分別和ArrayContainer、BitmapContainer比較空間占用大小,然后選擇是否轉(zhuǎn)換。
————————————————
原文鏈接:https://blog.csdn.net/yizishou/article/details/78342499