Pregunta

Estoy tratando de leer algunos archivos de texto, donde cada línea debe ser procesada. Por el momento sólo estoy usando un StreamReader, y después de leer cada línea individual.

Me pregunto si hay una manera más eficiente (en términos de la Línea de Control y facilidad de lectura) para hacer esto utilizando LINQ sin comprometer la eficiencia operativa. Los ejemplos que he visto implican cargar todo el archivo en la memoria y, a continuación, procesarla. En este caso, sin embargo no creo que sería muy eficiente. En el primer ejemplo, los archivos pueden disponer de aproximadamente 50 mil, y en el segundo ejemplo, no todas las líneas del archivo necesitan ser leídos (tamaños son típicamente <10k).

Se podría argumentar que hoy en día no tiene demasiada importancia para estos pequeños archivos, sin embargo, creo que el tipo de enfoque conduce a código ineficiente.

Primer ejemplo:

// Open file
using(var file = System.IO.File.OpenText(_LstFilename))
{
    // Read file
    while (!file.EndOfStream)
    {
        String line = file.ReadLine();

        // Ignore empty lines
        if (line.Length > 0)
        {
            // Create addon
            T addon = new T();
            addon.Load(line, _BaseDir);

            // Add to collection
            collection.Add(addon);
        }
    }
}

Segundo ejemplo:

// Open file
using (var file = System.IO.File.OpenText(datFile))
{
    // Compile regexs
    Regex nameRegex = new Regex("IDENTIFY (.*)");

    while (!file.EndOfStream)
    {
        String line = file.ReadLine();

        // Check name
        Match m = nameRegex.Match(line);
        if (m.Success)
        {
            _Name = m.Groups[1].Value;

            // Remove me when other values are read
            break;
        }
    }
}
¿Fue útil?

Solución

Puede escribir un lector de línea basado en LINQ con bastante facilidad utilizando un bloque de iterador:

static IEnumerable<SomeType> ReadFrom(string file) {
    string line;
    using(var reader = File.OpenText(file)) {
        while((line = reader.ReadLine()) != null) {
            SomeType newRecord = /* parse line */
            yield return newRecord;
        }
    }
}

o para hacer feliz Jon:

static IEnumerable<string> ReadFrom(string file) {
    string line;
    using(var reader = File.OpenText(file)) {
        while((line = reader.ReadLine()) != null) {
            yield return line;
        }
    }
}
...
var typedSequence = from line in ReadFrom(path)
                    let record = ParseLine(line)
                    where record.Active // for example
                    select record.Key;

A continuación, han ReadFrom(...) como una secuencia perezosamente evaluado sin búfer, perfecto para Where etc.

Tenga en cuenta que si utiliza OrderBy o la GroupBy estándar, se tendrá que amortiguar los datos de la memoria; ifyou necesidad de agrupación y agregación, "PushLINQ" tiene algo de código de lujo para permitirle realizar agregaciones de los datos, sino que la descartes (sin almacenamiento temporal). de Jon explicación es aquí .

Otros consejos

Es más sencillo de leer una línea y comprobar si es o no es nula que para comprobar si hay EndOfStream todo el tiempo.

Sin embargo, también tengo una clase LineReader en MiscUtil que hace todo esto mucho más simple - básicamente se expone a un archivo (o una Func<TextReader> como IEnumerable<string> que le permiten hacer cosas LINQ sobre él para que pueda hacer cosas como:

.
var query = from file in Directory.GetFiles("*.log")
            from line in new LineReader(file)
            where line.Length > 0
            select new AddOn(line); // or whatever

El corazón de LineReader es esta implementación de IEnumerable<string>.GetEnumerator:

public IEnumerator<string> GetEnumerator()
{
    using (TextReader reader = dataSource())
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

Casi todo el resto de la fuente es sólo dar formas flexibles de creación de dataSource (que es un Func<TextReader>).

Nota: . Es necesario tener cuidado con la solución IEnumerable<T>, ya que resultará en el archivo está abierto durante la duración del procesamiento

Por ejemplo, con la respuesta de Marc Gravell:

foreach(var record in ReadFrom("myfile.csv")) {
    DoLongProcessOn(record);
}

el archivo permanecerá abierta durante todo el proceso.

Gracias a todos por sus respuestas! Decidí ir con una mezcla, centrándose principalmente en la de Marc embargo, ya que sólo tendrá que leer las líneas de un archivo. Creo que se puede argumentar que se necesita separación en todas partes, pero je, la vida es demasiado corta!

En cuanto al mantener el archivo abierto, que no va a ser un problema en este caso, ya que el código es parte de una aplicación de escritorio.

Por último, me di cuenta de que toda cadena en minúsculas utilizado. Sé que en Java, hay una diferencia entre la cadena capitalizado capitalizado y no, pero pensé en la secuencia de C # minúscula era sólo una referencia a la cadena en mayúsculas?

public void Load(AddonCollection<T> collection)
{
    // read from file
    var query =
        from line in LineReader(_LstFilename)
        where line.Length > 0
        select CreateAddon(line);

    // add results to collection
    collection.AddRange(query);
}

protected T CreateAddon(String line)
{
    // create addon
    T addon = new T();
    addon.Load(line, _BaseDir);

    return addon;
}

protected static IEnumerable<String> LineReader(String fileName)
{
    String line;
    using (var file = System.IO.File.OpenText(fileName))
    {
        // read each line, ensuring not null (EOF)
        while ((line = file.ReadLine()) != null)
        {
            // return trimmed line
            yield return line.Trim();
        }
    }
}

Desde .NET 4.0, la método File.ReadLines() está disponible.

int count = File.ReadLines(filepath).Count(line => line.StartsWith(">"));
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top