【问题标题】:How to preserve newlines while reading a file using stream - java 8如何在使用流读取文件时保留换行符 - java 8
【发布时间】:2016-05-21 07:52:04
【问题描述】:
      try (Stream<String> lines = Files.lines(targetFile)) {  
     List<String> replacedContent = lines.map(line ->  
                                       StringUtils.replaceEach(line,keys, values))
                                       .parallel()
                                       .collect(Collectors.toList());
    Files.write(targetFile, replacedContent);
}

我正在尝试替换文件每一行中的多个文本模式。但我观察到“\r\n”(字节等效 10 和 13)被替换为“\r”(仅 10),并且我的比较测试失败了。

我想保留输入文件中的换行符,并且不希望 java 触摸它们。任何人都可以建议是否有一种方法可以做到这一点,而不必使用单独的默认替换“\r\n”。

【问题讨论】:

  • 抱歉错过了。刚刚添加。
  • 刚刚删除了 replaceEach 以隔离问题及其似乎正在执行此操作的 Files.line()。
  • 替换发生在哪里?您粘贴的代码创建了一个字符串列表,它没有任何换行符。
  • 您是说“"\r\n" ... 正在被替换为 "\r"”。问题是这种情况发生在哪里,因为流不这样做。 Files.lines 生成的字符串根本没有任何换行符。
  • Files.write 可以将字符串列表写入行,并为每行添加系统特定的换行符。在 Windows 上,它应该是所需的 \r\n 序列。

标签: java java-8 newline java-stream replaceall


【解决方案1】:

问题在于Files.lines() 是在BufferedReader.readLine() 之上实现的,它会读取一行直到行终止符并将其丢弃。然后,当您使用Files.write() 之类的内容编写行时,这会在每行之后提供系统特定的行终止符,这可能与读入的行终止符不同。

如果您真的想按原样保留行终止符,即使它们是不同行终止符的混合,您也可以使用正则表达式和Scanner

首先定义一个匹配包含有效行终止符或 EOF 的行的模式:

Pattern pat = Pattern.compile(".*\\R|.+\\z");

\\R 是一个特殊的换行符匹配器,它匹配常用的行终止符以及一些我从未听说过的 Unicode 行终止符。 :-) 如果您只需要常用的 CRLFCRLF 终止符,则可以使用 (\\r\\n|\\r|\\n) 之类的东西。

您必须包含 .+\\z 才能匹配文件中没有行终止符的潜在最后“行”。确保正则表达式始终匹配至少一个字符,以便扫描器到达文件末尾时找不到匹配项。

然后,使用Scanner 读取行,直到它返回null

try (Scanner in = new Scanner(Paths.get(INFILE), "UTF-8")) {
    String line;
    while ((line = in.findWithinHorizon(pat, 0)) != null) {
        // Process the line, then write the output using something like
        // FileWriter.write(String) that doesn't add another line terminator.
    }
}

【讨论】:

  • Stuart 和其他人,我想我不能将扫描仪与多线程程序一起使用,对吧?对于多线程程序,还有其他方法可以实现这一点吗?
  • @AshwiniR 您可以在多线程程序中一次仅从一个线程使用单个Scanner 实例。多个线程可以使用不同的Scanner 实例,只要没有两个线程对同一个实例进行操作。如果要并行处理单个文件中的行,这很困难,因为读取文件和写入输出是顺序的。如果每行有大量计算,它可能只值得并行运行。
  • 谢谢斯图尔特。我在一个线程中创建了一个 Scanner 实例。此实例一一读取所有行,创建行列表并关闭扫描仪。与该线程并行运行的任何其他线程都将拥有自己的Scanner 实例。所以我不需要担心Scanner 是线程不安全的,也不需要担心同步我使用Scanner 的方法对吧?
  • 一次处理多行:我创建了一个可调用的数组(每个可调用由一个线程执行以一次处理一组行)然后在这个数组上执行invokeAll()。结果将被收集在一个数组中,这样可以保持顺序,我可以用它来编写。这在我的测试中运行良好。您认为这种方法有什么问题吗?
【解决方案2】:

流中的行不包含任何换行符。

如果Files.lines() 的方法文档中提到了这一点,那就太好了。但是,如果您遵循实现,它最终会导致BufferedReader.readLine()。该方法被记录为返回行的内容,not including any line-termination characters

您可以在编写行时添加换行符。

系统相关的行分隔符由您调用的Files.write() 方法使用,如documented in its sibling。您还可以使用System.lineSeparator() 获取此系统相关的行分隔符。

如果您想要一个不同的行分隔符,并且知道它是什么,您可以指定它。例如:

    try ( PrintStream out = new PrintStream( Files.newOutputStream( targetFile ))) 
    {
        lines.forEach( line -> out.print( line + "\r\n") );
    }

如果您想要原始文件的行分隔符,您不能只依赖于将其剥离的方法。选项包括:

  • 读取第一行分隔符,并猜测它在整个文件中是一致的。这使您可以继续使用Files.lines() 来阅读这些行。
  • 使用允许您使用分隔符获取行的 API。
  • 逐个字符读取,而不是逐行读取,以便获取行分隔符。

警告:您的代码从同一个文件读取和写入。由于异常终止或错误,您可能会丢失原始数据。

【讨论】:

  • 似乎Files.write() 在给定列表中写入每一行时添加了“行尾”序列。
  • 我认为 Files.write 正在添加它们,但它仅添加“\r”。我的输入文件有“\r\n”。我没有看到在 Files.write() 中改变它的方法!
  • @AshwiniR - 您可以通过设置 line.separator 属性来做到这一点,但该 hack 会影响整个过程。使用Files.write() 以外的机制可能更可取。请参阅上面已编辑文本中的示例。另请注意评论后添加的警告。
猜你喜欢
  • 2022-12-03
  • 1970-01-01
  • 2019-11-20
  • 1970-01-01
  • 1970-01-01
  • 2012-04-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-19
相关资源
最近更新 更多