位图Bitmap(基于Java实现)
2021/7/17 20:08:48
本文主要是介绍位图Bitmap(基于Java实现),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
所谓bitmap,就是用每一位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的。
设计原则:
尽可能的最大化利用内存,极限挖掘、利用、发挥Java的性能。
设计思路:
使用long型数组来用作存储,
故位图Bitmap类的大小size使用long型(int型不够极限),所以理论上0<=size<=2^63-1;
又Java数组的长度最长为2^31-1(即int型字面量最大值),所以long类型的数组最多可以存储64*(2^31-1)个点(以只有两种状态的点来计算,即1bit表示一个点的状态),所以Bitmap的size不可能大于64*(2^31-1),故实际0<=size<=64*(2^31-1);
再继续分析位图中每个点不可能只有两种状态,所以需要设计出来的Bitmap可以自定义点的状态数量state,即1<state<=2^63-1(大于1是因为状态不可能只有1种,只有一种的话使用位图就失去了意义,小于等于2^63-1是因为2^63-1是long型字面量最大值,没有比该值还要大的整数值,符合宗旨:探索挖掘Java的极限使用);
此外自定义的点的状态数量需要使用多少位数来表示也是一个问题,自定义点的状态数量位数stateBitNum=(state的二进制表示的位数)
(
先做简单推导:
2种状态需要1bit,[0,1]
3~4种状态需要2bit,[00,01,10,11]
5~8种状态需要3bit,[000,001,010,011,100,101,110,111]
9~16种状态需要4bit,[...,...]
这里给出计算公式:stateBitNum=64 - Long.numberOfLeadingZeros(state - 1);
Long.numberOfLeadingZeros(long i)方法是计算i的补码形式最高位的1的左面的0的数量,
也就是补码中从左往右数0的个数,数到第一个非0的位置停止,0的数量即为该方法的返回值,举例:Long.numberOfLeadingZeros(2)=62,2的补码为10,故用64位补码(因为i是long型的)表示10前面还有62个0;因此64- Long.numberOfLeadingZeros(2)=2,而2种状态(state=2)其实只要用1位表示,所以state再传入该方法时要减一(因为0本身在计算机中就可以表示一种状态,比如8种状态可以用数字0~7来表示一样);再继续深挖会发现64位其实可以表示2^64种状态,但是Java的最大字面量值为2^63-1,所以最大状态数量state=2^63-1种状态需要63位保存,一个long型足以,这里还有一个坑Long.numberOfLeadingZeros(0)=64,但是我们在使用时不可能出现这种情况因为state最小为2,state-1最小为1,避免了这种情况;
至此结束!
);
其余Bitmap的各种操作,实现时说明。
实现:
/** * 位图类: * 1.最多可以储存(2^37-2^6)个2种状态值的点; * 2.理论上有(2^37-2^6)个位空间,实际上可能不能完全使用(见无参构造器中的介绍), * 故使用时所选的点的状态值种数(stateNum)最好满足 64% ⌈ log2(stateNum) ⌉ = 0,即stateNum的补码的位数正好是64的因数;(听不明白的话,自行体会) * 3.没有删除点的方法,只有增加,更新,查找操作;(问为什么没有删除方法的人不是脑子有病,就是脑子有病。。。) * 4.回答3中问题:首先不是实现不了,是没有必要,因为删除一个点需要在数组中通过移位来填充被删除的位置,由于数据量过大可能会极其浪费时间, * 而且位图本身存在的意义是表示海量数据中每一条数据的可能性,对于海量数据而言,出现极个别错误并不会影响其最终统计结果(对于偷跑,出错的数据仍然可以在下一层中进行重新过滤,要是听不懂就算了。。。), * 这里建议使用update方法将不需要的点置0,用于表示该点被弃用,具体的逻辑需要使用者自己去编辑; * 5.该类所花费的理论持久存储空间大约为260MB也就是elementData数组的大小的极限值,实际可能会多一点,但几乎可以忽略不计,最终实现存储2种状态值的点的数量大约20亿个, * 也可以自定义每个点的状态值种数stateNum和点的数量length最大可以设置成(2^63-1)个,但最终要满足 length > ⌈ log2(stateNum) ⌉(向上取整) / 64 * Integer.MAX_VALUE, * 对于日常使用一定是足够的;(在这里,总有杠精会问超过了怎么办?就算是2种状态值的点有20亿个话,我还是不够用啊?) * 6.回答5中的问题:你不会搞个数组吗?你不能多new几个一起用吗!!! */ public class Bitmap { /* *使用long型数组保存点 */ private final long[] elementData; /* * 位图中能存储的所有点的位数之和的最大值MAX_STATE_SIZE=137,438,953,408; * 64(MAX_STATE_SIZE=long类型的位数) * 0x7fffffff(int类型的最大值Integer.MAX_VALUE)= MAX_STATE_SIZE (2^37-2^6) */ // private static final long MAX_STATE_SIZE = 0x1fffffffc0L;//结果没用上,大意了。。。 /* 位图保存的点的状态种数 */ private final long stateNum; /* 存储一个位图保存的点的状态值所需要的位数 */ private final int stateBitNum; /* 位图可以储存的最大点数,初始化Bitmap类时指定 */ private final long MAX_SIZE; /* 一个long型变量可以保存的点的数量 */ private final long numOfOneLong; /* 该位图已经保存的点的数量 */ private long size; /** * @param stateNum 位图中点的状态数量,state>1 * @param length 位图大小,0<length * 如果 length > ⌈ log2(stateNum) ⌉(向上取整) / 64 * Integer.MAX_VALUE,则说明该 Bitmap 类无法储存 length 个 stateNum 种数的点,抛出异常 */ public Bitmap(long stateNum, long length) { if (stateNum > 1 && length > 0) { /* 计算状态值需要多少位来表示 */ stateBitNum = 64 - Long.numberOfLeadingZeros(stateNum - 1); /* 计算一个long型里面最多可以存放几个状态值, 如果不能整除的话,就舍弃余下位数, 因为在数组中跨元素进行位值的存取计算极其复杂,故舍弃部分位空间,方便位图的各项操作 */ numOfOneLong = 64 / stateBitNum; /* 一个long型最多可以存储numOfOneLong个状态值, 所以该位图最大能存取numOfOneLong*Integer.MAX_VALUE个点, 如果需要存储的数量length大于位图的最大存储数(numOfOneLong*Integer.MAX_VALUE)就说明该类Bitmap无法满足要求,抛出异常 */ if (length > numOfOneLong * Integer.MAX_VALUE) throw new RuntimeException("初始化的位图过大,无法存储!!!"); this.stateNum = stateNum; MAX_SIZE = length; if (length % numOfOneLong == 0) elementData = new long[(int) (length / numOfOneLong)]; /* 如果length个点不能被数组正好放下,就需要多添加一个数组元素来存储点 */ else elementData = new long[((int) (length / numOfOneLong)) + 1]; } else throw new RuntimeException("位图类的初始化传入参数值中没有负整数!!!"); } /** * 顺序添加点 * * @param state 点的状态 * @return true表示添加成功,产生false的情况有:位图满了;状态值越界 */ public boolean add(long state) { if (state > stateNum - 1 || state < 0 || size == MAX_SIZE) return false; int index = (int) (size / numOfOneLong); int left = (int) (size % numOfOneLong); elementData[index] |= state << (64 - stateBitNum * (1 + left)); ++size; return true; } public long find(long index) { if (index < 0 || index > size - 1) return -1; /* 计算数组中哪一个元素保存着index索引对应的点 */ int arrayIndex = (int) (index / numOfOneLong); /* 计算元素中从哪一位开始的位保存着index索引对应的点 */ int elementIndex = (int) (index % numOfOneLong); /* 通过左移,清空掉左边无用的位,再通过无符号右移清空掉右边无用的位,最终正好是需要查找的index位置对应的state值 */ return elementData[arrayIndex] << (stateBitNum * elementIndex) >>> (64 - stateBitNum); } public boolean update(long index, long state) { if (index < 0 || index > size - 1 || state > stateNum - 1 || state < 0) return false; int arrayIndex = (int) (index / numOfOneLong); int left = (int) (index % numOfOneLong); elementData[arrayIndex] |= state << (64 - stateBitNum * (1 + left)); return true; } /** * 返回位图中点的状态数量 */ public long getStateNum() { return stateNum; } /** * 返回位图可以存储的点的最大数量 */ public long getMaxSize() { return MAX_SIZE; } /** * 返回位图中实际已使用的数量 */ public long getSize() { return size; } /** * 辅助toString()方法,主要用于打印位图的具体显示 */ private String elementDataToString() { StringBuilder result = new StringBuilder("[\n"); for (long element : elementData) { String eleString = Long.toBinaryString(element); StringBuilder one = new StringBuilder(); for (int i = 0; i < 64 - eleString.length(); i++) one.append("0"); one.append(eleString); for (int i = 0; i < numOfOneLong + 1; i++) one.insert((stateBitNum + 1) * i, ','); result.append(one.substring(1, one.lastIndexOf(","))).append(",\n"); } return result.append("]").toString(); } @Override public String toString() { return "Bitmap{\n" + "elementData=" + elementDataToString() + ", \nstateNum=" + stateNum + ", \nstateBitNum=" + stateBitNum + ", \nMAX_SIZE=" + MAX_SIZE + ", \nsize=" + size + "\n}"; } }
测试:
public class test { public static void main(String[] args) { Bitmap bitmap = new Bitmap(16, 10); System.out.println(bitmap.add(2)); System.out.println(bitmap.add(3)); System.out.println(bitmap.add(4)); System.out.println(bitmap.add(5)); System.out.println(bitmap); System.out.println(bitmap.update(3, 7)); System.out.println(bitmap); System.out.println(bitmap.find(3)); } }
总结:
位图类: 1. 最多可以储存(2^37-2^6)个2种状态值的点; 2. 理论上有(2^37-2^6)个位空间,实际上可能不能完全使用(见无参构造器中的介绍),故使用时所选的点的状态值种数(stateNum)最好满足 64% ⌈ log2(stateNum) ⌉ = 0,即stateNum的补码的位数正好是64的因数;(听不明白的话,自行体会) 3. 没有删除点的方法,只有增加,更新,查找操作;(问为什么没有删除方法的人不是脑子有病,就是脑子有病。。。) 4. 回答3中问题:首先不是实现不了,是没有必要,因为删除一个点需要在数组中通过移位来填充被删除的位置,由于数据量过大可能会极其浪费时间,而且位图本身存在的意义是表示海量数据中每一条数据的可能性,对于海量数据而言,出现极个别错误并不会影响其最终统计结果(对于偷跑,出错的数据仍然可以在下一层中进行重新过滤,要是听不懂就算了。。。),这里建议使用update方法将不需要的点置0,用于表示该点被弃用,具体的逻辑需要使用者自己去编辑; 5. 该类所花费的理论持久存储空间大约为260MB也就是elementData数组的大小的极限值,实际可能会多一点,但几乎可以忽略不计,最终实现存储2种状态值的点的数量大约20亿个,也可以自定义每个点的状态值种数stateNum和点的数量length最大可以设置成(2^63-1)个,但最终要满足 length > ⌈ log2(stateNum) ⌉(向上取整)/ 64 * Integer.MAX_VALUE,对于日常使用一定是足够的;(在这里,总有杠精会问超过了怎么办?就算是2种状态值的点有20亿个话,我还是不够用啊?) 6. 回答5中的问题:你不会搞个数组吗?你不能多new几个一起用吗!!!
这篇关于位图Bitmap(基于Java实现)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-01后台管理开发学习:新手入门指南
- 2024-11-01后台管理系统开发学习:新手入门教程
- 2024-11-01后台开发学习:从入门到实践的简单教程
- 2024-11-01后台综合解决方案学习:从入门到初级实战教程
- 2024-11-01接口模块封装学习入门教程
- 2024-11-01请求动作封装学习:新手入门教程
- 2024-11-01登录鉴权入门:新手必读指南
- 2024-11-01动态面包屑入门:轻松掌握导航设计技巧
- 2024-11-01动态权限入门:新手必读指南
- 2024-11-01动态主题处理入门:新手必读指南