位图

位图是一种数据结构,是利用二进制的01特性进行储存的一种技术,这种方式可以很大程度的节约内存,因为只需要存储二进制位即可。

我们先看一道经典的题: 有0-40亿的QQ用户,每个用户有在线/离线两种状态,目前只有一台1G内存的机器,实现一种可行方式,能够通过输入QQ号,查询用户当前状态。
咋一看这题可以直接弄一个哈希表<key:QQ, value:State>进行存储,当我们试着去用一个哈希表存储40亿条整形数据时,我们计算一下需要的内存,一个整型数据是32位的,4个字节(Byte,以下简称B),4B * 4 * 10^9 = 16GB,显然超过了题目给定的1GB的内存,所以直接构造哈希表的方法行不通;那我们可以用硬盘存储QQ号,然后分批次遍历看看有没有出现,这种方式虽然可行,但是效率太低,显然不可行。
我们能不能利用有限的内存构造出一种能够存储,且查询迅速的结构或技术呢?答案是肯定的,我们先仔细看题,题目要求是QQ号对应只有两种状态在线/离线,我们可以以QQ号作为索引,建立一个位数组,值是二进制比特01,这样的话共需要内存就是1bit * 4 * 109=5*108B=500MB<1GB(注意一个字节8位,1B = 8bits),所以我们得出,可以用位存储的思想将原本存不下的整型数“存起来”。
上述思想如下图,一个32位int类型的位值图(在计算机中存储的形式),这是一个十进制整数的二进制表示,一个整数占据32位4个字节,非常浪费,我们采用映射的思想,将32个整数映射到这个整数上进行存储,1表示出现过,0表示未出现,这样一个32位整数就可以表示32个整数的情况,内存缩小了32倍。
在这里插入图片描述
比如上图中可见的2出现过以及5出现过,其他元素则没有出现。
如何利用代码实现呢?
在编程语言中,常见类型都是以字节为单位的,没有以位作为单位的类型,所以不能直接写代码,我们可以考虑用移位来帮忙,用int类型来存储二进制位,比如一个int类型的数是32位,就可以存储32个QQ号的状态,0表示离线,1表示在线。
综上分析,我们就可以写代码了:

package BitMap;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

/**
 * 有0-40亿的QQ用户,每个用户有在线/离线两种状态,目前只有一台1G内存的机器
 * 实现一种可行方式,能够通过输入QQ号,查询用户当前状态
 * 
 * 若存每个数,则需要int4个字节, 4 * 4 * 10^9共16G。
 * 实现思想,利用位图的思想,每个数对应的状态用二进制存储,这样原来一个整数32位的存储空间被压缩成1位,
 * 整个空间小了32倍,16G的内存只需要500M即可,这样内存足够。
 * 也就是说用一个int类型的数存储32个QQ号的状态,这样40亿个QQ号共需要500M内存
 * @author brooke
 * 
 */
public class QQ {
	
	private int length; //需要的整数个数,对32按向上整除
	private int[] qq; //存储整数映射的位图数组
	
	
	public QQ(int num) {
		this.length = num / 32 + (num % 32 == 0 ? 0 : 1);
		qq = new int[length];
	}

	/**
	 * 设置第i个位置的状态
	 * @param number
	 * @param state
	 */
	public void set(int number, boolean state) {
		int st = state ? 1 : 0;
		int b = 31 - number % 32;
		int a = number / 32;
		qq[a] |= st << b;
	}
	
	/**
	 * 获取第i个位置的状态
	 * @param number
	 * @return
	 */
	public boolean get(int number) {
		int a = number / 32;
		int b = 31 - number % 32;
		int value = 1 << b;
		value &= qq[a];
		return value != 0;
	}
	
	/**
	 * 测试思想:利用集合记录出现过的数字,并随机选择数字,测试位图的结果和集合的结果是否一致!
	 * @param args
	 */
	public static void main(String[] args) {
		int size = (int)(10000);//由于jvm内存不够1G,所以40亿无法测试
		QQ obj = new QQ(size);
		int i = 0;
		Random rand = new Random();
		Set<Integer> set = new HashSet<>();
		boolean flag = true;
		while(i++ < 10000) {
			int index = rand.nextInt(size);
			obj.set(index, true);
			set.add(index);
			index = rand.nextInt(size);
			if(set.contains(index) != obj.get(index)) {
				System.out.println(index + " "+set.contains(index)+" " + obj.get(index));
				flag = false;
				break;
			}
		}
		if(flag)
			System.out.println("Nice!");
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值