提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


文章目录

前言

  • 压缩后的文件名如何正确读取?
  • 如果正确读取内容?
  • 总结





前言

zip的悠久历史大家可以查下,这里讲下zip容易被忽略的知识。因为历史原因,ZIP的编码一般有两种,分别是CP437和UTF-8;这里zip的编码一般指zip里文件名的编码,文件内容的编码是原文件本身的编码,zip不会对文件内容编码做任何改版。原本文件名在不同操作系统下不一样,根据操作系统本身的编码有关。一般类Unix系统下默认是UTF-8,比如苹果系统、Linux.而windows系统下是GBK;如果zip编码只有两种,那windwos的GBK呢?那这个时候能正确读取吗


一、压缩后的文件名如何正确读取?

       这里要了解下字符集发展历史;CP437是EASCII的实现之一,Java里叫IBM437,后来又发展出了兼容部分CP437字符的ISO8859-1.而CP437和ISO8859-1作为EASCII的实现和扩展,都是单字节编码。

       zip压缩包本身没有字符集的标识,所以如何同时读取不同平台下的压缩的zip是一个问题;在实践中以ISO8859-1编码读取,因为单字节编码不会对原名称进行补码,所以不会引发解码异常;如果以UTF8读取其他字符集时会对文件名信息进行补码而造成改变,甚至引发解码异常。本文都是以apache compress为例

try (ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(inputStream, 
"ISO-8859-1")) {
                for (ZipArchiveEntry archiveEntry = zipInputStream.getNextZipEntry(); 
                      archiveEntry != null ; 
                        archiveEntry = zipInputStream.getNextZipEntry()) {

                           String archiveEntryName=decodeName(archiveEntry.getRawName());
                   }
}cathc(Exception e){

}

解码文件名

List<Charset>  charsets = Lists.newArrayList(StandardCharsets.UTF_8, Charset.forName("GBK"));
Charset nameCharset=null;
public String decodeName(byte[] bytes){
   if(nameCharset==null){
      for (Charset charset : charsets) {
       try {
         String name = charset.newDecoder()
                                .decode(ByteBuffer.wrap(nameBytes))
                                .toString();
         //对于拉丁语系的名称,第一次能解析出来
         //第二次遇到中文等其他语言就会乱码,所以其他碰到带有语言名称才保留字符集信息,避免重复解码
         if (name.getBytes("ISO8859-1").length != nameBytes.length) {
             nameCharset = charset;
           }
        return name;
      } catch (Exception e) {
        
     }
                      
   }
  }
 return new String(bytes,nameCharset);
}

二、如果正确读取内容?

byte[] bytes= new byte[(int) archiveEntry.getSize()];
zipInputStream.read(bytes);

String content=new String(bytes,"UTF-8");


一般我们采用上述方式读取,但是会有一下问题

               1.有些平台的工具不会把每个文件的大小信息放zip信息里,这个时候getSize()是-1;

               2.read的时候根据不同内存大小,不会全部读取到bytes里

               3.不同工具编辑后的文件内容的字符集可能差异很大

针对size不明的情况,只能以1字节读取。直到读取结果-1为止

ByteArrayOutputStream baos = new ByteArrayOutputStream();
 for (int b = zipInputStream.read(); b != -1;
       b = zipInputStream.read()) {
       baos.write(b);
    }
  baos.close();
byte[] bytes= baos.toByteArray();

针对不会一次性读取出来时,每次读取完都会返回当前读取的位置

byte[] bytes= new byte[(int) archiveEntry.getSize()];
   int offset = 0, len = bytes.length;
   for (int read; (read = zipInputStream.read(bytes, offset, len - offset)) > 0;
                             offset += read) {
 }


针对怎么识别字符集文本的问题,可以采用tika、icu4j等具体方法可以看下着两者api和包大小作取舍


总结

因为zip包特别大原因,不能直接加载到内存里或者解压再一个文件一个文件的读取。所以采用直接读取的方式。在读取的时候碰到文件中文报错,尝试过jdk的ZipFile、apache VFS2、apache comress。发现apache compress兼容性比较好且包比较小,遂采用,发现不同平台下报错,也通过测试和学习字符集知识了解到如何利用和规避异常以正确读取信息