hash模块

Hash模块就是为我们更好的生成hash值。

Bloom Filters

为啥需要布隆过滤器?

布隆过滤器的出现是为了超长数据的查找问题。虽然链表、树、哈希表都是可以进行查找的,但是要么在空间上消耗很大,要么在时间上耗费很大。

而布隆过滤器的查找效率是O(1),空间消耗上也相对其他的要小一些。

基本原理?

准备一个长度为N的bit向量数组(尽量长),初始数组初始化为0###

插入元素

  • 将元素X分别给到K个哈希函数k1 k2 k3...进行哈希计算
  • 得到的hash值就是就是bit数组上的一个点
  • 将对应的bit数组上的点置为1

查询元素

  • 将待查询的元素X分辨给K个哈希函数计算哈希值
  • 如果k个位置上有1个为0,则次元素必不存在
  • 如果k个位置上全部是1,则次元素可能存在

缺陷

  • 数据一旦记录,无法删除
  • 在查询存在的时候,存在一定的误判率
  • bit数组的大小和哈希函数的个数难以确定
  • bit数组的大小还是挺占空间的

bit数组大小的计算

应该是元素个数的1.44倍。具体的计算,读者可以查阅相关资料。

布隆过滤器的使用场景

  • 字处理软件,检查拼写是否正确
  • 犯罪嫌疑人是否在坏人名单上
  • 一个网站是否被网络爬虫访问过
  • 垃圾邮件、短信的过滤

BloomFilter.java

创建布隆过滤器

static <T> BloomFilter<T> create(
      Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy) {
    checkNotNull(funnel);
    checkArgument(
        expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions);
    checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp);
    checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp);
    checkNotNull(strategy);

    if (expectedInsertions == 0) {
      expectedInsertions = 1;
    }
    /*
     * TODO(user): Put a warning in the javadoc about tiny fpp values, since the resulting size
     * is proportional to -log(p), but there is not much of a point after all, e.g.
     * optimalM(1000, 0.0000000000000001) = 76680 which is less than 10kb. Who cares!
     */
    long numBits = optimalNumOfBits(expectedInsertions, fpp);
    int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
    try {
      return new BloomFilter<T>(new LockFreeBitArray(numBits), numHashFunctions, funnel, strategy);
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e);
    }
  }

其中需要关注的是bit数组和hash函数个数的计算:

static long optimalNumOfBits(long n, double p) {
    if (p == 0) {
      p = Double.MIN_VALUE;
    }
    return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
  }

  static int optimalNumOfHashFunctions(long n, long m) {
    // (m / n) * log(2), but avoid truncation due to division!
    return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
  }

使用的实例

BloomFilter filter = BloomFilter.create(Funnels.integerFunnel(), 500, 0.01);
filter.put(11);
filter.put(22);
Log.e("111", filter.mightContain(11)  + "  =====  ");
Log.e("111", filter.mightContain(121)  + "  =====  ");

扩扎阅读 —— BitSet

BitSet是按需增长的位向量,每个位用boolean值来表示,初始值都是false。

基本原理

BitSet是位操作的,值是false或true,内部维护了一个long的数组,缺省的数组的大小是64位,但是随着元素的增多,BitSet会自动的进行扩充。

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

举个例子,1G的空间,有8*1024*1024*1024=8.58*10^9bit,也就是可以表示85亿个不同的数。

使用的实例

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 char

        StringBuilder 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());
    }

Hash模块的架构

HashFunction

提供了无状态的方法,将任意的数据块映射固定数目的hash值。它的特点是保证相同的输入得到的hash输出一定是一样的,而不同的输入尽可能的保证不一样

Hasher

有状态的方法,提供了方法将数据添加到散列运算中,Hasher可以接受所有原生类型、字节数组、字节数组的片段、字符序列、特定字符集的字符序列等等,或者任何给定了Funnel实现的对象。

Funnel

这个是将一个java对象拆分成原生字段值。它的定义是:

public interface Funnel<T> extends Serializable {

  /**
   * Sends a stream of data from the {@code from} object into the sink {@code into}. There is no
   * requirement that this data be complete enough to fully reconstitute the object later.
   *
   * @since 12.0 (in Guava 11.0, {@code PrimitiveSink} was named {@code Sink})
   */
  void funnel(T from, PrimitiveSink into);
}

Funnels是内置的,对装箱的对象,如Long、Integer进行的装换。

HashCode

hash计算后的输出结果。

Hash的使用

Funnel<Person> personFunnel = new Funnel<Person>() {

            @Override
            public void funnel(Person person, PrimitiveSink into) {
                into.putInt(person.id)
                    .putString(person.firstName, Charsets.UTF_8)
                    .putString(person.lastName, Charsets.UTF_8)
                    .putInt(person.birthYear);

            }

        };

        HashFunction hf = Hashing.md5();
        Person person = new Person(1, "first", "last", 2018);
        HashCode hc = hf.newHasher()
                .putLong(1000)
                .putString("test", Charsets.UTF_8)
                .putObject(person, personFunnel)
                .hash();

一般在使用Hash模块,都是从Hashing类开始调用,guava为我们提供了基本的一些散列的函数实现:

  • MD5
  • SHA_XXX
  • CRC32
  • ALDER32
  • HMACXXX
  • sipHash

另外,guava还实现了一致性hash的功能,具体不分析了。

results matching ""

    No results matching ""