需求:
在项目开发中,我们会遇到单个文件大小超过1TB的文件,这样的文件只能进行单文件读取,往往会造成读取完成耗时过长,导致客户在使用体验过程中不满意。
为了解决提升大文件的解析速度,我想到了先分割大文件为小文件,之后进行并行多个文件同时解析入库方案。
那么,怎么才可以把一个大文件分割为多个小文件呢?
如果我按照大小来控制分割出来的小文件,会造成文件的丢失问题,如果按照行数来分割,一行一行进行读取务必会造成分割文件耗时过长。
讨论:如果一个1TB的文件,我们按照大小来控制文件个数,假设每个分割出来的文件大小为200M,这样的话1TB分割出来约5200个文件,这样子的话最多造成约10000行信息被破坏,可以忽略不计。
所以我们为了减少分割文件带来的耗时时间长度,采取分割方案采用定长控制分割出来的文件大小。
- 实现方案1:一次性读取1M,直到读取到200M为止,开始写入下一个分割文件。
1 using (FileStream readerStream = new FileStream(file, FileMode.Open, FileAccess.Read)) 2 { 3 // 如果大于1GB 4 using (BinaryReader reader = new BinaryReader(readerStream)) 5 { 6 int fileCursor = 0; 7 int readerCursor = 0; 8 char[] buffer = new char[1024 * 1024]; 9 int length = 0; 10 11 NextFileBegin: 12 string filePath = string.Format(splitFileFormat, fileCursor); 13 14 Console.WriteLine("开始读取文件【{1}】:{0}", filePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); 15 using (FileStream writerStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write)) 16 { 17 using (BinaryWriter writer = new BinaryWriter(writerStream)) 18 { 19 while ((length = reader.Read(buffer, 0, buffer.Length)) > 0) 20 { 21 readerCursor++; 22 23 writer.Write(buffer, 0, length); 24 25 if (readerCursor >= splitFileSize) 26 { 27 Console.WriteLine("结束读取文件【{1}】:{0}", filePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); 28 29 readerCursor = 0; 30 fileCursor++; 31 32 goto NextFileBegin; 33 } 34 } 35 } 36 } 37 } 38 }
- 实现方案2:一次性读取200M,立即写入分割文件,开始下一个分割文件操作。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.IO; 6 using System.Configuration; 7 8 namespace BigFileSplitTest 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 /* 15 * <!--是否开启大文件分隔策略--> 16 <add key="BigFile.Split" value="true"/> 17 <!--当文件大于这个配置项时就执行文件分隔,单位:GB --> 18 <add key="BigFile.SplitMinFileSize" value="10" /> 19 <!--当执行文件分割时,每个分隔出来的文件大小,单位:MB --> 20 <add key="BigFile.SplitFileSize" value="200"/> 21 * <add key="BigFile.FilePath" value="\172.x1.xx.xx文件拷贝xxFTPxx2016-04-07x_20160407.txt"/> 22 <add key="BigFile.FileSilitPathFormate" value="\172.x1.xx.xx文件拷贝liulongFTPxx2016-04-07x_20160407{0}.txt"/> 23 */ 24 25 string file = ConfigurationManager.AppSettings.Get("BigFile.FilePath"); 26 string splitFileFormat = ConfigurationManager.AppSettings.Get("BigFile.FileSilitPathFormate"); 27 int splitMinFileSize = Convert.ToInt32(ConfigurationManager.AppSettings.Get("BigFile.SplitMinFileSize")) * 1024 * 1024 * 1204; 28 int splitFileSize = Convert.ToInt32(ConfigurationManager.AppSettings.Get("BigFile.SplitFileSize")) * 1024 * 1024; 29 30 FileInfo fileInfo = new FileInfo(file); 31 if (fileInfo.Length > splitMinFileSize) 32 { 33 Console.WriteLine("判定结果:需要分隔文件!"); 34 } 35 else 36 { 37 Console.WriteLine("判定结果:不需要分隔文件!"); 38 Console.ReadKey(); 39 return; 40 } 41 42 int steps = (int)(fileInfo.Length / splitFileSize); 43 using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read)) 44 { 45 using (BinaryReader br = new BinaryReader(fs)) 46 { 47 int couter = 1; 48 bool isReadingComplete = false; 49 while (!isReadingComplete) 50 { 51 string filePath = string.Format(splitFileFormat, couter); 52 Console.WriteLine("开始读取文件【{1}】:{0}", filePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); 53 54 byte[] input = br.ReadBytes(splitFileSize); 55 using (FileStream writeFs = new FileStream(filePath, FileMode.Create)) 56 { 57 using (BinaryWriter bw = new BinaryWriter(writeFs)) 58 { 59 bw.Write(input); 60 } 61 } 62 63 isReadingComplete = (input.Length != splitFileSize); 64 if (!isReadingComplete) 65 { 66 couter += 1; 67 } 68 Console.WriteLine("完成读取文件【{1}】:{0}", filePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); 69 } 70 } 71 } 72 73 74 Console.WriteLine("分隔完成,请按下任意键结束操作。。。"); 75 Console.ReadKey(); 76 77 } 78 } 79 }
从实验结果发现:方案一的性能较方案二的性能约耗时10倍。
具体原因为什么?
请你思考下:
一次性读取1M,直到读取到200M为止,开始写入下一个分割文件。
一次性读取200M,立即写入分割文件,开始下一个分割文件操作。