[编辑]我更新了这篇文章,包括一个完整可编译的示例,并回答了@Daniel在下面评论中提出的问题。因此,此代码不再使用任何“危险”方法,并且没有代码分析警告。 [/编辑]
如果您的结构体仅包含可平置类型,则可以通过使用封送来直接将数据读入数组,而无需进行其他副本操作,如下所示(完整可编译示例):
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace ConsoleApplication1
{
internal class Program
{
struct TestStruct
{
public byte ByteValue;
public short ShortValue;
public int IntValue;
public long LongValue;
public float FloatValue;
public double DoubleValue;
}
static void Main()
{
var array = new TestStruct[10];
for (byte i = 0; i < array.Length; ++i)
{
array[i].ByteValue = i;
array[i].ShortValue = i;
array[i].IntValue = i;
array[i].LongValue = i;
array[i].FloatValue = i;
array[i].DoubleValue = i;
}
Directory.CreateDirectory("C:\\TEST");
using (var output = new FileStream(@"C:\TEST\TEST.BIN", FileMode.Create))
FastWrite(output, array, 0, array.Length);
using (var input = new FileStream(@"C:\TEST\TEST.BIN", FileMode.Open))
array = FastRead<TestStruct>(input, array.Length);
for (byte i = 0; i < array.Length; ++i)
{
Trace.Assert(array[i].ByteValue == i);
Trace.Assert(array[i].ShortValue == i);
Trace.Assert(array[i].IntValue == i);
Trace.Assert(array[i].LongValue == i);
Trace.Assert(array[i].FloatValue == i);
Trace.Assert(array[i].DoubleValue == i);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]
public static void FastWrite<T>(FileStream fs, T[] array, int offset, int count) where T: struct
{
int sizeOfT = Marshal.SizeOf(typeof(T));
GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
uint bytesWritten;
uint bytesToWrite = (uint)(count * sizeOfT);
if
(
!WriteFile
(
fs.SafeFileHandle,
new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64() + (offset*sizeOfT)),
bytesToWrite,
out bytesWritten,
IntPtr.Zero
)
)
{
throw new IOException("Unable to write file.", new Win32Exception(Marshal.GetLastWin32Error()));
}
Debug.Assert(bytesWritten == bytesToWrite);
}
finally
{
gcHandle.Free();
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]
public static T[] FastRead<T>(FileStream fs, int count) where T: struct
{
int sizeOfT = Marshal.SizeOf(typeof(T));
long bytesRemaining = fs.Length - fs.Position;
long wantedBytes = count * sizeOfT;
long bytesAvailable = Math.Min(bytesRemaining, wantedBytes);
long availableValues = bytesAvailable / sizeOfT;
long bytesToRead = (availableValues * sizeOfT);
if ((bytesRemaining < wantedBytes) && ((bytesRemaining - bytesToRead) > 0))
{
Debug.WriteLine("Requested data exceeds available data and partial data remains in the file.", "Dmr.Common.IO.Arrays.FastRead(fs,count)");
}
T[] result = new T[availableValues];
if (availableValues == 0)
return result;
GCHandle gcHandle = GCHandle.Alloc(result, GCHandleType.Pinned);
try
{
uint bytesRead;
if
(
!ReadFile
(
fs.SafeFileHandle,
gcHandle.AddrOfPinnedObject(),
(uint)bytesToRead,
out bytesRead,
IntPtr.Zero
)
)
{
throw new IOException("Unable to read file.", new Win32Exception(Marshal.GetLastWin32Error()));
}
Debug.Assert(bytesRead == bytesToRead);
}
finally
{
gcHandle.Free();
}
return result;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1415:DeclarePInvokesCorrectly")]
[DllImport("kernel32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool WriteFile
(
SafeFileHandle hFile,
IntPtr lpBuffer,
uint nNumberOfBytesToWrite,
out uint lpNumberOfBytesWritten,
IntPtr lpOverlapped
);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1415:DeclarePInvokesCorrectly")]
[DllImport("kernel32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ReadFile
(
SafeFileHandle hFile,
IntPtr lpBuffer,
uint nNumberOfBytesToRead,
out uint lpNumberOfBytesRead,
IntPtr lpOverlapped
);
}
}
您可以创建一个BlockingCollection
来存储输入的数据,然后使用一个线程将其填充,再使用另一个线程来消费它。
将数据读入队列的线程可能如下所示:
public void ReadIntoQueue<T>(FileStream fs, BlockingCollection<T[]> queue, int blockSize) where T: struct
{
while (true)
{
var data = FastRead<T>(fs, blockSize);
if (data.Length == 0)
{
queue.CompleteAdding();
break;
}
queue.Add(data);
}
}
消费线程会像这样从队列中删除内容:
public void ProcessDataFromQueue<T>(BlockingCollection<T[]> queue) where T : struct
{
foreach (var array in queue.GetConsumingEnumerable())
{
}
}