一、背景
之前公司項目需要對會員人群進行去重過濾,人群的維度是user_id;
因此采用了BitSet做簡單的去重,方案將user_id作為bitset中的bit索引;
通過and\or\xor基礎運算實現,以公司2億會員生產bitSet來算,容量24m(不壓縮),主要的and\or\xor運算平均耗時5毫秒,按現有BitSet的數據結構,未來可以支持20億會員;
舉例:
皇冠人群:1\3\5\63\65\67\69\127
活躍人群:5\65\68\127
業務需求:
1、需要提取出既是皇冠又是活躍的會員
2、需要提取出皇冠和活躍兩部分會員,但是要保證不重復
3、需要皇冠人群中不活躍的會員
假設兩個人群的量都是千萬級的人群,我們該如何處理?
這里我們借助了JavaBitSet的位運算來實現可以這樣來實現:
需求1:
1、皇冠人群 and 活躍人群 取出交集
需求2:
1、皇冠人群 or 活躍人群 取出並集
需求3:
1、活躍人群 and 皇冠人群 取出交集
2、皇冠人群 xor 交集人群 取出非活躍的皇冠會員
二、BitSet入門:
BitSet的原理
Java BitSet可以按位存儲,計算機中一個字節(byte)占8位(bit);
而BitSet是位操作的對象,值只有0或1(即true 和 false),內部維護一個long數組,初始化只有一個long segement,所以BitSet最小的size是64;隨着存儲的元素越來越多,BitSet內部會自動擴充,一次擴充64位,最終內部是由N個long segement 來存儲;
默認情況下,BitSet所有位都是0即false;
正如上述方案來說:
皇冠人群是一個BitSet,其中1\3\5\63\65\67\69\127對應位為1;即橙色部分;
活躍人群也是一個BitSet,其中5\65\68\127對應位為1;即橙色部分;
而64個位為一個long數組,因此64對應的位就被分配到第2個long數組;
BitSet的應用場景
海量數據去重、排序、壓縮存儲
BitSet的基本操作
and(與)、or(或)、xor(異或)
BitSet的優缺點
優點:
l 按位存儲,內存占用空間小
l 豐富的api操作
缺點:
l 線程不安全
l BitSet內部動態擴展long型數組,若數據稀疏會占用較大的內存
BitSet為什么選擇long型數組作為內部存儲結構
JDK選擇long數組作為BitSet的內部存儲結構是出於性能的考慮,在and和or的時候減少循環次數,提高性能;
因為BitSet提供and和or這種操作,需要對兩個BitSet中的所有bit位做and或者or,實現的時候需要遍歷所有的數組元素。使用long能夠使得循環的次數降到最低,所以Java選擇使用long數組作為BitSet的內部存儲結構。
舉個例子:
當我們進行BitSet中的and, or, xor操作時,要對整個bitset中的bit都進行操作,需要依次讀出bitset中所有的word,如果是long數組存儲,我們可以每次讀入64個bit,而int數組存儲時,只能每次讀入32個bit。
BitSet源碼解析
參考JunitTest斷點查看代碼,了解BitSet每個方法的實現邏輯
附:
源碼解析博文:http://www.cnblogs.com/lqminn/archive/2012/08/30/2664122.html
Java移位基礎知識:https://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html
三、Java BitSet API簡介
BitSet |
BitSet |
|
|
|
|
|
cardinality |
|
clear |
|
clear |
|
clear |
clone |
|
|
|
|
flip |
|
flip |
|
get |
get |
|
|
hashCode |
|
intersects |
|
isEmpty |
|
length |
|
nextClearBit |
|
nextSetBit |
|
|
|
set |
|
set |
|
set |
|
set |
|
size |
toString |
|
|
附本人的調試代碼:
1 package com.vip.amd.bitset; 2 3 import org.junit.*; 4 import org.junit.Test; 5 6 import java.util.BitSet; 7 8 /** 9 * @author xupeng.zhang 10 * @date 2017/12/2 0002 11 */ 12 public class BitSetTest { 13 //全量bitset 14 private static BitSet allBitSet = new BitSet(); 15 //偶數bitset 16 private static BitSet evenBitSet = new BitSet(); 17 //奇數bitset 18 private static BitSet oddBitSet = new BitSet(); 19 //空bitset 20 private static BitSet emptyBitSet = new BitSet(); 21 22 @BeforeClass 23 public static void init(){ 24 for (int i = 0; i < 63; i++) { 25 allBitSet.set(i); 26 if (i % 2 == 0) { 27 evenBitSet.set(i); 28 }else{ 29 oddBitSet.set(i); 30 } 31 } 32 } 33 34 //測試初始化 35 @Test 36 public void testInit(){ 37 //斷點進去看 38 BitSet initBitSet1 = new BitSet(55); 39 BitSet initBitSet2 = new BitSet(129); 40 } 41 42 //測試基礎的and\or\xor運算 43 @org.junit.Test 44 public void testOper(){ 45 //System.out.println(evenBitSet.toByteArray()); 46 evenBitSet.and(allBitSet); 47 System.out.println("偶數Bit and 全量Bit:"+evenBitSet); 48 evenBitSet.xor(allBitSet); 49 System.out.println("偶數Bit xor 全量Bit:"+evenBitSet); 50 evenBitSet.or(allBitSet); 51 System.out.println("偶數Bit or 全量Bit:"+evenBitSet); 52 } 53 54 //測試動態擴展,每次是以64位為單位 55 @org.junit.Test 56 public void testExpand(){ 57 testSize(); 58 allBitSet.set(100000000); 59 System.out.println("全量Bit-設置64之后大小:" + allBitSet.size()/8/1024/1024+"m"); 60 System.out.println("全量Bit-設置64之后長度:" + allBitSet.length()); 61 System.out.println("全量Bit-設置64之后實際true的個數:" + allBitSet.cardinality()); 62 } 63 64 //oddBitSet過濾掉evenBitSet 65 @Test 66 public void testOddFilterEvenBitSet(){ 67 oddBitSet.set(2); 68 oddBitSet.set(4); 69 oddBitSet.set(6); 70 System.out.println("過濾前:oddBitSet:"+oddBitSet); 71 evenBitSet.and(oddBitSet); 72 oddBitSet.xor(evenBitSet); 73 System.out.println("oddBitSet過濾evenBitSet相同的元素的結果:"+oddBitSet); 74 } 75 76 //偶數和奇數bitset合並去重之后和allbitSet內容一致 77 @Test 78 public void testOddAndEventBitSet(){ 79 oddBitSet.set(2); 80 oddBitSet.set(4); 81 oddBitSet.set(6); 82 System.out.println("偶數BitSet合並前 :"+evenBitSet); 83 System.out.println("奇數BitSet合並前 :"+oddBitSet); 84 System.out.println("------------------------"); 85 oddBitSet.or(evenBitSet); 86 System.out.println("偶數BitSet合並后 :"+evenBitSet); 87 System.out.println("奇數BitSet合並后 :"+oddBitSet); 88 System.out.println("全亮BitSet內容是 :"+allBitSet); 89 Assert.assertTrue(oddBitSet.equals(allBitSet)); 90 } 91 92 93 //返回true的個數 94 @org.junit.Test 95 public void testCardinality(){ 96 System.out.println("偶數Bit-true的個數:" + evenBitSet.cardinality()); 97 } 98 99 //判斷是否為空 100 @org.junit.Test 101 public void testIsEmpty(){ 102 System.out.println("全量Bit-判斷非空:" + allBitSet.isEmpty()); 103 System.out.println("空 Bit-判斷非空:" + emptyBitSet.isEmpty()); 104 } 105 106 //根據下表開始結束獲取 107 @org.junit.Test 108 public void testGetFromEnd(){ 109 System.out.println("全量Bit-[0,5]:" + allBitSet.get(0, 5)); 110 System.out.println("空 Bit-[0,5]:" + emptyBitSet.get(0, 5)); 111 } 112 113 //判斷是否存在bitset 114 @org.junit.Test 115 public void testGet(){ 116 System.out.println("全量Bit-下標為2是否存在:" + allBitSet.get(2)); 117 System.out.println("偶數Bit-下標為1是否存在:" + evenBitSet.get(1)); 118 System.out.println("偶數Bit-下標為2是否存在:" + evenBitSet.get(2)); 119 } 120 121 //計算bitset內存大小 122 @org.junit.Test 123 public void testSize(){ 124 System.out.println("空 Bit-大小::" + emptyBitSet.size()+"byte"); 125 System.out.println("偶數Bit-大小:" + evenBitSet.size() + "byte"); 126 System.out.println("全量Bit-大小:" + allBitSet.size() + "byte"); 127 } 128 129 //計算bitset長度(bitset最大數+1) 130 @org.junit.Test 131 public void testLength(){ 132 System.out.println("全量Bit-長度:" + allBitSet.length()); 133 System.out.println("偶數Bit-長度:" + evenBitSet.length()); 134 } 135 }