La migliore strategia per implementare il lettore per file di testo di grandi dimensioni

Abbiamo un’applicazione che registra i suoi passi di elaborazione in file di testo. Questi file vengono utilizzati durante l’implementazione e test per analizzare i problemi. Ogni file ha una dimensione massima di 10 MB e contiene fino a 100.000 righe di testo.

Attualmente l’analisi di questi registri viene eseguita aprendo un visualizzatore di testo (Notepad ++ ecc.) E cercando stringhe e dati specifici a seconda del problema.

Sto costruendo un’applicazione che aiuterà l’analisi. Permetterà all’utente di leggere file, cercare, evidenziare stringhe specifiche e altre operazioni specifiche legate all’isolamento del testo pertinente.

I file non saranno modificati!

Mentre giocavo un po ‘con alcuni concetti, ho scoperto immediatamente che TextBox (o RichTextBox) non gestiva molto bene la visualizzazione di testi di grandi dimensioni. Sono riuscito a implementare un visualizzatore che utilizza DataGridView con prestazioni accettabili, ma tale controllo non supporta l’evidenziazione del colore di stringhe specifiche.

Ora sto pensando di tenere l’intero file di testo in memoria come una stringa e di visualizzare solo un numero molto limitato di record in RichTextBox. Per scorrere e navigare ho pensato di aggiungere una barra di scorrimento indipendente.

Un problema che ho con questo approccio è come ottenere linee specifiche dalla stringa memorizzata.

Se qualcuno ha qualche idea, può evidenziare problemi con il mio approccio quindi grazie.

Suggerirei di caricare l’intera cosa in memoria, ma come una raccolta di stringhe piuttosto che una singola stringa. È molto facile farlo:

string[] lines = File.ReadAllLines("file.txt"); 

Quindi puoi cercare le linee corrispondenti con LINQ, visualizzarle facilmente ecc.

Ecco un approccio che si adatta bene alle CPU moderne con più core.

Si crea un blocco iteratore che produce le linee dal file di testo (o più file di testo se necessario):

 IEnumerable GetLines(String fileName) { using (var streamReader = File.OpenText(fileName)) while (!streamReader.EndOfStream) yield return streamReader.ReadLine(); } 

Quindi si utilizza PLINQ per cercare le linee in parallelo. In questo modo è ansible velocizzare notevolmente la ricerca se si dispone di una CPU moderna.

 GetLines(fileName) .AsParallel() .AsOrdered() .Where(line => ...) .ForAll(line => ...); 

Fornisci un predicato in Where corrisponde alle linee che devi estrarre. Quindi fornisci un’azione a ForAll che invierà le linee alla loro destinazione finale.

Questa è una versione semplificata di ciò che devi fare. L’applicazione è un’applicazione GUI e non è ansible eseguire la ricerca sul thread principale. Dovrai avviare un’attività in background per questo. Se si desidera che questa attività sia cancellabile, è necessario controllare un token di cancellazione nel ciclo while nel metodo GetLines .

ForAll chiamerà l’azione sui thread dal pool di thread. Se si desidera aggiungere le linee corrispondenti a un controllo dell’interfaccia utente, è necessario assicurarsi che questo controllo venga aggiornato sul thread dell’interfaccia utente. A seconda del framework UI che usi, ci sono diversi modi per farlo.

Questa soluzione presuppone che è ansible estrarre le linee necessarie eseguendo un singolo passaggio in avanti del file. Se è necessario eseguire più passaggi, in base all’input dell’utente, potrebbe essere necessario memorizzare nella cache tutte le righe del file in memoria. La memorizzazione nella cache di 10 MB non è molto, ma diciamo che decidi di cercare più file. Caching 1 GB può mettere a dura prova anche un computer potente, ma utilizzando meno memoria e più CPU, come suggerisco, ti consentirà di cercare file di grandi dimensioni in un tempo ragionevole su un PC desktop moderno.

Suppongo che, quando uno ha più gigabyte di RAM disponibili, uno sia naturalmente orientato verso il percorso “carica l’intero file in memoria”, ma qualcuno qui è veramente soddisfatto di una comprensione così superficiale del problema? Cosa succede quando questo ragazzo vuole caricare un file da 4 gigabyte? (Sì, probabilmente non è probabile, ma la programmazione riguarda spesso le astrazioni che scalano e la rapida soluzione per caricare l’intera memoria in memoria non è scalabile.)

Ci sono, ovviamente, pressioni concorrenziali: hai bisogno di una soluzione ieri o hai il lusso del tempo per scavare nel problema e imparare qualcosa di nuovo? Il framework influenza anche il tuo pensiero presentando i file in modalità blocco come flussi … devi controllare il valore BaseStream.CanSeek del stream e, se è vero, accedere al metodo BaseStream.Seek () per ottenere accesso casuale. Non fraintendetemi, adoro assolutamente il framework .NET, ma vedo un cantiere in cui un gruppo di “carpentieri” non può montare il canvasio di una casa perché il compressore d’aria è rotto e non lo fanno sapere come usare un martello. Wax-on, wax-off, insegnare a un uomo a pescare, ecc.

Quindi se hai tempo, guarda in una finestra scorrevole. Probabilmente puoi farlo nel modo più semplice usando un file mappato in memoria (lascia che il framework / OS gestisca la finestra scorrevole), ma la soluzione divertente è scrivere da solo. L’idea di base è che hai una piccola porzione del file caricata in memoria in qualsiasi momento (la parte del file che è visibile nella tua interfaccia con forse un piccolo buffer su entrambi i lati). Man mano che avanzi nel file, puoi salvare gli offset all’inizio di ogni riga in modo da poter facilmente cercare una qualsiasi sezione precedente del file.

Sì, ci sono implicazioni sul rendimento … benvenuti nel mondo reale in cui ci si trova di fronte a vari requisiti e vincoli e dobbiamo trovare l’equilibrio accettabile tra il tempo e l’utilizzo della memoria. Questo è il divertimento della programmazione … capire i vari modi in cui un objective può essere raggiunto e imparare quali sono i compromessi tra i vari percorsi. È così che cresci oltre i livelli di abilità di quel ragazzo in ufficio che vede ogni problema come un chiodo perché sa solo come usare un martello.

[/ Rant]

Vorrei suggerire di utilizzare MemoryMappedFile in .NET 4 (o tramite DllImport nelle versioni precedenti) per gestire solo una piccola porzione di file visibile sullo schermo invece di sprecare memoria e tempo con il caricamento dell’intero file.