2011-01-20 13 views
13

Scenario: 150 MB di file di testo che è la Posta in arrivo esportata di un vecchio account di posta elettronica. È necessario analizzare ed estrarre le email da un utente specifico e scriverle in un nuovo file singolo. Ho un codice che funziona, è solo ostinato.Ottimizza file C# IO

Sto utilizzando le stringhe di indicatore per cercare da dove iniziare/terminare la copia dal file originale.

Ecco la funzione principale:

StreamReader sr = new StreamReader("c:\\Thunderbird_Inbox.txt"); 
     string working = string.Empty; 
     string mystring = string.Empty; 
     while (!sr.EndOfStream) 
     { 
      while ((mystring = sr.ReadLine()) != null) 
      { 
       if (mystring == strBeginMarker) 
       { 
        writeLog(mystring); 

        //read the next line 
        working = sr.ReadLine(); 

         while(!(working.StartsWith(strEndMarker))) 
         { 
          writeLog(working); 
          working = sr.ReadLine(); 

         } 
        } 
      } 

     } 
     this.Text = "DONE!!"; 
     sr.Close(); 

La funzione che scrive i messaggi selezionati per il nuovo file:

public void writeLog(string sMessage) 
    { 
      fw = new System.IO.StreamWriter(path, true); 
      fw.WriteLine(sMessage); 
      fw.Flush(); 
      fw.Close(); 
    } 

Ancora una volta, questo processo funziona. Ottengo un buon file di output, ci vuole solo molto tempo e sono sicuro che ci sono modi per renderlo più veloce.

+0

BTW - È possibile prendere in considerazione l'istruzione using anziché Close() manualmente: è più sicuro se si verifica un'eccezione. Il mio esempio dimostra ... –

+1

'while (! Sr.EndOfStream)' è ridondante con 'while ((mystring = sr.ReadLine())! = Null)' –

risposta

19

L'ottimizzazione più grande sarebbe quella di modificare il metodo writeLog per aprire il file una volta all'inizio di questa operazione, scrivervi più volte, quindi chiuderlo alla fine.

In questo momento, si sta aprendo e chiudendo il file ogni iterazione in cui si scrive, che sicuramente rallenterà le cose.

provare quanto segue:

// Open this once at the beginning! 
using(fw = new System.IO.StreamWriter(path, true)) 
{ 
    using(StreamReader sr = new StreamReader("c:\\Thunderbird_Inbox.txt")) 
    { 
     string working; 
     string mystring; 
     while ((mystring = sr.ReadLine()) != null) 
     { 
      if (mystring == strBeginMarker) 
      { 
       writeLog(mystring); 

       //read the next line 
       working = sr.ReadLine(); 

       while(!(working.StartsWith(strEndMarker))) 
       { 
        fw.WriteLine(working); 
        working = sr.ReadLine(); 
       } 
      } 
     } 
    } 
} 
this.Text = "DONE!!"; 
+0

+1 - Batti ad esso. – ChaosPandion

+0

QUESTA CAMBIA TUTTO! :-) Risposta fantastica. Ciò che è stato necessario per 7 o 8 minuti è terminato in circa 2 secondi. Ancora meglio, ho imparato alcune preziose tecniche di codifica. – paparush

+0

@paparush: Siamo contenti di poter aiutare;) –

0

non ho un file di testo 150MB per testare, ma se il server dispone la memoria sarebbe Leggendo la cosa presa in una stringa e facendo una RegEx tirando fuori il lavoro messaggio ?

+1

RegEx funzionerebbe, ma possono essere abbastanza complessi e difficili da ottenere "perfettamente". La soluzione parser che ho dato è essenzialmente la stessa idea, ma più semplice per un programmatore meno esperto. –

0

È possibile dichiarare semplicemente l'oggetto StreamWriter al di fuori del ciclo while e scrivere semplicemente la linea all'interno del loop.

Ti piace questa:

StreamWriter sw = new StreamWriter(path, true); 
while 
{ 
    // ... 
    while(!(working.StartsWith(strEndMarker))) 
    { 
     sw.WriteLine(working); 
     working = sr.ReadLine(); 
    } 
} 
2

Penso che si dovrebbe:

  1. Aprire i file una volta.
  2. Carica il file sorgente nella memoria.
  3. Interrompila e utilizza più thread per l'elaborazione.
+1

Mentre mi piace questa risposta, in teoria - in pratica, probabilmente non sarà di grande aiuto. È probabile che l'OP sia ancora completamente legato all'IO in uscita (dal momento che è stato scritto su un singolo file di output), quindi il multithreading probabilmente produrrà pochissimi benefici. Non so se ne varrebbe la pena. –

+0

Sono d'accordo con Reed qui. Come lo romperesti in fili? Dovresti ancora avere la logica per farlo. Se lo si spezza in porzioni di dimensioni uguali, è necessario gestire il caso in cui un singolo messaggio inizia in un blocco e termina in un altro. Se hai fatto una logica per romperlo al limite di un marker, stai pre-analisi prima di aggiungere l'overhead del threading, probabilmente rendendolo * meno * efficiente. Questo non vuol dire che non si possa fare - è solo molto più lavoro di quanto sembri. –

+0

@Wonko the Sane, penso che il modo più semplice sia il primo: suddividerlo per parti uguali e trovare l'inizio del primo messaggio in ciascuno, secondo - iniziare il processo dal primo messaggio di ogni sezione. – acoolaum

2

Vorrei fare un semplice parser. Nota che questo presuppone (come fai nel tuo codice sopra) che i marcatori sono in effetti unici.

Potrebbe essere necessario giocare con la formattazione di un po 'della vostra uscita, ma qui è l'idea generale:

// Read the entire file and close it 
    using (StreamReader sr = new 
    StreamReader("c:\\Thunderbird_Inbox.txt");) 
    { 
     string data = sr.ReadToEnd(); 
    } 

    string newData = ""; 
    int position = data.IndexOf(strBeginMarker); 

    while (position > 0) 
    { 
     int endPosition = data.IndexOf(endMarker, position); 
     int markerLength = position + strBeginMarker.Length; 

    newData += data.Substring(markerLength, endPosition - markerLength); 

    position = data.IndexOf(strBeginMarker, position+ endStr.Length); 
    } 

    writeLog(newData); 

(Si noti che non ho un file di 150 MB per testare questo su - YMMV a seconda della macchina che si sta utilizzando).

+0

Si dovrebbe almeno avvisare OP sui potenziali pericoli del caricamento di un file da 150 MB in una sola volta tramite sr.ReadToEnd() ... –

+0

Appena fatto (in un modo molto semplice) –