内存映射,用法不同导致结果差异悬殊,百思不得其解。

Qiaorui 2012-05-29 11:25:02
新近写的代码用到内存映射,遇到了一个百思不得其解的问题:
使用“代码1”时,Program 类中 for 循环输出结果(最初1K字节)正确;

使用“代码1”时,Program 类中 for 循环输出结果(最初1K字节)错误,
但是:代码2中 if(i==0) 时,结果正确;
   而改 if(i==1) 时,结果同 Program 类中 for 循环,错误

郁闷至极,不知道错在哪?请大虾指教。

代码摘抄如下:


public class FileMap
{
public List<byte[]> list = null;

public void Test(string path)
{
list = new List<byte[]>();
...
long fileOffset = 0;
long fileSize = 0;
uint blockBytes = 64<<20;//64M
while (fileSize > 0)
{
IntPtr lpbMapAddress = MapViewOfFile(mappingFileHandle, FILE_MAP_COPY | FILE_MAP_READ,(uint)(fileOffset >> 32), (uint)(fileOffset & 0xFFFFFFFF),blockBytes);

byte[] temp = new byte[blockBytes];

// 代码1,Program 类中 for 循环输出结果(最初1K字节)正确
Marshal.Copy(lpbMapAddress, temp, 0, (int)blockBytes);
list.Add(temp);
// end:代码1

// 代码2,Program 类中 for 循环输出结果(最初1K字节)错误,但是代码2中 if(i==0) 时,结果正确;而改 if(i==1) 时,结果同 Program 类中 for 循环,错误
byte[] tmp = new byte[1024];

for(int i = 0; i < temp.Length; i+=1024)
{
Array.Clear(tmp,0,tmp.Length);
Buffer.BlockCopy(temp, i, tmp, 0, 1024);
//输出最初1K字节
if(i==0)// i==0:正确;i==1:错误
{
for(int j = 0; j < 1024; j++)
{
Console.Write(tmp[j].ToString("X2")+",");
}
}
list.Add(tmp);
}
// end:代码2


UnmapViewOfFile(lpbMapAddress);
fileOffset += blockBytes;
fileSize -= blockBytes;
}
}
}
public class Program
{
static void Main(string[] args)
{
FileMap fileMap = new FileMap();
fileMap.Test(@"test.rar");//文件大小:8388608B = 8M

Console.WriteLine(fileMap.list.Count.ToString());// 结果正确,代码1结果为:1,代码2结果为:8192

byte[] temp = (byte[])(fileMap.list[0]);

for(int i = 0; i < 1024; i++)
{
Console.Write(temp[i].ToString("X2")+",");
}
}
}
...全文
186 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
qldsrx 2012-05-30
  • 打赏
  • 举报
回复
给你写了2个通用方法,这样你得到了 IntPtr lpbMapAddress后,就直接调用FillBytesFromPointer方法进行数组的填充即可。

/// <summary>
/// 返回基地址某偏移处的字节。
/// </summary>
/// <param name="source">基地址</param>
/// <param name="offset">偏移量</param>
/// <returns>字节</returns>
public static byte PointerToByte(IntPtr source, int offset)
{
unsafe
{
byte* pbyte = (byte*)source.ToPointer();
pbyte += offset;
return *pbyte;
}
}
/// <summary>
/// 从基地址某偏移处填充给定大小的字节数组
/// </summary>
/// <param name="source">基地址</param>
/// <param name="offset">偏移量</param>
/// <param name="destination">目标数组</param>
public static void FillBytesFromPointer(IntPtr source, int offset, byte[] destination)
{
unsafe
{
byte* pbyte = (byte*)source.ToPointer();
pbyte += offset;
for (int i = 0; i < destination.Length; i++)
{
destination[i] = *pbyte;
pbyte++;
}
}
}

调用举例:

IntPtr lpbMapAddress = MapViewOfFile(mappingFileHandle, FILE_MAP_COPY | FILE_MAP_READ,(uint)(fileOffset >> 32), (uint)(fileOffset & 0xFFFFFFFF),blockBytes);

for(int i = 0; i < blockBytes; i+=1024)
{
byte[] tmp = new byte[1024];
FillBytesFromPointer(lpbMapAddress, i, tmp);
//输出最初1K字节
if(i==0)
{
for(int j = 0; j < 1024; j++)
{
Console.Write(tmp[j].ToString("X2")+",");
}
}
list.Add(tmp);
}
Qiaorui 2012-05-30
  • 打赏
  • 举报
回复
允许不安全代码,只是不大会用,因为内存映射这一块我也是新近才接触,不用内存映射的情况下,速度比较慢,所以决定修改代码,以提高性能。请赐教。
qldsrx 2012-05-30
  • 打赏
  • 举报
回复
首先,我想知道你是不是一定要分成1024小块存到List中去,直接将那个大的temp存入不是一样吗?
如果一定要转换为小的,并且不考虑效率的话,你应该将byte[] tmp = new byte[1024];放到循环内部,这样写:
            for(int i = 0; i < temp.Length; i+=1024)
{
byte[] tmp = new byte[1024];
Buffer.BlockCopy(temp, i, tmp, 0, 1024);
if(i==0)
{
for(int j = 0; j < 1024; j++)
{
Console.Write(tmp[j].ToString("X2")+",");
}
}
list.Add(tmp);
}

但是这样写的结果将导致内存消耗为文件的3倍,其一为MapViewOfFile调用消耗的内存,其二为temp消耗的内存,其三为tmp消耗的内存。
如果你允许不安全代码,使用指针操作,达到同样效果将只消耗1倍的内存,而且速度也可以提高10倍以上。
Qiaorui 2012-05-30
  • 打赏
  • 举报
回复
因为要处理大型的文件,所以内存映射还是有必要的,当然可以通过优化基于System.IO的代码来提高读取文件的性能。知道.NET4.0新增的对内存映射文件支持,但是现在修改的是基于.NET2.0的代码,不知道有什么好的方法的建议?
Qiaorui 2012-05-30
  • 打赏
  • 举报
回复
那该如何解决解决?我想将映射的内容载入List等备用。
qldsrx 2012-05-30
  • 打赏
  • 举报
回复
楼主那么喜欢内存映射的话,给你一个类,你去看看,.NET4.0新增的对内存映射文件支持:MemoryMappedFile,
这样无需调用API而可以很方便的使用了。
qldsrx 2012-05-30
  • 打赏
  • 举报
回复
tmp变量是引用类型,因此你list.Add(tmp);只是添加了它的引用地址而并非里面的字节内容,当i>0时,即添加了第二次以上时,每次添加的都是相同的tmp引用,结果第二次你刷新tmp内容就把第一次的内容给改掉了。
Qiaorui 2012-05-30
  • 打赏
  • 举报
回复
使用内存映射来读文件就是快,可似讨论的不多,再坚持一下/
Qiaorui 2012-05-30
  • 打赏
  • 举报
回复
做了以下测试,选择输出指定段的内容,结果一错一对,郁闷。

if(i==0)// i==0:正确;i==1:错误
{
for(int j = 0; j < 1024; j++)
{
Console.Write(tmp[j].ToString("X2")+",");
}
}



楼上的用法测试通过,可就是想不明白到底什么原因,陷入思维定势了。
Qiaorui 2012-05-30
  • 打赏
  • 举报
回复
测试结果很满意,速度快得惊人,呵呵。当然,由于用到unsafe,还得进行更仔细的测试。
多谢 qldsrx(青龙白虎) 的热情相助。
CS1024 2012-05-30
  • 打赏
  • 举报
回复
用代码1,结合以下用法,应该达到你想要的效果,当然,如果输出的文件与原文件相同的话。

for(int i=0;i<fileMap.list.Count;i++)
{
for(int j=0;j<fileMap.list[i].Length;j+=1024)
{
stream.Seek(j,SeekOrigin.Begin);
stream.Write(fileMap.list[i], j, 1024);
}
}

111,126

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Creator Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