逐行迭代文本文件的内容 - 是不是有最佳实践? (与 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):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<string>
的方法,您可以使用 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<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)
如果您真的想这样做,您可以禁用该规则。在我这边,我更喜欢前者。
【讨论】:
或发表评论// NOPMD
以上是关于逐行迭代文本文件的内容 - 是不是有最佳实践? (与 PMD 的 AssignmentInOperand 相比)的主要内容,如果未能解决你的问题,请参考以下文章