Перебирая содержимое текстового файла построчно - есть ли лучшая практика? (против AssignmentInOperand PMD) - PullRequest
37 голосов
/ 13 января 2011

У нас есть 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()?)

Ответы [ 8 ]

32 голосов
/ 23 августа 2012

Я знаю, что это старый пост, но у меня была такая же потребность (почти), и я решаю ее, используя 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

20 голосов
/ 04 апреля 2014

Поддержка потоков и 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
}
20 голосов
/ 13 января 2011

Я вообще предпочитаю первое.Я не обычно люблю побочные эффекты в сравнении, но этот конкретный пример - идиома, которая настолько распространена и удобна, что я не возражаю против этого.

(ВВ C # есть более приятная опция: метод, возвращающий IEnumerable<string>, который вы можете перебрать с помощью foreach; это не так хорошо в Java, потому что нет автоматического удаления в конце расширенного цикла for ... а также потому, чтовы не можете выбросить IOException из итератора, что означает, что вы не можете просто сделать одну замену другой.)

Другими словами: проблема с дублирующейся строкой беспокоит меня большечем вопрос присваивания внутри операнда.Я привык взглянуть на этот шаблон с первого взгляда - в версии с дублирующейся строкой мне нужно остановиться и проверить, что все в нужном месте.Это, наверное, привычка, как и все остальное, но я не думаю, что это проблема.

15 голосов
/ 12 марта 2014

Я обычно использую конструкцию 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 .... но он удовлетворит любой инструмент PMD, а логика все еще ясна и т. Д.

4 голосов
/ 13 января 2011

Основываясь на ответе Джона, я подумал, что создать декоратор, который будет действовать как файловый итератор, должно быть достаточно просто, чтобы вы могли использовать цикл 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();
            }

        };
    }

}

Справедливое предупреждение: это подавляет IOException, чтоможет произойти во время чтения и просто останавливает процесс чтения.Неясно, что в Java есть способ обойти это без исключений времени выполнения, так как семантика методов итератора хорошо определена и должна соответствовать, чтобы использовать синтаксис for-each.Кроме того, запуск нескольких итераторов здесь будет иметь странное поведение;поэтому я не уверен, что это рекомендуется.

Я все же проверил это, и оно работает.

В любом случае, вы получаете преимущество синтаксиса для каждого, используя его как своего рода декоратор:

for(String line : new BufferedReaderIterator(br)){
    // do some work
}
3 голосов
/ 04 января 2015

Я немного удивлен, следующая альтернатива не была упомянута:

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

До Java 8 она была моей любимой из-за своей ясности и не требующей повторения.IMO, break - лучший вариант для выражений с побочными эффектами.Это все еще вопрос идиом, однако.

3 голосов
/ 15 июля 2013

Google Библиотеки Guava предоставляют альтернативное решение, используя статический метод CharStreams.readLines (Readable, LineProcessor ) с реализацией 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();
    }
}
1 голос
/ 13 января 2011

AssignmentInOperand спорное правило в PMD, причина этого правила: «это может сделать код более сложным и трудным для чтения» (см http://pmd.sourceforge.net/rules/controversial.html)

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