有a, b两个文件,分别存放10亿条左右的URL,每个URL大概是64B, 请找出a, b两个文件共同的URL,机器的内存限制是1G
哈希分治法
该方法的核心思想是将大文件分割成若干小文件,确保每个小文件可以在内存中处理。
步骤一:文件分区 (Partitioning)
-
确定分区数量 (N):
- 每个文件约64GB。我们希望每个分区文件的大小能够轻松装入内存,并留有余量给哈希表的开销。
- 如果每个分区文件大小为约250MB,那么一个Python
set结构存储这些URL,加上其内部开销,应该能控制在1GB内存限制内。 - 所需的最小分区数量 N = 64GB / 250MB = 256。
- 选择一个2的幂次作为N(例如256或512)通常有助于哈希均匀分布。我们选择 N = 256。这意味着每个分区将包含大约 10亿 / 256 ≈ 390万条URL。
-
创建分区文件:
- 为文件a创建256个子文件:
a_part_0,a_part_1, ...,a_part_255。 - 为文件b创建256个子文件:
b_part_0,b_part_1, ...,b_part_255。 - 将这些文件分别存放在两个不同的临时目录下(例如
temp_a/和temp_b/)。
- 为文件a创建256个子文件:
-
处理文件a:
- 打开文件a,逐行读取URL。
- 对于每一条URL,计算其哈希值:
hash_value = hash(URL)。为了更好的哈希分布和跨系统一致性,可以使用如fnv1a或murmur3等通用哈希算法,或者简单地使用abs(hash(URL))并取模。 - 根据哈希值确定分区索引:
partition_index = hash_value % N。 - 将该URL写入对应的分区文件
a_part_partition_index。 - 为了提高效率,可以使用缓冲写入,即先收集一定数量的URL再批量写入文件。
-
处理文件b:
- 以与处理文件a相同的方式处理文件b。务必使用相同的哈希函数和分区数量N,将URL写入对应的分区文件
b_part_partition_index。 - 这样,如果一个URL同时存在于文件a和文件b中,它最终会落入相同的分区索引
i,即同时出现在a_part_i和b_part_i中。
- 以与处理文件a相同的方式处理文件b。务必使用相同的哈希函数和分区数量N,将URL写入对应的分区文件
步骤二:查找共同URL (Finding Intersections)
- 逐个处理分区对:
-
遍历
i从 0 到N-1:-
加载
a_part_i到内存:- 创建一个空的哈希集合(例如Python的
set)。 - 打开
a_part_i文件,逐行读取所有URL,并将它们添加到哈希集合中。 - 由于每个分区文件约250MB,将其URL加载到哈希集合中(即使考虑哈希表开销,通常会占用原始数据2-4倍的内存)应该能控制在1GB内存限制内。
- 创建一个空的哈希集合(例如Python的
-
处理
b_part_i:- 打开
b_part_i文件。 - 创建一个结果文件(例如
common_urls.txt),或以追加模式打开一个总的结果文件。 - 逐行读取
b_part_i中的URL。 - 对于
b_part_i中的每条URL,检查它是否在刚才加载到内存的哈希集合中(即URL in hash_set_from_a_part_i)。 - 如果存在,则说明这是一个共同的URL,将其写入
common_urls.txt。
- 打开
-
清理:
- 清空当前的哈希集合,释放内存,为处理下一个分区对做准备。
- 关闭所有打开的文件句柄。
-
-
注意事项
- 内存管理: 关键在于确保在步骤二中,任何时刻加载到内存中的单个哈希集合不会超过1GB。通过合理选择N值,可以控制每个分区的大小。如果N=256的分区在内存中依然紧张,可以增加N到512,每个分区文件大小会减半至125MB,内存压力会大大降低。
- 哈希函数: 选择一个能均匀分布URL的哈希函数至关重要,以避免某些分区文件过大,导致内存溢出。
- I/O 性能: 大量的文件读写操作可能会成为瓶颈。使用文件缓冲(buffered I/O)可以提高效率。
- 临时文件: 这个方法会产生
2 * N个临时文件。在处理完成后,这些临时文件可以删除以释放磁盘空间。 - 健壮性: 在实际实现中,需要考虑文件打开失败、读写错误等异常情况,并进行适当的错误处理。