點陣圖(bitmap)原理以及實現

藍胖子的程式設計夢發表於2023-09-19

大家好,我是藍胖子,我一直相信程式設計是一門實踐性的技術,其中演算法也不例外,初學者可能往往對它可望而不可及,覺得很難,學了又忘,忘其實是由於沒有真正搞懂演算法的應用場景,所以我準備出一個系列,囊括我們在日常開發中常用的演算法,並結合實際的應用場景,真正的感受演算法的魅力。

今天,我們就來學習下點陣圖bitmap的原理以及作用。

程式碼已經上傳github

https://github.com/HobbyBear/codelearning/tree/master/bitmap

點陣圖作用

bitmap 是一種高效的且佔用記憶體很小的 判斷 某個值 存在與否的資料結構。它用二進位制的某一位去表示某個值是否存在。

比如我們需要統計10億使用者是否簽到,正常的做法是你可以設計一個10億長度的map,將使用者的uid設定為key,是否簽到設計為value,假設uid是int64 型別,佔用8個位元組,10億使用者就需要大約8G的記憶體 ,而如果 設計成bitmap去儲存,則只需要大約125M 。極大的節約了記憶體。

原理

因為bitmap中用二進位制位代表某個uid是否存在,所以一個位元組能夠代表8個uid是否存在,如下圖所示:

image.png

bit位為1代表所在uid的使用者已經簽到,0則代表未簽到。圖中,uid為1和5的使用者都沒有簽到,uid為2,3,4,6,7,8的使用者都已經簽到。

實現

要實現這樣一個bitmap,我們可以用一個位元組陣列來儲存所有的bit位,將bit位 設定為1 就是確認某個數字或者說是像例子裡的uid,定位uid對應在這個位元組陣列的哪個位置,將特定位置的位元組定位到以後,再定位這個uid在位元組中的bit位是在什麼位置。

整個過程看程式碼會更加清晰,如下程式碼所示,我們在bitmap的建構函式里定義了整個bitmap的最大長度。

type BitMap struct {  
   flags []byte  
}  
  
func New(max int64) *BitMap {  
   flagLen := max/8 + 1  
   return &BitMap{flags: make([]byte, flagLen)}  
}

接著看下它的set方法,找到某個數字index再bitmap中的bit位,將其設定為1,一個位元組是8位,透過index/8 得到其bit位是在哪個位元組上,透過index%8 取餘得到設定的bit位 在位元組的第幾個bit為上,然後透過或運算將特定bit位設定為1。

func (b *BitMap) Set(index int64) {  
   arrIndex := index / 8  
   offset := index % 8  
   // 將offset位置設定為1,或運算,0 | 1 = 1  1|1= 1, 0|0 =0, 1的| 將原值設定為1 ,0的| 不改變原值  
   b.flags[arrIndex] = b.flags[arrIndex] | (0x1 << offset)  
}

我還定義了Exits 方法快速判斷某個值是否被bitmap記錄,同樣也是先找到index對應的bit位 在陣列中的位置,然後透過與運算去判斷特定bit位是0還是1。

func (b *BitMap) Exits(index int64) bool {  
   arrIndex := index / 8  
   offset := index % 8  
   res := b.flags[arrIndex] & (0x1 << offset)  
   if res == 0 {  
      return false  
   }  
   return true  
}

除此以外,你還可以定義一個remove方法,用於清除特定bit位上的值,

func (b *BitMap) Clean(index int64) {  
   arrIndex := index / 8  
   offset := index % 8  
   // 0 & 1 = 0 ,0 & 0 = 0, 1&1 =1  1的& 不會改變原來的值, 0的& 將原值變為0  
   b.flags[arrIndex] = b.flags[arrIndex] & ^(0x1 << offset)  
}

你可以看到bitmap用到的位運算其實本質上是用到下面的性質:

1, 1 與 0或者 1的 & 運算不會改變原值, 0 的& 會將特定bit位設定為0。
2, 0的或運算 不會改變原來的值, 1的或運算是將原來的bit位設定為1。

整個實現並不難,但這種結構的確在大資料量下達到了節約記憶體進行排重的目的,後續講到的布隆過濾器也是在這種資料結構上實現的。

相關文章