資料結構-05| 布隆過濾器| 快取機制
布隆過濾器 Bloom Filter
布隆過濾器和雜湊表類似,HashTable + 拉鍊表儲存重複元素:
元素 ---雜湊函式---> 對映到一個整數的下標位置index。比如Join Smith和Sandra Dee經過雜湊函式都對映到了152的下標,就在152的位置開一個連結串列,把多個元素都存在相同位置的連結串列處,往後邊不斷的積累積累。
它不僅有雜湊函式得到一個index值,且會把整個要素的元素都儲存到雜湊表裡邊去,這是一個沒有誤差的資料結構,有多少個元素,每個元素有多大,所有這些元素所佔的記憶體空間等在雜湊表裡邊都要有相應的記憶體大小給儲存起來。
但是在很多工業級應用中,我們並不需要儲存所有的元素本身,而只需要儲存一個資訊,即這個元素在這個表裡邊有沒有,這時就需要更高效的一種資料結構。
Bloom Filter VS HashTable
布隆過濾器即 一個很長的二進位制向量和一系列隨機對映函式。布隆過濾器可以用於檢索一個元素是否存在一個集合中。(而雜湊表不只是判斷元素是否在一個集合中,同時還可以存元素本身和元素的各種額外資訊。布隆過濾器只用於檢索一個元素它是否在還是不在的資訊,而不能存其它的額外資訊)
優點:空間效率和查詢時間都遠遠超過一般的演算法,
缺點是有一定的誤識別率和刪除困難。
布隆過濾器示意圖:
x,y,z不是同時往裡邊新增,一個一個的新增; 每一個元素它會分配到一系列的二進位制位中。 假設x會分配3個二進位制位,藍色線表示, 把x插入布隆過濾器時即把x對應的這三個位置置為1就可以了。 y插入進來,根據它的雜湊函式分為紅色的這三條線所對應的二進位制位,置為1。 同理z也置為1。 這個二進位制的陣列用來表示所有的已經存入的xyz是否已經在索引裡邊了。 重新插入一個x, 這時x始終會對應這3個藍色的二進位制位, 去表裡查就查到這三個都是1, 所以我們認為x是存在的。 如果是一個陌生的元素w進來, 它把它分配給通過布隆過濾器的二進位制索引的函式, w就得到灰色的這三個二進位制位110, 有一個為0說明w未在索引裡邊。 只要布隆過濾器中有一個為0就說明這個元素不在布隆過濾器的索引裡面, 且我們肯定它不在。 但比如又來一個元素q, 它剛好分配的三個二進位制都為1, 我們不一定說它是存在的。
儲存元素A和E都存入布隆過濾器中,置為1.
測試元素,A查到到它的二進位制位為1,可能是有的;C查到它的二進位制位有一個為0,則C肯定不在布隆過濾器裡邊;B恰好分配的二進位制位都為1,但從來沒有儲存過B,則我們會判斷B在這個索引裡邊,這個時候對於B的判斷就是有誤差的。
結論:
當布隆過濾器把元素全部插入完之後, 對於測試(新來)的元素要驗證它是否存在
當它驗證這個元素所對應的二進位制位是1時, 我們只能說它可能存在布隆過濾器裡邊。
但是當這個元素所對應的二進位制位只要有一個不為1, 則它肯定不在。
---一個元素去布隆過濾器裡邊查, 如果查到它不存在, 那麼它肯定就是不存在的。
如果查到它的二進位制都是1,為存在的狀態比如B,則它可能存在。
那我們到底怎麼判斷B這種元素是否存在呢 ?
布隆過濾器只是放在外邊當一個快取使用,來作為一個很快速的判斷來使用
當B查到了,布隆過濾器裡邊是存在的,則B會繼續在這個機器上的資料庫DB中來查詢,去查詢B是否存在。
而C就會直接打到布隆過濾器裡邊,這樣C就不用查詢了節省了查詢訪問資料庫的時間。
布隆過濾器只是擋在一臺機器前邊的快速查詢的快取。
案例
①.比特幣網路
②.分散式系統(Map-Reduce) -- Hadoop、 Searchengine
③.Redis快取
④.垃圾郵件、評論等的過濾
https://www.cnblogs.com/cpselvis/p/6265825.html
https://blog.csdn.net/tianyaleixiaowu/article/details/74721877
布隆過濾器的Java程式碼實現:
https://github.com/lovasoa/bloomfilter/blob/master/src/main/java/BloomFilter.java
https://github.com/Baqend/Orestes-Bloomfilter
Cache快取
①.記憶
②.錢包-儲物櫃
③.程式碼模組
CPUSocket
Understanding the Meltdown exploit – in my own simple words
上圖為四核CPU,三級快取(L1 Cache,L2 Cache ,L3 Cache);
每一個核裡邊就有L1 D-Cache,L1 l-Cache,L2 Cache,L3 Cache;最常用的資料馬上要給CPU的計算模組進行計算處理的就放在L1裡邊,次之不常用的就放在L1 l-Cache裡邊,再次之放在L2Cache裡邊,最後放在L3 Cache裡邊。外邊即記憶體。他們的速度 L1 D-Cache >L1 l-Cache >L2-Cache >L3-Cache
體積(能存的資料多少)即L1 D-Cache <L1 l-Cache < L2-Cache < L3-Cache
LRUCache
兩個要素:
大小
替換策略(least recent use -- 最近最少使用)
實現機制:
HashTable+DoubleLinkedList
複雜度分析:
O(1)查詢
O(1)修改、 更新
LRU Cache工作示例:
更新原則least recent use
替換策略:
LFU - least frequently used
LRU - least recently used
LRUCache Python
class LRUCache(object):
def __init__(self, capacity):
self.dic = collections.OrderedDict()
self.remain = capacity
def get(self, key):
if key not in self.dic:
return -1
v = self.dic.pop(key)
self.dic[key] = v # key as the newest one
return v
def put(self, key, value):
if key in
self.dic: self.dic.pop(key)
else:
if self.remain > 0:
self.remain -= 1
else: # self.dic is full
self.dic.popitem(last=False)
self.dic[key] = value
LRUCache Java
private Map<Integer, Integer> map;
public LRUCache(int capacity) {
map = new LinkedCappedHashMap<>(capacity);
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
return map.get(key);
}
public void put(int key, int value) {
map.put(key, value);
}
private static class LinkedCappedHashMap<K, V> extends LinkedHashMap<K, V> {
int maximumCapacity;
LinkedCappedHashMap(int maximumCapacity) {
// initialCapacity代表map的容量, loadFactor代表載入因子, accessOrder預設false,如果要按讀取順序排序需要將其設為true
super(16, 0.75f, true);//default initial capacity (16) and load factor (0.75) and accessOrder (false)
this.maximumCapacity = maximumCapacity;
}
/* 重寫 removeEldestEntry()函式,就能擁有我們自己的快取策略 */
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maximumCapacity;
}
}