使用 FileChannel 或 Files.copy 将文件复制到 jar 中

Use FileChannel or Files.copy to copy file inside jar

使用Java 8.

为了获得最佳性能,我尝试用 Files.copy() 复制文件,但很快就找到了 不支持汉字。例如:

try {
    Files.copy(
        Objects.requireNonNull(
            Main.class.getResourceAsStream("/amres/core/template.xlsx")),
        Paths.get("C:/我的/test.xlsx"), // "我的" means mine in Chinese
        StandardCopyOption.REPLACE_EXISTING
    );
} catch (IOException e) {
    e.printStackTrace();
}

代码打算从jar中复制一个文件,但是抛出异常(一个“我的”文件夹有 提前创建):java.nio.file.NoSuchFileException: C:\鎴戠殑\test.xlsx
问题是,“鎴戠殑”连中国人都看不懂,所以我在找一个 处理中文字符的解决方案。

我也试过FileChannel,但是失败了,意识到它是用于直接文件,而不是用于文件 在一个罐子里。我该怎么办?

您可能在编译 Java 到 class 文件时使用了错误的文件编码。如果您在使用值之前简单地打印出文件的路径名,那么这个问题应该很清楚:

System.out.println("C:/我的/test.xlsx");

如果该路径无法识别为中文文件夹名称,请尝试使用 javac -encoding 标志再次编译以匹配源文件的格式。

你找错人了。 Files.copy 与支持(或不支持)汉字没有任何关系,java 支持完整的 unicode 路径名。是的,很明显您的代码当前未按设计运行,并且可以修复,但问题不在于 Files.copy.

旁注:您的代码已损坏

Main.class.getResourceAsStream 是从您的代码库中提取资源的正确方法,但是,这是一个资源,因此,您必须关闭它。将它包装在一个 try 块中,这是聪明的做法。

Objects.requireNonNull 不应该在这里使用——它的目的是强行抛出一个NullPointerException。这就是它所做的一切。如果以某种方式缺少该资源,此代码将 已经 抛出 NPE。这意味着 requireNonNull 是完全没有意义的(它强制执行已经发生的事情),如果你想要干净的代码,那么任何一个都是不合适的:你应该重新抛出一个异常来正确传达应用程序已损坏的概念。

但是,这也不是一个好主意:我们不会为错误或损坏的部署抛出异常。如果您认为应该,那么您应该将整个 java 项目中的每一行代码都用 try/catch 块包装起来,毕竟,显然我们不能假设任何事情。我们甚至不能假设 java.lang.String 在 运行 时间可用 - 显然这不是可持续的观点。换句话说,您可以安全地假设资源不可能不存在用于异常流的目的。

因此,我们得到了这个更简单、更安全的代码:

try (var in = Main.class.getResourceAsStream("/amres/core/template.xlsx")) {
  Files.copy(in, Paths.get("C:/我的/test.xlsx"), StandardCopyOption.REPLACE_EXISTING);
}

请注意,一般来说,捕获异常并使用 e.printStackTrace() 处理它也很糟糕,在所有情况下:您正在将异常打印到它可能不应该去的地方,抛出有用的信息例如因果链,然后 让代码异常继续 即使您的代码的状态显然处于意外且因此未知的状态。最好的通用解决方案实际上是 throws 例外。如果那不可行,或者你只是不想现在关心它,因此依赖你的 IDE 的自动修复而不费心去编辑任何东西,那么至少修复你的 IDE的自动修复程序发出非白痴代码。 throw new RuntimeException("uncaught", e) 是正确的 'I do not want to care about this right now' 填充。所以,修复你的 IDE。它通常在设置中,在 'template'.

可能是什么原因造成的

每次将字符转换为字节,反之亦然,都涉及到字符集编码。文件名看起来像字符,当然,当您编写代码时,您正在编写字符,当您看到 NoSuchFileException 的文本时,那是字符——但是介于两者之间的所有内容呢?此外,文件系统本身的名称也不清楚:一些文件系统名称是基于字节的。例如,Apple 的 APFS 完全是基于字节的。文件名只是字节,将这些字节呈现到屏幕上(并将命令行上的 touch foobar.txt 翻译成文件名的字节序列值)的想法仅按照惯例使用 UTF-8 完成。相比之下,一些文件系统将这种集合编码的概念直接编码到其 API 中。最好的办法是 UTF_8 所有事情,这样事情出错的可能性最小。

那么,让我们完成以下步骤:

  1. 您在文本编辑器中编写 java 代码。你写字。
  2. File content 在所有文件系统上普遍基于字节。因此,当您在文本编辑器中点击 'save' 快捷方式时,您的字符将转换为字节。 检查您的编辑器是否配置为 UTF-8 模式。或者,使用反斜杠-u 转义来避免问题.
  3. 你编译这段代码。可能使用 javacecj 或基于这两者之一的东西。他们读入一个文件(因此,字节),但将输入解析为字符,因此正在发生转换。 确保使用 --encoding UTF-8 参数调用 javac/ecj。如果使用 maven 或 gradle 等构建工具,请确保已明确配置。
  4. 该代码是 运行 并将其错误打印到控制台。控制台向您显示它。控制台正在将字节(因为应用程序输出是基于字节的流)转换为字符以便向您显示。它是否配置为使用 UTF-8 执行此操作? 检查终端应用程序的设置。

检查所有加粗的项目,95% 以上的机会可以解决您的问题。