我正在寻找最有效/直接的方式来完成这个简单的C / C ++操作:
void ReadData(FILE *f, uint16 *buf, int startsamp, int nsamps)
{
fseek(f, startsamp*sizeof(uint16), SEEK_SET);
fread(buf, sizeof(uint16), nsamps, f);
}
在C#/ .NET中。 (为了清晰起见,我忽略了返回值 - 生产代码会检查它们。)具体来说,我需要读取许多(可能是10到100的数百万)2字节(16位)“ushort”整数数据样本(固定格式) ,不需要解析)以二进制形式存储在磁盘文件中。关于C方式的好处是它将样本直接读入“uint16 *”缓冲区,没有CPU参与,也没有复制。是的,它可能是“不安全的”,因为它使用void *指针指向未知大小的缓冲区,但似乎应该有一个“安全的”.NET替代方案。
在C#中实现这一目标的最佳方法是什么?我环顾四周,发现了一些提示(“使用FieldOffset的”工会“,使用指针编组的”不安全“代码,编组),但似乎没有一个适用于这种情况,没有使用某种复制/转换。我想避免使用BinaryReader.ReadUInt16(),因为这非常慢并且CPU密集。在我的机器上,带有ReadUInt16()的for()循环与使用单个Read()直接读入byte []数组之间的速度差异约为25倍。使用非阻塞I / O(在等待磁盘I / O时重叠“有用”处理),该比率可能更高。
理想情况下,我想简单地“伪装”一个ushort []数组作为byte []数组,这样我就可以用Read()直接填充它,或者以某种方式让Read()直接填充ushort []数组: / p>
// DOES NOT WORK!!
public void GetData(FileStream f, ushort [] buf, int startsamp, int nsamps)
{
f.Position = startsamp*sizeof(ushort);
f.Read(buf, 0, nsamps);
}
但是没有Read()方法接受一个ushort []数组,只有一个byte []数组。
这可以直接在C#中完成,还是需要使用非托管代码或第三方库,还是必须采用CPU密集型逐个样本转换?虽然“安全”是首选,但我可以使用“不安全”的代码,或使用Marshal的一些技巧,我还没想到它。
感谢任何指导!
[UPDATE]
我想按照dtb的建议添加一些代码,因为似乎有很少的ReadArray实例。这是一个非常简单的,没有显示错误检查。
public void ReadMap(string fname, short [] data, int startsamp, int nsamps)
{
var mmf = MemoryMappedFile.CreateFromFile(fname);
var mmacc = mmf.CreateViewAccessor();
mmacc.ReadArray(startsamp*sizeof(short), data, 0, nsamps);
}
数据被安全地转储到您传递的数组中。您还可以为更复杂的类型指定类型。它似乎能够自己推断出简单类型,但是使用类型说明符,它看起来像这样:
mmacc.ReadArray<short>(startsamp*sizeof(short), data, 0, nsamps);
[UPATE2]
我想按照Ben的获胜答案添加代码,以“裸骨”形式(类似于上面的内容)进行比较。此代码经过编译和测试,可以正常运行。我直接在DllImport中使用了SafeFileHandle类型(而不是更常用的IntPtr)来简化操作。
[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool ReadFile(SafeFileHandle handle, IntPtr buffer, uint numBytesToRead, out uint numBytesRead, IntPtr overlapped);
[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool SetFilePointerEx(SafeFileHandle hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod);
unsafe void ReadPINV(FileStream f, short[] buffer, int startsamp, int nsamps)
{
long unused; uint BytesRead;
SafeFileHandle nativeHandle = f.SafeFileHandle; // clears Position property
SetFilePointerEx(nativeHandle, startsamp*sizeof(short), out unused, 0);
fixed(short* pFirst = &buffer[0])
ReadFile(nativeHandle, (IntPtr)pFirst, (uint)nsamps*sizeof(short), out BytesRead, IntPtr.Zero);
}
答案 0 :(得分:8)
您可以使用MemoryMappedFile。在对内存映射文件后,您可以创建一个提供MemoryMappedViewAccessor方法的视图(即ReadArray<T>)。此方法可以从文件中读取结构而不进行编组,并且它适用于基本类型ushort
。
答案 1 :(得分:2)
dtb's answer是一种更好的方法(实际上,它也必须复制数据,没有增益),但我只想指出从中提取ushort值您应该使用BitConverter
而不是BinaryReader
编辑:p /调用ReadFile的示例代码:
[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool ReadFile(IntPtr handle, IntPtr buffer, uint numBytesToRead, out uint numBytesRead, IntPtr overlapped);
[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool SetFilePointerEx(IntPtr hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod);
unsafe bool read(FileStream fs, ushort[] buffer, int offset, int count)
{
if (null == fs) throw new ArgumentNullException();
if (null == buffer) throw new ArgumentNullException();
if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentException();
uint bytesToRead = 2 * count;
if (bytesToRead < count) throw new ArgumentException(); // detect integer overflow
long offset = fs.Position;
SafeFileHandle nativeHandle = fs.SafeFileHandle; // clears Position property
try {
long unused;
if (!SetFilePositionEx(nativeHandle, offset, out unused, 0);
fixed (ushort* pFirst = &buffer[offset])
if (!ReadFile(nativeHandle, new IntPtr(pFirst), bytesToRead, out bytesToRead, IntPtr.Zero)
return false;
if (bytesToRead < 2 * count)
return false;
offset += bytesToRead;
return true;
}
finally {
fs.Position = offset; // restore Position property
}
}
答案 2 :(得分:1)
我在游戏中可能会有点迟到......但我找到的最快的方法是使用之前答案的组合。
如果我执行以下操作:
MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(somePath);
Stream io = mmf.CreateViewStream();
int count;
byte[] byteBuffer = new byte[1024 << 2];
ushort[] dataBuffer = new ushort[buffer.Length >> 1];
while((count = io.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
Buffer.BlockCopy(buffer, 0, dataBuffer, 0, count);
这比接受的答案快了约2倍。
对我来说,unsafe
方法与没有Buffer.BlockCopy
的{{1}}方法相同。 MemoryMappedFile
减少了一段时间。