我正在试验一个边缘案件,我们看到在生产。我们有一个商业模式,客户端生成文本文件,然后将它们ftp到我们的服务器上。我们接收这些文件并在java后端(在centos机器上运行)处理它们。我们的大多数客户(95%+)都知道用utf-8生成这些文件,这正是我们想要的。然而,我们有一些顽固的客户机(但大帐户)生成这些文件在windows机器上与cp1252字符集。不过没问题,我们已经配置了我们的第三方lib(这是大多数“处理”工作为我们)来处理任何字符集的输入,通过一些神奇的voo-doo。
有时,我们会看到文件名中包含非法的utf-8字符(cp1252)。当我们的软件试图从ftp服务器读取这些文件时,正常的文件读取方法会阻塞并抛出错误 FileNotFoundException
:
File f = getFileFromFTPServer();
FileReader fReader = new FileReader(f);
String line = fReader.readLine();
// ...etc.
例外情况如下所示:
java.io.FileNotFoundException: /path/to/file/some-text-blah?blah.xml (No such file or directory) at java.io.FileInputStream.open(Native Method) at
java.io.FileInputStream.(FileInputStream.java:120) at java.io.FileReader.(FileReader.java:55) at com.myorg.backend.app.InputFileProcessor.run(InputFileProcessor.java:60) at
java.lang.Thread.run(Thread.java:662)
所以我认为发生的事情是,因为文件名本身包含非法字符,所以我们根本无法读取它。如果我们可以,那么不管文件的内容如何,我们的软件都应该能够正确处理它。因此,读取包含非法utf-8字符的文件名确实是个问题。
作为一个测试用例,我创建了一个非常简单的java“app”来部署在我们的一个服务器上并测试一些东西(下面提供了源代码)。然后我登录到一台windows机器,创建了一个测试文件并命名它 test£.txt
. 注意文件名中“test”后面的字符。我是alt-0163。我把这个发到服务器上,当我跑的时候 ls -ltr
在它的父目录中,我惊讶地看到它被列为 test?.txt
.
在我进一步讨论之前,这里是我为测试/再现这个问题而编写的java“应用程序”:
public Driver {
public static void main(String[] args) {
Driver d = new Driver();
d.run(args[0]); // I know this is bad, but its fine for our purposes here
}
private void run(String fileName) {
InputStreamReader isr = null;
BufferedReader buffReader = null;
FileInputStream fis = null;
String firstLineOfFile = "default";
System.out.println("Processing " + fileName);
try {
System.out.println("Attempting UTF-8...");
fis = new FileInputStream(fileName);
isr = new InputStreamReader(fis, Charset.forName("UTF-8"));
buffReader = new BufferedReader(isr);
firstLineOfFile = buffReader.readLine();
System.out.println("UTF-8 worked and first line of file is : " + firstLineOfFile);
}
catch(IOException io1) {
// UTF-8 failed; try CP1252.
try {
System.out.println("UTF-8 failed. Attempting Windows-1252...(" + io1.getMessage() + ")");
fis = new FileInputStream(fileName);
// I've also tried variations "WINDOWS-1252", "Windows-1252", "CP1252", "Cp1252", "cp1252"
isr = new InputStreamReader(fis, Charset.forName("windows-1252"));
buffReader = new BufferedReader(isr);
firstLineOfFile = buffReader.readLine();
System.out.println("Windows-1252 worked and first line of file is : " + firstLineOfFile);
}
catch(IOException io2) {
// Both UTF-8 and CP1252 failed...
System.out.println("Both UTF-8 and Windows-1252 failed. Could not read file. (" + io2.getMessage() + ")");
}
}
}
}
当我从终端运行这个时( java -cp . com/Driver t*
),我得到以下输出:
Processing test�.txt
Attempting UTF-8...
UTF-8 failed. Attempting Windows-1252...(test�.txt (No such file or directory))
Both UTF-8 and Windows-1252 failed. Could not read file.(test�.txt (No such file or directory))
``` `test�.txt` ?!?! 我做了一些研究发现�" 是unicode替换字符 `\uFFFD` . 所以我猜现在的情况是centos ftp服务器不知道如何处理alt-0163( `£` )所以它用 `\uFFFD` ( `�` ). 但我不明白为什么 `ls -ltr` 显示名为 `test?.txt` ...
在任何情况下,解决方案似乎都是添加一些逻辑来搜索文件名中是否存在此字符,如果找到,则将文件重命名为其他文件(例如,可能会执行字符串操作) `replaceAll("\uFFFD", "_")` 或者类似的东西)系统可以读取和处理。
问题是java在文件系统中甚至看不到这个文件。centos知道文件在那里( `test?.txt` ),但当该文件被传递到java中时,java将其解释为 `test�.txt` 出于某种原因 `No such file or directory` ...
如何让java看到这个文件,以便执行 `File::renameTo(String)` 在上面?很抱歉这里的背景故事,但我觉得它是相关的,因为每一个细节在这个场景的计数。提前谢谢!
2条答案
按热度按时间bhmjp9jg1#
欢迎来到精彩的文本编码世界。你有几个层次的问题,你需要把每一个问题分别解决。
首先,磁盘上的文件名是什么?它是包含有效的utf-8转义序列还是其他的?
这里的问题是您需要正确的文件名,否则windows文件系统将无法找到该文件。除此之外,windows可能会尝试将文件名中的非法字符转换为unicode
\uFFFD
因此,无论您尝试什么,都无法加载该文件(因为没有具有\uFFFD
在磁盘上)。怎么会这样?这是因为Map不是双向的。当windows从磁盘加载文件名时,它将替换
test�.txt
与test\uFFFD.txt
给了你这个名字。当你叫Windows打开的时候test\uFFFD.txt
,它将无法找到该文件,因为没有具有此名称的文件(只有test�.txt
). 你没有办法找出文件的真实名称。解决?您可以打开dos提示符并用模式重命名文件
ren test*.txt test.txt
. 因为模式只匹配一个文件,所以这样就行了。但是你不能在windows资源管理器上做同样的事情,因为它也找不到文件。下一步:ftp。ftp是一种面向人类的协议,它不适合自动数据交换。摆脱ftp。我不知道那会花你多少钱,但它总是值得的。使用sftp、scp或ftapi。
问题的一个来源可能是ftp以ascii格式传输文件名。ftp协议中不允许UMLAUT。。。或者更确切地说,ftp不期望任何。如果你幸运的话,你的ftp客户端会拒绝传输文件,但最简单的就是窃听。但当它们存在时,ftp只会。。。一些东西。不管是什么。这里通常的效果是名称中带有unicode的文件被编码为utf-8的两倍,或者unicode被替换为
?
(\u003f
).或者javaftp客户端可以使用
new String( bytes )
从ftp文件名创建一个字符串,这个字符串会用系统的默认编码强奸可怜的字节-不太好。解决:
使用ftp服务器,它拒绝名称中包含非法字符的文件,或者将这些字符替换为不会混淆文件系统/os的字符。
使用能正确处理具有奇怪名称的文件的文件系统。这通常意味着删除服务器上的windows。
确保用户只能上载到单个目录,并且此目录只能包含单个文件。这样,您就可以使用一个小的shell脚本和模式将其重命名为您可以阅读的内容。
dfty9e192#
这是旧的skool java文件api中的一个bug,也许只是在mac上?无论如何,新的java.nioapi工作得更好。我有几个包含unicode字符的文件无法使用java.io加载。。。班级。在将所有代码转换为java.nio.path之后,一切都开始工作了。我用java.nio.files替换了ApacheFileUtils(也有同样的问题)。。。
确保使用适当的字符集读取和写入文件内容,例如:files.readalllines(mypath,standardcharsets.utf8)