在下面使用OpenJDK 1.6.0_22在Linux中运行的Java程序中,我只是在命令行中列出了作为参数获取的目录的内容。该目录包含文件名为UTF-8的文件(例如印地语,普通话,德语等)。
import java.io.*;
class ListDir {
public static void main(String[] args) throws Exception {
//System.setProperty("file.encoding", "en_US.UTF-8");
System.out.println(System.getProperty("file.encoding"));
File f = new File(args[0]);
for(String c : f.list()) {
String absPath = args[0] + "" + c;
File cf = new File(args[0] + "/" + c);
System.out.println(cf.getAbsolutePath() + " --> " + cf.exists());
}
}
}
如果我将LC_ALL变量设置为en_US.UTF-8,则结果打印正常。但是如果我将LC_ALL变量设置为POSIX并将file.encoding和sun.jnu.encoding属性从命令行提供为UTF-8,则得到垃圾输出,cf.exists()返回false。
你能解释一下这种行为吗?正如我在很多网站上看到的那样,file.encoding足以读取文件名并将其用于操作。这看起来该属性根本没有效果。
更新1:如果我将file.encoding设置为GBK(中文),将LC_ALL变量设置为en_US.UTF-8,则cf.exists()返回true。只有 '?'出现而不是文件名。惊喜o_O。
更新2:更多调查,看起来它不是Java问题。看起来Linux上的libc使用区域设置来翻译文件名编码,这些设置将导致文件未找到错误/异常。 “file.encoding”是指Java如何解释文件名。
更新3 现在看来问题是Java如何解释文件名。以下简单的C代码适用于Linux,无论文件编码和LC_ALL环境变量的值如何(我很高兴这证明了这里给出的答案:https://unix.stackexchange.com/questions/39175/understanding-unix-file-name-encoding)。但我还不清楚Java如何解释LC_ALL变量。现在查看OpenJDK代码。
示例C代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
int main(int argc, char *argv[])
{
char *argdir = argv[1];
DIR *dp = opendir(argdir);
struct dirent *de;
while(de = readdir(dp)) {
char *abspath = (char *) malloc(strlen(argdir) + 1 + strlen(de->d_name) + 1);
strcpy(abspath, argdir);
abspath[strlen(argdir)] = '/';
strcpy(abspath + strlen(argdir) + 1, de->d_name);
printf("%d %s ", de->d_type, abspath);
FILE *fp = fopen(abspath, "r");
if (fp) {
printf("Success");
}
fclose(fp);
putchar('\n');
}
}
答案 0 :(得分:9)
注意:最后我认为我已经把它钉死了。我没有证实这是对的。但是通过一些代码读取和测试,这是我发现的,我没有额外的时间来研究它。如果有人有兴趣,他们可以检查出来,并判断这个答案是对还是错 - 我很高兴:)
我使用的引用来自OpenJDK网站上提供的这个tarball: 的的openjdk-6-SRC-b25-01_may_2012.tar.gz 强>
Java在本方法中将所有字符串本地转换为平台的本地编码:jdk/src/share/native/common/jni_util.c - JNU_GetStringPlatformChars()
。系统属性sun.jnu.encoding
用于确定平台的编码。
sun.jnu.encoding
的值使用libc的jdk/src/solaris/native/java/lang/java_props_md.c - GetJavaProperties()
方法设置为setlocale()
。环境变量LC_ALL
用于设置sun.jnu.encoding
的值。使用Java的-Dsun.jnu.encoding
选项在命令提示符处给出的值将被忽略。
File.exists()
的来电已在档案jdk/src/share/classes/java/io/File.java
中编码,并以
return ((fs.getBooleanAttributes(this) & FileSystem.BA_EXISTS) != 0);
getBooleanAttributes()
是jdk/src/share/native/java/io/UnixFileSystem_md.c
函数中的本地编码(我正在跳过代码浏览许多文件的步骤):
Java_java_io_UnixFileSystem_getBooleanAttributes0()
。在这里宏
WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path)
将路径字符串转换为平台的编码。
因此,转换为错误的编码实际上会向后续调用stat()
方法发送错误的C字符串(char数组)。它将返回结果无法找到文件。
课程: LC_ALL
非常重要
答案 1 :(得分:5)
我不确定你在哪里读到file.encoding
。我没有看到其他standard properties as documented with System.getProperties
提到它。但从我的实验来看,似乎这个值会影响文件内容的编码,而不会影响文件名称。如果System.out
为file.encoding
,则POSIX
特别不会打印非ASCII字符。
另一方面,确定哪种编码适用于文件名的Linux方法是当前语言环境设置的LC_CTYPE
方面。我认为Java没有理由覆盖它。由于许多其他平台(特别是Windows)始终将Unicode用于文件名而不是字节,因此将文件系统的字节级详细信息暴露给Java应用程序几乎没有意义。
答案 2 :(得分:2)
请参阅java.com上的bug 4163515。它解释说:
另请注意,即使更改file.encoding“适用于您的平台”,也不应该这样做 - 因为它不会更改Oracle JVM一般使用的默认编码,而只会更改某些子系统。由于错误显示字符串构造函数使用字节数组的默认编码不受此设置的影响。