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.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
随机推荐
- nodeJS从入门到进阶一(基础部分)
一.Node.js基础知识 1.概念 简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是JavaScript的运行环境 Node.js 使用了一个事件驱动.非阻塞 ...
- Java -- springboot 配置 freemarker
1.添加依赖 org.springframework.boot spring-boot-starter-freemarker 2.配置application.properties spring.fre ...
- Excel 97-2003版本内部结构与查看方法
MS-XLS内部结构:BIFF格式 https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/cd03cb5f-ca0 ...
- day 08 预科
目录 可变和不可变 不可变类型 可变类型 可变: 列表/字典 ---->值变id不变 不可变: 数字/字符串 ---->值变id也变 列表的内置方法 字典的内置方法 可变和不可变 可变和不 ...
- 使用kubeadm部署k8s
k8s组件 master,node master中包括apiserver,scheduler,controller.etcd apiserver:负责接收用户请求,并且保存至etcd中. schedu ...
- ARTS-week5
Algorithm 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组.说明:初始化 nums1 和 nums2 的元素数量分别 ...
- 项目Alpha冲刺--8/10
项目Alpha冲刺--8/10 作业要求 这个作业属于哪个课程 软件工程1916-W(福州大学) 这个作业要求在哪里 项目Alpha冲刺 团队名称 基于云的胜利冲锋队 项目名称 云评:高校学生成绩综合 ...
- Alpha冲刺(6/10)——2019.4.29
所属课程 软件工程1916|W(福州大学) 作业要求 Alpha冲刺(6/10)--2019.4.29 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪 ...
- sqlserver2005新特性介绍
1.更强的编程能力-CLR集成 增强了数据库的编程能力,将一些逻辑层(Bll)转移到数据库中,减少了网络中的数据流量,但是增加了服务器cpu的负荷,当我们需要操作大量的数据,但是产生很少的数据,把这种 ...
- PHP——file_put_contents()函数
前言 作为PHP的一个内置函数,他的作用就是将一个字符串写入文件 简介 使用 换行和追加写入 file_put_contents('./relation/bind.txt', $val['id'].' ...