如何高效合并两个有序文本文件并自动去重(基于时间顺序的增量追加)

本文介绍一种针对大型有序日志文件的智能追加方法:在保持严格时间顺序的前提下,自动识别并跳过两文件间的重叠行,避免全量去重开销,兼顾效率与正确性。

在处理时序敏感的日志或记录类数据(如交易流水、传感器采样、用户行为日志)时,常需将多个按时间排序的分段文件合并为一个连续数据集。典型场景是:Jan-Mar.txt 与 Mar-Jun.txt 合并为 Jan-Jun.txt——二者在 Mar-31 处可能完全重合,也可能存在空隙或错位。此时,简单拼接会引入重复;全局去重(如用 set 或 pandas.drop_duplicates())则破坏原有顺序、消耗内存且无法利用“数据已排序”这一关键前提。

最优策略是:利用有序性,仅检查首尾交界区域,实现 O(m+n) 时间复杂度的流式合并。

以下是一个高效、内存友好的 Python 实现:

def merge_sorted_files(file1_path, file2_path, output_path=None, key_func=None):
    """
    合并两个按指定键(默认为整行)严格升序排列的文本文件,
    自动跳过 file1 结尾与 file2 开头的重复行(基于 key_func 比较)。

    :param file1_path: 主文件路径(结果将写入此文件,或 output_path)
    :param file2_path: 待追加文件路径
    :param output_path: 可选,输出文件路径;若为 None,则覆盖 file1_path
    :param key_func: 可选,用于提取比较键的函数,例如 lambda x: x.split(',')[0].strip()
    """
    if key_func is None:
        key_func = lambda line: line  # 默认以整行作为唯一键

    # 读取 file1 全部内容(假设可载入内存;对超大文件建议逐行流式处理+临时缓冲)
    with open(file1_path, 'r', encoding='utf-8') as f1:
        lines1 = [line.rstrip('\n\r') for line in f1]

    # 读取 file2 全部内容
    with open(file2_path, 'r', encoding='utf-8') as f2:
        lines2 = [line.rstrip('\n\r') for line in f2]

    if not lines1:
        merged = lines2
    elif not lines2:
        merged = lines1
    else:
        # 提取 file1 最后一行的键 和 file2 第一行的键
        last_key1 = key_func(lines1[-1])
        first_key2 = key_func(lines2[0])

        if last_key1 == first_key2:
            # 完全重叠:跳过 file2 的首行(假设后续重叠连续,但实际只需跳首行即可保证无重复)
            # 更健壮做法:从 file2 开头找到第一个 key > last_key1 的位置
            i = 0
            while i < len(lines2) and key_func(lines2[i]) == last_key1:
                i += 1
            merged = lines1 + lines2[i:]
        elif last_key1 < first_key2:
            # 无重叠:直接拼接
            merged = lines1 + lines2
        else:
            # 逻辑错误:file1 末尾时间晚于 file2 开头 → 数据未按升序排列
            raise ValueError(f"Files are not chronologically ordered: "
                           f"last line of {file1_path} ({last_key1}) >= "
                           f"first line of {file2_path} ({first_key2})")

    # 写入结果
    target = output_path or file1_path
    with open(target, 'w', encoding='utf-8') as out:
        out.write('\n'.join(merged))
        if merged:
            out.write('\n')  # 确保末尾有换行符

# 示例:按日期时间字段去重(推荐用于真实场景)
def parse_datetime(line):
    """安全提取 ISO 格式时间戳,兼容示例数据"""
    try:
        return line.split(',', 1)[0].strip()
    except IndexError:
        return line

# 使用示例
merge_sorted_files(
    file1_path='data_jan_mar.txt',
    file2_path='data_mar_jun.txt',
    output_path='data_jan_jun.txt',
    key_func=parse_datetime
)

核心优势:

  • 精准去重:仅比对交界处,不扫描全量数据;
  • 保序高效:时间复杂度 O(n+m),空间复杂度 O(n+m)(可优化为 O(1) 缓冲区流式处理);
  • 灵活键控:通过 key_func 支持按任意字段(如时间、ID)判断重复;
  • 异常防护:自动校验输入文件是否真正有序,避免静默错误。

⚠️ 注意事项:

  • 若文件极大(GB 级),应改用生成器逐行读取 + 双指针归并,避免内存溢出;
  • 本方案假设重叠行内容完全一致(如示例中 Denise 行)。若存在语义重复但字符串不同(如毫秒级时间差异),需自定义更鲁棒的 key_func(如截断到秒级);
  • 原答案中使用 OrderedDict.fromkeys() 虽能去重,但会破坏原始顺序(因字典插入顺序在旧 Python 中不保证,且无法控制去重范围),不适用于本题的有序合并需求,故此处未采用。

总结:面对有序分块数据的合并,应抛弃“先拼后筛”的暴力思路,转而利用数据内在结构设计算法——这是工程效率与代码健壮性的双重胜利。