最近搞NLP爬了点数据,有地方需要判断一些字符串是否在一个大集合里面出现过,联想到了此前的经历过的一个面试题。
问:在做网络爬虫的时候,经常会有URL重复出现,怎么规避这种情况?
答:Java里面可以用HashSet保存已经访问过的URL。
问:如果这个URL的量很多呢?比如十亿条?
答:如果能够忍受一定错误率的话,可以使用布隆过滤器,balabala…
上面已经介绍了布隆过滤器的一个常用场景,那么布隆过滤器到底是一个什么东西呢?
这个Java版本布隆过滤器参考了这个Python上基于Redis的版本,因为是简单版本,所以使用Java中的BitSet
作为基本数据机构,然后使用了mmh3哈希(听说很快)。
import java.io.Serializable;
import java.util.*;
/**
* @author kidd
*/
public class BloomFilter implements Serializable {
private static final long serialVersionUID = -881375780720891535L;
private static final int[] SEEDS = {543, 460, 171, 876, 796, 607, 650, 81, 837, 545, 591, 946, 846, 521, 913, 636, 878, 735, 414, 372,
344, 324, 223, 180, 327, 891, 798, 933, 493, 293, 836, 10, 6, 544, 924, 849, 438, 41, 862, 648, 338,
465, 562, 693, 979, 52, 763, 103, 387, 374, 349, 94, 384, 680, 574, 480, 307, 580, 71, 535, 300, 53,
481, 519, 644, 219, 686, 236, 424, 326, 244, 212, 909, 202, 951, 56, 812, 901, 926, 250, 507, 739, 371,
63, 584, 154, 7, 284, 617, 332, 472, 140, 605, 262, 355, 526, 647, 923, 199, 518};
/**
* 总bit位数
*/
private final int bitCount;
/**
* 比特数组
*/
private final BitSet bitSet;
/**
* 对于每个字符串生成的hash个数
*/
private final int hashCount;
/**
* 当前采用的seed,对应每个hash函数
*/
private final int[] seeds;
/**
* 实际错误率
*/
private final double realErrorRate;
public BloomFilter() {
this(100000000, 0.00000001);
}
public BloomFilter(int capacity, double errorRate) {
this.bitCount = (int) Math.ceil(capacity * Math.log(Math.E) * Math.log(1 / errorRate));
this.hashCount = (int) Math.ceil(Math.log1p(2) * this.bitCount / capacity);
this.realErrorRate = Math.pow((1 - Math.pow(Math.E, - (double) capacity * hashCount / bitCount)), hashCount);
this.seeds = Arrays.copyOf(SEEDS, hashCount);
this.bitSet = new BitSet(bitCount);
}
public double getRealErrorRate () {
return this.realErrorRate;
}
/**
* 如果不存在就进行记录并返回false,如果存在了就返回true
**/
public boolean addIfNotExist(String value) {
boolean exits = true;
for (int hash : getHashList(value)) {
if (!bitSet.get(hash % (bitCount - 1))) {
bitSet.set(hash % (bitCount - 1));
exits = false;
}
}
return exits;
}
public void add(String value) {
for (int hash : getHashList(value)) {
bitSet.set(hash % (bitCount - 1));
}
}
public boolean isExisted(String value) {
for (int hash : getHashList(value)) {
if (!bitSet.get(hash % (bitCount - 1))) {
return false;
}
}
return true;
}
public List<Integer> getHashList(String value) {
List<Integer> hashList = new ArrayList<>(hashCount);
for (int seed : seeds) {
int hash = murmurhash3(value, seed);
if (hash < 0) {
hash = Integer.MAX_VALUE + hash;
}
hashList.add(hash);
}
return hashList;
}
public int murmurhash3(CharSequence data, int seed) {
final int c1 = 0xcc9e2d51;
final int c2 = 0x1b873593;
int h1 = seed;
int pos = 0;
int end = data.length();
int k1 = 0;
int k2 = 0;
int shift = 0;
int bits = 0;
// length in UTF8 bytes
int nBytes = 0;
while (pos < end) {
int code = data.charAt(pos++);
if (code < 0x80) {
k2 = code;
bits = 8;
}
else if (code < 0x800) {
k2 = (0xC0 | (code >> 6))
| ((0x80 | (code & 0x3F)) << 8);
bits = 16;
}
else if (code < 0xD800 || code > 0xDFFF || pos>=end) {
// we check for pos>=end to encode an unpaired surrogate as 3 bytes.
k2 = (0xE0 | (code >> 12))
| ((0x80 | ((code >> 6) & 0x3F)) << 8)
| ((0x80 | (code & 0x3F)) << 16);
bits = 24;
} else {
// surrogate pair
// int utf32 = pos < end ? (int) data.charAt(pos++) : 0;
int utf32 = (int) data.charAt(pos++);
utf32 = ((code - 0xD7C0) << 10) + (utf32 & 0x3FF);
k2 = (0xff & (0xF0 | (utf32 >> 18)))
| ((0x80 | ((utf32 >> 12) & 0x3F))) << 8
| ((0x80 | ((utf32 >> 6) & 0x3F))) << 16
| (0x80 | (utf32 & 0x3F)) << 24;
bits = 32;
}
k1 |= k2 << shift;
// int used_bits = 32 - shift; // how many bits of k2 were used in k1.
// int unused_bits = bits - used_bits; // (bits-(32-shift)) == bits+shift-32 == bits-newshift
shift += bits;
if (shift >= 32) {
// mix after we have a complete word
k1 *= c1;
k1 = (k1 << 15) | (k1 >>> 17); // ROTL32(k1,15);
k1 *= c2;
h1 ^= k1;
h1 = (h1 << 13) | (h1 >>> 19); // ROTL32(h1,13);
h1 = h1*5+0xe6546b64;
shift -= 32;
// unfortunately, java won't let you shift 32 bits off, so we need to check for 0
if (shift != 0) {
k1 = k2 >>> (bits-shift); // bits used == bits - newshift
} else {
k1 = 0;
}
nBytes += 4;
}
} // inner
// handle tail
if (shift > 0) {
nBytes += shift >> 3;
k1 *= c1;
k1 = (k1 << 15) | (k1 >>> 17); // ROTL32(k1,15);
k1 *= c2;
h1 ^= k1;
}
// finalization
h1 ^= nBytes;
// fmix(h1);
h1 ^= h1 >>> 16;
h1 *= 0x85ebca6b;
h1 ^= h1 >>> 13;
h1 *= 0xc2b2ae35;
h1 ^= h1 >>> 16;
return h1;
}
public static void main(String[] args) {
BloomFilter fileter = new BloomFilter();
System.out.println(fileter.addIfNotExist("1111111111111"));
System.out.println(fileter.addIfNotExist("2222222222222222"));
System.out.println(fileter.addIfNotExist("3333333333333333"));
System.out.println(fileter.addIfNotExist("444444444444444"));
System.out.println(fileter.addIfNotExist("5555555555555"));
System.out.println(fileter.addIfNotExist("6666666666666"));
System.out.println(fileter.addIfNotExist("1111111111111"));
}
}