Pergunta

Eu estou usando C # para ler um arquivo CSV de texto simples ~ 120 MB. Inicialmente eu fiz a análise por lê-lo linha por linha, mas determinaram recentemente que a leitura de todo o conteúdo do arquivo para a memória primeiro foi várias vezes mais rápido. A análise já é bastante lento porque o CSV tem vírgulas incorporadas dentro de aspas, o que significa que eu tenho que usar uma fração de regex. Esta é a única que eu descobri que funciona de forma confiável:

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,)
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))");
// from http://regexlib.com/REDetails.aspx?regexp_id=621

Para fazer a análise depois de ler todo o conteúdo na memória, eu faço uma divisão corda no caractere de nova linha para obter uma matriz contendo cada linha. No entanto, quando eu faço isso no arquivo de 120 MB, recebo uma System.OutOfMemoryException. Por que ficar sem memória tão rapidamente quando meu computador tem 4 GB de RAM? Existe uma maneira melhor para rapidamente analisar um CSV complicado?

Foi útil?

Solução

Você pode obter uma OutOfMemoryException para basicamente qualquer tamanho de alocação. Quando você alocar um pedaço de memória que você está realmente pedindo uma peça contínua de memória do tamanho solicitado. Se isso não pode ser honrado você verá um OutOfMemoryException.

Você também deve estar ciente de que, a menos que você estiver executando 64 bits do Windows, o 4 GB RAM é dividida em 2 GB kernel do espaço e 2 espaço do usuário GB, para que o seu aplicativo .NET não podem acessar mais que 2 GB por padrão.

Ao fazer operações de cadeia no .NET corre o risco de criar um monte de cordas temporárias devido ao fato de que o .NET strings são imutáveis. Portanto, você pode ver aumento utilização de memória de forma bastante dramática.

Outras dicas

Não role seu próprio analisador a menos que você precisa. Eu tive sorte com esta:

A Fast CSV Leitor

Se nada mais você pode olhar sob o capô e ver como alguém faz isso.

Se você tem todo o arquivo ler em uma string você provavelmente deve usar um StringReader .

StringReader reader = new StringReader(fileContents);
string line;
while ((line = reader.ReadLine()) != null) {
    // Process line
}

Esta deve ser roughtly a mesma transmissão de um arquivo com a diferença de que os conteúdos estão na memória já.

Editar depois de testar

Tentei o acima com um arquivo de 140MB em que o tratamento consistia em incrementar variável comprimento com line.Length. Isso levou cerca de 1,6 segundos em meu computador. Depois disto, eu tentei o seguinte:

System.IO.StreamReader reader = new StreamReader("D:\\test.txt");
long length = 0;
string line;
while ((line = reader.ReadLine()) != null)
    length += line.Length;

O resultado foi cerca de 1 segundo.

É claro que sua milhagem pode variar, especialmente se você estiver lendo de uma unidade de rede ou o processamento leva tempo suficiente para disco rígido de procurar em outro lugar. Mas também se você estiver usando FileStream para ler o arquivo e você não está buffering. StreamReader fornece buffer que aumenta muito a leitura.

Você pode não ser capaz de alocar um único objeto com que a memória muito contígua, nem que você deve esperar para ser capaz de fazer. Streaming é a forma ordinária de fazer isso, mas você está certo de que poderia ser mais lento (embora eu não acho que normalmente deve ser bastante que muito mais lento.)

Como um compromisso, você pode tentar ler uma parcela maior do arquivo (mas ainda não a coisa inteira) de uma vez, com uma função como StreamReader.ReadBlock() e processamento de cada parcela, por sua vez.

Como outras pôsteres dizer, o OutOfMemory é porque ele não pode encontrar um pedaço contíguo de memória do tamanho solicitado.

No entanto, você dizer que fazer a linha de análise por linha foi várias vezes mais rápido do que ler tudo de uma vez e, em seguida, fazer o seu processamento. Isto só faz sentido se você estivesse perseguindo a abordagem ingênua de fazer o bloqueio lê, por exemplo (no código pseudo):

while(! file.eof() )
{
    string line = file.ReadLine();
    ProcessLine(line);
}

Você deveria usar streaming de, onde o fluxo é preenchido pelo Write () chamadas a partir de um segmento alternativo que está lendo o arquivo, assim que a leitura de arquivo não é bloqueado por qualquer que seja sua ProcessLine () faz, e vice-versa. Isso deve ser on-par com o desempenho de leitura do arquivo inteiro de uma vez e, em seguida, fazer o seu processamento.

Você provavelmente deve tentar o CLR profiler para determinar o seu uso de memória real. Pode ser que existem outros limites de memória do que a RAM do sistema. Por exemplo, se este é um aplicativo do IIS, sua memória é limitada pelos agrupamentos de aplicações.

Com estas informações de perfil que você pode achar que você precisa usar uma técnica mais escalável como o streaming do arquivo CSV que você originalmente tentada.

Você está ficando sem memória na pilha, não a pilha.

Você poderia tentar re-factoring seu aplicativo de tal forma que você está processando a entrada em "pedaços" mais gerenciáveis ??de dados em vez de processar 120 MB de cada vez.

Eu concordo com a maioria todo mundo aqui, você precisa usar streaming de.

Eu não sei se alguém já disse até agora, mas você deve olhar para um método exstention.

E eu sei, com certeza, sem dúvida, a melhor técnica CSV divisão em .NET / CLR é este

Essa técnica me gerado + saída XML 10GB de de CSV de entrada, incluindo filtros de entrada exstensive e tudo, mais rápido do que qualquer outra coisa que eu já vi.

Você deve ler um pedaço em um buffer e trabalhar sobre isso. Em seguida, ler um outro pedaço e assim por diante.

Existem muitas bibliotecas lá fora, que vai fazer isso de forma eficiente para você. I manter um chamado CsvHelper . Há uma série de casos de borda que você precisa para lidar com, por exemplo, quando uma vírgula ou linha final é no meio de um campo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top