Qual é a maneira mais rápida de ler um arquivo de texto linha por linha?

319

Quero ler um arquivo de texto linha por linha. Eu queria saber se estou fazendo isso da maneira mais eficiente possível no escopo do .NET C #.

Isto é o que estou tentando até agora:

var filestream = new System.IO.FileStream(textFilePath,
                                          System.IO.FileMode.Open,
                                          System.IO.FileAccess.Read,
                                          System.IO.FileShare.ReadWrite);
var file = new System.IO.StreamReader(filestream, System.Text.Encoding.UTF8, true, 128);

while ((lineOfText = file.ReadLine()) != null)
{
    //Do something with the lineOfText
}
Loren C Fortner
fonte
7
Por Fastestque você quer dizer a partir de desempenho ou de desenvolvimento perspectivas?
SLL
1
Isso bloqueará o arquivo pela duração do método. Você pode usar File.ReadAllLines em uma matriz e processá-la.
Kell
17
BTW, coloque filestream = new FileStreamem using()declaração para evitar possíveis problemas irritantes com bloqueado identificador de arquivo
SLL
Em relação encerrando FileStream está usando () declaração, consulte StackOverflow sobre método recomendado: StackOverflow usando declaração filestream StreamReader
deegee
Eu acho que ReadToEnd () é mais rápido.
Dan Gifford

Respostas:

315

Para encontrar a maneira mais rápida de ler um arquivo linha por linha, você precisará fazer alguns testes comparativos. Fiz alguns pequenos testes no meu computador, mas você não pode esperar que meus resultados se apliquem ao seu ambiente.

Usando StreamReader.ReadLine

Este é basicamente o seu método. Por algum motivo, você define o tamanho do buffer para o menor valor possível (128). Aumentar isso geralmente aumentará o desempenho. O tamanho padrão é 1.024 e outras boas opções são 512 (o tamanho do setor no Windows) ou 4.096 (o tamanho do cluster no NTFS). Você precisará executar uma referência para determinar o tamanho ideal do buffer. Um buffer maior é - se não mais rápido - pelo menos não mais lento que um buffer menor.

const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(fileName))
  using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) {
    String line;
    while ((line = streamReader.ReadLine()) != null)
      // Process line
  }

O FileStreamconstrutor permite especificar FileOptions . Por exemplo, se você estiver lendo um arquivo grande sequencialmente do começo ao fim, poderá se beneficiar FileOptions.SequentialScan. Novamente, o benchmarking é a melhor coisa que você pode fazer.

Usando File.ReadLines

Isso é muito parecido com sua própria solução, exceto que ela é implementada usando um StreamReadertamanho de buffer fixo de 1.024. No meu computador, isso resulta em um desempenho um pouco melhor comparado ao seu código com o tamanho do buffer de 128. No entanto, você pode obter o mesmo aumento de desempenho usando um tamanho de buffer maior. Este método é implementado usando um bloco iterador e não consome memória para todas as linhas.

var lines = File.ReadLines(fileName);
foreach (var line in lines)
  // Process line

Usando File.ReadAllLines

Isso é muito parecido com o método anterior, exceto que esse método aumenta uma lista de cadeias usadas para criar a matriz de linhas retornada, para que os requisitos de memória sejam maiores. No entanto, ele retorna String[]e não IEnumerable<String>permite que você acesse as linhas aleatoriamente.

var lines = File.ReadAllLines(fileName);
for (var i = 0; i < lines.Length; i += 1) {
  var line = lines[i];
  // Process line
}

Usando String.Split

Esse método é consideravelmente mais lento, pelo menos em arquivos grandes (testados em um arquivo de 511 KB), provavelmente devido à maneira como String.Splité implementado. Ele também aloca uma matriz para todas as linhas, aumentando a memória necessária em comparação com a sua solução.

using (var streamReader = File.OpenText(fileName)) {
  var lines = streamReader.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  foreach (var line in lines)
    // Process line
}

Minha sugestão é usar File.ReadLinesporque é limpo e eficiente. Se você precisar de opções especiais de compartilhamento (por exemplo, use FileShare.ReadWrite), poderá usar seu próprio código, mas deverá aumentar o tamanho do buffer.

