Подтвердить что ты не робот

Итерация по содержимому текстового файла по строкам - есть ли наилучшая практика? (по сравнению с назначением 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()?)

4b9b3361

Ответ 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)

Вы можете отключить это правило, если вы действительно хотите это сделать. В моей стороне я предпочитаю первое.