Итерация по содержимому текстового файла по строкам - есть ли наилучшая практика? (по сравнению с назначением PMDInOperand)
У нас есть приложение Java, в котором есть несколько модулей, которые умеют читать текстовые файлы. Они делают это довольно просто с таким кодом:
BufferedReader br = new BufferedReader(new FileReader(file));
String line = null;
while ((line = br.readLine()) != null)
{
... // do stuff to file here
}
Я выполнил PMD в моем проекте и получил нарушение < AssignmentInOperand 'в строке while (...)
.
Есть ли более простой способ сделать этот цикл, кроме очевидного:
String line = br.readLine();
while (line != null)
{
... // do stuff to file here
line = br.readLine();
}
Является ли это лучшей практикой? (хотя мы "дублируем" код line = br.readLine()
?)
Ответы
Ответ 1
Обычно я предпочитаю первое. Обычно мне не нравятся побочные эффекты в сравнении, но этот конкретный пример - это идиома, которая настолько распространена и настолько удобна, что я не возражаю против нее.
(В С# есть более приятная опция: метод для возврата IEnumerable<string>
, который вы можете перебирать с помощью foreach; это не так приятно в Java, потому что в конце цикла for нет автоматического удаления... а также потому, что вы не можете выкинуть IOException
из итератора, а это значит, что вы не можете просто сделать замену для другого.)
Иными словами, проблема с дублирующейся строкой меня беспокоит больше, чем проблема с присваиванием внутри операнда. Я привык рассматривать этот шаблон с первого взгляда - с дублированной версией строки мне нужно остановиться и проверить, что все в нужном месте. Это, вероятно, привычка, как и все остальное, но я не думаю, что это проблема.
Ответ 2
Я знаю, это старый пост, но у меня была такая же потребность (почти), и я решаю его с помощью LineIterator из FileUtils в Apache Commons.
Из их javadoc:
LineIterator it = FileUtils.lineIterator(file, "UTF-8");
try {
while (it.hasNext()) {
String line = it.nextLine();
// do something with line
}
} finally {
it.close();
}
Проверьте документацию:
http://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/LineIterator.html
Ответ 3
Я обычно использую конструкцию while((line = br.readLine()) != null)
... но, недавно я прошел через эту приятную альтернативу:
BufferedReader br = new BufferedReader(new FileReader(file));
for (String line = br.readLine(); line != null; line = br.readLine()) {
... // do stuff to file here
}
Это все еще дублирует код вызова readLine()
, но логика понятна и т.д.
В другой раз я использую конструкцию while(( ... ) ...)
при чтении из потока в массив byte[]
...
byte[] buffer = new byte[size];
InputStream is = .....;
int len = 0;
while ((len = is.read(buffer)) >= 0) {
....
}
Это также можно преобразовать в цикл for с помощью:
byte[] buffer = new byte[size];
InputStream is = .....;
for (int len = is.read(buffer); len >= 0; len = is.read(buffer)) {
....
}
Я не уверен, что мне действительно нравятся альтернативы for-loop.... но он удовлетворит любой инструмент PMD, и логика все еще понятна и т.д.
Ответ 4
Поддержка потоков и Lambdas в java- 8 и Try-With-Resources java-7 позволяет вам добиться того, что вы хотите в более компактном синтаксисе.
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
}
Ответ 5
Основываясь на ответе Джона, я подумал, что должно быть достаточно легко создать декоратор, чтобы действовать как итератор файла, чтобы вы могли использовать цикл 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();
}
};
}
}
Справедливое предупреждение: это подавляет IOExceptions, которые могут возникать во время чтения, и просто останавливает процесс чтения. Неясно, существует ли способ обойти это на Java без исключения исключений во время выполнения, поскольку семантика методов итератора хорошо определена и должна быть согласована с тем, чтобы использовать синтаксис for-each. Кроме того, запуск нескольких итераторов здесь будет иметь странное поведение; поэтому я не уверен, что это рекомендуется.
Я действительно тестировал это, и он работает.
В любом случае, вы получаете преимущество для каждого синтаксиса, используя это как своего рода декоратор:
for(String line : new BufferedReaderIterator(br)){
// do some work
}
Ответ 6
Google Гуава-библиотеки предоставляют альтернативное решение, используя статический метод CharStreams. readLines (Readable, LineProcessor <T> ) с реализацией LineProcessor<T>
для обработки каждой строки.
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
CharStreams.readLines(br, new MyLineProcessorImpl());
} catch (IOException e) {
// handling io error ...
}
Тело цикла while
теперь помещается в реализацию 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();
}
}
Ответ 7
Меня немного удивляет следующая альтернатива:
while( true ) {
String line = br.readLine();
if ( line == null ) break;
... // do stuff to file here
}
До Java 8 это был мой любимый из-за его ясности и не требовал повторения. IMO, break
- лучший вариант для выражений с побочными эффектами. Однако это все еще вопрос идиомы.
Ответ 8
AssignmentInOperand является спорным правилом в PMD, причина этого правила заключается в следующем: "это может сделать код более сложным и трудным для чтения" (см. http://pmd.sourceforge.net/rules/controversial.html)
Вы можете отключить это правило, если вы действительно хотите это сделать. В моей стороне я предпочитаю первое.