一、背景

之前公司项目需要对会员人群进行去重过滤,人群的维度是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()
          创建一个新的位 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 参数执行逻辑异或操作。

附本人的调试代码:

 package com.vip.amd.bitset;

 import org.junit.*;
import org.junit.Test; import java.util.BitSet; /**
* @author xupeng.zhang
* @date 2017/12/2 0002
*/
public class BitSetTest {
//全量bitset
private static BitSet allBitSet = new BitSet();
//偶数bitset
private static BitSet evenBitSet = new BitSet();
//奇数bitset
private static BitSet oddBitSet = new BitSet();
//空bitset
private static BitSet emptyBitSet = new BitSet(); @BeforeClass
public static void init(){
for (int i = 0; i < 63; i++) {
allBitSet.set(i);
if (i % 2 == 0) {
evenBitSet.set(i);
}else{
oddBitSet.set(i);
}
}
} //测试初始化
@Test
public void testInit(){
//断点进去看
BitSet initBitSet1 = new BitSet(55);
BitSet initBitSet2 = new BitSet(129);
} //测试基础的and\or\xor运算
@org.junit.Test
public void testOper(){
//System.out.println(evenBitSet.toByteArray());
evenBitSet.and(allBitSet);
System.out.println("偶数Bit and 全量Bit:"+evenBitSet);
evenBitSet.xor(allBitSet);
System.out.println("偶数Bit xor 全量Bit:"+evenBitSet);
evenBitSet.or(allBitSet);
System.out.println("偶数Bit or 全量Bit:"+evenBitSet);
} //测试动态扩展,每次是以64位为单位
@org.junit.Test
public void testExpand(){
testSize();
allBitSet.set(100000000);
System.out.println("全量Bit-设置64之后大小:" + allBitSet.size()/8/1024/1024+"m");
System.out.println("全量Bit-设置64之后长度:" + allBitSet.length());
System.out.println("全量Bit-设置64之后实际true的个数:" + allBitSet.cardinality());
} //oddBitSet过滤掉evenBitSet
@Test
public void testOddFilterEvenBitSet(){
oddBitSet.set(2);
oddBitSet.set(4);
oddBitSet.set(6);
System.out.println("过滤前:oddBitSet:"+oddBitSet);
evenBitSet.and(oddBitSet);
oddBitSet.xor(evenBitSet);
System.out.println("oddBitSet过滤evenBitSet相同的元素的结果:"+oddBitSet);
} //偶数和奇数bitset合并去重之后和allbitSet内容一致
@Test
public void testOddAndEventBitSet(){
oddBitSet.set(2);
oddBitSet.set(4);
oddBitSet.set(6);
System.out.println("偶数BitSet合并前 :"+evenBitSet);
System.out.println("奇数BitSet合并前 :"+oddBitSet);
System.out.println("------------------------");
oddBitSet.or(evenBitSet);
System.out.println("偶数BitSet合并后 :"+evenBitSet);
System.out.println("奇数BitSet合并后 :"+oddBitSet);
System.out.println("全亮BitSet内容是 :"+allBitSet);
Assert.assertTrue(oddBitSet.equals(allBitSet));
} //返回true的个数
@org.junit.Test
public void testCardinality(){
System.out.println("偶数Bit-true的个数:" + evenBitSet.cardinality());
} //判断是否为空
@org.junit.Test
public void testIsEmpty(){
System.out.println("全量Bit-判断非空:" + allBitSet.isEmpty());
System.out.println("空 Bit-判断非空:" + emptyBitSet.isEmpty());
} //根据下表开始结束获取
@org.junit.Test
public void testGetFromEnd(){
System.out.println("全量Bit-[0,5]:" + allBitSet.get(0, 5));
System.out.println("空 Bit-[0,5]:" + emptyBitSet.get(0, 5));
} //判断是否存在bitset
@org.junit.Test
public void testGet(){
System.out.println("全量Bit-下标为2是否存在:" + allBitSet.get(2));
System.out.println("偶数Bit-下标为1是否存在:" + evenBitSet.get(1));
System.out.println("偶数Bit-下标为2是否存在:" + evenBitSet.get(2));
} //计算bitset内存大小
@org.junit.Test
public void testSize(){
System.out.println("空 Bit-大小::" + emptyBitSet.size()+"byte");
System.out.println("偶数Bit-大小:" + evenBitSet.size() + "byte");
System.out.println("全量Bit-大小:" + allBitSet.size() + "byte");
} //计算bitset长度(bitset最大数+1)
@org.junit.Test
public void testLength(){
System.out.println("全量Bit-长度:" + allBitSet.length());
System.out.println("偶数Bit-长度:" + evenBitSet.length());
}
}

