• JavaBitSet学习


    一、背景

    之前公司项目需要对会员人群进行去重过滤,人群的维度是user_id;

    因此采用了BitSet做简单的去重,方案将user_id作为bitset中的bit索引;

    通过andorxor基础运算实现,以公司2亿会员生产bitSet来算,容量24m(不压缩),主要的andorxor运算平均耗时5毫秒,按现有BitSet的数据结构,未来可以支持20亿会员;

    举例:

    皇冠人群:13563656769127

    活跃人群:56568127

    业务需求:

    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,其中13563656769127对应位为1;即橙色部分

    活跃人群也是一个BitSet,其中56568127对应位为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()
              创建一个新的位 set。

    BitSet(int nbits)
              创建一个位 set,它的初始大小足以显式表示索引范围在 0 到 nbits-1 的位。

     

    void

    and(BitSet set)
              对此目标位 set 和参数位 set 执行逻辑操作。

    void

    andNot(BitSet set)
              清除此 BitSet 中所有的位,其相应的位在指定的 BitSet 中已设置。

    int

    cardinality()
              返回此 BitSet 中设置为 true 的位数。

    void

    clear()
              将此 BitSet 中的所有位设置为 false

    void

    clear(int bitIndex)
              将索引指定处的位设置为 false

    void

    clear(int fromIndex, int toIndex)
              将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的位设置为 false

    Object

    clone()
              复制此 BitSet,生成一个与之相等的新 BitSet

    boolean

    equals(Object obj)
              将此对象与指定的对象进行比较。

    void

    flip(int bitIndex)
              将指定索引处的位设置为其当前值的补码。

    void

    flip(int fromIndex, int toIndex)
              将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的每个位设置为其当前值的补码。

    boolean

    get(int bitIndex)
              返回指定索引处的位值。

    BitSet

    get(int fromIndex, int toIndex)
              返回一个新的 BitSet,它由此 BitSet 中从 fromIndex(包括)到 toIndex(不包括)范围内的位组成。

    int

    hashCode()
              返回此位 set 的哈希码值。

    boolean

    intersects(BitSet set)
              如果指定的 BitSet 中有设置为 true 的位,并且在此 BitSet 中也将其设置为true,则返回 ture。

    boolean

    isEmpty()
              如果此 BitSet 中没有包含任何设置为 true 的位,则返回 ture。

    int

    length()
              返回此 BitSet 的“逻辑大小”:BitSet 中最高设置位的索引加 1。

    int

    nextClearBit(int fromIndex)
              返回第一个设置为 false 的位的索引,这发生在指定的起始索引或之后的索引上。

    int

    nextSetBit(int fromIndex)
              返回第一个设置为 true 的位的索引,这发生在指定的起始索引或之后的索引上。

    void

    or(BitSet set)
              对此位 set 和位 set 参数执行逻辑操作。

    void

    set(int bitIndex)
              将指定索引处的位设置为 true

    void

    set(int bitIndex, boolean value)
              将指定索引处的位设置为指定的值。

    void

    set(int fromIndex, int toIndex)
              将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的位设置为 true

    void

    set(int fromIndex, int toIndex, boolean value)
              将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的位设置为指定的值。

    int

    size()
              返回此 BitSet 表示位值时实际使用空间的位数。

    String

    toString()
              返回此位 set 的字符串表示形式。

    void

    xor(BitSet set)
              对此位 set 和位 set 参数执行逻辑异或操作。

     附本人的调试代码:

      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     //测试基础的andorxor运算
     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 }
  • 相关阅读:
    基于Lucene/XML的站内全文检索解决方案
    内容管理系统(CMS)的设计和选型
    Lucene入门与使用[转]
    为自己的系统搞个全文搜索 参考值:2 (转)
    C# 时间函数
    Lucene倒排索引原理(转)
    什么是内容管理系统CMS?
    网络测试常用命令
    C#与C的区别
    人生致命的八个经典问题
  • 原文地址:https://www.cnblogs.com/xupengzhang/p/7966755.html
Copyright © 2020-2023  润新知