位图、布隆过滤器、一致性哈希
位图
问:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
答:40亿个数大概160GB,对于一般的计算机,直接放入内存不太可能。我们只需要判断一个数在不在,可以用一个bit来表示,那么无符号类型有232个整数,一个整数只需要占1bit,那么使用229B,即512MB即可记录无符号范围内的每个数是否出现。
那么给一个无符号整数,我们只需要看对应位置的那一个bit是否为1即可。
- 定义:
- 位图是一种紧凑的数据结构,用于表示一组布尔值(0或1)。
- 它是一个连续的位(bit)数组,每个位代表一个元素的状态。
- 存储效率:
- 位图非常节省空间,因为每个位只占用一个二进制位(bit),而不是一个完整的字节(byte)。
- 例如,一个包含100万个布尔值的位图只需要约125KB(1000000 / 8)的内存。
- 操作:
- 设置位(Set Bit):可以通过位运算来设置特定位置的位为1。例如,将第i位设置为1可以使用位操作
bitmap[i / 8] |= (1 << (i % 8))
。 - 清除位(Clear Bit):可以通过位运算来清除特定位置的位为0。例如,将第i位清除为0可以使用位操作
bitmap[i / 8] &= ~(1 << (i % 8))
。 - 检查位(Check Bit):可以通过位运算来检查特定位置的位是否为1。例如,检查第i位是否为1可以使用位操作
(bitmap[i / 8] & (1 << (i % 8))) != 0
。
- 设置位(Set Bit):可以通过位运算来设置特定位置的位为1。例如,将第i位设置为1可以使用位操作
- 应用:
- 集合操作:位图可以高效地表示和操作大集合,特别适用于只包含整数的集合。
- 布隆过滤器(Bloom Filter):一种基于位图的概率数据结构,用于检索一个元素是否属于一个集合,具有很高的空间效率。
- 快速查找:位图可以用于实现快速查找操作,如查找第一个为1的位或第一个为0的位。
- 去重和计数:在大规模数据处理中,位图可以用于去重和计数,例如统计唯一用户数或检测重复元素。
- 性能:
- 位图操作通常非常高效,因为它们利用了低级位运算,位运算在现代处理器上执行速度非常快。
- 位图的空间效率和操作效率使其成为许多高性能算法和系统中的关键组件。
综上所述,位图是一种强大且高效的数据结构,广泛应用于各种需要高效存储和操作布尔值或整数集合的场景中。
布隆过滤器
位图主要适合处理整形数据,那么对于海量的字符串数据或其他类型呢?这时候就有请布隆过滤器登场!
布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用于判断一个元素是否在一个集合中。它由布隆(Burton Howard Bloom)在1970年发明。**布隆过滤器有误判的风险(因为哈希碰撞的存在),即可能会误认为不存在的元素存在,但不会误认为存在的元素不存在(即它认为存在的不一定不存,它认为不存在的一定不存在)。**它主要用于需要快速判断元素是否在集合中的场景,如垃圾邮件过滤(检查词语或关键字是否存在垃圾词库中)、网页缓存等(判断请求的URL是否已缓存)。所以说布隆过滤器虽然比位图的应用类型广泛了,但是有可能会出现误判,而位图则不会。
工作原理
- 初始化:布隆过滤器由一个位数组(bit array)和一组相互独立的哈希函数(hash functions)组成。位数组初始时所有位都设为0。
- 插入元素:
- 对于要插入的元素,通过多个哈希函数分别计算哈希值。
- 每个哈希值对应位数组中的一个位置,将这些位置上的值设为1。
- 查询元素:
- 对于要查询的元素,同样通过这些哈希函数计算哈希值。
- 检查这些哈希值对应的位数组中的位置,如果所有这些位置上的值都是1,则认为该元素可能在集合中。
- 如果任意一个位置上的值为0,则可以确定该元素不在集合中。
优点
- 空间效率高:布隆过滤器只需要少量的空间来存储大量元素的信息,特别适合存储大量数据时。
- 查询速度快:插入和查询操作的时间复杂度都是O(k),其中k是哈希函数的数量,通常是一个常数。
缺点
- 误判率:布隆过滤器可能会误认为一个不存在的元素在集合中,误判率随着插入元素的数量增加而增加。
- 不可删除:一旦元素被插入到布隆过滤器中,就不能简单地删除。虽然可以通过计数布隆过滤器(Counting Bloom Filter)解决这一问题,但会增加空间复杂度。
应用场景
- 网络缓存:布隆过滤器可以快速判断某个URL是否已经被缓存,以提高缓存的命中率。
- 垃圾邮件过滤:用于快速判断邮件是否为垃圾邮件,避免频繁查询黑名单。
- 数据库:在数据库查询中用于快速判断数据是否存在,以减少不必要的查询操作。
总的来说,布隆过滤器是一种高效且实用的数据结构,尤其在需要快速判断元素存在性的场景中,能显著提升性能。
一致性哈希
一致性哈希(Consistent Hashing)是一种用于分布式系统中的哈希算法,主要解决数据分布和负载均衡问题。它的设计初衷是应对动态变化的节点(如服务器)的场景,使得系统在添加或删除节点时,只需要重新映射少部分数据,从而保证系统的平稳运行。
问题1:节点的动态变化
在分布式系统中,服务器节点可能会随时增加或减少。如果每次节点变化都导致大量数据重新分配,会带来很高的成本和性能开销。
传统哈希方法的问题:
- 假设有N个节点,数据通过
hash(key) % N
分配到不同节点上。 - 当节点数量变化时,几乎所有数据的映射位置都会发生变化。
问题2:数据分布不均衡
简单的哈希方法可能导致数据在节点之间分布不均衡,某些节点负载过重,而其他节点几乎没有负载。
一致性哈希的解决方案
一致性哈希算法通过将节点和数据都映射到一个“哈希环”上来解决上述问题。
- 哈希环:
- 将哈希值空间视为一个环形结构。例如,哈希值范围是0到2^32-1,则将其首尾相接形成一个环。
- 节点映射:
- 每个节点通过哈希函数映射到环上的一个点(节点哈希值)。
- 数据映射:
- 每个数据项(或数据块)也通过哈希函数映射到环上的一个点(数据哈希值)。
- 数据项由顺时针方向上最近的节点负责存储(即数据项的哈希值落在节点的哈希值之间)。
举例说明
假设有三个节点A、B、C,它们通过哈希函数映射到哈希环上的位置如下:
- 节点A:哈希值10
- 节点B:哈希值50
- 节点C:哈希值90
数据项通过哈希函数映射到以下位置:
- 数据X:哈希值15(由节点B负责)
- 数据Y:哈希值70(由节点C负责)
- 数据Z:哈希值110(由节点A负责)
添加节点
当添加一个新节点D,哈希值为60时,只有一部分数据需要重新分配:
- 节点C上的数据Y(70)转移到新节点D。
删除节点
当节点B下线时,它负责的数据需要重新分配:
- 节点B上的数据X(15)重新分配给节点C。
虚拟节点
为解决数据分布不均衡的问题,一致性哈希引入了虚拟节点的概念:
- 每个物理节点对应多个虚拟节点,虚拟节点通过不同的哈希值映射到环上。
- 数据映射到虚拟节点,再由虚拟节点映射到物理节点。
- 这样可以更均匀地分配数据,减少负载不均衡的情况。
一致性哈希的优势
- 数据重分布小:节点的增加或减少只影响少部分数据,减少了数据迁移的开销。
- 负载均衡:通过虚拟节点机制,可以较好地实现负载均衡。
- 高可扩展性:适用于大规模分布式系统,可以平滑地扩展和收缩节点。
应用场景
一致性哈希广泛应用于分布式缓存系统、分布式存储系统和分布式数据库中,如:
- 分布式缓存系统(如Memcached)
- 分布式存储系统(如Amazon Dynamo)
- 分布式数据库(如Cassandra)
通过一致性哈希,分布式系统能够更高效地管理动态变化的节点,保证数据分布的均衡性和系统的高可用性。
题目
哈希切割
问:给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?又如何找到top K的IP?
答:讲100G的大文件分成1000个小文件,如何分呢?将每个ip哈希映射到1000个小文件中,那么ip相同的一定在一个文件中。即可依次读入1000个小文件,在内存中进行排序再计算,维护出现次数最多的IP即可。
位图
1.问:给定100亿个整数,设计算法找到只出现一次的整数?
答:与最开始讲位图类似,不同的是这里我们使用两个bit来表示一个整数的状态,因为至少需要表示出现0次,出现1次,出现1次以上三种状态。
2.问:给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
答:将每个文件的所有整数使用一个位图来表示,一个位图512MB两个位图刚好1G,再将两个位图与一下,最后看哪些位为1即是两个文件的交集。
3.问:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数?
答:与第一问一样,还是使用两个bit即可表示4中状态,出现0次,出现1次,出现2次,出现超过2次。
布隆过滤器
问:给两个文件,分别有100亿个sql 查询语句,每个语句平均30B到60B大小,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
答:近似算法:将其中一个文件读入布隆过滤器中,再读取另一个文件,检查语句是否在布隆过滤器中,存在即为交集可能的成员。
精确算法:两个100亿语句的文件使用同一个哈希函数各自映射到10000个文件(A0-A9999,B0-B9999)中,随后A0和B0比,找交集,存set里,再A1和B1比,依次下去。