43

我们有一个 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()代码?)

4

8 回答 8

38

我知道这是一篇旧帖子,但我(几乎)有同样的需求,我使用 Apache Commons 中 FileUtils 的 LineIterator 解决了它。从他们的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

于 2012-08-22T21:56:18.920 回答
24

java-8 中对流Lambda的支持以及 java-7 中的Try-With-Resources允许您以更紧凑的语法实现您想要的。

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
}
于 2014-04-04T08:08:28.047 回答
22

我一般更喜欢前者。我通常不喜欢比较中的副作用,但这个特殊的例子是一个非常常见和方便的习语,我不反对它。

(在 C# 中有一个更好的选择:一个返回 an 的方法IEnumerable<string>,您可以使用 foreach 对其进行迭代;这在 Java 中没有那么好,因为在增强的 for 循环结束时没有自动处置......而且还因为你不能IOException从迭代器中抛出,这意味着您不能只将一个替换为另一个。)

换句话说:重复行问题比操作数内赋值问题更困扰我。我习惯于一目了然地接受这种模式 - 对于重复的行版本,我需要停下来检查一切是否都在正确的位置。这可能和其他任何事情一样都是习惯,但我认为这不是问题。

于 2011-01-13T06:30:58.587 回答
21

我经常使用该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 工具,并且逻辑仍然清晰,等等。

于 2014-03-12T12:25:40.743 回答
4

根据乔恩的回答,我开始认为创建一个装饰器来充当文件迭代器应该很容易,这样您就可以使用 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-each 语法受益:

for(String line : new BufferedReaderIterator(br)){
    // do some work
}
于 2011-01-13T06:41:55.817 回答
3

我有点惊讶没有提到以下替代方案:

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

在 Java 8 之前,它是我最喜欢的,因为它清晰且不需要重复。IMO,break是具有副作用的表达式的更好选择。不过,这仍然是成语的问题。

于 2015-01-04T06:54:26.590 回答
3

Google 的Guava 库使用静态方法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();
    }
}
于 2013-07-15T13:30:29.790 回答
1

AssignmentInOperand 是 PMD 中一个有争议的规则,这个规则的原因是:“这会使代码更复杂,更难阅读”(请参考http://pmd.sourceforge.net/rules/controversial.html

如果您真的想这样做,您可以禁用该规则。在我这边,我更喜欢前者。

于 2011-01-13T06:37:54.357 回答