Java 逐行迭代文本文件的内容-有最佳实践吗?(与PMD&x27;s分配操作相比)

Java 逐行迭代文本文件的内容-有最佳实践吗?(与PMD&x27;s分配操作相比),java,while-loop,pmd,Java,While Loop,Pmd,我们有一个Java应用程序,它有几个模块可以读取文本文件。他们只需使用如下代码: BufferedReader br = new BufferedReader(new FileReader(file)); String line = null; while ((line = br.readLine()) != null) { ... // do stuff to file here } 我在我的项目上运行了PMD,并在while(…)行上得到了“Assignment

我们有一个Java应用程序,它有几个模块可以读取文本文件。他们只需使用如下代码:

BufferedReader br = new BufferedReader(new FileReader(file));  
String line = null;  
while ((line = br.readLine()) != null)  
{  
   ... // do stuff to file here  
} 
我在我的项目上运行了PMD,并在
while(…)
行上得到了“AssignmentInOperand”违规

除了显而易见的方法外,还有没有更简单的方法来执行此循环:

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

这被认为是更好的做法吗?(虽然我们“复制”了
line=br.readLine()。我一般不喜欢比较中的副作用,但这个特殊的例子是一个非常常见和方便的习惯用法,我不反对它

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


换一种说法:重复行问题比操作数内赋值问题更困扰我。我习惯于对这种模式一目了然——对于重复行版本,我需要停下来检查所有内容是否都在正确的位置。这可能是一种习惯,但我不认为这是一个问题。

AssignmeNTI操作数在PMD中是一个有争议的规则,该规则的原因是:“这会使代码更复杂,更难阅读”(请参阅)


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

根据Jon的回答,我开始认为创建一个装饰器作为文件迭代器应该很容易,这样您就可以使用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();
            }

        };
    }

}

我知道这是一篇老文章,但我也有同样的需求(几乎),我使用ApacheCommons中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();
}
检查文档:

谷歌提供了一种使用静态方法的替代解决方案,其中实现了
LineProcessor
来处理每一行

try (BufferedReader br = new BufferedReader(new FileReader(file))) {
    CharStreams.readLines(br, new MyLineProcessorImpl());
} catch (IOException e) {
    // handling io error ...
}
while
循环的主体现在放置在
LineProcessor
实现中

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();
    }
}
类MyLineProcessorImpl实现LineProcessor{
@凌驾
公共布尔processLine(字符串行)引发IOException{
if(//检查处理是否应继续){
//用线做某事
返回true;
}否则{
//停止处理
返回false;
}
}
@凌驾
公共对象getResult(){
//如果需要,返回基于已处理行的结果
返回新对象();
}
}

我经常使用
while((line=br.readLine())!=null)
构造…但是:

这仍然在复制
readLine()
调用代码,但逻辑是清晰的,等等

我使用
while((…)…)
构造的另一次是在从流中读取到
字节[]
数组时

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工具的要求,而且逻辑仍然清晰,等等。

对java-8和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
}
Path Path=Path.get(“c:/users/aksel/aksel.txt”);
try(流行=文件。行(路径)){
lines.forEachOrdered(line->System.out.println(line));
}捕获(IOE异常){
//出错
}

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

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

在Java 8之前,它是我的最爱,因为它清晰明了,不需要重复。在我看来,
break
是一个更好的选择,可以用来处理有副作用的表达式。不过,这仍然是一个习惯用法的问题。

我很好奇,您如何看待创建一个装饰器作为一种方便的机制,它可以抽象迭代的语义,从而可以使用foreach循环(请参阅下面我的回答和粗略建议)…@Mark E:它没有C#版本那么整洁,但也不错——除了异常。我会评论你的答案并编辑我的答案。我怀疑这不会编译,因为
readLine
可能会抛出IOException。迭代器接口不允许这样做,所以你必须将其包装在未检查的异常中,此时它看起来不像是一个异常我不太喜欢原始代码:(@Jon:你说得对,不幸的是,我很确定没有办法隐藏异常来获取语义。虽然很方便,但结果似乎很糟糕。很好的BufferedReaderIterator。我必须用r.mark(2)替换r.mark(1),否则会有一个“无效标记”一个大文件大约有100行。我不明白为什么。为
循环一次怎么样?
for(String line=br.readLine();line!=null;line=br.readLine()){…}
谢谢,以后它会派上用场的。万一有人需要这个依赖项(FileUtils)Maven:commons io commons io 2.4我敢肯定你也可以用
扫描仪做同样的事情。遗憾的是,它没有实现
自动关闭
:(看起来你应该可以这样使用:
for(字符串行:FileUtils.lineIterator(文件,“UTF-8”){/*做点什么*/}
。但遗憾的是,这无法干净地关闭迭代器。或者添加注释
//NOPMD
也可以将lambda缩短为方法引用:
行。forEachOrdered(System.out::println)
Nice approach!您还可以使用try with resources语句包装
BufferedReader
实例创建,如果它与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
}
while( true ) {
    String line = br.readLine();
    if ( line == null ) break;
    ... // do stuff to file here
}