2011-01-13 7 views
32

Abbiamo un'applicazione Java che ha un paio di moduli che sanno leggere i file di testo. Lo fanno semplicemente con un codice come questo:Iterare il contenuto di un file di testo riga per riga: esiste una best practice? (Vs AssignmentInOperand del PMD)

BufferedReader br = new BufferedReader(new FileReader(file)); 
String line = null; 
while ((line = br.readLine()) != null) 
{ 
    ... // do stuff to file here 
} 

ho corse PMD sul mio progetto e ha ottenuto il 'AssignmentInOperand' violazione sulla linea while (...).

C'è un modo più semplice di fare questo circuito diverso da quello ovvio:

String line = br.readLine(); 
while (line != null) 
{ 
    ... // do stuff to file here 
    line = br.readLine(); 
} 

È questo considerato una pratica migliore? (Anche se abbiamo "duplicato" il codice line = br.readLine()?)

+0

Nice BufferedReaderIterator. Ho dovuto sostituire r.mark (1) con r.mark (2), altrimenti avrei un "segno non valido" di circa 100 righe in un file di grandi dimensioni. Non capisco perché. –

+4

Che ne dici di un ciclo 'for'? 'for (String line = br.readLine(); line! = null; line = br.readLine()) {...}' –

risposta

19

io in genere preferisco la prima. Non faccio generalmente come effetti collaterali di un confronto, ma questo particolare esempio è un linguaggio che è così comune e così pratico che Non sono contrario ad esso.

