使用Java解压包含德语字符文件名的文件夹

5

我正在尝试解压包含德语字符的文件夹,例如Aufhänge。 我知道在Java 7中,默认使用utf-8编码,而我认为"ä"是utf-8字符之一。 这是我的代码片段:

public static void main(String[] args) throws IOException {
    ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(ZIP_PATH), StandardCharsets.UTF_8);
    ZipEntry zipEntry;
    while ((zipEntry = zipInputStream.getNextEntry()) != null) {
        System.out.println(zipEntry.getName());
    }
}

我收到了一个错误信息:java.lang.IllegalArgumentException: MALFORMED
使用Charset.forName("Cp437")可以解决此问题,但使用StandardCharsets.UTF_8无法解决。这与字符集有关。

非常容易复制,只需创建一个名为"Aufhänge"的文本文件并压缩它。 - Luka Gorgadze
我已经尝试过了,但是没有出现错误。 - Raj Hirani
我正在使用JAVA8进行编译。 - Raj Hirani
我也在使用Java 8。问题可能出在操作系统上。我正在使用Windows 10。我认为在Linux上,文件名的编码方式是不同的。 - Luka Gorgadze
我们已经使用Java 8和7zip复现了这个问题,当压缩包中有许多带有特殊字符的文件时,而不仅仅是一个文件。 - The Human Cat
显示剩余2条评论
1个回答

15
你没有提到你的操作系统,也没有提到你是如何创建zip文件的,但我还是设法重现了你的问题,在Windows 10上使用7-Zip:
  • 创建一个简单的文本文件,其中包含一些琐碎的内容(例如仅包含三个字符“abc”)。
  • 将文件保存为D:\Temp\Aufhänge.txt。注意文件名中的umlaut。
  • 在Windows文件浏览器中找到该文件。
  • 选择该文件并右键单击。从上下文菜单中选择7-Zip>添加到“Aufhänge.zip”以创建Aufhänge.zip

然后,在NetBeans中运行以下代码解压刚刚创建的文件:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class GermanZip {

    static String ZIP_PATH = "D:\\Temp\\Aufhänge.zip";

    public static void main(String[] args) throws FileNotFoundException, IOException {

        ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(ZIP_PATH), Charset.forName("UTF-8"));
        ZipEntry zipEntry;
        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
            System.out.println(zipEntry.getName());
        }
    }

}

正如您所指出的,当执行此语句时:zipEntry = zipInputStream.getNextEntry()) != null,代码会抛出java.lang.IllegalArgumentException: MALFORMED异常。
问题的原因是默认情况下7-Zip使用Cp437编码对zip文件中的文件名进行编码,正如来自7-Zip的此评论所述:

默认编码为OEM(DOS)编码。这是为了与旧版zip软件兼容。

这就是为什么使用Charset.forName("Cp437")而不是Charset.forName("UTF-8")时解压缩可以正常工作的原因。
如果您想要使用Charset.forName("UTF-8")进行解压缩,则必须强制7-Zip将zip文件中的文件名编码为UTF-8。要做到这一点,请在运行7-Zip时指定cu参数,正如链接评论中所述:
  • 在Windows文件资源管理器中选择文件并右键单击。
  • 从上下文菜单中选择7-Zip>添加到压缩文件...
  • 添加到压缩文件对话框中,在参数字段中指定cu:

    AddToArchive

  • 将已存储为UTF-8格式的压缩文件名替换为您的代码中的Charset.forName("Cp437"),并使用Charset.forName("UTF-8"),解压时将不会抛出任何异常。

此答案特定适用于Windows 10和7-Zip,但通用原则应适用于任何环境:如果为您的ZipInputStream指定UTF-8编码,请确保zip文件中的文件名确实使用UTF-8编码。您可以通过在二进制编辑器中打开zip文件并搜索压缩文件的名称来轻松验证此操作。


基于下面OP的评论/问题的更新:

很遗憾,.ZIP文件格式规范目前没有提供一种存储除一个例外之外的压缩文件名编码的方法,该例外在“附录D-语言编码(EFS)”中描述: D.2 如果通用位标志11未设置,则文件名和注释应符合原始的ZIP字符编码。如果通用位标志11已设置,则文件名和注释必须支持Unicode标准4.1.0或更高版本,使用UTF-8存储规范定义的字符编码形式。Unicode标准由Unicode Consortium(www.unicode.org)发布。在ZIP文件中存储的UTF-8编码数据不应包含字节顺序标记(BOM)。
因此,在您的代码中,对于每个压缩文件,请首先检查通用位标志的第11位是否已设置。如果是,则可以确定该压缩文件的名称使用UTF-8进行编码。否则,编码是创建压缩文件时使用的任何编码方式。默认情况下,在Windows上为Cp437,但如果您在Windows上运行并处理在Linux上创建的zip文件,则我认为没有简单的确定所使用编码的方法。
不幸的是,ZipEntry没有提供访问压缩文件的通用位标志字段的方法,因此需要以字节级别处理zip文件才能实现该功能。
要增加进一步复杂性,“编码”在这种情况下是指用于每个压缩文件名的编码,而不是zip文件本身的编码。一个压缩文件名可以使用UTF-8进行编码,另一个压缩文件名可以使用Cp437添加,等等。

谢谢,不过我正在为普通公众编写客户端,这意味着一些zip文件将使用Cp437编码,而另一些则使用其他编码。我应该只尝试一种编码,如果错误,则捕获异常,然后尝试另一种编码,以此类推吗?没有更好的方法吗? - Luka Gorgadze
@LukaGorgadze 我已经更新了我的答案。如果zip文件是在Windows上创建的,那么假设通用目的位标志的第11位被设置为UTF-8,就像我的更新中描述的那样。否则假设为Cp437,但也要捕获任何IllegalArgumentException异常,并研究用于创建该压缩文件的编码方式。然后根据需要增强您的代码以适应其他编码方式。有关更多信息,请参见如何正确解压具有希伯来名称的文件的ZIP存档? - skomisa

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