如何在Java中打开包含重音符号的文件?

时间:2010-06-18 18:58:55

标签: java unicode character-encoding

编辑以澄清并添加一些代码

您好, 我们需要解析来自世界各地的用户发送的数据。我们的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下运行时,它会以可以成功打开的方式读取文件名?

6 个答案:

答案 0 :(得分:8)

首先,使用的字符编码与语言环境没有直接关系。因此,更改区域设置无济于事。

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

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

所以有两个问题:

  1. 您的JVM正在将这些特殊字符读为
  2. 您的控制台正在使用ISO-8859-1来显示字符。
  3. 对于Sun JVM,VM参数-Dfile.encoding=UTF-8 应该修复第一个问题。第二个问题是在控制台设置中修复。如果您使用的是Eclipse,则可以在 Window>中进行更改。偏好>一般>工作区>文本文件编码。也将它设置为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,这个参数可能不起作用。

答案 1 :(得分:3)

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

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将修复某些情况,同时它会在其他情况下失败。

答案 2 :(得分:2)

这是旧的skool java File api中的一个错误,也许只是在Mac上?无论如何,新的java.nio api工作得更好。我有几个文件包含无法使用java.io ...类加载的unicode字符。转换我的所有代码后使用java.nio.Path一切开始工作。我用java.nio.Files ...

替换了apache FileUtils(它有同样的问题)

答案 3 :(得分:1)

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

java -Dfile.encoding=UTF-8 …

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

答案 4 :(得分:1)

我整天都被这个问题勒死了! 我以前的(错误的)代码和你一样:

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之外的所有用户 - 哪个btw肯定是导致的这个问题,但我没有改变这一点,没有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包中:它保存了我的一天!

希望有所帮助

答案 5 :(得分:0)

在DirectoryStream用法中,不要忘记关闭流(尝试使用资源可以在这里帮助)