在20亿个随机整数中找出某个数m是否存在其中,并假设32位操作系统,4G内存
在Java中,int占4字节, 1字节=8位(1 byte = 8 bit)
如果每个数字用int存储,那就是20亿个int,因而占用的空间约为 (2000000000*4/1024/1024/1024)= 7.47 G
如果按位存储就不一样了,20亿个数就是20亿位,占用空间约为 (2000000000/8/1024/1024/1024)= 0.233G
bitmap算法:
所谓的Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。
那么,问题来了,如何表示一个数呢?刚才说了,每一位表示一个数,0表示不存在,1表示存在,这正符合二进制。
这样我们可以很容易表示{1,2,4,6}这几个数:
计算机内存分配的最小单位是字节,也就是8位,那如果要表示{12,13,15}怎么办呢?
当然是在另一个8位上表示了:
这样的话,好像变成一个二维数组了
1个int占32位,那么我们只需要申请一个int数组长度为 int tmp[1+N/32] 即可存储,其中N表示要存储的这些数中的最大值,于是乎:
tmp[0]:可以表示0~31
tmp[1]:可以表示32~63
tmp[2]:可以表示64~95
。。。
如此一来,给定任意整数M,那么M/32就得到下标,M%32就知道它在此下标的哪个位置
这里有个问题,我们怎么把一个数放进去呢?例如,想把5这个数字放进去,怎么做呢?
首先,5/32=0,5%32=5,也是说它应该在tmp[0]的第5个位置,那我们把1向左移动5位,然后按位或,相当于b[0] = b[0] | (1<<5)
假设我们要6移除,该怎么做呢?b[0] = b[0] & (~(1<<(6%8)))
查找
前面我们也说了,每一位代表一个数字,1表示有(或者说存在),0表示无(或者说不存在)。通过把该为置为1或者0来达到添加和清除的小伙,那么判断一个数存不存在就是判断该数所在的位是0还是1
假设,我们想知道3在不在,那么只需判断 b[0] & (1<<3) 如果这个值是0,则不存在,非0,就表示存在。
public class BitMap {
public static void main(String[] args) {
BitMap bit = new BitMap(1000000L);
bit.setN(10000);
System.out.println(bit.isExist(10000));
bit.clear(10000);
System.out.println(bit.isExist(10000));
}
private long length;
private static int[] bitsMap;
public BitMap(long length)
{
this.length = length;
/**
* 根据长度算出,所需数组大小
* 当 length % 32 = 0 时大小等于
* = length / 32
* 当 length % 32 > 0 时大小等于
* = length / 32 + l
*/
bitsMap = new int[(int) (length >> 5) + ((length & 31) > 0 ? 1 : 0)];
//数组的length属性时int(long型数组也是int),所以理论最大应该时Integer.MAX_VALUE数组的极限大小受jvm设置的最大内存限制
}
public void setN(long n) {
if (n < 0 || n > length) {
throw new IllegalArgumentException("length value " + n + " is illegal!");
}
// 求出该n所在bitMap的下标,等价于 "n / 32"
int index = (int) n / 32;
// 求出该值的偏移量(求余),等价于"n % 32"
int offset = (int) n & 31;
bitsMap[index] |= 1 << offset;
printNum(n);
}
/**
* 获取值N是否存在
*/
public boolean isExist(long n) {
if (n < 0 || n > length) {
throw new IllegalArgumentException("length value illegal!");
}
int index = (int) n >> 5;
int offset = (int) n & 31;
int bits = bitsMap[index];
//printNum(n);
return (bits & (1 << offset)) != 0;
}
public void clear(long n)
{
if (n < 0 || n > length) {
throw new IllegalArgumentException("length value illegal!");
}
int index = (int) n >> 5;
int offset = (int) n & 31;
bitsMap[index] &= (~(1 << offset));
printNum(n);
}
public void printNum(long n)
{
if (n < 0 || n > length) {
throw new IllegalArgumentException("length value illegal!");
}
int index = (int) n >> 5;
int offset = (int) n & 31;
System.out.println("index " + index + " offset " + offset);
int tem = bitsMap[index];
int cur = 0;
while(cur <= 31)
{
cur++;
String split = cur % 4 == 0 ? " " : " ";
System.out.print(tem % 2 + split);
tem /= 2;
}
}
}
优点 :
- 运算效率高,不需要进行比较和移位;
- 占用内存少,比如N=10000000;只需占用内存为N/8=1250000Byte=1.25M
缺点 :
- 所有的数据不能重复。即不可对重复的数据进行排序和查找。
- 只有当数据比较密集时才有优势
bitSet:
在bitset中就是使用着bitmap方式,将数据存储在long数组中,主要的操作就是位操作。用非负的整数将BitSet的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个 BitSet修改另一个 BitSet的内容。 线程不安全。默认情况下,set 中所有位的初始值0,所以get的时候都是false。
BitSet的size方法:返回此 BitSet 表示位值时实际使用空间的位数,值是64的整数倍
length方法:返回此 BitSet 的“逻辑大小”:BitSet 中最高设置位的索引加 1。
案例:
有1万个随机数,随机数的范围在1- 1万之间。现在要求写出一种算法,将0到10001亿之间没有在随机数取出来,为了方便测试才设计的1万?
public static void main(String [] args)
{
int count = 10000;
Random random = new Random();
Set<Integer> ran = new HashSet<>();
while (ran.size() < count)
{
ran.add(random.nextInt(count) + 1);
}
BitSet bitSet = new BitSet(10000);
ran.forEach(bitSet::set);
IntStream.range(0, 10001).filter(e -> !bitSet.get(e)).forEach(System.out::println);//0
}
布隆过滤器:
当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组(Bit array)中的 K 个点,把它们置为 1 。检索时,只要看看这些点是不是都是1就知道元素是否在集合中;如果这些点有任何一个 0,则被检元素一定不在;如果都是1,则被检元素很可能在(之所以说“可能”是误差的存在)。误差举例:当bitmap的数组长度很小,而数据又很多的情况下,有可能判断元素在集合中,但是元素不在,原因:这个元素所有索引位置上面的1都是别的元素设置的,这就导致一定的误判几率。
BloomFilter 流程
- 首先需要 k 个 hash 函数,每个函数可以把 key 散列成为 1 个整数;
- 初始化时,需要一个长度为 n 比特的数组,每个比特位初始化为 0;
- 某个 key 加入集合时,用 k 个 hash 函数计算出 k 个散列值,并把数组中对应的比特位置为 1;
- 判断某个 key 是否在集合时,用 k 个 hash 函数计算出 k 个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
public class BloomFilter {
private static final int DEFAULT_SIZE = 2 << 24;
private static final int[] seeds = new int[] { 5, 7, 11, 13, 31, 37, 61 };
private BitSet bits = new BitSet(DEFAULT_SIZE);
private SimpleHash[] func = new SimpleHash[seeds.length];
// 内部类,simpleHash
public static class SimpleHash {
private int cap;
private int seed;
public SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
public int hash(String value) {
int result = 0;
int len = value.length();
for (int i = 0; i < len; i++) {
result = seed * result + value.charAt(i);
}
return (cap - 1) & result;
}
}
public BloomFilter() {
for (int i = 0; i < seeds.length; i++) {
func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}
public void add(String value) {
for (SimpleHash f : func) {
bits.set(f.hash(value), true);
}
}
public boolean contains(String value) {
if (value == null) {
return false;
}
boolean ret = true;
for (SimpleHash f : func) {
ret = ret && bits.get(f.hash(value));
}
return ret;
}
public static void main(String [] args)
{
BloomFilter bf = new BloomFilter();
List<String> strs = new ArrayList<String>()
{{
add("123456");
add("hello word");
add("transDocId");
add("123456");
add("transDocId");
add("hello word");
add("test");
}};
strs.forEach(e -> {
boolean bl = bf.contains(e);
if(bl){
System.out.println(e);// 123456 transDocId hello word
}else{
bf.add(e);
}
});
}
}