轉載請註明出處git
在互聯網公司中,每一個項目都須要數據統計、分析,便於項目組利用詳細數據研究項目的總體狀況,進行下一步的調整。在數據統計中,UV統計是最多見的,也是最廣泛的。有的場景要求實時性很高,有點場景要求準確性很高,有的場景比較在乎計算過程當中的內存。不一樣的場景使用不一樣的算法,下面咱們從0到1簡單介紹下UV統計領域。github
假設咱們的場景是商家這邊上架一系列水果,而後須要統計出一共上架幾種水果。具體以下所示:
算法
針對這個問題,咱們想到的最簡單的方式就是利用STL中的set處理。app
上架一個水果的時候,也同時在set中插入。最後須要統計的時候,直接計算set中一共有幾個水果便可。具體以下所示:
這種方式準確率是絕對準確的,可是這種方式耗費的內存是很大的。
假設每一個水果須要 K 字節,那麼若是有 M 個水果,一共須要 K * M 字節。那麼咱們能不能縮小這裏的內存呢?
稍微損失一點準確率換取內存?具體見下面HashMap的方式.net
這種算法在上架一個水果的時候,只須要在特定的位置置1便可,而不須要存儲這個位置上到底是何種水果。而後在統計的時候,只須要統計hashmap裏面有多少個1便可。具體以下所示:
具體以下所示:
那麼若是有M個水果,這裏其實只須要 M / 8 字節,相比set的方式內存直接縮小到1/8。固然Hash確定會有衝突的,因此這裏確定有必定準確率的損失。
可是若是涉及到海量數據的UV統計,這裏的內存仍是很大的。
可否用上統計學進一步縮小內存呢?具體見下面的Linear Count的方式。blog
這種算法在上架一個水果的時候,徹底跟hashmap一致,在相應位置置1。
而後在統計的時候,利用統計學的方式,根據hashmap中零的個數給出一個估算值。具體以下所示:
內存
假設M爲哈希桶長度,那麼每次上架水果,每一個桶被選中的機率爲:
$$\frac{1}{M}$$
而後在上架N個元素後,某個桶爲0的機率爲:
$$(1-\frac{1}{M}) ^N$$
因此在上架n個元素後,哈希桶中零的個數指望爲:
$$ZeroNum=\sum_{i=1}^M (1-\frac{1}{M}) ^N = M (1-\frac{1}{M}) ^N= M ((1+\frac{1}{-M}){-M}){-\frac{N}{M}}) \approx Me^{- \frac{N}{M}}$$
因此最終:
$$
N = UV = -M ln(\frac{ZeroNum}{M})
$$get
因此Linear Count算法中,只需統計下hashmap中零的個數,而後代入上式便可。
這種算法在N很小的時候,準確率是很高的,可是N很大的時候,它的準確率急劇降低。
針對海量數據的狀況,LogLog Count的算法更加魯棒hash
這種算法跟上面幾種都不一樣,上架水果的時候,在相應桶裏面記錄的是二進制數後面最長的連續零個數。而後統計的時候,利用統計學的方式,根據存儲中最長連續後綴零個數,得出一個估計值。具體以下所示:it
它的原理以下:
這裏若是隻使用一個桶來估計的話,它的偏差是很大,須要用分桶平均的方式來減小它的偏差。
既然這裏利用了分桶來減小偏差,那麼這裏統計的時候就必須合起來,這裏有4種方式:
LogLog Count利用的是算術平均的方式,因此最終估計值爲:
$$UV=2{\frac{\sum_{j=1}m{UV_j}}{m}}$$
這種算法對於基數大的狀況下準確率挺高的,可是基數小的狀況下準確率很低。
這種算法跟LogLog Count 相似,有個區別點就是它在求均值的時候利用了調和平均數,而不是算術平均數。這裏最終估計值爲:
$$UV=mm(\sum_{j=1}m{2{-M_j}})^{-1}$$
而後它還引入了分段偏差修正。
具體能夠看我github上的代碼:HyperLogLog
準確率 | 內存 | 耗時 | |
---|---|---|---|
Set | 絕對準確 | K * M | O(Mlog(M)) |
HashMap | 很高 | M/8 | O(M) |
Linear Count | 基數小高,基數大低 | M/8 | O(M/8) |
LogLog Count | 基數小低,基數大高 | ||
HyperLogLog Count | 高 |