c#文件读写之内存映射和StreamReader

针对2GB的大文件分析,对比了使用StreamReader逐行读取和内存映射文件的效率。StreamReader在时间上更快,分析发现内存映射的创建、Marshal.Copy操作以及编码转换是耗时的主要因素。通过测试,编码转换过程消耗约8秒。

契机:
这两天写逐行分析文件的程序,文件是GB量级的,我用来分析的文件大小是2GB大小。

分析:

       考虑到内存大小有限(内存2GB,磁盘128GB ssd,cpu:i5),那么不能将2GB的文件全部加载如内存,想到2个读取方法:

  1.  c#本身有StreamReader类,可以通过指定path读取文件,并且有ReadLine的方法,逐行读取。
  2. 通过内存映射,将文件映射到虚拟内存空间,由操作系统管理,需要对虚拟内存中的数据进行分析,以"\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



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值