布隆过滤器 大量数据去重:Bitmap和布隆过滤器(Bloom Filter)

版权声明:https://blog.csdn.net/lvtula https://blog.csdn.net/lvtula/article/details/82464066

大量数据去重:Bitmap和布隆过滤器(Bloom Filter)

5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该如何解决?如果是64bit的呢?

在面试时遇到的问题,问题的解决方案十分典型,但对于海量数据处理接触少的同学可能一时也想不到什么好方案。介绍两个算法,对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)

Bitmap算法

在网上并没有找到Bitmap算法的中文翻译,在《编程珠玑》中有提及。与其说是算法,不如说是一种紧凑的数据存储结构。其实如果并非如此大量的数据,有很多排重方案可以使用,典型的就是哈希表

public int[] removeDuplicates(int[] array) {
    int index = 0;
    Map<Integer, Boolean> maps = new LinkedHashMap<Integer, Boolean>();
    for(int num : array) {
        if(!maps.contains(num)) {
            array[index] = num;
            index++;
            maps.put(num, true);
        }
    }

    return newArray;
}
      
      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

实际上,哈希表实际上为每一个可能出现的数字提供了一个一一映射的关系,每个元素都相当于有了自己的独享的一份空间,这个映射由散列函数来提供(这里我们先不考虑碰撞)。实际上哈希表甚至还能记录每个元素出现的次数,这样的数据结构完成这个任务有点“大材小用”了。

我们拆解一下我们的需求:

  1. 集合中每个元素(示例中是int)有一个独享的空间
  2. 找到一个到这个空间的映射方法

这个空间要多大?对于我们的问题来说,一个boolean就够了,或者说,1个bit就够了,我们只想知道某个元素出现过没有。如果为每个所有可能的值分配1个bit,32bit的int所有可能取值需要内存空间为:

232bit=229Byte=512MB” role=”presentation” style=”text-align: center; position: relative;”>232bit=229Byte=512MB232bit=229Byte=512MB

那怎么样完成这个映射呢?其实就是Bitmap所要完成的工作了。如果我们把整型0x01、0x02、…、0x08的空间依次映射到一个Byte上,每个bit就代表这个int值是否出现过,初值为0(false)。

若扩展到整个int取值域,申请一个byte[]即可,示例代码如下:

public static final int _1MB = 1024 * 1024;
//每个byte记录8bit信息,也就是8个数是否存在于数组中
public static byte[] flags = new byte[ 512 * _1MB ];


public static void main(String[] args) {
    //待判重数据
    int[] array = {255, 1024, 0, 65536, 255};

    int index = 0;
    for(int num : array) {
        if(!getFlags(num)) {
            //未出现的元素
            array[index] = num;
            index = index + 1;
            //设置标志位
            setFlags(num);
            System.out.println("set " + num);
        } else {
            System.out.println(num + " already exist");
        }
    }
}

public static void setFlags(int num) {
    //使用每个数的低三位作为byte内的映射
    //例如: 255 = 0x11111111
    //低三位(也就是num & (0x07))为0x111 = 7, 则byte的第7位为1, 表示255已存在
    flags[num >> 3] |= 0x01 << (num & (0x07));
}

public static boolean getFlags(int num) {
    return (flags[num >> 3] >> (num & (0x07)) & 0x01) == 0x01;
}
      
      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

其实,就是按int从小到大的顺序依次摆放到byte[]中,仅涉及到一些除以2的整次幂和对2的整次幂取余的位操作小技巧。很显然,对于小数据量、数据取值很稀疏,上面的方法并没有什么优势,但对于海量的、取值分布很均匀的集合进行去重,Bitmap极大地压缩了所需要的内存空间。于此同时,还额外地完成了对原始数组的排序工作。缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(Bloom Filter)

然而Bitmap不是万能的,如果数据量大到一定程度,如开头写的64bit类型的数据,还能不能用Bitmap?我们来算一算:

264bit=261Byte=2048PB=2EB” role=”presentation” style=”text-align: center; position: relative;”>264bit=261Byte=2048PB=2EB264bit=261Byte=2048PB=2EB

