Java组件开发之文件压缩与解压详解
作者:gersy
缘起
在服务端项目开发的过程中,需要要各种不同的数据打交道,有时为了方便用户上传的下载会将数据以压缩包的形式打包后进行传输,那么就会遇到压缩与解压的问题了。在现在拥有如此多的开源组件的情况下,压缩本身并不算一个复杂的任务,只需要找到一些合适的maven库即可解决咱们的问题。那么为什么我们还需要做一个处理压缩的公共组件呢? 不妨我们想象一下这样一些需求:
- 压缩包中包含很多种不同的文件,我们只需要解压其中的部分类型的数据;
- 待压缩的目录中只有满足条件的文件需要被添加到压缩包中;
- 压缩格式类型多样,需要针对每种都单独适配;
- ...
如果分析这些需求就会发现这些都是通用功能,如果封装好了,业务方只需要简单调用即可,省去了很多重复的功能开发。
设计
通过分析上面的需求可知,需求大致分为两大类:
- 文件过滤;
- 兼容不同的压缩文件类型;
好了,那么我们可以继续的针对需求来实现咱们的组件了。
如何处理文件过滤的功能呢?对于一个公共组件而言,它是没办法聪明到了解所有的业务方的需求的,也没有必要了解,因为需求变化像海一样,哪里能做到面面俱到呢,你们说是吧?既然如此,那就只能是将过滤的功能暴露给业务方,组件里面接收过滤后的结果后进行相应处理即可,恩...完美!那么如何来实现呢?相信聪明的你可能想到了java8里的文件遍历Files.walkFileTree()。是的,咱们想到了一块。咱们可以仿照这个写法,让业务方传入FileVisitor的实现即可,这样就可以在调用方以最少的认知的情况下了解如何来使用这个公共组件。这样后面推动的时候就更容易啦,你说是吧?
上代码:
/** * 压缩示例,目录下的文件全部压缩,不需要过滤 */ Compressions.compress(new File(parent), new File(parent, "test.zip"));
/** * 解压示例,全部解压,不需要过滤 */ Compressions.uncompress(new File(parent,"test.zip"), new File(parent,"test"), true);
/** * 压缩示例,按需求过滤 */ Compressions.compress(new File(parent), new File(parent, "test.zip"), new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { if (dir.getFileName().toString().equals("testq")) { return FileVisitResult.SKIP_SUBTREE; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().endsWith("zip")) { return FileVisitResult.TERMINATE; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } });
/** * 解压示例,按需求过滤 */ Compressions.uncompress(new File(path), new SimpleFileVisitor<>() { @Override public FileVisitResult preVisitDirectory(FileEntry dir, BasicFileAttributes attrs) throws IOException { File file = new File(parent, dir.getPath()); if (!file.exists()) { file.mkdirs(); } return super.preVisitDirectory(dir, attrs); } @Override public FileVisitResult visitFile(FileEntry fileEntry, BasicFileAttributes attrs) throws IOException { System.out.println("visitFile:" + fileEntry.getPath()); if (fileEntry.getPath().endsWith("jpg")){ File file = new File(parent, fileEntry.getPath()); fileEntry.transferTo(file); } return super.visitFile(fileEntry, attrs); } @Override public FileVisitResult visitFileFailed(FileEntry<?> file, IOException exc) throws IOException { //读取出异常的压缩包全路径 String path2 = file.getPath(); return FileVisitResult.CONTINUE; } });
怎么样,使用起来是不是很方便,很灵活。
文件过滤的问题解决了,那么咱们再进一步,兼容下比较常用的压缩文件类型。 自己写是不可能自己去写的了,别忘了咱们是调用工程师。为了支持zip/7z/rar/jar/tar/tar.gz等常用的格式,咱们得导入几个开源库:
- net.sf.sevenzipjbinding (zip/7z/rar/jar/tar);
- commons-io (tar.gz)
插个题外话,这里要重点表扬下net.sf.sevenzipjbinding 在解压缩文件过滤上拥有显著的性能优势,性能优势从何而来呢,这里就要讲到net.sf.sevenzipjbinding和commons-io是如何处理需要跳过的文件流了,net.sf.sevenzipjbinding是真的跳过,就像RandomAccessFile的seek功能,而commons-io则是将跳过的文件流完整读一遍,但不作处理,哪个性能会更好,聪明的你肯定已经有了答案。事实上我也考虑过把commons-io中读流的操作改为跳过,技术上是可实现的,奈何我比较懒...思路大概是用RandomAccessFile或者SeekableByteChannel来实现Seek跳转,感觉RandomAccessFile可能会更容易些,毕竟跟它里面使用的api是一个时代的东东。
好了,咱们已经兼容了好几种不同的压缩格式,那么我们就可以根据不同的文件类型来自动使用匹配的压缩处理类。调用方可以做到完全无感,就很nice!当然我现在偷懒,是通过文件的扩展名来匹配的,更好的办法应该是通过文件的元信息来匹配,比如魔数。没办法,我就是懒,而且目前用起来确实够用了,哈哈。
到此这篇关于Java组件开发之文件压缩与解压详解的文章就介绍到这了,更多相关Java文件压缩与解压内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!