2021.05.12
学习于极客时间[https://time.geekbang.org/column/article/76827]
0. 跳表
跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
跳表结构代码
private static class SkipListNode<E> {
/**
* key 信息
*/
int key;
E value;
/**
* 向前的指针
* 跳表是多层的,这个向前的指针,最多和层数一样。
*/
SkipListNode<E>[] forwards;
@SuppressWarnings("all")
public SkipListNode(int key, E value, int maxLevel) {
this.key = key;
this.value = value;
this.forwards = new SkipListNode[maxLevel];
}
@Override
public String toString() {
return "SkipListNode{" +
"key=" + key +
", value=" + value +
", forwards=" + Arrays.toString(forwards) +
'}';
}
}
1. 位图
位图是通过将数组下标与应用中的一些值关联映射,数组中该下标所指定的位置上的元素可以用来标识值的情况。
位图代码
public class BitMap { // Java中char类型占2个字节,即16bit
private char[] bytes;
private int nbits;
public BitMap(int nbits) {
this.nbits = nbits;
this.bytes = new char[nbits/16+1];
}
public void set(int k) {
if (k > nbits) return;
int byteIndex = k / 16;
int bitIndex = k % 16;
bytes[byteIndex] |= (1 << bitIndex);
}
public boolean get(int k) {
if (k > nbits) return false;
int byteIndex = k / 16;
int bitIndex = k % 16;
return (bytes[byteIndex] & (1 << bitIndex)) != 0;
}
}
2. 布隆过滤器
布隆过滤器的原理是,当一个元素被加入集合时,通过K个哈希函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就大约知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。
布隆过滤器为了降低哈希冲突的概率,使用 K 个哈希函数,对同一个数字进行求哈希值,那会得到 K 个不同的哈希值,我们分别记作 X1,X2,X3,…,XK。我们把这 K 个数字作为位图中的下标,将对应的 BitMap[X1],BitMap[X2],BitMap[X3],…,BitMap[XK]都设置成 true,也就是说,我们用 K 个二进制位,来表示一个数字的存在。当我们要查询某个数字是否存在的时候,我们用同样的 K 个哈希函数,对这个数字求哈希值,分别得到 Y1,Y2,Y3,…,YK。我们看这 K 个哈希值,对应位图中的数值是否都为 true,如果都是 true,则说明,这个数字存在,如果有其中任意一个不为 true,那就说明这个数字不存在。布隆过滤器非常适合不需要 100% 准确的、允许存在小概率误判的大规模判重场景。
网页爬虫是搜索引擎中的非常重要的系统,同一个网页链接有可能被包含在多个页面中,这就会导致爬虫在爬取的过程中重复爬取相同的网页,那么该如何避免这些重复的爬取呢?
我们用布隆过滤器 (CPU 密集型)来记录已经爬取过的网页链接,假设需要判重的网页有 10 亿,那我们可以用一个 10 倍大小的位图(用以减少误判)来存储,也就是 100 亿个二进制位,换算成字节,那就是大约 1.2GB。如果用散列表判重,需要至少 100GB 的空间。相比来讲,布隆过滤器在存储空间的消耗上,降低了非常多。