2015-07-06 12 views
5

Come posso leggere una stringa molto lunga dal file di testo e quindi elaborarla (divisa in parole)?Come dividere un file enorme in parole?

Ho provato il metodo StreamReader.ReadLine(), ma ottengo un'eccezione OutOfMemory. Apparentemente, le mie battute sono estremamente lunghe. Questo è il mio codice per la lettura del file:

using (var streamReader = File.OpenText(_filePath)) 
    { 

     int lineNumber = 1; 
     string currentString = String.Empty; 
     while ((currentString = streamReader.ReadLine()) != null) 
     { 

      ProcessString(currentString, lineNumber); 
      Console.WriteLine("Line {0}", lineNumber); 
      lineNumber++; 
     } 
    } 

E il codice che si divide la linea in parole:

var wordPattern = @"\w+"; 
var matchCollection = Regex.Matches(text, wordPattern); 
var words = (from Match word in matchCollection 
      select word.Value.ToLowerInvariant()).ToList(); 
+0

Quale algoritmo/metodo si è utilizzato per l'operazione di suddivisione? – byako

+0

@byako, qui è il metodo che uso per questo: 'pubblici static IEnumerable GetLowercasedWords (stringa di testo) { se (String.IsNullOrEmpty (testo)) { ritorno null; } var wordPattern = @ "\ w +"; var matchCollection = Regex.Matches (testo, wordPattern); var words = (dalla parola corrispondente a matchCollection selezionare word.Value.ToLowerInvariant()). ToList(); parole di ritorno; } 'Ci scusiamo per la formattazione. –

+0

Meglio aggiungere come è stato implementato lo StreamReader, in modo che le persone possano trovare errori lì. Non c'è un limite di 140 caratteri per le domande. – Jens

risposta

5

Si potrebbe leggere da char, costruendo parole, come si va, utilizzando yield per renderlo differita in modo da non dover leggere l'intero file in una sola volta:

private static IEnumerable<string> ReadWords(string filename) 
{ 
    using (var reader = new StreamReader(filename)) 
    { 
     var builder = new StringBuilder(); 

     while (!reader.EndOfStream) 
     { 
      char c = (char)reader.Read(); 

      // Mimics regex /w/ - almost. 
      if (char.IsLetterOrDigit(c) || c == '_') 
      { 
       builder.Append(c); 
      } 
      else 
      { 
       if (builder.Length > 0) 
       { 
        yield return builder.ToString(); 
        builder.Clear(); 
       } 
      } 
     } 

     yield return builder.ToString(); 
    } 
} 

Il codice legge il file da carattere, e quando incontra un carattere diverso da una parola, verrà convertita in yield return fino a quel momento (solo per il primo carattere non lettera). Il codice utilizza uno StringBuilder per creare la stringa di parole.

Char.IsLetterOrDigit() si comporta come the regex word character w per i caratteri, ma i caratteri di sottolineatura (tra gli altri) rientrano anche in quest'ultima categoria. Se il tuo input contiene più caratteri che desideri includere, dovrai modificare lo if().

+2

Non sarebbe 'StringBuilder' un'opzione migliore per questo approccio? –

+2

Stai attento! Questo non è l'equivalente di '\ w +' in quanto non gestisce nulla tranne gli spazi (ad esempio trattini, punteggiatura). – Bas

+0

'\ w' include anche cifre e trattino basso. – Bas

0

Cut in sezioni di dimensioni bit. In modo che invece di provare a leggere 4gb, che credo sia circa la dimensione di una pagina, prova a leggere 8 blocchi da 500mb e questo dovrebbe aiutare.

+0

Devo dire che non lo dividerete in blocchi precisi. Ma relativamente abbastanza pezzi. Il motivo per cui lo dico è che se hai un taglio da 500 mb vorresti dividere il file alla fine o all'inizio di una parola, non al centro. Quindi non limitarti a tagliare il file. Fatelo in modo più intelligente. – trinityalps

+0

hai ragione. E questo è il motivo principale per cui ho incontrato difficoltà –

0

La raccolta dei rifiuti può essere una soluzione. Non sono davvero sicuro che sia la fonte del problema. Ma se è così, un semplice GC.Collect è spesso insufficiente e, per ragioni di prestazioni, dovrebbe essere chiamato solo se realmente richiesto. Provare la seguente procedura che chiama il garbage quando la memoria disponibile è troppo bassa (sotto la soglia fornita come parametro di procedura).

int charReadSinceLastMemCheck = 0 ; 
using (var streamReader = File.OpenText(_filePath)) 
{ 

    int lineNumber = 1; 
    string currentString = String.Empty; 
    while ((currentString = streamReader.ReadLine()) != null) 
    { 

     ProcessString(currentString, lineNumber); 
     Console.WriteLine("Line {0}", lineNumber); 
     lineNumber++; 
     totalRead+=currentString.Length ; 
     if (charReadSinceLastMemCheck>1000000) 
     { // Check memory left every Mb read, and collect garbage if required 
      CollectGarbage(100) ; 
      charReadSinceLastMemCheck=0 ; 
     } 
    } 
} 


internal static void CollectGarbage(int SizeToAllocateInMo) 
{ 
     long [,] TheArray ; 
     try { TheArray =new long[SizeToAllocateInMo,125000]; }low function 
     catch { TheArray=null ; GC.Collect() ; GC.WaitForPendingFinalizers() ; GC.Collect() ; } 
     TheArray=null ; 
} 
+0

@CodeCaster: hai scritto nel tuo commento rimosso "Non dovresti crossare il codice voodoo che hai trovato su un forum francese". Se è contro i principi di StackOverflow duplicare il codice che hai già postato in un altro forum, lascerò velocemente il forum. Se guardi l'autore della risposta del forum francese, vedrai che il suo nome è anche "Graffito". Ma sicuramente pensi che sia un altro ragazzo. – Graffito

+1

Graffito, il metodo CollectGarbage è il diavolo. Lo riconosco dalla tua risposta precedente. – usr