Martin Liversage
fonte
1
Obrigado por isso - sua inclusão do parâmetro de tamanho do buffer no construtor StreamReader foi realmente útil. Estou transmitindo da API S3 da Amazon e usar um tamanho de buffer correspondente acelera consideravelmente as coisas em conjunto com ReadLine ().
Richard K.
Eu não entendo Em teoria, a grande maioria do tempo gasto na leitura do arquivo seria o tempo de busca no disco e as despesas gerais da manipulação de fluxos, como o que você faria com o File.ReadLines. O File.ReadLines, por outro lado, deve ler tudo de um arquivo na memória de uma só vez. Como poderia ser pior no desempenho?
H9uest
2
Não posso dizer sobre o desempenho da velocidade, mas uma coisa é certa: é muito pior no consumo de memória. Se você precisar lidar com arquivos muito grandes (GB, por exemplo), isso é muito crítico. Ainda mais se isso significa que ele precisa trocar de memória. No lado da velocidade, você pode adicionar que o ReadAllLine precisa ler TODAS as linhas ANTES de retornar o resultado que atrasa o processamento. Em alguns cenários, a IMPRESSÃO da velocidade é mais importante que a velocidade bruta.
bkqc
Se você ler o fluxo como matrizes de bytes, ele lerá o arquivo de 20% a 80% mais rápido (dos testes que fiz). O que você precisa é obter a matriz de bytes e convertê-la em string. Foi assim que fiz: Para ler, use stream.Read () Você pode fazer um loop para fazê-lo ler em pedaços. Depois de anexar todo o conteúdo em uma matriz de bytes (use System.Buffer.BlockCopy ), você precisará converter os bytes em string: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (new string [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage
200

Se você estiver usando o .NET 4, basta usar o File.ReadLinesque faz tudo por você. Eu suspeito que é muito o mesmo que o seu, a não ser que também pode usar FileOptions.SequentialScane um buffer maior (128 parece muito pequena).

Jon Skeet
fonte
Outro benefício ReadLines()é que é preguiçoso, portanto funciona bem com o LINQ.
precisa saber é o seguinte
35

Embora File.ReadAllLines()seja uma das maneiras mais simples de ler um arquivo, também é uma das mais lentas.

Se você deseja apenas ler linhas em um arquivo sem fazer muito, de acordo com esses benchmarks , a maneira mais rápida de ler um arquivo é o antigo método de:

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do minimal amount of work here
        }
}

No entanto, se você precisar fazer muito com cada linha, este artigo conclui que a melhor maneira é a seguinte (e é mais rápido pré-alocar uma string [] se você souber quantas linhas vai ler):

AllLines = new string[MAX]; //only allocate memory here

using (StreamReader sr = File.OpenText(fileName))
{
        int x = 0;
        while (!sr.EndOfStream)
        {
               AllLines[x] = sr.ReadLine();
               x += 1;
        }
} //Finished. Close the file

//Now parallel process each line in the file
Parallel.For(0, AllLines.Length, x =>
{
    DoYourStuff(AllLines[x]); //do your work here
});
Free Coder 24
fonte
13

Use o seguinte código:

foreach (string line in File.ReadAllLines(fileName))

Essa foi uma enorme diferença no desempenho da leitura.

Ele custa o consumo de memória, mas vale a pena!

user2671536
fonte
eu preferiria File.ReadLines (clique em mim) do queFile.ReadAllLines
newbieguy
5

Há um bom tópico sobre isso na pergunta Stack Overflow. O retorno do rendimento é mais lento que o retorno da "velha escola"? .

Diz:

ReadAllLines carrega todas as linhas na memória e retorna uma string []. Tudo bem se o arquivo for pequeno. Se o arquivo for maior do que caberá na memória, você ficará sem memória.

ReadLines, por outro lado, usa retorno de rendimento para retornar uma linha por vez. Com ele, você pode ler qualquer arquivo de tamanho. Não carrega o arquivo inteiro na memória.

Digamos que você queira encontrar a primeira linha que contém a palavra "foo" e saia. Usando ReadAllLines, você teria que ler o arquivo inteiro na memória, mesmo que "foo" ocorra na primeira linha. Com o ReadLines, você lê apenas uma linha. Qual seria o mais rápido?

Marcel James
fonte
4

Se o tamanho do arquivo não for grande, será mais rápido ler o arquivo inteiro e dividi-lo depois

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);
Saeed Amiri
fonte
6
File.ReadAllLines()
Javauffin
@jgauffin Eu não sei por trás da implementação de file.ReadAlllines (), mas acho que ele tem um buffer limitado e o buffer fileReadtoEnd deve ser maior, portanto, o número de acessos ao arquivo será diminuído dessa maneira, e fazendo string.Split no caso o tamanho do arquivo não seja grande, é mais rápido que o acesso múltiplo ao arquivo.
perfil completo de Saeed Amiri
Duvido que File.ReadAllLinestenha um tamanho de buffer fixo, pois o tamanho do arquivo é conhecido.
Javauffin
1
@ jgauffin: No .NET 4.0, File.ReadAllLinescria uma lista e é adicionada a essa lista em um loop usando StreamReader.ReadLine(com realocação potencial da matriz subjacente). Este método usa um tamanho de buffer padrão 1024. StreamReader.ReadToEndEvita a parte de análise de linha e o tamanho do buffer pode ser definido no construtor, se desejado.
9788 Martin Liversage
Seria útil definir "GRANDE" em relação ao tamanho do arquivo.
Paul
2

Se você tiver memória suficiente, encontrei alguns ganhos de desempenho lendo o arquivo inteiro em um fluxo de memória e abrindo um leitor de fluxo para ler as linhas. Contanto que você realmente planeje ler o arquivo inteiro de qualquer maneira, isso poderá gerar algumas melhorias.

Kibbee
fonte
1
File.ReadAllLinesparece ser uma escolha melhor então.
Javauffin 07/11
2

Você não pode ficar mais rápido se quiser usar uma API existente para ler as linhas. Mas ler pedaços maiores e encontrar manualmente cada nova linha no buffer de leitura provavelmente seria mais rápido.

jgauffin
fonte