**问题:**给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?
目录:
文章目录
-
- Step 1 产生50亿 URL
- Step 2 将50亿URL大文件哈希为10000个小文件
- Step 2.1 字符串哈希函数BKDRHash
- Step 2.2 获取文件大小
- Step 2.3 获取某一目录下指定后缀的所有文件
- Step 3 使用set将小文件进行求交操作,最终得到相同URL
方案1:每个文件50亿个URL,每个URL最长64个字节,可以估计每个文件安的大小为5000,000,000 ×64bit=320,000,000,000bit ≈ 300,000G,远远大于内存限制的4G,同时需要大硬盘(这里不考虑分布式计算)。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。
-
遍历文件a,对每个url求取 h a s h ( u r l ) % 100 , 000 hash(url)\%100,000 hash(url)%100,000,然后根据所取得的值将url分别存储到100,000个小文件(记为 a 0 , a 1 , a 2 , . . . , a 99998 , a 99999 a_0,a_1,a_2, ..., a_{99998},a_{99999} a0,a1,a2,...,a99998,a99999)中。这样每个小文件的大约为3G。
-
遍历文件b,采取和a相同的方式将url分别存储到10000小文件中(记为 b 0 , b 1 , b 2 , . . . , b 99998 , b 99999 b_0,b_1,b_2, ..., b_{99998},b_{99999} b0,b1,b2,...,b99998,b99999)。这样处理后,所有可能相同的url都在对应的小文件( a 0 v . s . b 0 , a 1 v . s . b 1 , . . . , a 99999 v . s . b 99999 a_0 v.s. b_0, a_1 v.s. b_1, ..., a_{99999} v.s. b_{99999} a0v.s.b0,a1v.s.b1,...,a99999v.s.b99999)中,不对应的小文件不可能有相同的url。然后我们只要求出10000对小文件中相同的url即可。
-
求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。
方案2:如果允许有一定的错误率,可以使用Bloom filter,4G内存大概可以表示340亿bit。将其中一个文件中的url使用Bloom filter映射为这340亿bit,然后挨个读取另外一个文件的url,检查是否与Bloom filter,如果是,那么该url应该是共同的url(注意会有一定的错误率)。
读者反馈@crowgns:
- hash后要判断每个文件大小,如果hash分的不均衡有文件较大,还应继续hash分文件,换个hash算法第二次再分较大的文件,一直分到没有较大的文件为止。这样文件标号可以用A1-2表示(第一次hash编号为1,文件较大所以参加第二次hash,编号为2)
- 由于1存在,第一次hash如果有大文件,不能用直接set的方法。建议对每个文件都先用字符串自然顺序排序,然后具有相同hash编号的(如都是1-3,而不能a编号是1,b编号是1-1和1-2),可以直接从头到尾比较一遍。对于层级不一致的,如a1,b有1-1,1-2-1,1-2-2,层级浅的要和层级深的每个文件都比较一次,才能确认每个相同的uri。
以上是网上流传最广的思路,基于以上想法。我主要有两点想法,
-
其一,优秀的哈希算法是经过密码学家证明推敲的,不会随着取模操作而造成大范围冲突。本文将使用到《字符串哈希算法——BKDRHash》算法。
-
其二,对于以上读者反馈中需要二次哈希的地方(这种概率极小)。可以将整个过程视为一场递归——即,将一次哈希中大小超过阈值的文件暂不处理(假设一次哈希后,所得文件 a 9527 > 10 G a_{9527} > 10G a9527>10G,那么这里先不处理它,而直接查询对应的 b 9527 b_{9527} b9527,如果对应的b不存在,那么可以丢弃 a 9527 a_{9527} a9527。否则,在后续处理中,对于 a 9527 a_{9527} a9527使用另外一种哈希算法重新哈希,同时对 b 9527 b_{9527} b9527也必须使用同一种哈希算法重新哈希,整个过程转化为了递归)。
以下是对上述算法的实现,