背景:
一次偶然的项目开发中,因为数据量大,为了有效利用缓存空间来缓存数据。采用将源数据压缩的方式来缓存,于是乎,我便开始使用java中的gzip流,由于史无前例,所以我也开始了面向百度编程。但是我并没有老老实实的按照百度的做法来使用,我使用了jdk7提供的新特性(稍后演示给你看),也就是这个新特新把我坑惨了,话不多说,直接上案例!!!
用例
下面是我使用gzip的代码(采用新特性,自认为没问题):
public class GzipUtil {
private static final Logger LOG = LoggerFactory.getLogger(GzipUtil.class);
public static byte[] gzip(String data) {
if (StringUtils.isBlank(data)) {
LOG.warn("执行gzip数据压缩,数据无效,data:{}", data);
return new byte[0];
}
try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(byteOut)) {
gzipOut.write(data.getBytes("utf-8"));
return byteOut.toByteArray();
} catch (IOException e) {
LOG.error("执行gzip压缩数据,出现IO异常,data:{},charset:{}", data, e);
return new byte[0];
}
}
public static String ungzip(byte[] data) {
if (data.length == 0) {
LOG.warn("执行gzip数据压缩,数据无效,data:{}", data);
return null;
}
try (
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ByteArrayInputStream byteIn = new ByteArrayInputStream(data);
GZIPInputStream gzipIn = new GZIPInputStream(byteIn)
) {
byte[] buffer = new byte[4096];
int len;
while ((len = gzipIn.read(buffer)) >= 0) {
byteOut.write(buffer, 0, len);
}
return byteOut.toString("utf-8");
} catch (IOException e) {
LOG.error("执行unzip解压数据,出现IO异常,data:{},charset:{}", data, e);
return null;
}
}
public static void main(String[] args) throws JsonProcessingException {
Person person = new Person().setName("张三").setAge(20).setLikes(Lists.newArrayList("足球", "游泳"));
System.out.println("person:" + person);
String json = JsonUtil.getObjectMapperInstance().writeValueAsString(person);
System.out.println("json:" + json);
byte[] data = GzipUtil.gzip("{\"name\":\"张三\",\"age\":20,\"likes\":[\"足球\",\"游泳\"]}");
System.out.println("zip:" + Arrays.toString(data));
String unJson = GzipUtil.ungzip(data);
System.out.println("unjson:" + unJson);
Person value = JsonUtil.getObjectMapperInstance().readValue(unJson, Person.class);
System.out.println("unzip value:" + value);
}
@Data
@Accessors(chain = true)
static class Person {
private String name;
private int age;
private List<String> likes;
}
}
运行后出现了未知问题:压缩失败了!!!
,截图如下:
查了很久都没有找到我所需要的解决方案,很让我困惑!好在功夫不负有心人,最后在stackflow里找到了答案!!!(个人觉得stackflow很优质,很喜欢!!!
)
找到问题后,修复gzip方法:
public static byte[] gzip(String data) {
if (StringUtils.isBlank(data)) {
LOG.warn("执行gzip数据压缩,数据无效,data:{}", data);
return new byte[0];
}
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = null;
try {
// byteOut = new ByteArrayOutputStream();
gzipOut = new GZIPOutputStream(byteOut);
gzipOut.write(data.getBytes("utf-8"));
// 因为Gzip压缩处理后只有在调用close()方法才会将最后一部分压缩数据写入,
// 使用flush()来刷新流并没有任何作用,可以使用finish()来替代close()将最后一部分压缩数据写入
// gzipOut.flush();
gzipOut.finish();
return byteOut.toByteArray();
} catch (IOException e) {
LOG.error("执行gzip压缩数据,出现IO异常,data:{},charset:{}", data, e);
return new byte[0];
} finally {
if (gzipOut != null) {
try {
gzipOut.close();
} catch (IOException e) {
LOG.error("exception,",e);
}
}
}
}
修复后,我重新执行后,终于雨过天晴了,运行结果如下:
思考
Q:为什么会出现这个问题?
A:答案我已经写在了修复的gzip方法的注释中。这里简单解释一下,gzip在压缩数据时,只有流在关闭时(调用流的close()
*),才会将最后一部分缓冲数据写入到输出流中,熟悉try-catch-finally
原理的同学应该明白了,因为我们在try语块中已经return
时,此时返回值就已经在方法返回值的数据栈中了,哪怕我们的try()
语句块自动帮我们执行了finally,调用了流的close,但是并不会改变我们的返回结果!
测试
试着执行下面的代码,看看返回的是什么呢?
public class Test {
public static void main(String[] args) {
System.out.println(test());
}
private static String test() {
String result = "";
try {
result = "try语句块值";
return result;
} finally {
result = "finally 语句块值";
}
}
}