位圖法
位圖是通過將數組下標與應用中的一些值關聯映射,數組中該下標所指定的位置上的元素可以用來標識應用中值的情況(是否存在或者數目 或者計數等),位圖數組中每個元素在內存中佔用1位,所以可以節省存儲空間。位圖是一種非常簡潔快速的數據結構,它能同時使存儲空間和速度最優化。如可用一個10位長的字符串來表示一個所有元素都小於10的簡單的非負整數集合,例如,可以用如下字符串表示集合{1,2,4,5,8} ,對應位置數字存在標記爲1,否則標記爲0。
JAVA
Bitmap(即Bitset)
Bitmap是一串連續的2進制數字(0或1),每一位所在的位置爲偏移(offset),在bitmap上可執行AND,OR,XOR以及其它位操作。
BitSet是java的一個類,詳細的使用可以參考https://www.cnblogs.com/xupengzhang/p/7966755.html。其常用的方法有get(),set(),cardinality(),and(),or(),xor()等等
位圖計數(Population Count)
位圖計數統計的是bitmap中值爲1的位的個數。位圖計數的效率很高,例如,一個bitmap包含10億個位,90%的位都置爲1,在一臺MacBook Pro上對其做位圖計數需要21.1ms。SSE4甚至有對整形(integer)做位圖計數的硬件指令。
Redis
Redis允許使用二進制數據的Key(binary keys) 和二進制數據的Value(binary values)。在redis中,字符串是以二進制的形式存儲的,因此位圖在redis中並不是一種數據類型,而是一種字符串的表現形式。
在redis中如果使用位圖,其常見的命令:getbit,setbit,bitop,BITCOUNT等等。
關於getbit,setbit,bitop的命令可以參考https://blog.csdn.net/a692055612/article/details/79717146
日活躍用戶
爲了統計今日登錄的用戶數,我們建立了一個bitmap,每一位標識一個用戶ID。當某個用戶訪問我們的網頁或執行了某個操作,就在bitmap中把標識此用戶的位置爲1。在Redis中獲取此bitmap的key值是通過用戶執行操作的類型和時間戳獲得的。
這個簡單的例子中,每次用戶登錄時會執行一次redis.setbit(daily_active_users, user_id, 1)。將bitmap中對應位置的位置爲1,時間複雜度是O(1)。統計bitmap結果顯示有今天有9個用戶登錄。Bitmap的key是daily_active_users,它的值是1011110100100101。
因爲日活躍用戶每天都變化,所以需要每天創建一個新的bitmap。我們簡單地把日期添加到key後面,實現了這個功能。例如,要統計某一天有多少個用戶至少聽了一個音樂app中的一首歌曲,可以把這個bitmap的redis key設計爲play:yyyy-mm-dd-hh。當用戶聽了一首歌曲,我們只是簡單地在bitmap中把標識這個用戶的位置爲1,時間複雜度是O(1)。
JAVA代碼示例
public void setActiveUserCount(Integer id) {
Jedis jedis = factory.getConnection().getNativeConnection();
Date currentTime = new Date();
String currentStr = new SimpleDateFormat("yyyy-MM-dd").format(currentTime);
String key = activeName+currentStr;
jedis.setbit(key, id, true);
}
解析:
以id爲偏移量,1爲用戶今天已登錄,0爲用戶今天未登陸。
示例代碼是spring配置管理的factory是spring管理的JedisConnectionFactory。關於如何在spring中配置redis,並且如何使用Jedis可百度
統計活躍用戶
需求:
統計七天內有進行至少一次登錄操作的用戶人數
思考:
- 採取按位存儲的方式,bit的下標爲用戶的id,如果用戶id的下標value爲1,那麼該用戶在當天有進行登錄
- 將所用的用戶生成一個位圖(0/1 未登錄/登錄)
- 統計七天內,有過一次登錄的用戶,那麼他就是活躍用戶,所以是獲取七天的字節數組,取他們的並集,用or。如果想獲取七天都有登錄的用戶,那麼需要的是取他們的交集,用and
@Autowired
JedisConnectionFactory factory;
private static final String activeName = "activeUser:";
public int getActiveUserCount() {
Jedis jedis = factory.getConnection().getNativeConnection();
Date currentTime = new Date();
BitSet all = new BitSet();
for(int i=0;i<7;i++){
//獲得前一天的日期
currentTime=DateUtils.addDays(currentTime, -1);
String currentStr = new SimpleDateFormat("yyyy-MM-dd").format(currentTime);
String key = activeName+currentStr;
//獲得這一天日期的活躍用戶字節數組
byte[] loginByte = jedis.get(key.getBytes());
//有可能當天沒有一個用戶登錄
if(loginByte!=null){
BitSet user = BitSet.valueOf(loginByte);
//取其並集
all.or(user);
}
}
//統計人數
int count = all.cardinality();
return count;
}
解析:
如果需求是統計七天內都有登錄的用戶人數,那麼取其交集,用and方法
總結:
上面代碼示例並沒有將數據截圖展示,但個人私下已經進行了數據填充,且進行了測試,代碼通過。除了使用java的BitSet的方法進行統計,還能使用redis的命令進行統計,java只需獲取命令返回接口即可。