Eu tenho a adorável tarefa de descobrir como lidar com arquivos grandes sendo carregados no editor de script de nosso aplicativo (é como o VBA para nosso produto interno para macros rápidas). A maioria dos arquivos tem cerca de 300-400 KB, o que é um bom carregamento. Mas, quando ultrapassam 100 MB, o processo passa por momentos difíceis (como seria de esperar).
O que acontece é que o arquivo é lido e colocado em um RichTextBox que é navegado - não se preocupe muito com esta parte.
O desenvolvedor que escreveu o código inicial está simplesmente usando um StreamReader e fazendo
[Reader].ReadToEnd()
que pode demorar um pouco para ser concluído.
Minha tarefa é dividir esse trecho de código, lê-lo em partes em um buffer e mostrar uma barra de progresso com uma opção para cancelá-lo.
Algumas suposições:
- A maioria dos arquivos terá 30-40 MB
- O conteúdo do arquivo é texto (não binário), alguns em formato Unix, outros em DOS.
- Depois que o conteúdo é recuperado, descobrimos qual terminador é usado.
- Ninguém se preocupa depois que ele é carregado, o tempo que leva para renderizar na caixa de texto rico. É apenas o carregamento inicial do texto.
Agora, para as perguntas:
- Posso simplesmente usar StreamReader, verificar a propriedade Length (portanto, ProgressMax) e emitir um Read para um tamanho de buffer definido e iterar em um loop while WHILST dentro de um trabalhador de segundo plano, de modo que não bloqueie o thread de IU principal? Em seguida, retorne o stringbuilder para o thread principal quando estiver concluído.
- O conteúdo irá para um StringBuilder. posso inicializar o StringBuilder com o tamanho do fluxo se o comprimento estiver disponível?
Estas são (na sua opinião profissional) boas ideias? Eu tive alguns problemas no passado com a leitura de conteúdo do Streams, porque sempre perderá os últimos bytes ou algo assim, mas farei outra pergunta se for o caso.
fonte
Respostas:
Você pode melhorar a velocidade de leitura usando um BufferedStream, como este:
ATUALIZAÇÃO de março de 2013
Recentemente, escrevi código para leitura e processamento (pesquisa de texto em) arquivos de texto de 1 GB (muito maiores do que os arquivos envolvidos aqui) e obtive um ganho de desempenho significativo usando um padrão produtor / consumidor. A tarefa do produtor lia linhas de texto usando o
BufferedStream
e os entregava a uma tarefa separada do consumidor que fazia a pesquisa.Usei isso como uma oportunidade de aprender TPL Dataflow, que é muito adequado para codificar rapidamente esse padrão.
Por que BufferedStream é mais rápido
ATUALIZAÇÃO DE dezembro de 2014: sua milhagem pode variar
Com base nos comentários, FileStream deve estar usando um BufferedStream internamente. No momento em que esta resposta foi fornecida pela primeira vez, medi um aumento significativo no desempenho adicionando um BufferedStream. Na época, eu tinha como alvo o .NET 3.x em uma plataforma de 32 bits. Hoje, visando o .NET 4.5 em uma plataforma de 64 bits, não vejo nenhuma melhoria.
Relacionados
Eu me deparei com um caso em que o streaming de um grande arquivo CSV gerado para o stream de resposta de uma ação ASP.Net MVC era muito lento. Adicionar um BufferedStream melhorou o desempenho em 100x nesta instância. Para obter mais informações, consulte Saída sem buffer muito lenta
fonte
Se você ler as estatísticas de desempenho e benchmark neste site , verá que a maneira mais rápida de ler (porque ler, escrever e processar são diferentes) um arquivo de texto é o seguinte trecho de código:
Todos os cerca de 9 métodos diferentes foram marcados, mas aquele parece sair na frente na maioria das vezes, até mesmo desempenhando o leitor bufferizado como outros leitores mencionaram.
fonte
StringBuilder
para carregá-los na memória, carrega mais rápido, pois não cria uma nova string cada vez que você adiciona caracteres)Você diz que foi solicitado a mostrar uma barra de progresso enquanto um arquivo grande está sendo carregado. Isso é porque os usuários realmente desejam ver a% exata de carregamento do arquivo ou apenas porque desejam um feedback visual de que algo está acontecendo?
Se o último for verdadeiro, a solução se torna muito mais simples. Apenas faça
reader.ReadToEnd()
em um thread de segundo plano e exibir uma barra de progresso do tipo letreiro em vez de uma adequada.Levanto esse ponto porque, em minha experiência, costuma ser esse o caso. Quando você está escrevendo um programa de processamento de dados, os usuários definitivamente estarão interessados em um número% completo, mas para atualizações de IU simples, mas lentas, é mais provável que eles apenas queiram saber se o computador não travou. :-)
fonte
StreamReader
loop. No entanto, ainda será mais simples porque não há necessidade de ler adiante para calcular o indicador de progresso.Para arquivos binários, a maneira mais rápida de lê-los que encontrei é esta.
Em meus testes, é centenas de vezes mais rápido.
fonte
Use um trabalhador de segundo plano e leia apenas um número limitado de linhas. Leia mais apenas quando o usuário rolar.
E tente nunca usar ReadToEnd (). É uma das funções que você pensa "por que eles fizeram isso?"; é um script kiddies ' ajudante de que vai bem com pequenas coisas, mas como você vê, é péssimo para arquivos grandes ...
Aqueles caras que estão dizendo para você usar StringBuilder precisam ler o MSDN com mais frequência:
Considerações de desempenho
Os métodos Concat e AppendFormat concatenam novos dados a um objeto String ou StringBuilder existente. Uma operação de concatenação de objeto String sempre cria um novo objeto a partir da string existente e dos novos dados. Um objeto StringBuilder mantém um buffer para acomodar a concatenação de novos dados. Novos dados são acrescentados ao final do buffer se houver espaço disponível; caso contrário, um novo buffer maior é alocado, os dados do buffer original são copiados para o novo buffer e, em seguida, os novos dados são anexados ao novo buffer. O desempenho de uma operação de concatenação para um objeto String ou StringBuilder depende da frequência com que ocorre uma alocação de memória.
Uma operação de concatenação String sempre aloca memória, enquanto uma operação de concatenação StringBuilder aloca memória apenas se o buffer do objeto StringBuilder for muito pequeno para acomodar os novos dados. Conseqüentemente, a classe String é preferível para uma operação de concatenação se um número fixo de objetos String for concatenado. Nesse caso, as operações de concatenação individuais podem até ser combinadas em uma única operação pelo compilador. Um objeto StringBuilder é preferível para uma operação de concatenação se um número arbitrário de strings for concatenado; por exemplo, se um loop concatena um número aleatório de strings de entrada do usuário.
Isso significa uma enorme alocação de memória, o que se torna um grande uso de sistema de arquivos de swap, que simula seções do seu disco rígido para agirem como a memória RAM, mas um disco rígido é muito lento.
A opção StringBuilder parece boa para quem usa o sistema como um único usuário, mas quando você tem dois ou mais usuários lendo arquivos grandes ao mesmo tempo, você tem um problema.
fonte
Isso deve ser o suficiente para você começar.
fonte
Dê uma olhada no seguinte trecho de código. Você mencionou
Most files will be 30-40 MB
. Este afirma ler 180 MB em 1,4 segundos em um Intel Quad Core:Artigo original
fonte
Talvez seja melhor usar arquivos mapeados em memória aqui . O suporte a arquivos mapeados em memória estará disponível no .NET 4 (acho ... ouvi isso por meio de outra pessoa falando sobre isso), por isso este wrapper que usa p / invoca para fazer o mesmo trabalho ..
Edit: Veja aqui no MSDN para saber como funciona, aqui está a entrada do blog que indica como isso é feito no próximo .NET 4 quando ele for lançado. O link que forneci anteriormente é um invólucro em torno da pinvoke para conseguir isso. Você pode mapear o arquivo inteiro na memória e visualizá-lo como uma janela deslizante ao rolar pelo arquivo.
fonte
Todas as respostas excelentes! no entanto, para quem procura uma resposta, elas parecem um tanto incompletas.
Como uma String padrão só pode ter tamanho X, 2 Gb a 4 Gb dependendo da sua configuração, essas respostas não atendem realmente à pergunta do OP. Um método é trabalhar com uma Lista de Strings:
Alguns podem querer tokenizar e dividir a linha durante o processamento. A String List agora pode conter grandes volumes de texto.
fonte
Um iterador pode ser perfeito para este tipo de trabalho:
Você pode chamá-lo usando o seguinte:
Conforme o arquivo é carregado, o iterador retornará o número de progresso de 0 a 100, que você pode usar para atualizar sua barra de progresso. Assim que o loop terminar, o StringBuilder conterá o conteúdo do arquivo de texto.
Além disso, como você deseja texto, podemos apenas usar o BinaryReader para ler os caracteres, o que garantirá que seus buffers se alinhem corretamente ao ler quaisquer caracteres multibyte ( UTF-8 , UTF-16 , etc.).
Tudo isso é feito sem o uso de tarefas em segundo plano, threads ou máquinas de estado personalizadas complexas.
fonte
Meu arquivo tem mais de 13 GB:
O link abaixo contém o código que lê um pedaço do arquivo facilmente:
Leia um grande arquivo de texto
Mais Informações
fonte