契机:
这两天写逐行分析文件的程序,文件是GB量级的,我用来分析的文件大小是2GB大小。
分析:
考虑到内存大小有限(内存2GB,磁盘128GB ssd,cpu:i5),那么不能将2GB的文件全部加载如内存,想到2个读取方法:
- c#本身有StreamReader类,可以通过指定path读取文件,并且有ReadLine的方法,逐行读取。
- 通过内存映射,将文件映射到虚拟内存空间,由操作系统管理,需要对虚拟内存中的数据进行分析,以"\r\n"为换行符。
读取效率比较:
1. 使用StreamReader对2GB文件进行读计时:
Stopwatch sw = new Stopwatch();
string path = @"c:\Users\Songrong\Desktop\zt.xyz";
sw.Start();
using (StreamReader sr = new StreamReader(path))
{
while (!sr.EndOfStream)
sr.ReadLine();
}
sw.Stop();
Console.WriteLine("Elapsed time: {0} ms", sw.ElapsedMilliseconds);
Console.Read();
//Output
Elapsed time: 15500 ms
2. 使用内存映射文件计时:
//fill the buffer with the file content. blockBytes=65535 * 100.
private void fillBuffer()
{
if (this.EndOfFile)
return;
UInt32 buffSize = this.blockBytes;
if (this.fileSize - this.fileOffset < buffSize)
buffSize = (UInt32)(this.fileSize - this.fileOffset);
IntPtr lpbMapAddress = MapViewOfFile(
mappingFileHandle,
FILE_MAP_COPY | FILE_MAP_READ | FILE_MAP_WRITE,
(uint)(this.fileOffset >> 32),
(uint)(this.fileOffset & 0xFFFFFFFF),
buffSize);
if (IntPtr.Zero == lpbMapAddress)
return;
Marshal.Copy(lpbMapAddress, this._byteBuff, 0, (Int32)buffSize);
this.decoder.GetChars(this._byteBuff, 0, (Int32)buffSize, this._charbuffer, 0);
UnmapViewOfFile(lpbMapAddress);
this._buffStart = 0;
this._buffEnd = (Int32)buffSize;
this.fileOffset += buffSize;
}
//Test the memory-mapping method.
public void Test()
{
Stopwatch sw = new Stopwatch();
sw.Start();
while (this.fileOffset < this.fileSize)
{
fillBuffer();
}
sw.Stop();
Console.WriteLine("Test: {0}ms", sw.ElapsedMilliseconds);
}
//Output
Test: 18000ms
甚至没有经过ReadLine对buffer进行分析,单单是从磁盘取出数据复制到buffer中,时间比StreamReader花费更长。时间上,StreamReader要比我写的内存映射方法快,同时我查看了StreamReader的代码,ReadBuffer通过Stream.Read将数据加载到bytebuffer中,利用decoder解码bytebuffer,转换成charbuffer,这和我的FillBuffer做的内容一样,可以做出一些推测:
1. 内存映射的创建和取消产生一些消耗。
2. Marshal.Copy 内存的复制
3. byte数组转码成utf-16 char的消耗
我对上面3种情况进行了测试:
1.去除了2、3的代码,计时2ms,可以看到内存映射的操作本身是不太消耗时间的,因为它只是修改了进程的page table等映射资源
2.去除了3的代码,计时10s
3.通过总时间18s - 10s = 8s,那么编码转换消耗8s
文件解码(decoder):
于是,我查看了StreamReader中的decoder的实施,有一个DetectEncoding方法,根据文件的编码格式对解码器进行了设置,就当是补充知识
1. bigedian & unicode byte order mark
文件首字节0xFE 0xFF
2. littleEndian & unicode byte order mark
文件首字节0xFF 0xFE NonZero NonZero => UnicodeEncoding
文件首字节0xFF 0xFE 0 0 => UTF32Encoding
3. UTF8
文件首 0xEF 0xBB 0xBF
4. BigEndien & UTF32Encoding
文件首0 0 0xFE 0xFF
针对2GB的大文件分析,对比了使用StreamReader逐行读取和内存映射文件的效率。StreamReader在时间上更快,分析发现内存映射的创建、Marshal.Copy操作以及编码转换是耗时的主要因素。通过测试,编码转换过程消耗约8秒。

145

被折叠的 条评论
为什么被折叠?



