在c#中读取一个大文件到字节数组的最佳方法?

我有一个网络服务器,它将读取大二进制文件(几兆字节)到字节数组。服务器可能同时读取多个文件(不同的页面请求),因此我正在寻找一种最优化的方式来执行此操作,而不会对CPU造成太多负担。下面的代码足够好吗?

public byte[] FileToByteArray(string fileName)
{
byte[] buff = null;
FileStream fs = new FileStream(fileName,
FileMode.Open,
FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
long numBytes = new FileInfo(fileName).Length;
buff = br.ReadBytes((int) numBytes);
return buff;
}
620208 次浏览

简单地将整个内容替换为:

return File.ReadAllBytes(fileName);

然而,如果你关心内存消耗,你应该一次性将整个文件读入内存。你应该分块做。

我会这样想:

byte[] file = System.IO.File.ReadAllBytes(fileName);

你的代码可以分解成这个(代替File.ReadAllBytes):

public byte[] ReadAllBytes(string fileName)
{
byte[] buffer = null;
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
buffer = new byte[fs.Length];
fs.Read(buffer, 0, (int)fs.Length);
}
return buffer;
}

注意整数。MaxValue -由Read方法设置的文件大小限制。换句话说,一次只能读取2GB的数据块。

还要注意FileStream的最后一个参数是缓冲区大小。

我还建议阅读文件流BufferedStream

一如既往,一个简单的示例程序来分析哪个是最快的将是最有益的。

此外,底层硬件对性能也有很大影响。您是否使用基于服务器的具有大缓存的硬盘驱动器和带有板载内存缓存的RAID卡?还是使用连接到IDE端口的标准驱动器?

使用c#中的BufferedStream类来提高性能。缓冲区是内存中用于缓存数据的字节块,从而减少对操作系统的调用次数。缓冲区可以提高读写性能。

参见下面的代码示例和其他解释: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx < / p >

我可能会说这里的答案一般是“不”。除非你一次绝对需要所有数据,否则考虑使用基于__abc0的API(或一些reader / iterator的变体)。当你有多个并行操作(如问题所建议的)来最小化系统负载和最大化吞吐量时,特别是很重要。

例如,如果您正在向调用者传输数据:

Stream dest = ...
using(Stream source = File.OpenRead(path)) {
byte[] buffer = new byte[2048];
int bytesRead;
while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
dest.Write(buffer, 0, bytesRead);
}
}

根据操作的频率、文件大小和查看的文件数量,还需要考虑其他性能问题。需要记住的一件事是,每个字节数组都将在垃圾收集器的支配下被释放。如果你没有缓存任何这些数据,你最终可能会创建大量垃圾,并将大部分性能损失给GC时间百分比。如果块大于85K,你将分配给大对象堆(LOH),这将需要所有代的集合来释放(这是非常昂贵的,并且在服务器上将停止所有的执行)。此外,如果在LOH上有大量对象,最终可能会出现LOH碎片(LOH从未被压缩),从而导致性能较差和内存不足异常。一旦你达到某个点,你就可以循环使用这个过程,但我不知道这是否是一个最佳实践。

关键是,在以最快的方式将所有字节读入内存之前,你应该考虑应用程序的整个生命周期,否则你可能会以整体性能换取短期性能。

我建议尝试Response.TransferFile()方法,然后使用Response.Flush()Response.End()来处理大文件。

如果你在处理大于2 GB的文件,你会发现上面的方法失败了。

将流传递给MD5并允许它为你分块文件要容易得多:

private byte[] computeFileHash(string filename)
{
MD5 md5 = MD5.Create();
using (FileStream fs = new FileStream(filename, FileMode.Open))
{
byte[] hash = md5.ComputeHash(fs);
return hash;
}
}

我会说BinaryReader很好,但可以重构为这个,而不是所有那些获取缓冲区长度的代码行:

public byte[] FileToByteArray(string fileName)
{
byte[] fileData = null;


using (FileStream fs = File.OpenRead(fileName))
{
using (BinaryReader binaryReader = new BinaryReader(fs))
{
fileData = binaryReader.ReadBytes((int)fs.Length);
}
}
return fileData;
}

应该比使用.ReadAllBytes()更好,因为我在包括.ReadAllBytes()的顶部响应的评论中看到,其中一个评论者对文件> 600mb有问题,因为BinaryReader是为这类事情准备的。此外,将它放在using语句中可以确保FileStreamBinaryReader被关闭和释放。

用这个:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;

如果“大文件”意味着超出4GB限制,那么我下面写的代码逻辑是合适的。需要注意的关键问题是与SEEK方法一起使用的LONG数据类型。LONG可以指向2^32以外的数据边界。 在本例中,代码首先以1GB的块处理大文件,在整个1GB的大块处理完之后,再处理剩余的1GB字节。我使用这段代码来计算超过4GB大小的文件的CRC。 (在本例中使用https://crc32c.machinezoo.com/进行crc32c计算)

private uint Crc32CAlgorithmBigCrc(string fileName)
{
uint hash = 0;
byte[] buffer = null;
FileInfo fileInfo = new FileInfo(fileName);
long fileLength = fileInfo.Length;
int blockSize = 1024000000;
decimal div = fileLength / blockSize;
int blocks = (int)Math.Floor(div);
int restBytes = (int)(fileLength - (blocks * blockSize));
long offsetFile = 0;
uint interHash = 0;
Crc32CAlgorithm Crc32CAlgorithm = new Crc32CAlgorithm();
bool firstBlock = true;
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
buffer = new byte[blockSize];
using (BinaryReader br = new BinaryReader(fs))
{
while (blocks > 0)
{
blocks -= 1;
fs.Seek(offsetFile, SeekOrigin.Begin);
buffer = br.ReadBytes(blockSize);
if (firstBlock)
{
firstBlock = false;
interHash = Crc32CAlgorithm.Compute(buffer);
hash = interHash;
}
else
{
hash = Crc32CAlgorithm.Append(interHash, buffer);
}
offsetFile += blockSize;
}
if (restBytes > 0)
{
Array.Resize(ref buffer, restBytes);
fs.Seek(offsetFile, SeekOrigin.Begin);
buffer = br.ReadBytes(restBytes);
hash = Crc32CAlgorithm.Append(interHash, buffer);
}
buffer = null;
}
}
//MessageBox.Show(hash.ToString());
//MessageBox.Show(hash.ToString("X"));
return hash;
}

概述:如果您的图像被添加为action= embedded资源,则使用GetExecutingAssembly检索jpg资源到流中,然后将流中的二进制数据读入字节数组

   public byte[] GetAImage()
{
byte[] bytes=null;
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "MYWebApi.Images.X_my_image.jpg";


using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
bytes = new byte[stream.Length];
stream.Read(bytes, 0, (int)stream.Length);
}
return bytes;


}