¿Cuál es la forma más rápida de analizar texto con delimitadores personalizados y algunos valores de campo muy, muy grandes en C #?

StackOverflow https://stackoverflow.com/questions/339496

Pregunta

He estado tratando de lidiar con algunos archivos de texto delimitados que tienen delimitadores no estándar (no delimitados por comas / comillas o tabulaciones). Los delimitadores son caracteres ASCII aleatorios que no aparecen a menudo entre los delimitadores. Después de buscar, parece que no he encontrado soluciones en .NET que satisfagan mis necesidades y las bibliotecas personalizadas que la gente ha escrito para esto parecen tener algunos defectos cuando se trata de una entrada gigantesca (archivo de 4GB con algunos valores de campo que tienen muy fácilmente varios millones de caracteres).

Si bien esto parece ser un poco extremo, en realidad es un estándar en la industria de detección electrónica de documentos (EDD) para algunos programas de revisión que tienen valores de campo que contienen el contenido completo de un documento. Como referencia, anteriormente hice esto en python usando el módulo csv sin problemas.

Aquí hay una entrada de ejemplo:

Field delimiter = 
quote character = þ

þFieldName1þþFieldName2þþFieldName3þþFieldName4þ
þValue1þþValue2þþValue3þþSomeVery,Very,Very,Large value(5MB or so)þ
...etc...

Editar: Así que seguí adelante y creé un analizador de archivos delimitado desde cero. Estoy un poco cansado de usar esta solución, ya que puede ser propenso a errores. Tampoco se siente "elegante" o corregir tener que escribir mi propio analizador para una tarea como esta. También tengo la sensación de que probablemente no tuve que escribir un analizador desde cero para esto de todos modos.

¿Fue útil?

Solución

Utilice la API de ayudantes de archivo . Es .NET y de código abierto. Tiene un rendimiento extremadamente alto al usar código IL compilado para establecer campos en objetos fuertemente tipados y es compatible con la transmisión.

Admite todo tipo de tipos de archivo y delimitadores personalizados; Lo he usado para leer archivos de más de 4 GB.

Si por alguna razón eso no lo hace por usted, intente leer línea por línea con un string.split:

public IEnumerable<string[]> CreateEnumerable(StreamReader input)
{
    string line;
    while ((line = input.ReadLine()) != null)
    {
        yield return line.Split('þ');
    }
}

Eso le dará arreglos de cadenas simples que representan las líneas de una manera fluida en la que puede incluso Linq;) Recuerde, sin embargo, que IEnumerable está cargado de manera diferida, así que no cierre ni modifique StreamReader hasta que haya iterado ( o provocó una operación de carga completa como ToList / ToArray o similar; sin embargo, dado su tamaño de archivo, ¡supongo que no lo hará!).

Aquí hay una buena muestra de uso:

using (StreamReader sr = new StreamReader("c:\\test.file"))
{
    var qry = from l in CreateEnumerable(sr).Skip(1)
              where l[3].Contains("something")
              select new { Field1 = l[0], Field2 = l[1] };
    foreach (var item in qry)
    {
        Console.WriteLine(item.Field1 + " , " + item.Field2);
    }
}
Console.ReadLine();

Esto omitirá la línea del encabezado, luego imprimirá los dos primeros campos del archivo donde el cuarto campo contiene la cadena '' algo ''. Lo hará sin cargar todo el archivo en la memoria.

Otros consejos

Windows y E / S de alto rendimiento significa, use puertos IO Completion . Es posible que tenga que realizar algunas tuberías adicionales para que funcione en su caso.

Esto es con el entendimiento de que desea usar C # /. NET, y de acuerdo con Joe Duffy

  

18) No utilice llamadas de procedimiento asincrónico (APC) de Windows en   código.

Tuve que aprenderlo de la manera difícil;), pero descartando el uso de APC, IOCP es la única opción sensata. También es compatible con muchos otros tipos de E / S, que se usan con frecuencia en servidores de socket.

En cuanto al análisis del texto real, consulte Blog de Eric White para un uso optimizado de la transmisión.

Me inclinaría a usar una combinación de archivos de mapas de memoria ( punto msdn a un contenedor .NET aquí ) y un análisis incremental simple, que devuelve a una lista IEnumerable de su línea de registro / texto (o lo que sea)

Usted menciona que algunos campos son muy, muy grandes, si intenta leerlos en su totalidad en la memoria puede estar metiéndose en problemas. Leería el archivo en 8K (o pequeños fragmentos), analizaría el búfer actual y realizaría un seguimiento del estado.

¿Qué intentas hacer con estos datos que estás analizando? ¿Estás buscando algo? ¿Lo estás transformando?

No veo ningún problema con usted escribiendo un analizador personalizado. Los requisitos parecen lo suficientemente diferentes a cualquier cosa que ya haya proporcionado el BCL, así que adelante.

" Elegancia " Obviamente es una cosa subjetiva. En mi opinión, si la API de su analizador se ve y funciona como una API de tipo "lector" BCL estándar, entonces eso es bastante "elegante".

En cuanto a los grandes tamaños de datos, haga que su analizador funcione leyendo un byte a la vez y use una máquina de estado simple para determinar qué hacer. Deje la transmisión y el almacenamiento en búfer a la clase subyacente FileStream . Debería estar bien con el rendimiento y el consumo de memoria.

Ejemplo de cómo podría usar una clase de analizador de este tipo:

using(var reader = new EddReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, 8192)) {
    // Read a small field
    string smallField = reader.ReadFieldAsText();
    // Read a large field
    Stream largeField = reader.ReadFieldAsStream();
}

Si bien esto no ayuda a abordar el gran problema de entrada, una posible solución al problema de análisis podría incluir un analizador personalizado que utilice el patrón de estrategia para proporcionar un delimitador.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top