Java 7在OSX上使用jnlp/webstart时文件名编码问题

11

我遇到了一个问题,经过几天的不成功搜索和尝试解决方法。我们现在有一个内部Java Swing程序,通过jnlp / webstart分发给OSX和Windows计算机,在下载一些来自WebDav的文件等操作时出现问题。 最近,在具有OSX 10.8和Java 7的测试机器上,带有重音字符的文件名和目录名开始被替换为问号。

在先前版本的Java之前,OSX上没有这个问题。

例如:

XXXYYY_è_ABCD/

变成

XXXYYY_?_ABCD/

在原始字符串上使用java.text.Normalizer(NFD、NFC、NFKD、NFKC)会得到不同但仍然错误的结果:

XXXYYY_e?_ABCD/

XXXYYY_e_ABCD/

我从[andrew.brygin at oracle.com]和[mik3hall at gmail.com]之间的信件中获知:

是的,file.encoding是基于jvm运行的语言环境设置的,如果你在xxxx.UTF-8 编码环境下运行你的java vm,那么file.encoding就应该是UTF-8, 如果设成MacRoman将会有问题。所以我认为Oracle/OpenJDK7的表现是正确的。话虽如此,正如Andrew Thompson所指出的,如果所有以前的Apple JDK版本都使用MacRoman作为英语/UTF-8语言环境的file.encoding,那么这里就存在一个“兼容性”问题,可能值得在发行说明中提醒Oracle / OpenJDK MacOS用户。

原始邮件

Joni Salonen的博客(java-and-file-names-with-invalid-characters)中我了解到:

你可能知道Java使用“默认字符编码”将二进制数据转换为字符串。您可以使用InputStreamReader或OutputStreamWriter来读取或写入另一种编码的文本。但是,在API深处进行数据到文本转换时,您别无选择,只能更改默认编码。

还有

file.encoding怎么样?

file.encoding系统属性也可用于设置Java用于I/O的默认字符编码。不幸的是,它似乎对将文件名解码为字符串的方式没有影响。

在jnlp内部执行locale命令,总是会打印:

LANG=
LC_COLLATE="C"
LC_CTYPE="C"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL=

与此类似的stackoverflow问题及其解决方案如下:

encoding-issues-on-java-7-file-names-in-os-x

但是解决方案是使用脚本包装Java程序的执行。

#!/bin/bash
export LC_CTYPE="UTF-8" # Try other options if this doesn't work
exec java your.program.Here

但我认为这个选项对我不可用,因为webstart的原因,而且我还没有找到任何方法从程序内部设置LC_CTYPE环境变量。

有什么解决方案或变通方法吗?

P.S. :

如果我们直接从Shell运行程序,则即使在OSX 10+Java 7上它也可以正确地写入文件/目录。问题仅出现在JNLP+OSX+Java7的组合中。


有人建议我使用jnlp属性来设置系统属性,就像这里所做的那样:https://dev59.com/qFbUa4cB1Zd3GeqPCdmM 但是我认为(请原谅我对jnlp相关事项的一般无知),这些属性不会影响环境变量,例如LC_CTYPE。这是正确的吗? - Duralumin
你的代码中是否有使用默认字符集的方法(例如,请参见此列表)? - assylias
我进行了搜索,发现有一些函数(例如 toLowerCase)在代码中被使用,但并未用于出现问题的功能。为什么呢? - Duralumin
你发现了这个解决方案的问题吗? - Fotis Paraskevopoulos
@Fotis 还没有。我们的系统管理员向 Oracle 发送了一个错误请求。我想是这样。希望如此。还在等待中。 - Duralumin
5个回答

5
我认为使用最大ASCII表示法的文件名是可以接受的,这在几乎任何编码中都有效。
首先,您需要使用特定的NFKD,以便在ASCII形式中保留最大信息。例如,"2⁵"变为"25"而不仅仅是"2";"fi"变为"fi"而不是"",一旦过滤掉非ASCII和非控制字符。
String str = "XXXYYY_è_ABCD/";
str = Normalizer.normalize(str, Normalizer.Form.NFKD);
str = str.replaceAll( "[^\\x20-\\x7E]", "");
//The file name will be XXXYYY_e_ABCD no matter what system encoding

你随后将始终通过此过滤器传递文件名以获取其文件系统名称。你失去的只是一些独特性,例如文件asdé.txtasde.txt相同,在该系统中它们无法区分。

很遗憾,这是不可行的。我也提议从文件和目录名称中剥离或替换重音和特殊字符;最终它们只是为了提供一个可读的参考唯一代码(该代码作为目录结构的一部分编写),而且容易出现问题(就像在这种情况下)。但是,不,这是不可接受的。 - Duralumin
@Duralumin 好的,那我很抱歉,我误解了你的标准化尝试。因为你尝试这样做,所以我认为这是可以的。 - Esailija
不是你的错,无论如何还是谢谢你。如果解决方案能像那样明智就好了... =) - Duralumin
@Duralumin 对于文件名使用URI编码并将其抽象化,你怎么看? - Esailija
做不到。不是我的选择。=/ - Duralumin

1

编辑:在进一步尝试了OS X后,我意识到我的答案完全错误,所以我正在重新做。

如果您的JVM支持在JVM命令行上使用-Dfile.encoding=UTF-8,那么可能会解决该问题。我相信这是一个标准属性,但我不确定。

HFS Plus和其他符合POSIX标准的文件系统一样,将文件名存储为字节。但与Linux的ext3文件系统不同的是,它强制文件名为有效的分解UTF-8。这可以在此处看到,在我的OS X系统上的Python解释器中,从空目录开始。

$ python
Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53) 
>>> import os
>>> os.mkdir('\xc3\xa8')
>>> os.mkdir('e\xcc\x80')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 17] File exists: 'e\xcc\x80'
>>> os.mkdir('\x8f')
>>> os.listdir('.')
['%8F', 'e\xcc\x80']
>>> ^D
$ ls
%8F è

这证明了你的文件系统上的目录名称不能使用Mac-Roman编码(即使用字节值8F来表示的è),只要它是HFS Plus文件系统。但当然,JVM并不保证是HFS Plus文件系统,并且SMB和NFS没有相同的编码保证,因此JVM不应该假定这种方案。

因此,您必须说服JVM使用UTF-8编码来解释文件和目录名称,以便正确地将它们读取为java.lang.String对象。


强制使用-Dfile.encoding是我们尝试的第一件事,因为它通常是这类问题的解决方案。但在这种情况下不起作用。Joni Salonen在博客中确认: “file.encoding系统属性也可以用于设置Java用于I/O的默认字符编码。不幸的是,它似乎对将文件名解码为字符串没有影响。” - Duralumin
好的,到目前为止我只做了你所做的。现在很容易去查看JVM源代码而不是去处理LC_变量。 - wberry
没错,最终只能选其一。我已经在尝试提交一个错误报告,但是通过官方渠道请求并等待Oracle的修复需要相当长的时间。希望能找到某种解决方法。 - Duralumin

1

猜测:文件编码不会影响文件名的创建方式,只会影响内容写入文件的方式 - 可以查看这个链接:http://jonisalonen.com/2012/java-and-file-names-with-invalid-characters/

这里有一篇苹果公司的简短文章:http://developer.apple.com/library/mac/#qa/qa1173/_index.html

http://docs.oracle.com/javase/tutorial/i18n/text/normalizerapi.html进行比较,我认为您需要在将文件名传递给File构造函数之前对其进行规范化处理。

normalized_string = Normalizer.normalize(target_chars, Normalizer.Form.NFD);

这样做是否有帮助?


请仔细阅读我的回答 :) 有不同的规范化策略(NFD,NFC,NFKD,NFKC),这些策略在苹果操作系统中处理方式不同。我的建议是尝试不同的规范化机制。 - Stefan
1
糟糕,我也不确定这是否有效。你的文件名的来源是什么?它存储在.java文件中还是从其他地方读取?这些人遇到了类似的问题:https://dev59.com/u3A65IYBdhLWcg3w1SRC - Stefan
之前没有看到过那个问题。是的,问题似乎相似,但他们似乎遇到了读取问题,而不是文件名写入问题(我们在Java6中也可以正确地写入文件名)。文件名是从数据库中读取/处理的。 - Duralumin
代码只是一个文件 file = new File(path);file.mkdirs()。前面的部分似乎没有任何影响。关键是你必须从带有Java 7的jnlp在OSX上启动。我们有各种测试机器,但这个组合是唯一出现问题的。 - Duralumin
嘿,好久没设置JNLP了,如果我有时间的话,今晚我会试一下。 - Stefan
显示剩余4条评论

0

这是旧版Java文件API中的一个错误,也许只在Mac上出现?无论如何,新的java.nio API效果要好得多。我有几个包含Unicode字符和内容的文件,使用java.io.File和相关类加载失败。将所有代码转换为使用java.nio.Path后,一切都开始正常工作了。我还用java.nio.Files替换了具有相同问题的org.apache.commons.io.FileUtils...

...请确保使用适当的字符集读取和写入文件内容,例如:

Files.readAllLines(myPath, StandardCharsets.UTF_8)

0

我觉得现在没有真正的解决方案。

同时,我得出结论,程序内打印的“C”环境变量来自于Java Web Start沙盒,(显然)你不能使用jnlp来影响它们。

公司认可的变通方法/妥协是使用bash脚本从javaws启动jnlp。

显然,从浏览器或finder启动jnlp会创建一个新的沙盒环境,并且LANG未设置(因此设置为等于ASCII的“C”)。反之,从命令行启动jnlp会从系统默认值中继承正确的LANG,从shell继承。

这允许至少保留jnlp和依赖项的自动更新功能。

无论如何,我们向Oracle发送了Bug报告,但我个人不希望它能在短时间内得到解决,甚至可能永远都不会解决。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接