海量數據判重
1. 問題描述
對於海量數據,要求判斷一個數據是否已經存在。這個數據很有可能是字符串,例如 URL。
2. HashSet
最直觀的方法是使用 HashSet 存儲,那麼就能以 O(1) 的時間複雜度判斷一個數據是否已經存在。
考慮到數據是海量的,那麼就需要使用拆分的方式將數據拆分到多臺機器上,分別在每臺機器上使用 HashSet 存儲。我們需要使得相同的數據拆分到相同的機器上,可以使用哈希取模的拆分方式進行實現。
3. BitSet
如果海量數據是整數,並且範圍不大時,就可以使用 BitSet 存儲。通過構建一定大小的比特數組,並且讓每個整數都映射到這個比特數組上,就可以很容易地知道某個整數是否已經存在。因爲比特數組比整型數組小的多,所以通常情況下單機就能處理海量數據。
以下是一個 BitSet 的實現,當然在實際開發中可以直接使用語言內置的實現。
class BitSet {
int[] bitset;
public BitSet(int size) {
bitset = new int[(size >> 5) + 1]; // divide by 32
}
boolean get(int pos) {
int wordNumber = (pos >> 5); // divide by 32
int bitNumber = (pos & 0x1F); // mod 32
return (bitset[wordNumber] & (1 << bitNumber)) != 0;
}
void set(int pos) {
int wordNumber = (pos >> 5); // divide by 32
int bitNumber = (pos & 0x1F); // mod 32
bitset[wordNumber] |= 1 << bitNumber;
}
}
使用 BitSet 還可以很容易地解決一個整數出現次數的問題,例如使用兩個比特數組就可以存儲 0~3 的信息。其實判重問題也可以簡單看成一個數據出現的次數是否爲 1,因此一個比特數組就夠了。
4. 布隆過濾器
布隆過濾器能夠以極小的空間開銷解決海量數據判重問題,但是會有一定的誤判概率。它主要用在網頁黑名單系統、垃圾郵件過濾系統、爬蟲的網址判重系統。
布隆過濾器也是使用 BitSet 存儲數據,但是它進行了一定的改進,從而解除了 BitSet 要求數據的範圍不大的限制。在存儲時,它要求數據先經過 k 個哈希函得到 k 個位置,並將 BitSet 中對應位置設置爲 1。在查找時,也需要先經過 k 個哈希函數得到 k 個位置,如果所有位置上都爲 1,那麼表示這個數據存在。
由於哈希函數的特點,兩個不同的數通過哈希函數得到的值可能相同。如果兩個數通過 k 個哈希函數得到的值都相同,那麼使用布隆過濾器會將這兩個數判爲相同。
可以知道,令 k 和 m 都大一些會使得誤判率降低,但是這會帶來更高的時間和空間開銷。
布隆過濾器會誤判,也就是將一個不存在的數判斷爲已經存在,這會造成一定的問題。例如在垃圾郵件過濾系統中,會將一個郵件誤判爲垃圾郵件,那麼就收不到這個郵件。可以使用白名單的方式進行補救。
5. Trie
Trie 樹又叫又叫字典樹、前綴樹、單詞查找樹,它是一顆多叉查找樹。與二叉查找樹不同,鍵不是直接保存在節點中,而是由節點在樹中的位置決定。
如果海量數據是字符串數據,那麼就可以用很小的空間開銷構建一顆 Trie 樹,空間開銷和樹高有關。