彻底解决 Zip4j 解压中文文件名乱码问题(含混合编码与 Mac 特殊情况)

问题背景:为什么 ZIP 会乱码?

ZIP 是一个"历史包袱很重"的压缩格式,早期并没有规定统一的文件名编码方式。

常见的情况是:

系统 常用编码
Windows GBK
macOS / Linux UTF-8

因此:

  • Windows 下压的包,用 UTF-8 解压会乱码;

  • macOS 下压的包,用 GBK 解压也会乱码;

  • 而 ZIP 文件内部并没有强制说明使用了哪种编码。

相比之下:

  • 7z → 文件名标准使用 UTF-16,几乎不会乱码;

  • RAR → 新版写了 Unicode 信息,问题较少;

  • ZIP → 完全靠猜。

👉 所以:文件名乱码几乎是 ZIP 独有的老问题。

遇到问题1:出现乱码

原因分析

最开始我写的时候,是这样写的:

Kotlin 复制代码
val zipFile = ZipFile(file)
zipFile.charset = Charset.forName("GBK")

这意味着无论压缩包来源于哪里,都强制使用 GBK 解码。但如果该文件是 macOS 或 Linux 压缩的(即 UTF-8 编码),文件名就会乱码。

解决方案

思路是先用 GBK 尝试解压,如果检测出乱码,再切换到 UTF-8。

如何判断有没有乱码,我就通过文件名称中是否存在"�"去判断的

当然这种方式后面遇到了别的问题,所以不适合

遇到问题2:混合编码(部分 GBK,部分 UTF-8)

下面两个图片分别展示:同一个压缩包文件使用不同编码的情况

可以看到使用GBK的话,上半部分乱码,下半部分正常

使用UTF-8的话,上半部分正常,下半部分乱码

原因

有些压缩包内部的文件名并不是统一编码的,比如:

  • 一部分文件使用GBK;

  • 一部分文件使用UTF-8

这种情况如果你给整个 zipFile 设置一种编码,就必定会有部分文件乱码。

解决方案:逐文件判断编码

不再给 zipFile 设置统一编码,而是 在读取每个文件头时动态判断编码

通过 fileHeader.isFileNameUTF8Encoded 能判断哪些用UTF-8,哪些用GBK,如果要使用GBK,就从Cp437转换到GBK

修改:删除给zipfile设置的统一编码,改为每个文件头都判断

Kotlin 复制代码
val zipFile = ZipFile(file)
// 删除下面
// zipFile.charset = Charset.forName("GBK")
Kotlin 复制代码
private fun getDecodedFileName(fileHeader: FileHeader): String {
    var result = fileHeader.fileName
    try {
        if (fileHeader.isFileNameUTF8Encoded) {
            LogUtil.d(TAG, "UTF-8,不用处理")
        } else {
            LogUtil.d(TAG, "GBK 编码")
            result = String(
                fileHeader.fileName.toByteArray(Charset.forName("Cp437")),
                Charset.forName("GBK")
            )
        }
    } catch (e: Exception) {
        LogUtil.e(TAG, "解码文件名时发生错误: ${e.message}")
    }
    return result
}

为什么要从Cp437转换到GBK?Zip4j的底层编码是什么样的?

Zip4j库 的解码优先级:

a. 如果开发者手动设置了编码,则使用开发者设置的编码;

b. 否则如果文件头带 UTF-8 标志,则使用 UTF-8 解码;

c. 否则使用 CP437;

上述方案满足了大部分情况,但是满足不了下面的mac操作系统压缩的文件的情况

遇到问题3:macOS 压缩包的特殊情况

下面是同一个mac压缩包里面的文件,使用不同编码的情况

原因

macOS 创建的 ZIP 文件中,虽然文件名实际使用 UTF-8 编码,但它 没有在文件头中设置 UTF-8 标志

这导致:

Kotlin 复制代码
fileHeader.isFileNameUTF8Encoded == false

从而错误地使用 GBK 解码 → 文件名乱码。

解决方案:检测是否是 Mac 压缩包

我发现几乎所有的mac压缩的压缩包,都会包含一个隐藏文件夹"__MACOSX/",所以我通过判断有没有这个文件夹,来区分是不是Mac操作系统压缩的

判断mac操作系统的方法:

Kotlin 复制代码
private fun isMacZipFile(zipFile: ZipFile): Boolean {
    val fileHeaders: List<FileHeader> = zipFile.fileHeaders
    for (fileHeader in fileHeaders) {
        val name = fileHeader.fileName
        if (name.startsWith("__MACOSX/")) {
            return true
        }
    }
    return false
}

所以最终的解决方案如下

最终解决方案

Kotlin 复制代码
/**
 * 获取正确编码的文件名,解决文件名乱码问题
 *
 * 逻辑:
 * 1. Zip4j库 的解码优先级:
 *    a. 如果开发者手动设置了编码,则使用开发者设置的编码;
 *    b. 否则如果文件头带 UTF-8 标志,则使用 UTF-8 解码;
 *    c. 否则使用 CP437;
 * 2. macOS 生成的 ZIP 文件未设置 UTF-8 标志,但文件名实际是 UTF-8;
 * 3. 其他系统根据UTF-8标识设置
 */
private fun getDecodedFileName(isMacZipFile: Boolean, fileHeader: FileHeader): String {
    var result = fileHeader.fileName

    try {
        when {
            isMacZipFile -> {
                // mac 的直接设置 UTF_8
                result = String(
                    fileHeader.fileName.toByteArray(Charset.forName("Cp437")),
                    Charsets.UTF_8
                )
            }

            !fileHeader.isFileNameUTF8Encoded -> {
                // 非 mac 且 没有 UTF-8 标识:直接使用 GBK
                result = String(
                    fileHeader.fileName.toByteArray(Charset.forName("Cp437")),
                    Charset.forName("GBK")
                )
            }
            // 有 UTF-8 标识的,Zip4j 已正确用 UTF-8 处理
        }
    } catch (e: Exception) {
        LogUtil.e(TAG, "解码文件名时发生错误: ${e.message}")
    }
    return result
}


private fun isMacZipFile(zipFile: ZipFile): Boolean {
    val fileHeaders: List<FileHeader> = zipFile.fileHeaders
    for (fileHeader in fileHeaders) {
        val name = fileHeader.fileName
        if (name.startsWith("__MACOSX/")) {
            return true
        }
    }
    return false
}
相关推荐
飞雪金灵15 小时前
mac电脑 Maven下载安装和配置环境变量
macos·maven·maven下载安装·mac电脑环境
雨声不在15 小时前
macos 12使用docker
macos·docker·容器
一叶龙洲16 小时前
ubuntu26.04 xfce美化成mac
服务器·ubuntu·macos
李小白杂货铺1 天前
Oh My Zsh 简记
macos·macbook·zsh·主题·插件·oh my zsh·omz
fukai77222 天前
macOS防止休眠的菜单栏小工具
macos
web守墓人2 天前
【go语言】gotar:使用go语言复刻tar命令,并加入7z支持,可独立运行在windows、linux、macos上
linux·macos·golang
之歆3 天前
从 Mac 回到 Windows:用 PowerToys + AutoHotkey 找回熟悉手感
windows·macos
星辰即远方3 天前
字符串合法性检验
macos·ios·cocoa
2501_916008893 天前
苹果上架工具怎么选 不用 Mac 上架 App Store 的几种方案
android·macos·ios·小程序·uni-app·iphone·webview
秋雨梧桐叶落莳4 天前
iOS——3GShare项目总结
学习·macos·ios·objective-c·cocoa