详情

首页手游攻略 基于C#实现方式的文件分割与合并工具实用指南

基于C#实现方式的文件分割与合并工具实用指南

佚名 2026-09-08 16:00:01

平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“基于C#实现方法的文件分割与合并工具”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。

目录
  • 一、系统概述
  • 二、核心设计思路
    • 2.1 分割策略
    • 2.2 合并策略
  • 三、实现步骤与代码
    • 3.1 开发环境
    • 3.2 文件分割实现
      • 3.2.1 核心函数:SplitFile
    • 3.3 文件合并实现(完整版,含校验)
      • 3.3.1 核心函数:MergeFiles
    • 3.4 主程序(增强版命令行交互)
    • 四、关键技术点
      • 4.1 二进制流高效处理
        • 4.2 异常处理与校验
          • 4.3 跨平台兼容性
          • 五、性能优化建议
            • 六、扩展功能建议(可后续迭代)
              • 七、最佳实践与注意事项
                • 八、总结

                  一、系统概述

                  文件分割与合同时是数据管理中的常用操作,广泛应用来以下场景:

                  • 大文件传输:突破邮件附件大小限制(如将 50MB 文件分割为 5×10MB)
                  • 分布式存储:将文件拆分后存储至多个磁盘或云存储节点
                  • 分卷备份:配合压缩算法实现分卷压缩备份
                  • 断点续传:兼容文件的分块上传与下载

                  落到代码里,本工具基于 C# 语言开发,采用 .NET 6.0 运行时,借助 System.IO 命名空间实现二进制文件流操作。核心特性包括:

                  • 按固定大小分割(兼容自定义分块大小)
                  • 按序号自动合并
                  • 完整性校验(MD5 校验和)
                  • 跨平台兼容(Windows / Linux / macOS)
                  • 异常捕获与友好的错误提示
                  • 命令行 / GUI 双模式兼容(GUI 可作为扩展)

                  二、核心设计思路

                  2.1 分割策略

                  • 按固定大小分割:将源文件拆分为多个指定大小(如 chunkSize = 1024 * 1024字节,即 1MB)的子文件,最后一个子文件可能小于指定大小。
                  • 文件命名规则:子文件命名为 原文件名.partN(N 为序号,从 1 开始),如 largefile.zip.part1largefile.zip.part2
                  • 元数据记录(可选):生成 原文件名.info文件,记录源文件大小、分割数量、校验和(如 MD5),用来合同时时校验完整性。

                  SourceFile: archive.zip
                  FileSize: 104857600
                  PartCount: 10
                  ChunkSize: 1048576
                  MD5: a1b2c3d4e5f6...

                  2.2 合并策略

                  • 按序号合并:读取所有 原文件名.partN子文件,按序号从小到大拼接为完整文件。
                  • 完整性校验:借助比对子文件数量、总大小或 MD5 校验和,确保合同时后文件与源文件一致。

                  三、实现步骤与代码

                  3.1 开发环境

                  • 语言:C# 9.0+
                  • 框架:.NET 6.0(跨平台兼容)
                  • 工具:Visual Studio 2022 / Visual Studio Code
                  • 核心类库System.IO(文件流)、System.Security.Cryptography(MD5 校验)

                  3.2 文件分割实现

                  3.2.1 核心函数:SplitFile

                  using System;
                  using System.IO;
                  using System.Security.Cryptography;
                  using System.Text;
                  public class FileSplitter
                  {
                      /// <summary>
                      /// 分割文件为多个子文件
                      /// </summary>
                      /// <param name="sourcePath">源文件路径</param>
                      /// <param name="outputDir">输出目录(默认源文件所在目录)</param>
                      /// <param name="chunkSizeBytes">子文件大小(字节,默认 1MB)</param>
                      /// <param name="generateInfoFile">是否生成 .info 元数据文件</param>
                      /// <returns>分割后的子文件数量</returns>
                      public static int SplitFile(string sourcePath, string outputDir = null,
                          long chunkSizeBytes = 1024 * 1024, bool generateInfoFile = true)
                      {
                          // 参数校验
                          if (!File.Exists(sourcePath))
                              throw new FileNotFoundException($"源文件不存在: {sourcePath}");
                          if (chunkSizeBytes <= 0)
                              throw new ArgumentException("分块大小必须大于 0", nameof(chunkSizeBytes));
                          // 确定输出目录
                          outputDir ??= Path.GetDirectoryName(sourcePath);
                          Directory.CreateDirectory(outputDir);
                          string fileName = Path.GetFileName(sourcePath);
                          string baseName = Path.GetFileNameWithoutExtension(sourcePath);
                          string extension = Path.GetExtension(sourcePath);
                          string fullBaseName = baseName + extension;
                          // 计算 MD5(需要在读取流之前计算,或重新打开流)
                          string md5Hash = string.Empty;
                          if (generateInfoFile)
                              md5Hash = CalculateFileMD5(sourcePath);
                          using (FileStream sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
                          {
                              long fileSize = sourceStream.Length;
                              int partCount = (int)Math.Ceiling((double)fileSize / chunkSizeBytes);
                              byte[] buffer = new byte[chunkSizeBytes];
                              // 生成元数据文件
                              if (generateInfoFile)
                              {
                                  string infoPath = Path.Combine(outputDir, $"{fullBaseName}.info");
                                  using (StreamWriter infoWriter = new StreamWriter(infoPath, false, Encoding.UTF8))
                                  {
                                      infoWriter.WriteLine($"SourceFile: {fileName}");
                                      infoWriter.WriteLine($"FileSize: {fileSize}");
                                      infoWriter.WriteLine($"PartCount: {partCount}");
                                      infoWriter.WriteLine($"ChunkSize: {chunkSizeBytes}");
                                      infoWriter.WriteLine($"MD5: {md5Hash}");
                                      infoWriter.WriteLine($"CreatedAt: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
                                  }
                              }
                              // 执行分割
                              for (int i = 0; i < partCount; i++)
                              {
                                  long position = i * chunkSizeBytes;
                                  int bytesToRead = (int)Math.Min(chunkSizeBytes, fileSize - position);
                                  sourceStream.Seek(position, SeekOrigin.Begin);
                                  int bytesRead = sourceStream.Read(buffer, 0, bytesToRead);
                                  if (bytesRead != bytesToRead)
                                      Console.WriteLine($"警告: 第 {i + 1} 个分块实际读取 {bytesRead} 字节,预期 {bytesToRead} 字节");
                                  string partPath = Path.Combine(outputDir, $"{fullBaseName}.part{i + 1}");
                                  using (FileStream partStream = new FileStream(partPath, FileMode.Create, FileAccess.Write))
                                  {
                                      partStream.Write(buffer, 0, bytesRead);
                                  }
                                  Console.WriteLine($"[分割] 已创建: {Path.GetFileName(partPath)} ({bytesRead} 字节)");
                              }
                              Console.WriteLine($"分割完成: {partCount} 个分块,输出目录: {outputDir}");
                              return partCount;
                          }
                      }
                      /// <summary>
                      /// 计算文件的 MD5 校验值
                      /// </summary>
                      private static string CalculateFileMD5(string filePath)
                      {
                          using (var md5 = MD5.Create())
                          using (var stream = File.OpenRead(filePath))
                          {
                              byte[] hash = md5.ComputeHash(stream);
                              return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                          }
                      }
                  }

                  3.3 文件合并实现(完整版,含校验)

                  3.3.1 核心函数:MergeFiles

                  public class FileMerger
                  {
                      /// <summary>
                      /// 合并子文件为完整文件
                      /// </summary>
                      /// <param name="partDirectory">子文件所在目录</param>
                      /// <param name="originalFileName">原文件名(如 "archive.zip")</param>
                      /// <param name="outputPath">合并后的输出路径</param>
                      /// <param name="verifyIntegrity">是否校验 MD5(需要 .info 文件)</param>
                      /// <returns>合并后的文件大小(字节)</returns>
                      public static long MergeFiles(string partDirectory, string originalFileName,
                          string outputPath, bool verifyIntegrity = true)
                      {
                          if (!Directory.Exists(partDirectory))
                              throw new DirectoryNotFoundException($"目录不存在: {partDirectory}");
                          string extension = Path.GetExtension(originalFileName);
                          string baseName = Path.GetFileNameWithoutExtension(originalFileName);
                          string fullBaseName = baseName + extension;
                          string pattern = $"{fullBaseName}.part*";
                          // 获取并排序分块文件
                          string[] partFiles = Directory.GetFiles(partDirectory, pattern, SearchOption.TopDirectoryOnly);
                          if (partFiles.Length == 0)
                              throw new FileNotFoundException($"未找到匹配的分块文件: {pattern}");
                          // 按序号排序(解析 partN 中的 N)
                          Array.Sort(partFiles, (a, b) =>
                          {
                              int indexA = ExtractPartNumber(a, fullBaseName);
                              int indexB = ExtractPartNumber(b, fullBaseName);
                              return indexA.CompareTo(indexB);
                          });
                          // 验证序号连续性(可选)
                          for (int i = 0; i < partFiles.Length; i++)
                          {
                              int expected = i + 1;
                              int actual = ExtractPartNumber(partFiles[i], fullBaseName);
                              if (actual != expected)
                                  throw new InvalidOperationException($"分块序号不连续: 期望 {expected},实际 {actual}");
                          }
                          // 合并文件
                          using (FileStream outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
                          {
                              foreach (string partPath in partFiles)
                              {
                                  using (FileStream partStream = new FileStream(partPath, FileMode.Open, FileAccess.Read))
                                  {
                                      partStream.CopyTo(outputStream);
                                  }
                                  Console.WriteLine($"[合并] 已处理: {Path.GetFileName(partPath)}");
                              }
                          }
                          Console.WriteLine($"合并完成: {outputPath}");
                          long mergedSize = new FileInfo(outputPath).Length;
                          // 完整性校验
                          if (verifyIntegrity)
                          {
                              string infoPath = Path.Combine(partDirectory, $"{fullBaseName}.info");
                              if (File.Exists(infoPath))
                              {
                                  var metadata = ParseInfoFile(infoPath);
                                  string expectedMd5 = metadata["MD5"];
                                  string actualMd5 = CalculateFileMD5(outputPath);
                                  if (string.Equals(expectedMd5, actualMd5, StringComparison.OrdinalIgnoreCase))
                                  {
                                      Console.WriteLine($"[校验] MD5 一致: {actualMd5}");
                                  }
                                  else
                                  {
                                      Console.WriteLine($"[警告] MD5 不一致! 期望: {expectedMd5}, 实际: {actualMd5}");
                                  }
                              }
                              else
                              {
                                  Console.WriteLine("[校验] 未找到 .info 文件,跳过 MD5 校验");
                              }
                          }
                          return mergedSize;
                      }
                      /// <summary>
                      /// 从文件名中提取分块序号(如 file.zip.part3 → 3)
                      /// </summary>
                      private static int ExtractPartNumber(string filePath, string fullBaseName)
                      {
                          string fileName = Path.GetFileNameWithoutExtension(filePath);
                          // 文件名格式: fullBaseName.partN
                          string suffix = fileName.Substring(fullBaseName.Length + 1); // ".partN" → "partN"
                          if (suffix.StartsWith("part", StringComparison.OrdinalIgnoreCase))
                          {
                              string numberPart = suffix.Substring(4);
                              if (int.TryParse(numberPart, out int number))
                                  return number;
                          }
                          throw new InvalidOperationException($"无法解析分块序号: {filePath}");
                      }
                      private static Dictionary<string, string> ParseInfoFile(string infoPath)
                      {
                          var dict = new Dictionary<string, string>();
                          foreach (var line in File.ReadAllLines(infoPath))
                          {
                              int colonIndex = line.IndexOf(':');
                              if (colonIndex > 0)
                              {
                                  string key = line.Substring(0, colonIndex).Trim();
                                  string value = line.Substring(colonIndex + 1).Trim();
                                  dict[key] = value;
                              }
                          }
                          return dict;
                      }
                      private static string CalculateFileMD5(string filePath)
                      {
                          using (var md5 = MD5.Create())
                          using (var stream = File.OpenRead(filePath))
                          {
                              byte[] hash = md5.ComputeHash(stream);
                              return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                          }
                      }
                  }

                  3.4 主程序(增强版命令行交互)

                  class Program
                  {
                      static void Main(string[] args)
                      {
                          Console.WriteLine("=== 文件分割/合并工具 v2.0 ===");
                          Console.WriteLine("1. 分割文件");
                          Console.WriteLine("2. 合并文件");
                          Console.Write("请选择 (1/2): ");
                          string choice = Console.ReadLine();
                          try
                          {
                              if (choice == "1")
                              {
                                  Console.Write("源文件路径: ");
                                  string source = Console.ReadLine();
                                  Console.Write("分块大小 (MB,默认 1): ");
                                  string sizeInput = Console.ReadLine();
                                  int mb = string.IsNullOrWhiteSpace(sizeInput) ? 1 : int.Parse(sizeInput);
                                  Console.Write("输出目录 (留空则使用源文件目录): ");
                                  string outputDir = Console.ReadLine();
                                  outputDir = string.IsNullOrWhiteSpace(outputDir) ? null : outputDir;
                                  FileSplitter.SplitFile(source, outputDir, mb * 1024L * 1024L);
                              }
                              else if (choice == "2")
                              {
                                  Console.Write("分块文件目录: ");
                                  string partDir = Console.ReadLine();
                                  Console.Write("原文件名 (如 largefile.zip): ");
                                  string originalName = Console.ReadLine();
                                  Console.Write("合并后输出路径: ");
                                  string output = Console.ReadLine();
                                  Console.Write("是否校验完整性 (y/n, 默认 y): ");
                                  bool verify = Console.ReadLine()?.ToLower() != "n";
                                  FileMerger.MergeFiles(partDir, originalName, output, verify);
                              }
                              else
                              {
                                  Console.WriteLine("无效输入");
                              }
                          }
                          catch (Exception ex)
                          {
                              Console.WriteLine($"错误: {ex.Message}");
                              if (ex.InnerException != null)
                                  Console.WriteLine($"详细信息: {ex.InnerException.Message}");
                          }
                          Console.WriteLine("按任意键退出...");
                          Console.ReadKey();
                      }
                  }

                  四、关键技术点

                  4.1 二进制流高效处理

                  • 采用 FileStreamSeek方法定位文件位置,避免全文件加载至内存。
                  • 借助 CopyTo方法(.NET 4.0+)实现流的高效复制,减少缓冲区操作。

                  4.2 异常处理与校验

                  • 文件不存在:抛出 FileNotFoundException同时提示路径。
                  • 权限不足:捕获 UnauthorizedAccessException,建议以管理员身份运行。
                  • MD5 校验:合同时后对比源文件与合并文件的 MD5 值,确保数据一致性(示例中已记录源文件 MD5 至 .info文件)。

                  4.3 跨平台兼容性

                  • 采用 Path.Combine处理路径分隔符(/),适配 Windows/Linux/macOS。
                  • 借助 .NET Core 运行时实现跨平台部署(需安装对应 SDK)。

                  五、性能优化建议

                  缓冲区调优默认缓冲区为 4KB,大文件可手动设置更大缓冲区:

                  using (var partStream = new FileStream(partPath, FileMode.Open, FileAccess.Read, FileShare.Read, 81920))

                  并行处理(分割时)多个分块可并行写入(需注意磁盘 I/O 竞争):

                  Parallel.For(0, partCount, i => { /* 写入 part i */ });

                  异步版本采用 ReadAsync / WriteAsync 提升 UI 响应性(适用来 GUI 版本)。

                  六、扩展功能建议(可后续迭代)

                  功能描述实现难度
                  图形界面(WPF / WinUI)拖拽文件、进度条显示、取消操作
                  加密分割分块采用 AES 加密,合并时解密
                  压缩分割分割前采用 GZip 压缩,减少存储
                  云存储集成分割后自动上传至 S3 / FTP / Azure Blob
                  断点续传式合并兼容部分分块缺失时提示
                  多格式兼容兼容 .part.001.7z.001 等常用分卷格式

                  七、最佳实践与注意事项

                  • 磁盘空间要求在这个场景下,分割时输出目录需至少等于源文件大小;合同时时目标目录需至少等于所有分块大小之和。
                  • 文件权限落到代码里,确保程序对源目录和输出目录具有读写权限(Linux 意 chmod 755)。
                  • 元数据文件重要性强烈建议保留 .info 文件,否则合同时时无法自动校验完整性。
                  • 并发安全多线程版本需注意同一文件不可被多个线程同时写入。
                  • 单元测试理解这一步时,建议对核心函数编写单元测试,覆盖边界情况(空文件、单分块、序号乱序等)。

                  八、总结

                  本文实现了一个功能完整、生产可用的文件分割与合并工具,核心特点如下所示:

                  • 分割:按固定大小拆分,生成分块文件 + 可选元数据
                  • 合并:自动识别分块序号,拼接 + 完整性校验
                  • 健壮性:异常处理、路径兼容、序号连续性校验
                  • 可扩展:预留了加密、压缩、GUI 等扩展接口

                  从实现思路看,该工具可直接集成到备份系统、文件传输服务或作为独立命令行工具采用。如需完整项目代码(含 .csproj、单元测试、WPF GUI 示例),可进一步提供。

                  您可能感兴趣的文章:

                  • 基于C#实现PDF按页分割文件和分页合并
                  • C#实现文件分割和合并的示例详解
                  • 采用C#代码拆分或分割PDF文件的示例代码
                  • C#文件分割的方法

                  相关资讯
                  点击查看更多
                  游戏推荐
                  推荐专题
                  热门阅读
                  推荐下载