臭味相投的朋友们,我在这里:
猿in小站:http://www.yuanin.net
csdn博客:https://blog.csdn.net/jiabeis
简书:https://www.jianshu.com/u/4cb7d664ec4b
微信免费订阅号“猿in”

文章目录

  • Bitset基础
  • Java的Bitset
  • 使用场景解析
  • 参考

Bitset基础

介绍

bitset(bitmap)也就是位图,由于可以用非常紧凑的格式来表示给定范围的连续数据而经常出现在各种算法设计中。

类实现了一个按需增长的位向量。位 set的每个组件都有一个boolean值。用非负的整数将BitSet的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个BitSet修改另一个BitSet的内容。

默认情况下,set 中所有位的初始值都是false。
每个位 set 都有一个当前大小,也就是该位 set 当前所用空间的位数。注意,这个大小与位 set 的实现有关,所以它可能随实现的不同而更改。位 set 的长度与位 set 的逻辑长度有关,并且是与实现无关而定义的。

除非另行说明,否则将 null 参数传递给BitSet中的任何方法都将导致NullPointerException。

在没有外部同步的情况下,多个线程操作一个BitSet是不安全的

下面的图来自c++库中bitset的一张图。

基本原理

BitSet是位操作的对象,值只有0或1即false和true,内部维护了一个long数组,初始只有一个long,所以BitSet最小的size是64,当随着存储的元素越来越多,BitSet内部会动态扩充,最终内部是由N个long来存储,这些针对操作都是透明的。

用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用用的时候既可根据某一个是否为0表示,此数是否出现过。

一个1G的空间,有 8102410241024=8.5810^9bit,也就是可以表示85亿个不同的数

使用场景

常见的应用是那些需要对海量数据进行一些统计工作的时候,比如日志分析、用户数统计等等

如统计40亿个数据中没有出现的数据,将40亿个不同数据进行排序等。

现在有1千万个随机数,随机数的范围在1到1亿之间。现在要求写出一种算法,将1到1亿之间没有在随机数中的数求出来

Java的Bitset

Bitset这种结构虽然简单,实现的时候也有一些细节需要主要。其中的关键是一些位操作,比如如何将指定位进行反转、设置、查询指定位的状态(0或者1)等。

本文,分析一下java中bitset的实现,抛砖引玉,希望给那些需要自己设计位图结构的需要的程序员有所启发。

Bitmap的基本操作有:

  • 初始化一个bitset,指定大小。
  • 清空bitset。
  • 反转某一指定位。
  • 设置某一指定位。
  • 获取某一位的状态。
  • 当前bitset的bit总位数。

参考代码:

