Qual è il modo più veloce per analizzare il testo con delimitatori personalizzati e alcuni valori di campo molto, molto grandi in C #?

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

Domanda

Ho provato a gestire alcuni file di testo delimitati che hanno delimitatori non standard (non delimitati da virgole / virgolette o tabulazioni). I delimitatori sono caratteri ASCII casuali che non vengono visualizzati spesso tra i delimitatori. Dopo aver cercato in giro, sembra che non abbia trovato nessuna soluzione in .NET adatta alle mie esigenze e le librerie personalizzate che le persone hanno scritto per questo sembrano avere alcuni difetti quando si tratta di input giganteschi (file da 4 GB con alcuni valori di campo con molto facilmente diversi milioni di caratteri).

Anche se questo sembra essere un po 'estremo, in realtà è uno standard nel settore della scoperta di documenti elettronici (EDD) per alcuni software di revisione avere valori di campo che contengono l'intero contenuto di un documento. Per riferimento, ho già fatto questo in Python usando il modulo CSV senza problemi.

Ecco un esempio di input:

Field delimiter = 
quote character = þ

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

Modifica: Quindi sono andato avanti e ho creato un parser di file delimitato da zero. Sono stanco di usare questa soluzione in quanto potrebbe essere soggetto a bug. Inoltre, non si sente "elegante" o corretto dover scrivere il mio parser per un'attività come questa. Ho anche la sensazione che probabilmente non avrei dovuto scrivere un parser da zero per questo comunque.

È stato utile?

Soluzione

Utilizza l ' API di File Helpers . È .NET e open source. È estremamente performante utilizzando il codice IL compilato per impostare campi su oggetti fortemente tipizzati e supporta lo streaming.

Supporta tutti i tipi di tipi di file e delimitatori personalizzati; L'ho usato per leggere file di dimensioni superiori a 4 GB.

Se per qualche motivo che non lo fa per te, prova a leggere riga per riga con string.split:

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

Questo ti darà semplici matrici di stringhe che rappresentano le linee in modo fluido in cui puoi persino Linq;) Ricorda tuttavia che IEnumerable è caricato in modo lazy, quindi non chiudere o modificare StreamReader fino a quando non hai ripetuto ( o ha causato un'operazione a pieno carico come ToList / ToArray o simili - data la dimensione del tuo file, tuttavia, suppongo che non lo farai!).

Ecco un buon esempio di esso:

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();

Questo salterà la riga di intestazione, quindi stampa i primi due campi dal file in cui il quarto campo contiene la stringa "qualcosa". Lo farà senza caricare l'intero file in memoria.

Altri suggerimenti

Windows e I / O ad alte prestazioni significano utilizzare Completamento IO . Potrebbe essere necessario un altro impianto idraulico per farlo funzionare nel tuo caso.

Questo è con la consapevolezza che si desidera utilizzare C # /. NET e in base a Joe Duffy

  

18) Non utilizzare le Chiamate di procedura asincrone (APC) di Windows in gestite   codice.

Ho dovuto imparare quello nel modo più difficile;), ma escludendo l'uso di APC, IOCP è l'unica opzione sana. Supporta anche molti altri tipi di I / O, usati frequentemente nei server socket.

Per quanto riguarda l'analisi del testo effettivo, controlla il blog di Eric White per un uso semplificato del flusso.

Sarei propenso a utilizzare una combinazione di file di memoria mappati ( punto msdn a un wrapper .NET qui ) e una semplice analisi incrementale, che riporta a un elenco IEnumerable del tuo record / riga di testo (o qualsiasi altra cosa)

Dici che alcuni campi sono molto grandi, se provi a leggerli nella loro interezza in memoria potresti essere nei guai. Vorrei leggere il file in 8K (o piccoli blocchi), analizzare il buffer corrente, tenere traccia dello stato.

Cosa stai cercando di fare con questi dati che stai analizzando? Stai cercando qualcosa? Lo stai trasformando?

Non vedo problemi a scrivere un parser personalizzato. I requisiti sembrano sufficientemente diversi da qualsiasi cosa già fornita dal BCL, quindi vai avanti.

" eleganza " è ovviamente una cosa soggettiva. Secondo me, se l'API del tuo parser sembra e funziona come un'API di tipo "BCL" di tipo "BCL standard", allora è abbastanza "elegante".

Per quanto riguarda le grandi dimensioni dei dati, fai funzionare il tuo parser leggendo un byte alla volta e usa una semplice macchina a stati per capire cosa fare. Lascia lo streaming e il buffering alla classe FileStream sottostante. Dovresti essere OK con prestazioni e consumo di memoria.

Esempio di come è possibile utilizzare una tale classe di parser:

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();
}

Anche se questo non aiuta a risolvere il grande problema di input, una possibile soluzione al problema di analisi potrebbe includere un parser personalizzato che utilizza il modello di strategia per fornire un delimitatore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top