【问题标题】:How can I open files containing accents in Java?如何在 Java 中打开包含重音符号的文件?
【发布时间】:2010-06-18 18:58:54
【问题描述】:

编辑澄清并添加一些代码

你好, 我们需要解析来自世界各地的用户发送的数据。我们的 Linux 系统具有 en_US.UTF-8 的默认语言环境。但是,我们经常收到名称中带有变音符号的文件,例如“special_á_ã_è_characters.doc”。尽管操作系统可以很好地处理这些文件,并且 strace 显示操作系统将正确的文件名传递给 Java 程序,但 Java 会修改名称并抛出“找不到文件”io 异常来尝试打开它们。

这个简单的程序可以说明问题:

import java.io.*;
import java.text.*;

public class load_i18n
{
  public static void main( String [] args ) {
    File actual = new File(".");
    for( File f : actual.listFiles()){
      System.out.println( f.getName() );
    }
  }
}

在包含文件special_á_ã_è_characters.doc 的目录中运行此程序,默认的美国英语语言环境给出:

special_�_�_�_characters.doc

通过 export LANG=es_ES@UTF-8 设置语言可以正确打印出文件名(但这是一个不可接受的解决方案,因为整个系统现在都以西班牙语运行。)在程序中显式设置 Locale 如下所示没有效果任何一个。下面我将程序修改为 a) 尝试打开文件 b) 在无法打开文件时以 ASCII 和字节数组的形式打印出名称:

import java.io.*;
import java.util.Locale;
import java.text.*;

public class load_i18n
{
  public static void main( String [] args ) {
    // Stream to read file
    FileInputStream fin;

    Locale locale = new Locale("es", "ES");
    Locale.setDefault(locale);
    File actual = new File(".");
    System.out.println(Locale.getDefault());
    for( File f : actual.listFiles()){
      try {
        fin = new FileInputStream (f.getName());
      }
      catch (IOException e){
        System.err.println ("Can't open the file " + f.getName() + ".  Printing as byte array.");
        byte[] textArray = f.getName().getBytes();
        for(byte b: textArray){
          System.err.print(b + " ");
        }
        System.err.println();
        System.exit(-1);
      }

      System.out.println( f.getName() );
    }
  }
}

这会产生输出

es_ES
load_i18n.class
Can't open the file special_�_�_�_characters.doc.  Printing as byte array.
115 112 101 99 105 97 108 95 -17 -65 -67 95 -17 -65 -67 95 -17 -65 -67 95 99 104 97 114 97 99 116 101 114 115 46 100 111 99

这表明问题不仅仅是控制台显示的问题,因为相同的字符并且它们的表示以字节或 ASCII 格式输出。事实上,即使在某些实用程序(如 bash 的 echo)中使用 LANG=en_US.UTF-8 时,控制台显示也能正常工作:

[mjuric@arrhchadm30 tmp]$ echo $LANG
en_US.UTF-8
[mjuric@arrhchadm30 tmp]$ echo *
load_i18n.class special_á_ã_è_characters.doc
[mjuric@arrhchadm30 tmp]$ ls
load_i18n.class  special_?_?_?_characters.doc
[mjuric@arrhchadm30 tmp]$

是否可以修改此代码,使其在具有 LANG=en_US.UTF-8 的 Linux 下运行时,以可以成功打开的方式读取文件名?

【问题讨论】:

  • 您的示例没有显示您尝试打开这些文件,只是打印名称。 Java 是否可以打开文件和你的标准输出控制台(与 Java 无关)是否可以正确渲染字符是两个非常不同的事情。向我们展示给出 IOException 的代码并给出 IOException 详细信息和堆栈跟踪。
  • 在此处查看推荐使用 Java 系统属性(user.language、user.country、user.variant)的答案:stackoverflow.com/questions/64038/setting-java-locale-settings
  • 对不起 - 我从来没有达到打开文件的地步。调用 FileInputStream 会失败,因为我无法将文件的正确名称传递给它。文件“special_�_�_�_characters.doc”不存在。文件“special_á_ã_è_characters.doc”确实如此,但我的目录迭代从未列出。
  • 谢谢劳里。我尝试了所有这些技巧,但没有一个奏效。我实际上在其中一次运行期间运行了一个 strace (Linux),并且操作系统将正确的文件名传递给 Java,但是当 Java 解释从 getdents() 系统调用传递的内容时,它会被破坏。这是来自 strace 的相关系统调用: 21993 getdents64(3, {... {d_ino=119, d_off=1692303532, d_type=DT_REG, d_reclen=48, d_name="special_á_ã_è_characters.doc"} ... }, 4096) = 704 当 Java 读取该文件并将其传递给函数以打开文件时,它会尝试打开不存在的“special_�_�_�_characters.doc”。
  • Mark J,Mark P 的意思是,你并没有证明你不能将正确的文件名传递给 open 调用;您证明您无法将其打印到控制台。我或多或少愿意保证 'f.getName()' 返回正确的文件名;问题出在 println (以及您的控制台目标和编码),而不是 listFiles()。

标签: java unicode character-encoding


【解决方案1】:

首先,使用的字符编码与语言环境没有直接关系。所以更改语言环境不会有太大帮助。

其次,� 是典型的 Unicode replacement character U+FFFD 以 ISO-8859-1 而不是 UTF-8 打印。这是一个证据:

System.out.println(new String("�".getBytes("UTF-8"), "ISO-8859-1")); // �

