Scorrere il contenuto di una riga file di testo per riga - c'è una buona pratica? (Vs AssignmentInOperand di PMD)
-
10-10-2019 - |
Domanda
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:
BufferedReader br = new BufferedReader(new FileReader(file));
String line = null;
while ((line = br.readLine()) != null)
{
... // do stuff to file here
}
I corse PMD sul mio progetto ed ha ottenuto la ' 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();
}
E 'questo considerato una pratica migliore? (Anche se abbiamo "duplicato" il codice line = br.readLine()
?)
Soluzione
In generale, preferisco la prima. Io non in generale come effetti collaterali all'interno di un confronto, ma questo particolare esempio è un idioma che è così comune e così a portata di mano che non lo faccio oggetto ad esso.
(In C # c'è un'opzione 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 si può buttare IOException
dal iteratore, che significa che è possibile non solo fare un un rimpiazzo per l'altro.)
Per dirla in altro modo: i duplicati fastidi problema linea di me più di quanto l'assegnazione-entro-operando problema. Sono abituato a prendere in questo schema a colpo d'occhio - con la versione a linea duplicata ho bisogno di fermare e controllare che tutto è al posto giusto. Questo è probabilmente un'abitudine tanto quanto qualsiasi altra cosa, ma non credo che sia un problema.
Altri suggerimenti
Lo 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();
}
Controllare la documentazione: http: // Commons .apache.org / corretto / commons-IO / javadocs / api-release / org / apache / comuni / IO / LineIterator.html
Il supporto per flussi e Lambda in java-8 e Prova-Con-Resources di java-7 consente di achive quello che vuoi in più compatto sintassi.
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
}
Io abitualmente uso il costrutto while((line = br.readLine()) != null)
... ma, recente ho mai incontrato questo bel alternativa:
BufferedReader br = new BufferedReader(new FileReader(file));
for (String line = br.readLine(); line != null; line = br.readLine()) {
... // do stuff to file here
}
Questa è ancora duplicare il codice di chiamata readLine()
, ma la logica è chiara, ecc.
L'altra volta che uso il costrutto while(( ... ) ...)
è durante la lettura da un flusso in una matrice 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)) {
....
}
Non sono sicuro ho davvero preferisco le alternative per-loop .... ma, è in grado di soddisfare qualsiasi strumento PMD, e la logica è ancora chiaro, ecc.
In base a 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: Ciò sopprime IOExceptions che potrebbero verificarsi durante legge e semplicemente si ferma il processo di lettura. Non è chiaro che c'è un modo per aggirare questo in Java senza gettare eccezioni di runtime come la semantica dei metodi iteratore sono ben definiti e devono essere conformi al fine di utilizzare il for-each sintassi. Inoltre, l'esecuzione di più iteratori qui avrebbe qualche strano comportamento; quindi non sono sicuro che questo è raccomandato.
Ho fatto prova di questo, però, e funziona.
In ogni caso, 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
}
Google Guava Biblioteche fornire una soluzione alternativa utilizzando il metodo statico < a href = "http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/io/CharStreams.html#readLines%28java.lang.Readable,%20com.google .common.io.LineProcessor% 29" rel = "nofollow"> CharStreams.readLines (leggibili, LineProcessor LineProcessor<T>
per elaborare ogni riga.
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 realizzazione LineProcessor<T>
.
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();
}
}
Sono sorpreso la seguente alternativa non era menzionato un po ':
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
è una scelta migliore per le espressioni con effetti collaterali. E 'ancora una questione di idiomi, però.
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 farlo in quel modo. Nella parte mia preferisco il primo.