import java.util.BitSet;public class BitSetDemo {public static void main(String args[]) {BitSet bits1 = new BitSet(16);BitSet bits2 = new BitSet(16);// set some bitsfor(int i=0; i<16; i++) {if((i%2) == 0) bits1.set(i);if((i%5) != 0) bits2.set(i);}System.out.println("Initial pattern in bits1: ");System.out.println(bits1);System.out.println("\nInitial pattern in bits2: ");System.out.println(bits2);// AND bitsbits2.and(bits1);System.out.println("\nbits2 AND bits1: ");System.out.println(bits2);// OR bitsbits2.or(bits1);System.out.println("\nbits2 OR bits1: ");System.out.println(bits2);// XOR bitsbits2.xor(bits1);System.out.println("\nbits2 XOR bits1: ");System.out.println(bits2);}
}
package util;import java.util.Arrays;
import java.util.BitSet;public class BitSetDemo {/*** 求一个字符串包含的char* */public static void containChars(String str) {BitSet used = new BitSet();for (int i = 0; i < str.length(); i++)used.set(str.charAt(i)); // set bit for charStringBuilder sb = new StringBuilder();sb.append("[");int size = used.size();System.out.println(size);for (int i = 0; i < size; i++) {if (used.get(i)) {sb.append((char) i);}}sb.append("]");System.out.println(sb.toString());}/*** 求素数 有无限个。一个大于1的自然数,如果除了1和它本身外,不能被其他自然数整除(除0以外)的数称之为素数(质数) 否则称为合数*/public static void computePrime() {BitSet sieve = new BitSet(1024);int size = sieve.size();for (int i = 2; i < size; i++)sieve.set(i);int finalBit = (int) Math.sqrt(sieve.size());for (int i = 2; i < finalBit; i++)if (sieve.get(i))for (int j = 2 * i; j < size; j += i)sieve.clear(j);int counter = 0;for (int i = 1; i < size; i++) {if (sieve.get(i)) {System.out.printf("%5d", i);if (++counter % 15 == 0)System.out.println();}}System.out.println();}/*** 进行数字排序*/public static void sortArray() {int[] array = new int[] { 423, 700, 9999, 2323, 356, 6400, 1,2,3,2,2,2,2 };BitSet bitSet = new BitSet(2 << 13);// 虽然可以自动扩容,但尽量在构造时指定估算大小,默认为64System.out.println("BitSet size: " + bitSet.size());for (int i = 0; i < array.length; i++) {bitSet.set(array[i]);}//剔除重复数字后的元素个数int bitLen=bitSet.cardinality(); //进行排序,即把bit为true的元素复制到另一个数组int[] orderedArray = new int[bitLen];int k = 0;for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {orderedArray[k++] = i;}System.out.println("After ordering: ");for (int i = 0; i < bitLen; i++) {System.out.print(orderedArray[i] + "\t");}System.out.println("iterate over the true bits in a BitSet");//或直接迭代BitSet中bit为true的元素iterate over the true bits in a BitSetfor (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {System.out.print(i+"\t");}System.out.println("---------------------------");}/*** 将BitSet对象转化为ByteArray* @param bitSet* @return*/public static byte[] bitSet2ByteArray(BitSet bitSet) {byte[] bytes = new byte[bitSet.size() / 8];for (int i = 0; i < bitSet.size(); i++) {int index = i / 8;int offset = 7 - i % 8;bytes[index] |= (bitSet.get(i) ? 1 : 0) << offset;}return bytes;}/*** 将ByteArray对象转化为BitSet* @param bytes* @return*/public static BitSet byteArray2BitSet(byte[] bytes) {BitSet bitSet = new BitSet(bytes.length * 8);int index = 0;for (int i = 0; i < bytes.length; i++) {for (int j = 7; j >= 0; j--) {bitSet.set(index++, (bytes[i] & (1 << j)) >> j == 1 ? true: false);}}return bitSet;}/*** 简单使用示例*/public static void simpleExample() {String names[] = { "Java", "Source", "and", "Support" };BitSet bits = new BitSet();for (int i = 0, n = names.length; i < n; i++) {if ((names[i].length() % 2) == 0) {bits.set(i);}}System.out.println(bits);System.out.println("Size : " + bits.size());System.out.println("Length: " + bits.length());for (int i = 0, n = names.length; i < n; i++) {if (!bits.get(i)) {System.out.println(names[i] + " is odd");}}BitSet bites = new BitSet();bites.set(0);bites.set(1);bites.set(2);bites.set(3);bites.andNot(bits);System.out.println(bites);}public static void main(String args[]) {//BitSet使用示例BitSetDemo.containChars("How do you do? 你好呀");BitSetDemo.computePrime();BitSetDemo.sortArray();BitSetDemo.simpleExample();//BitSet与Byte数组互转示例BitSet bitSet = new BitSet();bitSet.set(3, true);bitSet.set(98, true);System.out.println(bitSet.size()+","+bitSet.cardinality());//将BitSet对象转成byte数组byte[] bytes = BitSetDemo.bitSet2ByteArray(bitSet);System.out.println(Arrays.toString(bytes));//在将byte数组转回来bitSet = BitSetDemo.byteArray2BitSet(bytes);System.out.println(bitSet.size()+","+bitSet.cardinality());System.out.println(bitSet.get(3));System.out.println(bitSet.get(98));for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {System.out.print(i+"\t");}}
}

使用场景解析

Redis用bitset(bitmap)来统计日活跃量

假设这样一个场景,假如每个网站有1亿的用户,那么我们怎么来统计这个网站的日登陆数或者说有哪些用户登录过这个网站。
最常见的做法就是设计一张用户登录表:

user_login:

user_uid login_date
0 2017-7-1
1 2017-7-1
0 2017-7-2

如果平均一个人一天登录1次,那么1亿个用户一个星期就会产生1 * 1 * 7 = 7亿条数据,一个月就会产生30亿条数据,这对数据库的压力是很大的,只是统计一下用户登录,没必要花费这么多的资源。

这个时候我们就可以用reids 的bitmap来解决。

用户是否登录可以用0/1来表示,0代表用户不登陆,1表示登录,那么1bit 就可以表示用户是否登录。

1亿个用户一天的数据量也就 1 0000 0000bit = 11.92m,也就是说用户一天的登录信息也就产生11.92m的数据量。一个月也就357.63m的数据量。

具体实现过程(为了实验方便,我们就假设4个用户0,1,2,3,统计两天的登录量):

mon: 1010 (用户0未登录,用户1登录,用户2未登录,用户3登录)

tue: 1101 (用户0登录,用户1未登录,用户2登录,用户3登录)

初始化数据:

127.0.0.1:6379> setbit mon 0 0
(integer) 1
127.0.0.1:6379> setbit mon 1 1
(integer) 1
127.0.0.1:6379> setbit mon 2 0
(integer) 0
127.0.0.1:6379> setbit mon 3 1
(integer) 0

127.0.0.1:6379> setbit tue 0 1
(integer) 1
127.0.0.1:6379> setbit tue 1 0
(integer) 1
127.0.0.1:6379> setbit tue 3 1
(integer) 0
127.0.0.1:6379> setbit tue 4 1
(integer) 1

如果要统计这两天都登陆的用户:

127.0.0.1:6379> bitop AND result mon tue
(integer) 1
127.0.0.1:6379> getbit result 0
(integer) 0
127.0.0.1:6379> getbit result 1
(integer) 0
127.0.0.1:6379> getbit result 2
(integer) 0
127.0.0.1:6379> getbit result 3
(integer) 1

可以看到mon 和 tue做and运算,得到结果result为:0001,则表示用户3连续两天都登陆,其他用户两天中只有一天登录。

基于bitset实现手机号的黑白名单方案

目前很多应用都把手机号码作为登录的账户名,本文介绍一种高效的基于手机号,来实现黑白名单的方案。

在这里我先用一个例子来说明位图。

假设我有一个0到31的集合,集合里面的元素不重复,比如这样{0,3,1,5,2,19,7,8,31,21,10}。通过位图,我可以将这样的集合表示为11110001101000000001010000000001, 其中1表示该数值为下标的数存在在集合中,比如第一个1表示0存在集合中,第二个1表示1存在集合中,等等。

通过这样做,我们起码可以得到两个好处

  1. 节省空间–我们可以用二进制一个位来存储存储两个信息,一是存不存在,而是存在的数是多少(通过一个bit就可以得到这么多信息,真了不起)。
  2. 排序–从左到右遍历这个为图,我们可以得到排序的集合,比如上例中,我们可以得到集合 {0,1,2,3,7,8,10,19,21,31}

如果将所有这电话号码用位图表示,那么需要9999999999个bit (10个9, 考虑到手机号码的第一位都是1)。 9999999999 bit = (9999999999/8) byte = (9999999999 / (8 * 1024)) KB = (9999999999 / 8 10241024) M = 1192M

嗯…,1192M,内存占有还是太大,我们前期的黑白名单所占内存远远低于这个值。 考虑到手机号码的前3位都差不多,而且总数最多30个,所以考虑到手机号码拆分为两部分(头3位和剩余8位),这样就把位图的位数就降2个数量级了。所以如果我们要在内存中装入手机号码后8位,需要99999999(8个9)个bit,算一下:99999999 bit = (99999999/8) byte = (99999999 / (8 * 1024)) KB = (99999999 / 8 10241024) M = 11.92M

内存中装入头2位(手机号第一位始终为1,因此只需装入后面2位),需要99(2个9)个bit,算下
99 bit = 99/8 byte =12.375 byte。

大约12M 内存,就能表示所有手机号码,达到黑白名单。

这个比用redis等缓存,高效很多,java里面的bitset也是这个原理。

分享自我的微信订阅号“猿in”,可以搜索关注。

参考

[Redis用bitset(bitmap)来统计日活跃量]

https://blog.csdn.net/qq_30788949/article/details/74120254

[基于bitset实现手机号的黑白名单方案]

https://blog.csdn.net/nature502/article/details/52371299

[Java中BitSet的使用及详解]

https://blog.csdn.net/jiangnan2014/article/details/53735429

[Java Bitset类]

https://www.runoob.com/java/java-bitset-class.html

bitset(位图)原理与用法相关推荐

  1. mysql 实时聚合分析,mysql累积聚合原理与用法实例分析

    本文实例讲述了mysql累积聚合原理与用法.分享给大家供大家参考,具体如下: 累积聚合为聚合从序列内第一个元素到当前元素的数据,如为每个员工返回每月开始到现在累积的订单数量和平均订单数量 行号问题有两 ...

  2. python装饰器原理-Python装饰器原理与用法分析

    这篇文章主要介绍了Python装饰器原理与用法,结合实例形式分析了Python装饰器的概念.原理.使用方法及相关操作注意事项,需要的朋友可以参考下 本文实例讲述了Python装饰器原理与用法.分享给大 ...

  3. python的编程模式-Python设计模式之状态模式原理与用法详解

    本文实例讲述了Python设计模式之状态模式原理与用法.分享给大家供大家参考,具体如下: 状态模式(State Pattern):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类 ...

  4. python装饰器原理-Python函数装饰器原理与用法详解

    本文实例讲述了Python函数装饰器原理与用法.分享给大家供大家参考,具体如下: 装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值 ...

  5. python装饰器原理-python装饰器原理与用法深入详解

    本文实例讲述了python装饰器原理与用法.分享给大家供大家参考,具体如下: 你会Python嘛? 我会! 那你给我讲下Python装饰器吧! Python装饰器啊?我没用过哎 以上是我一个哥们面试时 ...

  6. python定义私有变量的方法_Python面向对象程序设计之私有变量,私有方法原理与用法分析...

    本文实例讲述了Python面向对象程序设计之私有变量,私有方法原理与用法.分享给大家供大家参考,具体如下: 私有变量,私有方法:python的私有化是为了规划私有属性,避免非相关的访问[假如!我有老婆 ...

  7. mysql 实时聚合分析_mysql滑动聚合/年初至今聚合原理与用法实例分析

    本文实例讲述了mysql滑动聚合/年初至今聚合原理与用法.分享给大家供大家参考,具体如下: 滑动聚合是按顺序对滑动窗口范围内的数据进行聚合的操作.下累积聚合不同,滑动聚合并不是统计开始计算的位置到当前 ...

  8. java 字节缓冲_Java字节缓冲流原理与用法详解

    本文实例讲述了Java字节缓冲流原理与用法.分享给大家供大家参考,具体如下: 一 介绍 BufferInputStresm和BufferOutputStream 这两个流类为IO提供了带缓冲区的操作, ...

  9. mysql的存储过程原理_mysql存储过程原理与用法详解

    本文实例讲述了Mysql存储过程原理与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是存储过程 存储过程的创建 存储过程的使用 查看存储过程 修改存储过程 删除存储过程 首发日期:2018- ...

最新文章

  1. 【阿里云域名】我都有服务器了,为什么还要购买域名?
  2. Laravel大型项目系列教程(五)之文章和标签管理
  3. Django的电子商务网站的调研
  4. python list对象
  5. 带有ActiveMQ的JMS
  6. Spring Boot和SSM本质上的区别
  7. 摆脱了Excel重复做表,换个工具轻松实现报表自动化,涨薪三倍
  8. Acrobat Pro DC 教程,如何发送电子签名文件?
  9. [math][mathematica] archlinux 下 mathematica 的安装 (科学计算软件 mathematica/matlab/sagemath)...
  10. C/C++学校运动会管理系统
  11. 无线网络技术复习笔记(5)——无线城域网、广域网
  12. 教学中计算机软件的应用,计算机软件应用类课程教学方法
  13. [BZOJ4340][BJOI2015]隐身术(后缀数组)
  14. 误入 GitHub 游戏区,意外地收获颇丰
  15. pgpool-II常见错误
  16. android+美拍加表情,美拍怎么添加表情文字在哪
  17. win10下如何使用的debug
  18. 程序员每天会阅读哪些技术网站来提升自己?
  19. PreSonus Studio One 5 Professional v5.5.0 WiN-MAC 音乐制作宿主软件
  20. git branch命令解析

热门文章

  1. zsh:command not found:conda的解决方法
  2. 云服务器怎么设置虚拟IP,云服务器能起虚拟ip吗
  3. SQL语句(三) 更新语句(增 删 改)
  4. 利用ArcGIS提取高光谱图像每个像素的光谱信息,再利用matlab显示每个像素的光谱信息
  5. 【nn.LSTM详解】
  6. Canence第5篇之用IPC-7351 LP Wizard生成PCB lib
  7. 为保证系统的可用性的事前事中事后
  8. 程序员必读书单1.0
  9. 为什么设计思维对产品设计有帮助?
  10. 1. jack-server报错