Skip to content

有a, b两个文件,分别存放10亿条左右的URL,每个URL大概是64B, 请找出a, b两个文件共同的URL,机器的内存限制是1G

哈希分治法

该方法的核心思想是将大文件分割成若干小文件,确保每个小文件可以在内存中处理。

步骤一:文件分区 (Partitioning)

  1. 确定分区数量 (N):

    • 每个文件约64GB。我们希望每个分区文件的大小能够轻松装入内存,并留有余量给哈希表的开销。
    • 如果每个分区文件大小为约250MB,那么一个Python set 结构存储这些URL,加上其内部开销,应该能控制在1GB内存限制内。
    • 所需的最小分区数量 N = 64GB / 250MB = 256。
    • 选择一个2的幂次作为N(例如256或512)通常有助于哈希均匀分布。我们选择 N = 256。这意味着每个分区将包含大约 10亿 / 256 ≈ 390万条URL。
  2. 创建分区文件:

    • 为文件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/)。
  3. 处理文件a:

    • 打开文件a,逐行读取URL。
    • 对于每一条URL,计算其哈希值:hash_value = hash(URL)。为了更好的哈希分布和跨系统一致性,可以使用如 fnv1amurmur3 等通用哈希算法,或者简单地使用 abs(hash(URL)) 并取模。
    • 根据哈希值确定分区索引:partition_index = hash_value % N
    • 将该URL写入对应的分区文件 a_part_partition_index
    • 为了提高效率,可以使用缓冲写入,即先收集一定数量的URL再批量写入文件。
  4. 处理文件b:

    • 以与处理文件a相同的方式处理文件b。务必使用相同的哈希函数和分区数量N,将URL写入对应的分区文件 b_part_partition_index
    • 这样,如果一个URL同时存在于文件a和文件b中,它最终会落入相同的分区索引 i,即同时出现在 a_part_ib_part_i 中。

步骤二:查找共同URL (Finding Intersections)

  1. 逐个处理分区对:
    • 遍历 i 从 0 到 N-1

      • 加载 a_part_i 到内存:

        • 创建一个空的哈希集合(例如Python的 set)。
        • 打开 a_part_i 文件,逐行读取所有URL,并将它们添加到哈希集合中。
        • 由于每个分区文件约250MB,将其URL加载到哈希集合中(即使考虑哈希表开销,通常会占用原始数据2-4倍的内存)应该能控制在1GB内存限制内。
      • 处理 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 个临时文件。在处理完成后,这些临时文件可以删除以释放磁盘空间。
  • 健壮性: 在实际实现中,需要考虑文件打开失败、读写错误等异常情况,并进行适当的错误处理。