EB(Exabyte,艾字节)这个计算机科学中统计数据量的单位有多大,有兴趣的小伙伴可以查阅下资料。这个量级的Bitmap,已经不是人类硬件所能承担的了。我相信谁也不会想用集群去计算这么一个问题吧?所以Bitmap的好处在于空间复杂度不随原始集合内元素的个数增加而增加,而它的坏处也源于这一点——空间复杂度随集合内最大元素增大而线性增大

所以接下来,我们要引入另一个著名的工业实现——布隆过滤器(Bloom Filter)。如果说Bitmap对于每一个可能的整型值,通过直接寻址的方式进行映射,相当于使用了一个哈希函数,那布隆过滤器就是引入了k(k&gt;1)” role=”presentation” style=”position: relative;”>k(k>1)k(k>1)时的布隆过滤器。
图1 布隆过滤器(来源:wiki)

x,y,z” role=”presentation” style=”position: relative;”>x,y,zx,y,z不在集合中。

那么布隆过滤器的误差有多少?我们假设所有哈希函数散列足够均匀,散列后落到Bitmap每个位置的概率均等。Bitmap的大小为m” role=”presentation” style=”position: relative;”>mm

  1. 1个散列函数时,接收一个元素时Bitmap中某一位置为0的概率为:
    1&#x2212;1m” role=”presentation” style=”text-align: center; position: relative;”>11m1−1m
  2. k” role=”presentation” style=”position: relative;”>kk个相互独立的散列函数,接收一个元素时Bitmap中某一位置为0的概率为:
    (1&#x2212;1m)k” role=”presentation” style=”text-align: center; position: relative;”>(11m)k(1−1m)k
  3. 假设原始集合中,所有元素都不相等(最严格的情况),将所有元素都输入布隆过滤器,此时某一位置仍为0的概率为:
    (1&#x2212;1m)nk” role=”presentation” style=”text-align: center; position: relative;”>(11m)nk(1−1m)nk

    某一位置为1的概率为:
    1&#x2212;(1&#x2212;1m)nk” role=”presentation” style=”text-align: center; position: relative;”>1(11m)nk1−(1−1m)nk
  4. 当我们对某个元素进行判重时,误判即这个元素对应的k” role=”presentation” style=”position: relative;”>kk为:
    &#x03B5;&#x2248;[1&#x2212;(1&#x2212;1m)nk]k” role=”presentation” style=”text-align: center; position: relative;”>ε[1(11m)nk]kε≈[1−(1−1m)nk]k

    这个误判率应当比实际值大,因为将判断正确的情况也算进去了。根据著名极限limn&#x2192;&#x221E;(1+1n)n=e” role=”presentation” style=”position: relative;”>limn(1+1n)n=elimn→∞(1+1n)n=e可以得到:
    &#x03B5;&#x2248;[1&#x2212;e&#x2212;nkm]k” role=”presentation” style=”text-align: center; position: relative;”>ε[1enkm]kε≈[1−e−nkm]k

    &#x03B5;” role=”presentation” style=”position: relative;”>εε得到最优解1,当且仅当:
    k=mnln&#x2061;2&#x2248;0.7mn” role=”presentation” style=”text-align: center; position: relative;”>k=mnln20.7mnk=mnln⁡2≈0.7mn

    此时,误判率&#x03B5;” role=”presentation” style=”position: relative;”>εε与数集大小和
    &#x03B5;&#x2248;(1&#x2212;e&#x2212;ln&#x2061;2)ln2mn=0.5ln2mn=0.5k” role=”presentation” style=”text-align: center; position: relative;”>ε(1eln2)ln2mn=0.5ln2mn=0.5kε≈(1−e−ln⁡2)ln2mn=0.5ln2mn=0.5k

回到我们的问题中,有趣的是由于硬盘空间是限制死的,集合元素个数n” role=”presentation” style=”position: relative;”>nn的大小反而与单个数据的比特数成反比,数据长度为64bit时,

n=5TB64bit=5&#x00D7;240Byte8Byte&#x2248;234” role=”presentation” style=”text-align: center; position: relative;”>n=5TB64bit=5×240Byte8Byte234n=5TB64bit=5×240Byte8Byte≈234

若以m=16n” role=”presentation” style=”position: relative;”>m=16nm=16n。并且要知道,以上计算的都是误差的上限

布隆过滤器通过引入一定错误率,使得海量数据判重在可以接受的内存代价中得以实现。从上面的公式可以看出,随着集合中的元素不断输入过滤器中(n” role=”presentation” style=”position: relative;”>nn(指bit数)足够大时,比如比所有可能出现的不重复元素个数还要大10倍以上时,错误概率是可以接受的。

最后我们所要做的,就是实现一个布隆过滤器,然后利用它对硬盘上的5TB数据一一判重,并写回硬盘中。这其中可能涉及到利用读写的buffer,待有时间补上。

附录

这里有一个google实现的布隆过滤器,我们来看看它的误判率:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.util.HashSet;
import java.util.Random;

public class testBloomFilter {

    static int sizeOfNumberSet = Integer.MAX_VALUE >> 4;

    static Random generator = new Random();

    public static void main(String[] args) {

        int error = 0;
        HashSet<Integer> hashSet = new HashSet<Integer>();
        BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), sizeOfNumberSet);

        for(int i = 0; i < sizeOfNumberSet; i++) {
            int number = generator.nextInt();
            if(filter.mightContain(number) != hashSet.contains(number)) {
                error++;
            }
            filter.put(number);
            hashSet.add(number);
        }

        System.out.println("Error count: " + error + ", error rate = " + String.format("%f", (float)error/(float)sizeOfNumberSet));
    }
}
      
      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

在这个实现中,Bitmap的集合m” role=”presentation” style=”position: relative;”>mm

/**
 * Creates a {@link BloomFilter BloomFilter<T>} with the expected number of
 * insertions and a default expected false positive probability of 3%.
 */
public static <T> BloomFilter<T> create(Funnel<T> funnel, int expectedInsertions /* n */) {
    return create(funnel, expectedInsertions, 0.03); // FYI, for 3%, we always get 5 hash functions
}
      
      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

而还有一个很有趣的地方是,实际使用的却并不是5个哈希函数。实际进行映射时,而是分别使用了一个64bit哈希函数的高、低32bit进行循环移位。注释中包含着这个算法的论文“Less Hashing, Same Performance: Building a Better Bloom Filter”,论文中指明其对过滤器性能没有明显影响。很明显这个实现对于m&gt;232” role=”presentation” style=”position: relative;”>m>232m>232的下标在算法中并不能被映射到。

enum BloomFilterStrategies implements BloomFilter.Strategy {
  /**
   * See "Less Hashing, Same Performance: Building a Better Bloom Filter" by Adam Kirsch and
   * Michael Mitzenmacher. The paper argues that this trick doesn't significantly deteriorate the
   * performance of a Bloom filter (yet only needs two 32bit hash functions).
   */
  MURMUR128_MITZ_32() {
    @Override public <T> boolean put(T object, Funnel<? super T> funnel,
        int numHashFunctions, BitArray bits) {
      long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
      int hash1 = (int) hash64;
      int hash2 = (int) (hash64 >>> 32);
      boolean bitsChanged = false;
      for (int i = 1; i <= numHashFunctions; i++) {
        int nextHash = hash1 + i * hash2;
        if (nextHash < 0) {
          nextHash = ~nextHash;
        }
        bitsChanged |= bits.set(nextHash % bits.bitSize());
      }
      return bitsChanged;
    }

    @Override public <T> boolean mightContain(T object, Funnel<? super T> funnel,
        int numHashFunctions, BitArray bits) {
      long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
      int hash1 = (int) hash64;
      int hash2 = (int) (hash64 >>> 32);
      for (int i = 1; i <= numHashFunctions; i++) {
        int nextHash = hash1 + i * hash2;
        if (nextHash < 0) {
          nextHash = ~nextHash;
        }
        if (!bits.get(nextHash % bits.bitSize())) {
          return false;
        }
      }
      return true;
    }
  };
  ...
}
      
      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该如何解决?如果是64bit的呢?

在面试时遇到的问题,问题的解决方案十分典型,但对于海量数据处理接触少的同学可能一时也想不到什么好方案。介绍两个算法,对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)