(In C# c'è una possibilità più bello: un metodo per restituire un IEnumerable<string> cui è possibile iterare con foreach, che non è così bello in Java perché non c'è auto-smaltire alla fine di una maggiore ciclo for .. e anche perché non è possibile lanciare IOException dall'iteratore, il che significa che non è possibile crearne uno sostitutivo per l'altro.)

Per dirla in altro modo: il problema della riga duplicata mi disturba di più rispetto al problema di assegnazione all'interno dell'operando. Sono abituato a cogliere questo schema a colpo d'occhio - con la versione di linea duplicata che ho bisogno di fermare e controllare che tutto sia nel posto giusto. Probabilmente è abitudine come qualsiasi altra cosa, ma non penso che sia un problema.

+0

Sono curioso di sapere cosa ne pensi della creazione di un decoratore come meccanismo di convenienza che astrae il semantica dell'iterazione in modo che tu possa usare un ciclo foreach (vedi la mia risposta con un * rough * suggerimento sotto) ... –

+0

@ Mark E: Non è così pulito come la versione C#, ma non male - tranne che per le eccezioni. Commenterò la tua risposta e modificherò la mia. –

2

AssignmentInOperand è una regola controverso PMD, la ragione di questa regola è: "questo può rendere il codice più complicato e difficile da leggere" (si veda http://pmd.sourceforge.net/rules/controversial.html)

Si potrebbe disabilitare questa regola se si vuole veramente fai così. Nella mia parte preferisco la prima.

+1

Oppure metti un commento '// NOPMD' –

4

in base alla risposta di Jon ho avuto modo di pensare dovrebbe essere abbastanza facile creare un decoratore di agire come un file iteratore in modo da poter utilizzare un ciclo foreach:

public class BufferedReaderIterator implements Iterable<String> { 

    private BufferedReader r; 

    public BufferedReaderIterator(BufferedReader r) { 
     this.r = r; 
    } 

    @Override 
    public Iterator<String> iterator() { 
     return new Iterator<String>() { 

      @Override 
      public boolean hasNext() { 
       try { 
        r.mark(1); 
        if (r.read() < 0) { 
         return false; 
        } 
        r.reset(); 
        return true; 
       } catch (IOException e) { 
        return false; 
       } 
      } 

      @Override 
      public String next() { 
       try { 
        return r.readLine(); 
       } catch (IOException e) { 
        return null; 
       } 
      } 

      @Override 
      public void remove() { 
       throw new UnsupportedOperationException(); 
      } 

     }; 
    } 

} 

Fair Warning: questa sopprime IOExceptions che potrebbero si verificano durante la lettura e interrompe semplicemente il processo di lettura. Non è chiaro che ci sia un modo per aggirare questo in Java senza generare eccezioni di runtime poiché la semantica dei metodi iteratori è ben definita e deve essere conforme per poter utilizzare la sintassi for-each. Inoltre, eseguire più iteratori qui avrebbe un comportamento strano; quindi non sono sicuro che sia raccomandato.

ho fatto test di questo, però, e funziona.

Ad ogni modo, si ottiene il vantaggio di for-each sintassi di utilizzare questo come una sorta di decoratrice:

for(String line : new BufferedReaderIterator(br)){ 
    // do some work 
} 
+0

Sospetto che questo non verrà compilato, perché' readLine' potrebbe lanciare IOException. L'interfaccia Iterator non lo permetterà, quindi dovresti avvolgerlo in un'eccezione non controllata, a quel punto inizia a sembrare sempre meno simile al codice originale :( –

+0

@Jon: hai ragione, e sfortunatamente io Sono abbastanza sicuro che non ci sia modo di nascondere le eccezioni per ottenere la semantica, mentre è conveniente che il guadagno appaia triste –

29

quello che so è un vecchio post, ma ho appena avuto la stessa necessità (quasi) e mi risolverlo utilizzando un LineIterator da FileUtils in Apache Commons. Dal loro javadoc:

LineIterator it = FileUtils.lineIterator(file, "UTF-8"); 
try { 
    while (it.hasNext()) { 
    String line = it.nextLine(); 
    // do something with line 
    } 
} finally { 
    it.close(); 
} 

Consultare la documentazione: http://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/LineIterator.html

+0

Grazie, può tornare utile in futuro. – RonK

+1

Nel caso in cui qualcuno ha bisogno di questa dipendenza (fileutils) da Maven: commons-io commons-io 2,4

+0

Sono abbastanza certo che si può fare lo stesso genere di cose con un 'Scanner anche questo –

3

Google Guava Libraries fornire una soluzione alternativa utilizzando il metodo statico CharStreams.readLines(Readable, LineProcessor<T>) con un'implementazione di LineProcessor<T> per la lavorazione di ogni linea.

try (BufferedReader br = new BufferedReader(new FileReader(file))) { 
    CharStreams.readLines(br, new MyLineProcessorImpl()); 
} catch (IOException e) { 
    // handling io error ... 
} 

Il corpo del ciclo while si trova ora nella LineProcessor<T> attuazione.

class MyLineProcessorImpl implements LineProcessor<Object> { 

    @Override 
    public boolean processLine(String line) throws IOException { 
     if (// check if processing should continue) { 
      // do sth. with line 
      return true; 
     } else { 
      // stop processing 
      return false; 
     } 
    } 

    @Override 
    public Object getResult() { 
     // return a result based on processed lines if needed 
     return new Object(); 
    } 
} 
13

Io abitualmente usa la while((line = br.readLine()) != null) costrutto ... ma, recently I came accross this nice alternative:

BufferedReader br = new BufferedReader(new FileReader(file)); 

for (String line = br.readLine(); line != null; line = br.readLine()) { 
    ... // do stuff to file here 
} 

Questo è ancora duplicare il codice readLine() chiamata, ma la logica è chiara, ecc

L'altra volta che utilizzo il costrutto while((...) ...) durante la lettura da un flusso in un array byte[] ...

byte[] buffer = new byte[size]; 
InputStream is = .....; 
int len = 0; 
while ((len = is.read(buffer)) >= 0) { 
    .... 
} 

questo può anche essere trasformato in un ciclo for con:

byte[] buffer = new byte[size]; 
InputStream is = .....; 
for (int len = is.read(buffer); len >= 0; len = is.read(buffer)) { 
    .... 
} 

io non sono sicuro che davvero preferisco le alternative per-loop .... ma, è in grado di soddisfare qualsiasi strumento PMD, e la la logica è ancora chiaro, ecc

+1

Approccio gradevole! È anche possibile eseguire il wrapping della creazione dell'istanza' BufferedReader' con l'istruzione try-with-resources se è utilizzata con Java 7, ridurrà l'ambito della variabile e aggiungi automaticamente chiudi il lettore tutte le linee sono gestite –

11

Il supporto per streams e Lambdas in java-8 e Try-With-Resources di java-7 si permette di achive ciò che si vuole nella sintassi più compatta.

Path path = Paths.get("c:/users/aksel/aksel.txt"); 

try (Stream<String> lines = Files.lines(path)) { 
    lines.forEachOrdered(line->System.out.println(line)); 
} catch (IOException e) { 
    //error happened 
} 
+1

Puoi anche abbreviare la lambda a un metodo di riferimento: 'lines.forEachOrdered (System.out :: println)' –

3

io sono un po 'sorpreso la seguente alternativa non è stato menzionato:

while(true) { 
    String line = br.readLine(); 
    if (line == null) break; 
    ... // do stuff to file here 
} 

Prima di Java 8 è stato il mio preferito per la sua chiarezza e che non richiedono la ripetizione. IMO, break è un'opzione migliore per le espressioni con effetti collaterali. È ancora una questione di idiomi, però.

Problemi correlati