Melhor maneira de ler um arquivo grande em uma matriz de bytes em C #?
-
19-09-2019 - |
Pergunta
Eu tenho um servidor web que irá ler arquivos binários grandes (vários megabytes) em matrizes de bytes. O servidor pode estar lendo vários arquivos ao mesmo tempo (diferentes solicitações de página), então eu estou procurando a maneira mais otimizada para fazer isso sem sobrecarregar a CPU muito. É o código abaixo bom o suficiente?
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;
}
Solução
Basta substituir a coisa toda com:
return File.ReadAllBytes(fileName);
No entanto, se você está preocupado com o consumo de memória, você deve não ler o arquivo inteiro na memória de uma só vez a todos. Você deve fazer isso em pedaços.
Outras dicas
Eu poderia argumentar que a resposta aqui geralmente é "não". A menos que você absolutamente necessário todos os dados ao mesmo tempo, considerar o uso de uma API baseada em Stream
(ou alguma variante do leitor / iterator). Isso é especialmente importante quando você tem várias operações paralelas (como sugerido pela pergunta) para minimizar a carga do sistema e maximizar a produção.
Por exemplo, se estiver transmitindo dados para um chamador:
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);
}
}
Eu acho que isso:
byte[] file = System.IO.File.ReadAllBytes(fileName);
Seu código pode ser tomada a esse (em vez de 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;
}
Observe o Integer.MaxValue - limitação de tamanho de arquivo colocado pelo método Read. Em outras palavras, você pode apenas ler um pedaço de 2GB ao mesmo tempo.
Observe também que o último argumento para o FileStream é um tamanho do buffer.
Também gostaria de sugerir a leitura sobre FileStream BufferedStream .
Como sempre um programa de exemplo simples de perfil que é mais rápido será mais benéfico.
Também o seu hardware subjacente terá um grande efeito sobre o desempenho. Você está usando discos rígidos baseados em servidor com grandes caches e um cartão de RAID com cache de memória onboard? Ou você está usando uma unidade padrão conectado à porta IDE?
Dependendo da frequência das operações, o tamanho dos arquivos, e o número de arquivos que você está olhando, existem outros problemas de desempenho a ter em consideração. Uma coisa a lembrar é que cada uma de suas matrizes de bytes será lançado à mercê do coletor de lixo. Se você não está cache de qualquer desses dados, você pode acabar criando um monte de lixo e estar perdendo a maior parte de seu desempenho para % Time in GC . Se os pedaços são maiores do que 85K, você estará alocando à Large Object Heap (LOH) que vai exigir uma coleção de todas as gerações para liberar (isto é muito caro, e no servidor vai parar toda a execução enquanto ele está acontecendo ). Além disso, se você tem uma tonelada de objetos no LOH, você pode acabar com LOH fragmentação (LOH nunca é compactado) que leva a um desempenho ruim e fora de memória exceções. Você pode reciclar o processo uma vez que você atingiu um certo ponto, mas eu não sei se isso é uma boa prática.
O ponto é, você deve considerar o ciclo de vida do seu aplicativo antes necessariamente apenas ler todos os bytes na memória a maneira mais rápida possível, ou você pode estar negociando desempenho de curto prazo para o desempenho global.
Eu diria BinaryReader
é bom, mas pode ser reformulado para isso, em vez de todas aquelas linhas de código para obter o comprimento do buffer:
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;
}
Deve ser melhor do que usar .ReadAllBytes()
, desde que eu vi nos comentários sobre a resposta de topo que inclui .ReadAllBytes()
que um dos comentadores teve problemas com arquivos> 600 MB, uma vez que um BinaryReader
é destinado a esse tipo de coisa. Além disso, colocá-lo em um comunicado using
garante a FileStream
e BinaryReader
estão fechados e eliminados.
Em caso de 'um grande arquivo' é significado para além do limite de 4 GB, então a minha seguinte lógica código escrito é apropriado. A questão-chave a notar é o tipo de dados LONG usado com o método Seek. Como tempo é capaz de apontar para além de 2 ^ 32 limites de dados. Neste exemplo, o código está processando primeiro processamento do arquivo grande em pedaços de 1GB, depois de toda a grandes pedaços de 1GB são processados, os que sobraram (<1GB) bytes são processados. Eu uso este código com o cálculo do CRC dos arquivos além do tamanho de 4 GB. (Usando https://crc32c.machinezoo.com/ para o cálculo crc32c neste exemplo)
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;
}
Use a classe BufferedStream em C # para melhorar o desempenho. Um tampão é um bloco de bytes de memória usada para armazenar dados, reduzindo assim o número de chamadas para o sistema operativo. Buffers melhoram a ler e desempenho de gravação.
Consulte o seguinte para um exemplo de código e uma explicação adicional: http://msdn.microsoft.com/en-us/ biblioteca / system.io.bufferedstream.aspx
usar este:
bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
Eu recomendaria tentar o método Response.TransferFile()
em seguida, um Response.Flush()
e Response.End()
para servir seus arquivos grandes.
Se você está lidando com arquivos acima de 2 GB, você verá que os métodos acima falhar.
É muito mais fácil apenas para entregar o fluxo de fora para MD5 e permitir que a fatia seu arquivo para você:
private byte[] computeFileHash(string filename)
{
MD5 md5 = MD5.Create();
using (FileStream fs = new FileStream(filename, FileMode.Open))
{
byte[] hash = md5.ComputeHash(fs);
return hash;
}
}