逐行迭代文本文件的内容 - 是不是有最佳实践? (与 PMD 的 AssignmentInOperand 相比)

Posted

技术标签:

【中文标题】逐行迭代文本文件的内容 - 是不是有最佳实践? (与 PMD 的 AssignmentInOperand 相比)【英文标题】:Iterating over the content of a text file line by line - is there a best practice? (vs. PMD's AssignmentInOperand)逐行迭代文本文件的内容 - 是否有最佳实践? (与 PMD 的 AssignmentInOperand 相比) 【发布时间】:2011-06-08 07:46:44 【问题描述】:

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

【问题讨论】:

不错的 BufferedReaderIterator。我不得不用 r.mark(2) 替换 r.mark(1),否则在一个大文件中会有大约 100 行的“无效标记”。不明白为什么。 for 循环怎么样? for (String line = br.readLine(); line != null; line = br.readLine()) ... 【参考方案1】:

我知道这是一篇旧帖子,但我(几乎)有同样的需求,我使用 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

【讨论】:

谢谢,以后可以派上用场了。 如果有人需要来自 Maven 的这个依赖项(FileUtils):commons-iocommons-io2.4依赖> 我很确定你也可以用Scanner 做同样的事情。 可惜没有实现AutoClosable :( 看来您应该可以像这样使用它:for(String line: FileUtils.lineIterator(file,"UTF-8")) /*do something */ 。但遗憾的是,这不会彻底关闭迭代器。【参考方案2】:

java-8 中对streams 和Lambdas 的支持以及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

【讨论】:

还可以将 lambda 缩短为方法引用:lines.forEachOrdered(System.out::println) 我喜欢这个,但我想在迭代文件时设置布尔值以表示我已经到达感兴趣的区域,但它说我不能在正文中使用非最终变量循环,所以这对我来说是不行的。我改用@rolfl 的答案***.com/a/22351492/529256【参考方案3】:

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

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

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

【讨论】:

我很好奇您对创建装饰器作为一种便捷机制的看法i> 建议如下)... @Mark E:它不像 C# 版本那么整洁,但也不错——除了例外。我会评论你的答案并编辑我的。【参考方案4】:

我经常使用while((line = br.readLine()) != null) 构造...但是,recently I came accross this nice alternative:

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

【讨论】:

不错的方法!如果与 Java 7 一起使用,您还可以使用 try-with-resources 语句包装 BufferedReader 实例创建,它将缩小变量的范围并自动添加关闭阅读器所有行都被处理。【参考方案5】:

根据 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();
            

        ;
    


公平警告:这会抑制读取期间可能发生的 IOException,并简单地停止读取过程。尚不清楚在 Java 中是否有解决此问题的方法,而不会引发运行时异常,因为迭代器方法的语义已明确定义并且必须遵守才能使用 for-each 语法。此外,在这里运行多个迭代器会产生一些奇怪的行为;所以我不确定这是推荐的。

不过,我确实对此进行了测试,并且确实有效。

不管怎样,使用这种作为一种装饰器的 for-each 语法,你会得到好处:

for(String line : new BufferedReaderIterator(br))
    // do some work

【讨论】:

我怀疑这不会编译,因为readLine 可能会抛出 IOException。 Iterator 接口不允许这样做,因此您必须将其包装在未经检查的异常中,此时它开始看起来越来越不像原始代码:( @Jon:你是对的,不幸的是我很确定没有办法隐藏异常来获得语义。虽然很方便,但回报似乎很惨淡。【参考方案6】:

Google 的 Guava Libraries 提供了一种替代解决方案,使用静态方法 CharStreams.readLines(Readable, LineProcessor<T>) 实现 LineProcessor&lt;T&gt; 来处理每一行。

try (BufferedReader br = new BufferedReader(new FileReader(file))) 
    CharStreams.readLines(br, new MyLineProcessorImpl());
 catch (IOException e) 
    // handling io error ...

while 循环的主体现在放置在 LineProcessor&lt;T&gt; 实现中。

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)

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

【讨论】:

或发表评论// NOPMD

以上是关于逐行迭代文本文件的内容 - 是不是有最佳实践? (与 PMD 的 AssignmentInOperand 相比)的主要内容,如果未能解决你的问题,请参考以下文章

如何用VBS逐行读取文本文件的内容,并输入变量

使用 Azure 函数迭代 Blob 存储中的文本文件

构建自动化 - 命名最佳实践

阿里最新开源配置中心和注册中心: Nacos 限流最佳实践

TypeScript:无法逐行迭代上载的文件(Angular 9)

如何在 Java 中逐行读取文本文件并分隔每一行的内容?