java解压ZIP 解决中文乱码 (GBK和UTF-8)
工具使用 : zip4j
GitHub : zip4j
版本 : 2.2.8
Maven :
<dependency>
<groupId>net.lingala.zip4j</groupId>
<artifactId>zip4j</artifactId>
<version>2.2.8</version>
</dependency>
ZIP解压现状
ZIP格式在不同平台上使用不同软件压缩,大致结果为两类:
1. Windows下使用WinRAR、好压、快压、百度压缩等工具压缩的文件
特点:文件名为GBK编码
2. 使用Linux、MacOS等系统压缩的zip文件
特点:文件名为UTF-8编码
过往解决方案
通过指定解压时的文件名编码类型来解决,最简单粗暴的就是
ZipFile zip = new ZipFile(dest);
//直接指定GBK,反正大多数人用win操作
zip.setFileNameCharset("GBK");
zip.extractAll(Constants.GOODS_ITEM_IMG_PATH);
但是现在用macOS办公的人越来越多,这种写法已经不满足需求了
新解决方案
通过阅读ZIP的协议文档,我们可以发现,Info-ZIP Unicode Path Extra Field (0x7075)
这个额外信息可以解决我们的问题,据笔者测试,WinRAR和百度压缩等使用GBK作为文件编码的压缩软件,
在这个区域会记录文件名的UTF-8编码的名称,但是因为这个字段不是必要字段,文件名使用UTF-8编码的
MacOS归档、Deepin归档等软件不会填充这个信息。
解决方案代码
String extractAll(MultipartFile file) throws Exception {
String path = RECEIVABLE_SHEET_PATH;
if(!new File(path).mkdirs()) {
return "上传文件失败,无法创建临时文件夹";
}
File dest = new File(path + "/"+file.getOriginalFilename());
file.transferTo(dest);
/* 解压 */
try {
ZipFile zip = new ZipFile(dest);
zip.setCharset(Charset.forName("utf-8"));
System.out.println("begin unpack zip file....");
zip.getFileHeaders().forEach(v->{
String extractedFile = getFileNameFromExtraData(v);
try {
zip.extractFile(v, path,extractedFile);
} catch (ZipException e) {
System.out.println("解压失败 :"+extractedFile);
e.printStackTrace();
return;
}
System.out.println("解压成功 :"+extractedFile);
});
System.out.println("unpack zip file success");
} catch (ZipException e) {
if(!new File(path).mkdirs())
return "解压失败";
}
return "success";
}
public static String getFileNameFromExtraData(FileHeader fileHeader) {
if(fileHeader.getExtraDataRecords()!=null){
for (ExtraDataRecord extraDataRecord : fileHeader.getExtraDataRecords()) {
long identifier = extraDataRecord.getHeader();
if (identifier == 0x7075) {
byte[] bytes = extraDataRecord.getData();
ByteBuffer buffer = ByteBuffer.wrap(bytes);
byte version = buffer.get();
assert (version == 1);
int crc32 = buffer.getInt();
System.out.println("使用:fileHeader.getExtraDataRecords() ");
return new String(bytes, 5, buffer.remaining(), StandardCharsets.UTF_8);
}
}
}
System.out.println("使用:fileHeader.getFileName()");
return fileHeader.getFileName();
}
参考ZIP规范资料
Third party mappings commonly used are:
0x07c8 Macintosh
0x2605 ZipIt Macintosh
0x2705 ZipIt Macintosh 1.3.5+
0x2805 ZipIt Macintosh 1.3.5+
0x334d Info-ZIP Macintosh
0x4341 Acorn/SparkFS
0x4453 Windows NT security descriptor (binary ACL)
0x4704 VM/CMS
0x470f MVS
0x4b46 FWKCS MD5 (see below)
0x4c41 OS/2 access control list (text ACL)
0x4d49 Info-ZIP OpenVMS
0x4f4c Xceed original location extra field
0x5356 AOS/VS (ACL)
0x5455 extended timestamp
0x554e Xceed unicode extra field
0x5855 Info-ZIP UNIX (original, also OS/2, NT, etc)
0x6375 Info-ZIP Unicode Comment Extra Field
0x6542 BeOS/BeBox
0x7075 Info-ZIP Unicode Path Extra Field
0x756e ASi UNIX
0x7855 Info-ZIP UNIX (new)
0xa220 Microsoft Open Packaging Growth Hint
0xfd4a SMS/QDOS
-Info-ZIP Unicode Path Extra Field (0x7075):
Stores the UTF-8 version of the file name field as stored in the
local header and central directory header. (Last Revision 20070912)
Value Size Description
----- ---- -----------
(UPath) 0x7075 Short tag for this extra block type ("up")
TSize Short total data size for this block
Version 1 byte version of this extra field, currently 1
NameCRC32 4 bytes File Name Field CRC32 Checksum
UnicodeName Variable UTF-8 version of the entry File Name
Currently Version is set to the number 1. If there is a need
to change this field, the version will be incremented. Changes
may not be backward compatible so this extra field should not be
used if the version is not recognized.
The NameCRC32 is the standard zip CRC32 checksum of the File Name
field in the header. This is used to verify that the header
File Name field has not changed since the Unicode Path extra field
was created. This can happen if a utility renames the File Name but
does not update the UTF-8 path extra field. If the CRC check fails,
this UTF-8 Path Extra Field should be ignored and the File Name field
in the header should be used instead.
The UnicodeName is the UTF-8 version of the contents of the File Name
field in the header. As UnicodeName is defined to be UTF-8, no UTF-8
byte order mark (BOM) is used. The length of this field is determined
by subtracting the size of the previous fields from TSize. If both
the File Name and Comment fields are UTF-8, the new General Purpose
Bit Flag, bit 11 (Language encoding flag (EFS)), can be used to
indicate that both the header File Name and Comment fields are UTF-8
and, in this case, the Unicode Path and Unicode Comment extra fields
are not needed and should not be created. Note that, for backward
compatibility, bit 11 should only be used if the native character set
of the paths and comments being zipped up are already in UTF-8. It is
expected that the same file name storage method, either general
purpose bit 11 or extra fields, be used in both the Local and Central
Directory Header for a file.