一次偶然的项目开发中,因为数据量大,为了有效利用缓存空间来缓存数据。采用将源数据压缩的方式来缓存,于是乎,我便开始使用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 语句块值"; } } }