IAm*_*aja 6 java filenotfoundexception utf-8 character-encoding windows-1252
我正在试验我们在生产中看到的边缘情况.我们有一个业务模型,客户端生成文本文件,然后将它们FTP到我们的服务器.我们在Java后端(在CentOS机器上运行)中提取这些文件并对其进行处理.大多数(95%以上)我们的客户都知道以UTF-8生成这些文件,这正是我们想要的.但是,我们有一些顽固的客户端(但很大的帐户)在Windows机器上使用CP1252字符集生成这些文件.没问题,我们已经配置了我们的第三方库(这是大多数"处理"工作对我们来说)通过一些神奇的voo doo来处理任何字符集中的输入.
偶尔,我们会看到一个文件,其名称中包含非法的UTF-8字符(CP1252).当我们的软件试图从FTP服务器读取这些文件时,正常的文件读取方法扼杀并抛出FileNotFoundException:
File f = getFileFromFTPServer();
FileReader fReader = new FileReader(f);
String line = fReader.readLine();
// ...etc.
Run Code Online (Sandbox Code Playgroud)
例外看起来像这样:
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)
Run Code Online (Sandbox Code Playgroud)
所以我认为发生的事情是因为文件名本身包含非法字符,所以我们从来没有读过它.如果可以,那么无论文件的内容如何,我们的软件都应该能够正确处理它.因此,读取带有非法UTF-8字符的文件名实际上是一个问题.
作为一个测试案例,我创建了一个非常简单的Java"app",可以部署在我们的一台服务器上并测试一些东西(源代码如下所示).然后我登录到Windows机器并创建了一个测试文件并将其命名test£.txt.注意文件名中"test"后面的字符.这是Alt-0163.我将其FTP到我们的服务器,当我ls -ltr在其父目录上运行时,我惊讶地发现它被列为test?.txt.
在我进一步讨论之前,这里是我为测试/复制此问题而编写的Java"app":
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() + ")");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
当我从终端(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))
Run Code Online (Sandbox Code Playgroud)
test�.txt?!?!我做了一些研究,发现"�"是Unicode替换字符\uFFFD.所以我想发生的事情是CentOS FTP服务器不知道如何处理Alt-0163(£),所以用\uFFFD(�)代替它.但我不明白为什么ls -ltr显示一个名为test?.txt... 的文件
无论如何,似乎解决方案是添加一些逻辑来搜索文件名中是否存在此字符,如果找到,则将文件重命名为其他内容(例如,可以执行String-wise replaceAll("\uFFFD", "_")或类似的操作)系统可以读取和处理.
问题是Java甚至没有在文件系统上看到这个文件.CentOS知道文件是在那里(test?.txt),但是当该文件被传递到Java时,Java将其解释为test�.txt由于某种原因No such file or directory......
如何让Java看到这个文件以便我可以执行File::renameTo(String)它?对于这里的背景故事感到抱歉,但我认为这是相关的,因为在这种情况下每个细节都很重要.提前致谢!
欢迎来到精彩的文本编码世界.您有几个级别的问题,您需要单独对它们进行排序.
首先,磁盘上的文件名是什么?它是否包含有效的UTF-8转义序列还是其他的?
这里的问题是您需要正确的文件名,否则Windows文件系统将无法找到该文件.最重要的是,Windows可能会尝试将文件名中的非法字符转换为Unicode,\uFFFD因此无论您尝试什么,您都无法加载该文件(因为\uFFFD磁盘上没有文件).
怎么可能?发生这种情况是因为映射不是双向的.当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协议中不允许使用变音符号...或者说,FTP不期望任何变音符号.如果你很幸运,你的FTP客户端将拒绝传输文件,但大多数情况下都是错误的.但是当它们存在时,FTP就会做......某事.无论那是什么.这里的常用效果是名称中带有Unicode的文件被编码两次,因为UTF-8或Unicode被替换为?(\u003f).
或者Java FTP客户端可以使用new String( bytes )从FTP文件名创建一个字符串,这将使用您的系统的默认编码强奸不良字节 - 不漂亮.
解决方案: