Inflater 和 Deflater 解压缩中的坑
Posted 枯木fc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Inflater 和 Deflater 解压缩中的坑相关的知识,希望对你有一定的参考价值。
使用 Inflater 和 Deflater 解压缩可能会踩到一个死循环的坑。
在网上搜索 Inflater 和 Deflater 的 demo 时,往往都会看到类似下面的写法:
注意看 7-10 行的写法,假如我传一个不完整的压缩后的数据去解压(比如 main() 方法里的),那么 7-10 行就会出现一个死循环
public static byte[] uncompress(byte[] input) throws IOException, DataFormatException Inflater inflater = new Inflater(); inflater.setInput(input); ByteArrayOutputStream baos = new ByteArrayOutputStream(input.length); byte[] buff = new byte[1024];
// 注意这里的写法,有死循环的风险 while (!inflater.finished()) int count = inflater.inflate(buff); baos.write(buff, 0, count); baos.close(); inflater.end(); return baos.toByteArray(); public static byte[] compress(byte[] data) throws IOException Deflater compress = new Deflater(); compress.reset(); compress.setInput(data); compress.finish(); ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length); byte[] buf = new byte[1024]; while (!compress.finished()) int i = compress.deflate(buf); bos.write(buf, 0, i); bos.close(); compress.end(); return bos.toByteArray();
// 用来复现死循环的例子 public static void main(String[] args) throws IOException, DataFormatException byte[] after = compress("xxxxxx".getBytes(StandardCharsets.UTF_8)); int len = after.length - 1; byte[] newByte = new byte[len]; System.arraycopy(after, 0, newByte, 0, len); after = uncompress(newByte); System.out.println(new String(after));
所以 7-10 行的逻辑需要加一些判断,比如增加对返回值的判断,及时跳出循环
看 inflate() 方法的注释可知,当返回值是 0 时,表示需要调用 needsInput() 或者 needsDictionary() 来判断是否需要继续追加数据或者需要字典
继续看 inflate(byte[] b) -> inflate(byte[] b, int off, int len) -> inflateBytes(long addr, byte[] b, int off, int len) 的 JDK 源码注释和内容可知,当解压出现其它异常时也会返回 0,因此这里个人认为, needsInput() 或者 needsDictionary() 均为 false 时,也有概率返回 0。
while (!inflater.finished()) int count = inflater.inflate(buff); if (count == 0) break; baos.write(buff, 0, count);
综上,虽然网上这两工具类的 demo 都基本一样,但都还是有风险的,需要多加小心
外网有帖子提到过这个问题:Java Inflater will loop infinitely sometimes
Deflater压缩算法
Deflater是同时使用了LZ77算法与哈夫曼编码的一个无损数据压缩算法。
依赖
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
代码实现
package utils;
import org.apache.commons.codec.binary.Base64;
import page.User;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
/**
* DeflaterUtils 压缩字符串
*/
public class DeflaterUtils {
/**
* 压缩
*/
public static String zipString(String unzipString) {
/*
* https://www.yiibai.com/javazip/javazip_deflater.html#article-start
* 0 ~ 9 压缩等级 低到高
* public static final int BEST_COMPRESSION = 9; 最佳压缩的压缩级别。
* public static final int BEST_SPEED = 1; 压缩级别最快的压缩。
* public static final int DEFAULT_COMPRESSION = -1; 默认压缩级别。
* public static final int DEFAULT_STRATEGY = 0; 默认压缩策略。
* public static final int DEFLATED = 8; 压缩算法的压缩方法(目前唯一支持的压缩方法)。
* public static final int FILTERED = 1; 压缩策略最适用于大部分数值较小且数据分布随机分布的数据。
* public static final int FULL_FLUSH = 3; 压缩刷新模式,用于清除所有待处理的输出并重置拆卸器。
* public static final int HUFFMAN_ONLY = 2; 仅用于霍夫曼编码的压缩策略。
* public static final int NO_COMPRESSION = 0; 不压缩的压缩级别。
* public static final int NO_FLUSH = 0; 用于实现最佳压缩结果的压缩刷新模式。
* public static final int SYNC_FLUSH = 2; 用于清除所有未决输出的压缩刷新模式; 可能会降低某些压缩算法的压缩率。
*/
//使用指定的压缩级别创建一个新的压缩器。
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
//设置压缩输入数据。
deflater.setInput(unzipString.getBytes(StandardCharsets.UTF_8));
//当被调用时,表示压缩应该以输入缓冲区的当前内容结束。
deflater.finish();
final byte[] bytes = new byte[512];
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(512);
while (!deflater.finished()) {
//压缩输入数据并用压缩数据填充指定的缓冲区。
int length = deflater.deflate(bytes);
outputStream.write(bytes, 0, length);
}
//关闭压缩器并丢弃任何未处理的输入。
deflater.end();
return Base64.encodeBase64String(outputStream.toByteArray());
//处理回车符
// return zipString.replaceAll("[\\r\\n]", "");
}
/**
* 解压缩
*/
public static String unzipString(String zipString) {
byte[] decode = Base64.decodeBase64(zipString);
//创建一个新的解压缩器 https://www.yiibai.com/javazip/javazip_inflater.html
Inflater inflater = new Inflater();
//设置解压缩的输入数据。
inflater.setInput(decode);
final byte[] bytes = new byte[512];
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(512);
try {
//finished() 如果已到达压缩数据流的末尾,则返回true。
while (!inflater.finished()) {
//将字节解压缩到指定的缓冲区中。
int length = inflater.inflate(bytes);
outputStream.write(bytes, 0, length);
}
} catch (DataFormatException e) {
e.printStackTrace();
return null;
} finally {
//关闭解压缩器并丢弃任何未处理的输入。
inflater.end();
}
try {
return outputStream.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User(1,1,"北京11"));
userList.add(new User(2,2,"北京22"));
String json = GsonUtil.bean2Json(userList);
System.out.println("压缩前:"+json);
String json1 = DeflaterUtils.zipString(json);
System.out.println("压缩后:"+json1);
String json2 = DeflaterUtils.unzipString(json1);
System.out.println("解压后:"+json2);
}
}
执行结果
总结
压缩前的字节长度为:1825
压缩后的字节长度为:284
压缩率为63.73%,压缩后体积为原来的36.27%
以上是关于Inflater 和 Deflater 解压缩中的坑的主要内容,如果未能解决你的问题,请参考以下文章
使用java对象Deflater对一个String类型压缩,在linux下使用c++调用zlib库inflate解压时为乱码