JavaBitSet学习
一、背景
之前公司项目需要对会员人群进行去重过滤,人群的维度是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 |
|
|
附本人的调试代码:
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学习的更多相关文章
- 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代
2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...
- Angular2学习笔记(1)
Angular2学习笔记(1) 1. 写在前面 之前基于Electron写过一个Markdown编辑器.就其功能而言,主要功能已经实现,一些小的不影响使用的功能由于时间关系还没有完成:但就代码而言,之 ...
- ABP入门系列(1)——学习Abp框架之实操演练
作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...
- 消息队列——RabbitMQ学习笔记
消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- Unity3d学习 制作地形
这周学习了如何在unity中制作地形,就是在一个Terrain的对象上盖几座小山,在山底种几棵树,那就讲一下如何完成上述内容. 1.在新键得项目的游戏的Hierarchy目录中新键一个Terrain对 ...
- 《Django By Example》第四章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:祝大家新年快乐,这次带来<D ...
- 菜鸟Python学习笔记第一天:关于一些函数库的使用
2017年1月3日 星期二 大一学习一门新的计算机语言真的很难,有时候连函数拼写出错查错都能查半天,没办法,谁让我英语太渣. 关于计算机语言的学习我想还是从C语言学习开始为好,Python有很多语言的 ...
- 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
随机推荐
- Vue父子,子父,非父子组件之间传值
Vue组件基础 纯属随笔记录,具体详细教程,请查阅vue.js网站 子组件给父组件传值: <body> <div id="app"> <my-app& ...
- spring 中的 bean 是线程安全的吗?
spring 中的 bean 是线程安全的吗? Spring 不保证 bean 的线程安全. 默认 spring 容器中的 bean 是单例的.当单例中存在竞态条件,即有线程安全问题.如下面的例子 计 ...
- Celery:routing
参考:http://docs.celeryproject.org/en/latest/userguide/routing.html#guide-routing
- SparkSQL之dataframe写入mysql报错
一.异常情况及解决方案 在使用Spark SQL的dataframe数据写入到相应的MySQL表中时,报错,错误信息如下: 代码的基本形式为: df.write.jdbc(url, result_ta ...
- 你真的会使用 VMware Workstation 吗
你真的会使用VMware Workstation吗?网上有很多教程,虽然都还可以,但总感觉差强人意.所以笔者在这里分享自己的使用心得,让大家参考一下,个人认为是最好的了. 简介 VMware Work ...
- nodejs 删除空文件
var fs = require("fs") var path = require("path") var listRealPath = path.resolv ...
- Netlink: 内核与用户空间传输数据的socket协议
https://en.wikipedia.org/wiki/Netlink https://stackoverflow.com/questions/12899055/how-kernel-notify ...
- Deployment
Deployment RC是kubernetes中的一个核心概念,Deployment 是新一代的RC,除了拥有RC的功能外,还具备一下特性: 支持事件和状态查看:可以查看Deployment升级的状 ...
- curl请求https资源的时候出现400
在nginx上配置了一个新的域名, 习惯性地用curl请求看看有没有配置错误 因为是https的, 所以 $curl 'https://test.test.com/' -x 127.0.0.1:443 ...
- MySQL数据库扫盲篇
MySQL数据库扫盲篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL概述 1>.什么是MySQL MySQL是瑞典的MySQL AB公司开发的一个可用于各 ...