【问题标题】:file.encoding has no effect, LC_ALL environment variable does itfile.encoding 没有效果,LC_ALL 环境变量可以
【发布时间】:2012-07-05 19:52:11
【问题描述】:

在以下使用 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 并从命令行以 UTF-8 格式提供 file.encoding 和 sun.jnu.encoding 属性,我会得到垃圾输出并且 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 如何解释文件名。无论文件编码和 LC_ALL 环境变量的值如何,以下简单的 C 代码都可以在 Linux 上运行(我很高兴这证明了这里给出的答案: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');
    }
}

【问题讨论】:

  • 我不确定到底是谁负责解码命令行参数。可能是外壳,也可能是 Java。如果您从其他地方(字符串文字,或从标准输入读取,或从属性文件)获取文件名,它是否按预期工作?
  • 你为什么要跳 absPath 舞?只是为了演示目的吗?您可以使用 f.listFiles() 而不是 f.list() 直接获取 File 对象(这可能有效)。
  • @Thilo 这只是一个示例程序。
  • @Thilo:问题中提到的sun.jnu.encoding 似乎会影响命令行解释。在 LC_CTYPE=POSIX 设置但使用 UTF-8 终端时,如果我通过 -Dsun.jnu.encoding=UTF-8,来自命令行参数的非 ASCII 字符将打印为单个 ?,即使它是两个字节。如果我也通过-Dfile.encoding=UTF-8,那么它也会被打印为 UTF-8。如果我只通过后者,那么我会打印出两个 unicode 不可用的符号字形。

标签: java linux file encoding internationalization


【解决方案1】:

我不确定您在哪里读到有关 file.encoding 的信息。我没有看到其他standard properties as documented with System.getProperties 提到它。但是从我的实验来看,这个值似乎影响了文件content的编码,而不是文件names。如果file.encodingPOSIX,则System.out 尤其不会打印非ASCII 字符。

另一方面,Linux 决定哪种编码适用于文件名的方法是当前语言环境设置的LC_CTYPE 方面。我认为 Java 没有理由重写它。由于许多其他平台(尤其是 Windows)总是使用 Unicode 来表示文件名,而不是字节,因此将文件系统的字节级细节暴露给 Java 应用程序几乎没有意义。

【讨论】:

【解决方案2】:

注意:所以最后我认为我已经确定了。我不确认它是正确的。但是通过一些代码阅读和测试,这是我发现的,我没有额外的时间来研究它。如果有人有兴趣,他们可以检查一下并判断这个答案是对还是错 - 我会很高兴:)

我使用的参考来自 OpenJDK 网站上的这个 tarball: openjdk-6-src-b25-01_may_2012.tar.gz

  1. Java 在此方法中将所有字符串本地转换为平台的本地编码:jdk/src/share/native/common/jni_util.c - JNU_GetStringPlatformChars()。系统属性sun.jnu.encoding 用于确定平台的编码。

  2. sun.jnu.encoding 的值使用 libc 的 setlocale() 方法设置为 jdk/src/solaris/native/java/lang/java_props_md.c - GetJavaProperties()。环境变量LC_ALL 用于设置sun.jnu.encoding 的值。在命令提示符下使用 Java 的 -Dsun.jnu.encoding 选项给出的值将被忽略。

  3. File.exists() 的调用已编码在文件jdk/src/share/classes/java/io/File.java 中,它返回为

    return ((fs.getBooleanAttributes(this) &amp; FileSystem.BA_EXISTS) != 0);

  4. getBooleanAttributes() 在函数中的jdk/src/share/native/java/io/UnixFileSystem_md.c 中进行了本机编码(并且我跳过了浏览许多文件的代码步骤): Java_java_io_UnixFileSystem_getBooleanAttributes0()。这里的宏 WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) 将路径字符串转换为平台的编码。

  5. 因此转换为错误编码实际上会发送一个错误的 C 字符串(char 数组)到后续调用 stat() 方法。它会返回找不到文件的结果。

教训:LC_ALL 很重要

【讨论】:

    【解决方案3】:

    请在 java.com 上查看bug 4163515。它解释说:

    1. file.encoding 特定于 Sun(现在是 Oracle)的 JVM 实现 - 其他人可能不支持它
    2. 应被视为只读
    3. 要更改它,您应该修改 JVM 运行的环境(这是您对 LC_ALL 所做的)

    还请注意,即使更改 file.encoding 对您的平台“有效”,您也不应该这样做 - 因为它不会更改 Oracle JVM 通常使用的默认编码,而只会在某些子系统中更改。由于该错误显示采用字节数组的 String 构造函数使用的默认编码不受此设置的影响。

    【讨论】:

      猜你喜欢
      • 2023-03-21
      • 2019-02-21
      • 1970-01-01
      • 2022-10-07
      • 1970-01-01
      • 2011-03-19
      • 2021-06-15
      • 2013-10-16
      • 2019-07-15
      相关资源
      最近更新 更多