使用Java的Gzip压缩时,发现的小秘密

背景:

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

运行后出现了未知问题:压缩失败了!!!,截图如下:
使用Java的Gzip压缩时,发现的小秘密
查了很久都没有找到我所需要的解决方案,很让我困惑!好在功夫不负有心人,最后在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);
                }
            }
        }
    }

修复后,我重新执行后,终于雨过天晴了,运行结果如下:
使用Java的Gzip压缩时,发现的小秘密

思考

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  语句块值"; 
		}
   }
}
上一篇:HttpServletResponseWrapper的使用


下一篇:Nginx开启gzip的配置