JavaBitSet学习的更多相关文章

  1. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  2. Angular2学习笔记(1)

    Angular2学习笔记(1) 1. 写在前面 之前基于Electron写过一个Markdown编辑器.就其功能而言,主要功能已经实现,一些小的不影响使用的功能由于时间关系还没有完成:但就代码而言,之 ...

  3. ABP入门系列(1)——学习Abp框架之实操演练

    作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...

  4. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

  5. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  6. Unity3d学习 制作地形

    这周学习了如何在unity中制作地形,就是在一个Terrain的对象上盖几座小山,在山底种几棵树,那就讲一下如何完成上述内容. 1.在新键得项目的游戏的Hierarchy目录中新键一个Terrain对 ...

  7. 《Django By Example》第四章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:祝大家新年快乐,这次带来<D ...

  8. 菜鸟Python学习笔记第一天:关于一些函数库的使用

    2017年1月3日 星期二 大一学习一门新的计算机语言真的很难,有时候连函数拼写出错查错都能查半天,没办法,谁让我英语太渣. 关于计算机语言的学习我想还是从C语言学习开始为好,Python有很多语言的 ...

  9. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

随机推荐

  1. Vue父子,子父,非父子组件之间传值

    Vue组件基础 纯属随笔记录,具体详细教程,请查阅vue.js网站 子组件给父组件传值: <body> <div id="app"> <my-app& ...

  2. spring 中的 bean 是线程安全的吗?

    spring 中的 bean 是线程安全的吗? Spring 不保证 bean 的线程安全. 默认 spring 容器中的 bean 是单例的.当单例中存在竞态条件,即有线程安全问题.如下面的例子 计 ...

  3. Celery:routing

    参考:http://docs.celeryproject.org/en/latest/userguide/routing.html#guide-routing

  4. SparkSQL之dataframe写入mysql报错

    一.异常情况及解决方案 在使用Spark SQL的dataframe数据写入到相应的MySQL表中时,报错,错误信息如下: 代码的基本形式为: df.write.jdbc(url, result_ta ...

  5. 你真的会使用 VMware Workstation 吗

    你真的会使用VMware Workstation吗?网上有很多教程,虽然都还可以,但总感觉差强人意.所以笔者在这里分享自己的使用心得,让大家参考一下,个人认为是最好的了. 简介 VMware Work ...

  6. nodejs 删除空文件

    var fs = require("fs") var path = require("path") var listRealPath = path.resolv ...

  7. Netlink: 内核与用户空间传输数据的socket协议

    https://en.wikipedia.org/wiki/Netlink https://stackoverflow.com/questions/12899055/how-kernel-notify ...

  8. Deployment

    Deployment RC是kubernetes中的一个核心概念,Deployment 是新一代的RC,除了拥有RC的功能外,还具备一下特性: 支持事件和状态查看:可以查看Deployment升级的状 ...

  9. curl请求https资源的时候出现400

    在nginx上配置了一个新的域名, 习惯性地用curl请求看看有没有配置错误 因为是https的, 所以 $curl 'https://test.test.com/' -x 127.0.0.1:443 ...

  10. MySQL数据库扫盲篇

    MySQL数据库扫盲篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   一.MySQL概述 1>.什么是MySQL MySQL是瑞典的MySQL AB公司开发的一个可用于各 ...