解壓后文件名亂碼
由於zip格式文件無編碼存儲的結構,因此解壓時無法知道原先的編碼。
當解壓zip格式文件時使用的編碼和原編碼不一致時,就可能會出現解壓后文件名亂碼問題。
猜測編碼
基於上述問題,需要對zip格式文件所使用的編碼進行猜測。
筆者使用基於統計學原理的 juniversalchardet
庫來猜測編碼,下面是maven依賴坐標。
<dependency>
<groupId>com.googlecode.juniversalchardet</groupId>
<artifactId>juniversalchardet</artifactId>
<version>1.0.3</version>
</dependency>
下面是一個簡單的例子,只需要提供待猜測文本的字節數據即可。
UniversalDetector detector = new UniversalDetector(null);
String text = "這是一段測試文本";
byte[] buf = text.getBytes("GB18030");
detector.handleData(buf, 0, buf.length);
detector.dataEnd();
System.out.println(detector.getDetectedCharset()); // GB18030
zip格式文件結構
zip格式文件的結構如下(具體解析見其他文章),這里需要其中的 CENTRAL_DIRCETORY_HEADER
區域,該區域存儲zip里所有的文件及文件夾的名稱。
|LOCAL_FILE_HEADER|
|EXTRA_LOCAL_HEADER|
|CENTRAL_DIRCETORY_HEADER|
|END_OF_CENTRAL_DIRCETORY_RECORD|
利用Apache Compress庫獲取名稱字節數據
筆者一般推薦使用 Apache Compress
庫來處理zip格式文件,可以使用 ZipArchiveEntry.getRawName
方法來獲取名稱的字節數據。
下面是檢測zip格式文件編碼的例子,其中為了加快速度忽略解析Local file header區域。
public String detectCharset(File file) throws IOException {
// Ignore resolve local file header to improve rate.
ZipFile zipFile = new ZipFile(file, StandardCharsets.UTF_8.name(), true, false);
Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
UniversalDetector detector = new UniversalDetector(null);
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
byte[] buf = entry.getRawName(); // Get the byte data of file name.
detector.handleData(buf, 0, buf.length);
}
detector.dataEnd();
return detector.getDetectedCharset();
}
下面是Apache Compress庫的依賴坐標。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.21</version>
</dependency>
注意事項
數據量越大 juniversalchardet
庫檢測編碼就越准確,反之可能得到空結果或者不准確的編碼。
例如猜測編碼章節中讓 text="中文"
,最后輸出的結果是 KOI8-R
。
為此實際使用中要根據業務場景來做限定,比方說系統面向的用戶只可能是中文用戶,那么當出現 KOI8-R
這種斯拉夫文字的編碼時應當限定為中文的 GB18030
編碼(數據量小時GB18030非常大概率會被檢測成KOI8-R)。