了解为什么 Java 模式匹配缓冲的 .java 文件中的不正确文本?

Understanding why a Java pattern matches incorrect text inside buffered .java file?

完全披露:对 Java 仍然很陌生。

我正在为开源 xmage project 做贡献,我们同意从项目中的所有文件中删除版权 header 文本,以支持单个 LICENSE.txt 文件项目根。

由于我的 IDE 跨多个文件识别正则表达式模式的局限性,我决定编写一个脚本。这是脚本:

注意: 已更新且有效的脚本(不会覆盖隐藏文件或符号链接)

/*
  Remove the copy right header from all files inside project.
*/
import java.io.IOException;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Pattern;

public class RemoveHeaders {

  private static String readEntireFile(String filePath) {
    String content = "";

    try {
      content = new String(Files.readAllBytes(Paths.get(filePath)));
    }
    catch (IOException e) {
      e.printStackTrace();
    }

    return content;
  }

  private static void saveFileToDisk(String filePath, String content) {
    File file = new File(filePath);
    Path path = Paths.get(filePath);

    try (FileWriter writer = new FileWriter(file)) {
      if (Files.isWritable(path) && !Files.isSymbolicLink(path) && !Files.isHidden(path)) {
        writer.write(content);
        writer.flush();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private static String removeMatchingText(String content, Pattern pattern) {
    return pattern.matcher(content).replaceAll("");
  }

  public static void recursivelyGetFilesAndRemoveHeaders(String path) {
    Pattern copyrightHeader = Pattern.compile("(?i)/\*(?:\r?\n|\r) ?\*.*?Copyright[\S\s]*?\*/");
    File currentDirectory = new File(path);
    File[] files = currentDirectory.listFiles();

    if (files == null) {
        return;
    }

    for (File file : files) {
      if (file.isDirectory()) {
        recursivelyGetFilesAndRemoveHeaders(file.getAbsolutePath());
      } else {
        String filePath = file.getAbsolutePath();
        String fileContents = readEntireFile(filePath);
        String updatedContents = removeMatchingText(fileContents, copyrightHeader);
        if (fileContents != updatedContents) {
          saveFileToDisk(filePath, updatedContents);
        }
      }
    }
  }

  public static void main(String args[]) {
    String rootPath = System.getProperty("user.dir");
    recursivelyGetFilesAndRemoveHeaders(rootPath);
  }
}

为了清楚和方便参考,这是正在使用的(更新的)正则表达式:

"(?i)/\*(?:\r?\n|\r) ?\*.*?Copyright[\S\s]*?\*/"

这里是事情变得奇怪的地方:我在 this file and to my surprise, not only was the copyright header comment removed, but the pattern matched line 1428 and removed everything up until line 1554.

上测试了模式

我认为这可能是一个缓冲问题,因此使用 FileInputStream 重写了 readEntireFile 函数,确保在返回读取文件的结果之前关闭流 - 但这产生了相同的结果。

我现在的想法是 Java 中的正则表达式处理程序有些怪癖?在 JavaScript on the same file 中使用完全相同的模式会产生预期的匹配(仅版权 header)。

以防万一这里是系统和java详细信息:

openjdk version "1.8.0_171"
OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11)
OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)

感谢您花时间阅读本文 - 一整天都在研究这个问题,我完全被卡住了,有点困惑。干杯!

您忘记刷新输出。

将此添加到 saveFileToDisk 方法中可以:

writer = new FileWriter(file);
writer.write(content);
writer.flush();

(对我来说,它没有删除版权 header,但至少省略了其余的删除。但是,前者可能与其中一个原因有关,Wiktor 已经在his/her 条评论。)

因此,自 java 7 起,鼓励使用 try-with-resources 语句:

try (FileWriter writer = new FileWriter(file)) {
    writer.write(content);
}

这条语句会自动关闭FileWriter,这意味着刷新。


解释:

java(OutputStreamWriter)中的流输出在概念上是缓冲的。这意味着每个实现 class 都可以在内部缓冲吞吐量,而无需明确记录它。

因此,所有 write 方法不一定会立即写入底层资源。但是,两个基础 classes 都提供了一个 flush 方法来完成此操作:它将内部缓冲区刷新到底层资源。