所以有两个问题:

  1. 您的 JVM 正在将这些特殊字符读取为
  2. 您的控制台使用 ISO-8859-1 显示字符。

对于 Sun JVM,VM 参数 -Dfile.encoding=UTF-8 应该解决第一个问题。第二个问题是在控制台设置中修复。如果您使用例如 Eclipse,您可以在 Window > Preferences > General > Workspace > Text File Encoding 中更改它。也将其设置为 UTF-8。


更新:根据您的更新:

byte[] textArray = f.getName().getBytes();

排除平台默认编码的影响,应该如下:

byte[] textArray = f.getName().getBytes("UTF-8");

如果仍然显示相同,那么问题就更深了。你到底在使用什么JVM?做一个java -version。如前所述,-Dfile.encoding 参数是特定于 Sun JVM 的。一些 Linux 机器附带 GNU JVM 或 OpenJDK 的 JVM,然后这个参数可能不起作用。

【讨论】:

  • 我试过了,但没有用。 java -Dfile.encoding=UTF-8 load_i18n es_ES special_�_�_�_characters.doc 我可能错了,但我不相信还有控制台问题。我将输出重定向到一个文件,因此不涉及控制台,我仍然得到相同的结果。我在文件上做了一个“od -a”,这是相关的输出:0000200 e f i l e nl s p e c i a l _ o ? 0000220 = _ ? = _ ? = _ c h a r a c 0000240 t e r s 。 d o c nl r e a d _ i 1
  • 至于第一个问题:这可能是特定于平台/JVM 的。从这里开始很难说。至于第二个问题:文件是用OutputStreamWriter用UTF-8写的,用支持UTF-8的查看器查看吗?
  • @Mark,不知道你为什么在命令行上传递“损坏的”文件名。流程似乎是(1)Java 从操作系统获取正确的文件名(2)Java 将文件名写入标准输出,在那里它会被损坏(3)您将损坏的文件名传递回另一个工具(4)Java 将损坏的文件名到操作系统,无法找到该文件。修复(2),问题消失;在 (3) 中传递 MANGLED 文件名只会让事情变得更糟。
  • 另外 - “我将输出重定向到一个文件,因此不涉及控制台,我仍然得到相同的结果。” - 你的意思是在代码中重定向,使用例如一个作家,或使用你的外壳的命令行重定向?如果问题是 Java 在写入 System.out 时选择的编码,那么只是那些(不正确的)字节会被您的 shell 重定向到文件中,从而产生完全相同的问题。
  • 我的文件名为“03.滫¬«Ñ¡ (feat.Äô74).mp3”,我在文件输入流中找不到错误文件,请帮助我使用你的文件,但仍然出现相同的错误
【解决方案2】:

这是 JRE/JDK 中存在多年的 bug。

How to fix java when if refused to open a file with special charater in filename?

File.exists() fails with unicode characters in name

我现在正在向他们重新提交一份新的错误报告,因为 LC_ALL=en_us 将修复某些情况,同时它会失败一些其他情况。

【讨论】:

    【解决方案3】:

    这是 old-skool java File api 中的一个错误,可能只是在 mac 上?无论如何,新的 java.nio api 工作得更好。我有几个文件包含无法使用 java.io... 类加载的 unicode 字符。在将我的所有代码转换为使用 java.nio.Path 之后,一切都开始工作了。我用java.nio.Files替换了apache FileUtils(有同样的问题)...

    【讨论】:

    • 这对我有用。接受的答案对我的情况没有好处。
    【解决方案4】:

    Java 系统属性file.encoding 应与控制台的字符编码匹配。在命令行启动java时必须设置该属性:

    java -Dfile.encoding=UTF-8 …
    

    这通常会自动发生,因为控制台编码通常是平台默认编码,如果您没有明确指定,Java 将使用平台默认编码。

    【讨论】:

    • file.encoding 是文件内容而不是文件名
    【解决方案5】:

    好吧,我整天都被这个问题扼杀了! 我之前的(错误的)代码和你一样:

    for(File f : dir.listFiles()) {
     String filename = f.getName(); // The filename here is wrong !
     FileInputStream fis = new FileInputStream (filename);
    }
    

    它不起作用(我在 CentOS 6 上使用 Java 1.7 Oracle,LANG 和 LC_CTYPE=fr_FR.UTF-8 用于除 zimbra => LANG 和 LC_CTYPE=C 之外的所有用户 - 顺便说一句,这肯定是原因问题,但如果没有 Zimbra 停止工作的风险,我无法更改它......)

    所以我决定使用 java.nio.file 包的新类(Files and Paths):

    DirectoryStream<Path> paths = Files.newDirectoryStream(Paths.get(outputName));
    for (Iterator<Path> iterator = paths.iterator(); iterator.hasNext();) {
      Path path = iterator.next();
      String filename = path.getFileName().toString(); // The filename here is correct
      ...
    }
    

    因此,如果您使用的是 Java 1.7,您应该尝试将新类放入 java.nio.file 包中:它拯救了我的一天!

    希望对你有帮助

    【讨论】:

      【解决方案6】:

      在使用 DirectoryStream 时不要忘记关闭流(try-with-resources 可以在这里提供帮助)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-03
        • 1970-01-01
        • 2011-12-02
        • 1970-01-01
        • 2017-02-01
        • 1970-01-01
        相关资源
        最近更新 更多