Bitmap算法

在网上并没有找到Bitmap算法的中文翻译,在《编程珠玑》中有提及。与其说是算法,不如说是一种紧凑的数据存储结构。其实如果并非如此大量的数据,有很多排重方案可以使用,典型的就是哈希表

public int[] removeDuplicates(int[] array) {
    int index = 0;
    Map<Integer, Boolean> maps = new LinkedHashMap<Integer, Boolean>();
    for(int num : array) {
        if(!maps.contains(num)) {
            array[index] = num;
            index++;
            maps.put(num, true);
        }
    }

    return newArray;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

实际上,哈希表实际上为每一个可能出现的数字提供了一个一一映射的关系,每个元素都相当于有了自己的独享的一份空间,这个映射由散列函数来提供(这里我们先不考虑碰撞)。实际上哈希表甚至还能记录每个元素出现的次数,这样的数据结构完成这个任务有点“大材小用”了。

我们拆解一下我们的需求:

  1. 集合中每个元素(示例中是int)有一个独享的空间
  2. 找到一个到这个空间的映射方法

这个空间要多大?对于我们的问题来说,一个boolean就够了,或者说,1个bit就够了,我们只想知道某个元素出现过没有。如果为每个所有可能的值分配1个bit,32bit的int所有可能取值需要内存空间为:

232bit=229Byte=512MB” role=”presentation” style=”text-align: center; position: relative;”>232bit=229Byte=512MB232bit=229Byte=512MB

那怎么样完成这个映射呢?其实就是Bitmap所要完成的工作了。如果我们把整型0x01、0x02、…、0x08的空间依次映射到一个Byte上,每个bit就代表这个int值是否出现过,初值为0(false)。

若扩展到整个int取值域,申请一个byte[]即可,示例代码如下:

public static final int _1MB = 1024 * 1024;
//每个byte记录8bit信息,也就是8个数是否存在于数组中
public static byte[] flags = new byte[ 512 * _1MB ];


public static void main(String[] args) {
    //待判重数据
    int[] array = {255, 1024, 0, 65536, 255};

    int index = 0;
    for(int num : array) {
        if(!getFlags(num)) {
            //未出现的元素
            array[index] = num;
            index = index + 1;
            //设置标志位
            setFlags(num);
            System.out.println("set " + num);
        } else {
            System.out.println(num + " already exist");
        }
    }
}

public static void setFlags(int num) {
    //使用每个数的低三位作为byte内的映射
    //例如: 255 = 0x11111111
    //低三位(也就是num & (0x07))为0x111 = 7, 则byte的第7位为1, 表示255已存在
    flags[num >> 3] |= 0x01 << (num & (0x07));
}

public static boolean getFlags(int num) {
    return (flags[num >> 3] >> (num & (0x07)) & 0x01) == 0x01;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

其实,就是按int从小到大的顺序依次摆放到byte[]中,仅涉及到一些除以2的整次幂和对2的整次幂取余的位操作小技巧。很显然,对于小数据量、数据取值很稀疏,上面的方法并没有什么优势,但对于海量的、取值分布很均匀的集合进行去重,Bitmap极大地压缩了所需要的内存空间。于此同时,还额外地完成了对原始数组的排序工作。缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(Bloom Filter)

然而Bitmap不是万能的,如果数据量大到一定程度,如开头写的64bit类型的数据,还能不能用Bitmap?我们来算一算:

264bit=261Byte=2048PB=2EB” role=”presentation” style=”text-align: center; position: relative;”>264bit=261Byte=2048PB=2EB264bit=261Byte=2048PB=2EB

EB(Exabyte,艾字节)这个计算机科学中统计数据量的单位有多大,有兴趣的小伙伴可以查阅下资料。这个量级的Bitmap,已经不是人类硬件所能承担的了。我相信谁也不会想用集群去计算这么一个问题吧?所以Bitmap的好处在于空间复杂度不随原始集合内元素的个数增加而增加,而它的坏处也源于这一点——空间复杂度随集合内最大元素增大而线性增大

所以接下来,我们要引入另一个著名的工业实现——布隆过滤器(Bloom Filter)。如果说Bitmap对于每一个可能的整型值,通过直接寻址的方式进行映射,相当于使用了一个哈希函数,那布隆过滤器就是引入了k(k&gt;1)” role=”presentation” style=”position: relative;”>k(k>1)k(k>1)时的布隆过滤器。
图1 布隆过滤器(来源:wiki)

x,y,z” role=”presentation” style=”position: relative;”>x,y,zx,y,z不在集合中。

那么布隆过滤器的误差有多少?我们假设所有哈希函数散列足够均匀,散列后落到Bitmap每个位置的概率均等。Bitmap的大小为m” role=”presentation” style=”position: relative;”>mm

  1. 1个散列函数时,接收一个元素时Bitmap中某一位置为0的概率为:
    1&#x2212;1m” role=”presentation” style=”text-align: center; position: relative;”>11m1−1m
  2. k” role=”presentation” style=”position: relative;”>kk个相互独立的散列函数,接收一个元素时Bitmap中某一位置为0的概率为:
    (1&#x2212;1m)k” role=”presentation” style=”text-align: center; position: relative;”>(11m)k(1−1m)k
  3. 假设原始集合中,所有元素都不相等(最严格的情况),将所有元素都输入布隆过滤器,此时某一位置仍为0的概率为:
    (1&#x2212;1m)nk” role=”presentation” style=”text-align: center; position: relative;”>(11m)nk(1−1m)nk

    某一位置为1的概率为:
    1&#x2212;(1&#x2212;1m)nk” role=”presentation” style=”text-align: center; position: relative;”>1(11m)nk1−(1−1m)nk
  4. 当我们对某个元素进行判重时,误判即这个元素对应的k” role=”presentation” style=”position: relative;”>kk为:
    &#x03B5;&#x2248;[1&#x2212;(1&#x2212;1m)nk]k” role=”presentation” style=”text-align: center; position: relative;”>ε[1(11m)nk]kε≈[1−(1−1m)nk]k

    这个误判率应当比实际值大,因为将判断正确的情况也算进去了。根据著名极限limn&#x2192;&#x221E;(1+1n)n=e” role=”presentation” style=”position: relative;”>limn(1+1n)n=elimn→∞(1+1n)n=e可以得到:
    &#x03B5;&#x2248;[1&#x2212;e&#x2212;nkm]k” role=”presentation” style=”text-align: center; position: relative;”>ε[1enkm]kε≈[1−e−nkm]k

    &#x03B5;” role=”presentation” style=”position: relative;”>εε得到最优解1,当且仅当:
    k=mnln&#x2061;2&#x2248;0.7mn” role=”presentation” style=”text-align: center; position: relative;”>k=mnln20.7mnk=mnln⁡2≈0.7mn

    此时,误判率&#x03B5;” role=”presentation” style=”position: relative;”>εε与数集大小和
    &#x03B5;&#x2248;(1&#x2212;e&#x2212;ln&#x2061;2)ln2mn=0.5ln2mn=0.5k” role=”presentation” style=”text-align: center; position: relative;”>ε(1eln2)ln2mn=0.5ln2mn=0.5kε≈(1−e−ln⁡2)ln2mn=0.5ln2mn=0.5k

回到我们的问题中,有趣的是由于硬盘空间是限制死的,集合元素个数n” role=”presentation” style=”position: relative;”>nn的大小反而与单个数据的比特数成反比,数据长度为64bit时,

n=5TB64bit=5&#x00D7;240Byte8Byte&#x2248;234” role=”presentation” style=”text-align: center; position: relative;”>n=5TB64bit=5×240Byte8Byte234n=5TB64bit=5×240Byte8Byte≈234

若以m=16n” role=”presentation” style=”position: relative;”>m=16nm=16n。并且要知道,以上计算的都是误差的上限

布隆过滤器通过引入一定错误率,使得海量数据判重在可以接受的内存代价中得以实现。从上面的公式可以看出,随着集合中的元素不断输入过滤器中(n” role=”presentation” style=”position: relative;”>nn(指bit数)足够大时,比如比所有可能出现的不重复元素个数还要大10倍以上时,错误概率是可以接受的。

最后我们所要做的,就是实现一个布隆过滤器,然后利用它对硬盘上的5TB数据一一判重,并写回硬盘中。这其中可能涉及到利用读写的buffer,待有时间补上。

附录

这里有一个google实现的布隆过滤器,我们来看看它的误判率:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.util.HashSet;
import java.util.Random;

public class testBloomFilter {

    static int sizeOfNumberSet = Integer.MAX_VALUE >> 4;

    static Random generator = new Random();

    public static void main(String[] args) {

        int error = 0;
        HashSet<Integer> hashSet = new HashSet<Integer>();
        BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), sizeOfNumberSet);

        for(int i = 0; i < sizeOfNumberSet; i++) {
            int number = generator.nextInt();
            if(filter.mightContain(number) != hashSet.contains(number)) {
                error++;
            }
            filter.put(number);
            hashSet.add(number);
        }

        System.out.println("Error count: " + error + ", error rate = " + String.format("%f", (float)error/(float)sizeOfNumberSet));
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

在这个实现中,Bitmap的集合m” role=”presentation” style=”position: relative;”>mm

/**
 * Creates a {@link BloomFilter BloomFilter<T>} with the expected number of
 * insertions and a default expected false positive probability of 3%.
 */
public static <T> BloomFilter<T> create(Funnel<T> funnel, int expectedInsertions /* n */) {
    return create(funnel, expectedInsertions, 0.03); // FYI, for 3%, we always get 5 hash functions
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

而还有一个很有趣的地方是,实际使用的却并不是5个哈希函数。实际进行映射时,而是分别使用了一个64bit哈希函数的高、低32bit进行循环移位。注释中包含着这个算法的论文“Less Hashing, Same Performance: Building a Better Bloom Filter”,论文中指明其对过滤器性能没有明显影响。很明显这个实现对于m&gt;232” role=”presentation” style=”position: relative;”>m>232m>232的下标在算法中并不能被映射到。

enum BloomFilterStrategies implements BloomFilter.Strategy {
  /**
   * See "Less Hashing, Same Performance: Building a Better Bloom Filter" by Adam Kirsch and
   * Michael Mitzenmacher. The paper argues that this trick doesn't significantly deteriorate the
   * performance of a Bloom filter (yet only needs two 32bit hash functions).
   */
  MURMUR128_MITZ_32() {
    @Override public <T> boolean put(T object, Funnel<? super T> funnel,
        int numHashFunctions, BitArray bits) {
      long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
      int hash1 = (int) hash64;
      int hash2 = (int) (hash64 >>> 32);
      boolean bitsChanged = false;
      for (int i = 1; i <= numHashFunctions; i++) {
        int nextHash = hash1 + i * hash2;
        if (nextHash < 0) {
          nextHash = ~nextHash;
        }
        bitsChanged |= bits.set(nextHash % bits.bitSize());
      }
      return bitsChanged;
    }

    @Override public <T> boolean mightContain(T object, Funnel<? super T> funnel,
        int numHashFunctions, BitArray bits) {
      long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
      int hash1 = (int) hash64;
      int hash2 = (int) (hash64 >>> 32);
      for (int i = 1; i <= numHashFunctions; i++) {
        int nextHash = hash1 + i * hash2;
        if (nextHash < 0) {
          nextHash = ~nextHash;
        }
        if (!bits.get(nextHash % bits.bitSize())) {
          return false;
        }
      }
      return true;
    }
  };
  ...
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

猜你喜欢

转载自blog.csdn.net/lvtula/article/details/82464066