Gzip压缩和解压的实现

Gzip压缩使用起来很简单,以前我也只是在客户端使用,服务器端不用管,所以我只用过GZIPInputStream来读取,用起来也没有问题。后来OkHttp开始流行,后来听说OkHttp会自动处理Gzip压缩的数据,不需要我们使用GZIPInputStream来处理,于是我想验证一下是否真的是这样的,这时我就需要写个服务器端Demo了,发现行不通,会报错,找不到原因,老办法,先写个最简单Demo,把Gzip流用起来,整体思路如下:

  1. 创建一个很长的数据,一长串1,如:1111111111111111,1万个1
  2. 使用GZIPOutputStream压缩数据
  3. 使用GZIPInputStream解压数据

具体代码如下(使用Kotlin语言编写):

fun main() {// 原始数据val sb = StringBuffer()repeat(10000) { sb.append(1) } // 生成1万个1的字符串val rawBytes = sb.toString() .toByteArray(Charsets.UTF_8) // 原始数据println("压缩前size = ${rawBytes.size},  数据 = ${rawBytes.map { byteToHex(it) }}")// 压缩数据var baos = ByteArrayOutputStream()val gzipOut = GZIPOutputStream(baos)gzipOut.write(rawBytes)val gzipBytes = baos.toByteArray() // 拿到压缩后的数据println("压缩后size = ${gzipBytes.size},  数据 = ${gzipBytes.map { byteToHex(it) }}")// 解压数据val gzipIn = GZIPInputStream(ByteArrayInputStream(gzipBytes))baos.reset() // 重置内存流,以便重新使用var byte: Intwhile (gzipIn.read().also { byte = it } != -1) baos.write(byte)println("解压结果:size = ${baos.size()}, 数据 = ${String(baos.toByteArray(), Charsets.UTF_8)}")
}/** 把字byte转换为十六进制的表现形式,如ff  */
fun byteToHex(byte: Byte) = String.format("%02x", byte.toInt() and 0xFF)

代码很简单,看似没什么问题,运行结果却出了异常,如下:

压缩前size = 10000,  数据 = [31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31]
压缩后size = 10,  数据 = [1f, 8b, 08, 00, 00, 00, 00, 00, 00, 00]
Exception in thread "main" java.io.EOFException: Unexpected end of ZLIB input streamat java.base/java.util.zip.InflaterInputStream.fill(InflaterInputStream.java:245)at java.base/java.util.zip.InflaterInputStream.read(InflaterInputStream.java:159)at java.base/java.util.zip.GZIPInputStream.read(GZIPInputStream.java:118)at java.base/java.util.zip.InflaterInputStream.read(InflaterInputStream.java:123)at KtMainKt.main(KtMain.kt:31)at KtMainKt.main(KtMain.kt)

想不出这么简单的代码哪里会出问题,不就是一个读一个写吗?而且我发现不论我写什么数据,压缩后的数据结果都是一样的,说明是在写数据(压缩)的时候出了问题,百度也找不到原因,百度Gzip压缩的相关知识并没有得到答案,百度上面报的异常信息也没有答案,最后是读了一下JDK文档才发现了问题的原因(所以JDK文档是个好东西,要多看看),在GZIPOutputStream文档上有这么一个方法:

finish() 完成将压缩数据写入输出流的操作,无需关闭底层流。

之前我有试过调用输出流的flush方法,没想到要调用的竟然是finish方法,在GZIPOutputStream的wirte方法执行之后再调用一下finish方法即可,运行结果如下:

压缩前size = 10000,  数据 = [31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31]
压缩后size = 46,  数据 = [1f, 8b, 08, 00, 00, 00, 00, 00, 00, 00, ed, c1, 01, 0d, 00, 00, 00, c2, a0, 4c, ef, 5f, ce, 1c, 6e, 40, 01, 00, 00, 00, 00, 00, 00, 00, 00, c0, bf, 01, 5e, 62, 1a, 8f, 10, 27, 00, 00]
解压结果:size = 10000,  数据 = 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

从结果可以看到,压缩前的数据长度为10000,压缩后的长度为46,真是牛B呀。解压结果正确还原了1万个1,因为1万个1太长,上面的打印结果我是进行了删除的,所以大家不要觉得结果不对。

学会了Gzip大家平时做传输时就可以节省大量的流量了,比如获取服务器端数据时可以让服务器压缩后再传输,我们给服务器传参数时,如果参数很大也可Gzip压缩后再传给服务器,又比如上传文本文件(如bug文件、log日志等)到服务器时,也可以Gzip压缩后再上传,节省流量、加快访问、提高用户体验耿耿的!

OkHttp中的Gzip压缩处理

听说Gzip压缩是自动处理的,我也是最近看到别人文章上说的,我之前一直是在请求头上加上gzip的,读流的时候就自己用GZIPInputStream来解压,也是没问题的,但关键是既然OkHttp自动处理了,则我们就不要自己处理了,于是我要写个Demo来验证一下。

服务器端

打开陈年老旧的Eclipse(因为在IntelliJ上写JavaEE不熟),写了个JavaEE的Demo,创建了一个Servlet,如下:

/** 服务器端 */
public class Hello extends HttpServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 数据内容:一段Jsonbyte[] datas = "{name:\"张三\",age:18}".getBytes("UTF-8");                           // 告诉客户端我们发送的数据是经过Gzip压缩的response.setHeader("Content-Encoding", "gzip");        // 告诉客户端我们发送的数据是什么类型,以及用的什么编码response.setContentType("application/json; charset=UTF-8"); // 创建一个内存流,用于保存压缩后的数据ByteArrayOutputStream baos = new ByteArrayOutputStream();   // 压缩数据GZIPOutputStream gzipOut = new GZIPOutputStream(baos);gzipOut.write(datas);gzipOut.finish();// 告诉客户端我们发送的数据总共的长度response.setContentLength(baos.size()); // 把压缩后的数据传给客户端response.getOutputStream().write(baos.toByteArray());}}

客户端

客户端就使用了IntelliJ,Kotlin语言编写,OkHttp进行数据请求,代码如下:

fun main() {val url = "http://localhost:8080/WebDemo/Hello"val request = Request.Builder().url(url).build()OkHttpClient().newCall(request).execute().use { response ->println(response.body()?.string())}
}

Kotlin是个好东西,OkHttp也是个好东西,写个请求就是这么简单,几行代码搞定,运行结果如下:

{name:"张三",age:18}

没有出现乱码,实验证明OkHttp自动帮我们进行了Gzip解压,我们不需要特殊处理,而且我们看打印结果有中文,中文并没有显示成乱码,说明OkHttp还会根据服务器端指定的编码来处理String。这样在我们去面试时,如果别人要问OkHttp的优点时这里就有两点可说了:

  1. OkHttp会自动进行Gzip处理
  2. OkHttp会根据响应头中指定的编码来处理字符数据

接下来我们继续实验上面的两条优点

1、实验:OkHttp对Gzip数据的处理

我们在客户端代码中打印一下响应体中的所有响应头:

OkHttpClient().newCall(request).execute().use { response ->response.headers()?.names()?.forEach { key -> println("$key=${response.header(key)}")}println(response.body()?.string())
}

运行客户端,打印结果如下:

Content-Type=application/json;charset=UTF-8
Date=Mon, 23 Mar 2020 09:22:38 GMT
Server=Apache-Coyote/1.1
{name:"张三",age:18}

姨,奇怪了!服务器明明告诉了客户端数据是经过Gzip压缩的,怎么响应头里看不到Gzip呢?这其是OkHttp把这个Gzip的Header给删除了,因为它已经帮我们把Gzip的数据给解压了,所以Header里面就没必要有Gzip的Header存在,意思就是告诉你数据没有压缩了,你直接使用就行了,千万别自己拿个GZIPInputStream再解压了,如果再解压一次肯定就乱码了。

接下来把服务器端代码中的这行代码注释掉:

response.setHeader("Content-Encoding", "gzip");

再次运行客户端,打印结果如下:

Content-Length=42
Content-Type=application/json;charset=UTF-8
Date=Mon, 23 Mar 2020 09:23:54 GMT
Server=Apache-Coyote/1.1��K�M�Rz�g���J:��V�� )F��

乱码,因为服务器没有告诉客户端数据是经过Gzip压缩的,所以OkHttp就不会使用Gzip来解压,不解压直接当字符串来用肯定是乱码啊。

这时,我们把上面注释的代码再恢复回来,修改一下客户端,以告诉服务器我们可以处理Gzip压缩的数据:

val request = Request.Builder().url(url).header("Accept-Encoding", "gzip").build()

再次运行,客户端,打印结果如下:

Content-Encoding=gzip
Content-Length=42
Content-Type=application/json;charset=UTF-8
Date=Mon, 23 Mar 2020 09:25:35 GMT
Server=Apache-Coyote/1.1��K�M�Rz�g���J:��V�� )F��

从上面打印的响应头中我们看到了关于Gzip的Header,这就说明数据是经过压缩的,需要进行解压,然而我们并没有进行解压,所以数据乱码了,这说明只要我们在请求头里加上Gzip的Header,就表示我们希望要自己处理Gzip,需要自己解压,OkHttp就不会帮我们处理了。这样有什么用呢?有时候想自己解压啊,比如我们在获取数据的时候想显示获取进度的百分比,这种情况就需要自己去读流,而不是OKHttp帮我们读流。

对于Gzip请求头,服务器在收到请求时会读取这个请求头,如果有gzip头,则服务器把数据gzip压缩后再传给客户端,如果没有gzip头,则服务器不会压缩,直接把数据传给客户端。这时你可能会觉得郁闷,如果我不加Gzip请求头,则服务器不会给我压缩数据,如果我加了Gzip请求头,虽然服务器给我压缩数据了,但是OkHttp又不会自动给我解压,怎么解?其实不存在这个问题,虽然我们在请求时没有加入Gzip请求头,但是OkHttp会自动帮我们加入的,怎么验证呢?使用Fiddler抓包工具抓包一看就知道,具体如何抓包这里就不讲解了,大家可以百度一下很多教程的,这里就截图给大家看一下抓包OkHttp请求,OkHttp确实是自动给我们加入了Gzip请求头的:

2、实验:OkHttp对字符编码的处理

把服务器端的这行代码注释掉:

response.setContentType("application/json; charset=UTF-8");

运行客户端,打印结果如下:

Date=Mon, 23 Mar 2020 10:01:57 GMT
Server=Apache-Coyote/1.1
{name:"张三",age:18}

在响应头中看不到关于数据编码的响应头了,但是客户端的中文显示正常,说明OkHttp默认使用UTF-8进行编码,我们再把服务器端设置编码为GBK,如下:

response.setContentType("application/json; charset=GBK");

再次运行客户端,打印结果如下:

Content-Type=application/json;charset=GBK
Date=Mon, 23 Mar 2020 10:07:25 GMT
Server=Apache-Coyote/1.1
{name:"寮犱笁",age:18}

结果中文显示为乱码,因为服务器的数据是以UTF-8编码进行传输的,却告诉客户端使用GBK编码来解析,肯定是乱码的。我们修改服务器的数据以GBK进行编码,如下:

byte[] datas = "{name:\"张三\",age:18}".getBytes("GBK");

再次运行,结果如下:

Content-Type=application/json;charset=GBK
Date=Mon, 23 Mar 2020 10:09:40 GMT
Server=Apache-Coyote/1.1
{name:"张三",age:18}

OK,结果正常,这些实验充分说明了OkHttp会以响应头中指定的编码来处理字符数据。

总结

  1. 在发送请求时,OkHttp会自动加入Gzip请求头,当返回的数据带有Gzip响应头时,OkHttp会自动帮我们解压数据。也就是说,对于Gzip,我们不需要做任何处理。如果我们在请求里加入Gzip请求头,则表明我们想要自己处理Gzip数据,此时OkHttp就不会给我们解压数据了。
  2. 在处理字符数据时,如果是使用OkHttp的response.body()?.string()方法,或者使用OkHttp的response.body().charStream()来读取字符,则OkHttp会根据响应头中的编码来处理字符,如果响应头中没有编码,则默认使用UTF-8编码来处理字符,也可以认为对于字符数据的编码我们不需要做任何处理。除非服务器端没有指定字符编码,比如服务器使用GBK编码发送数据,但是又没在响应头中声明编码,则OkHttp会以UTF-8处理则会乱码,这样的情况应该很少出现,除非是小学生写服务器端。

最简单易懂的Gzip压缩实现,最清晰的OkHttp的Gzip压缩详解相关推荐

  1. Ubuntu下rar格式压缩文件的处理及rar、unrar命令详解

    @在Ubuntu中解压or压缩rar格式的压缩文件 Ubuntu下压缩or解压.rar格式的压缩文件及rar.unrar命令详解   在Ubuntu中常用的压缩文件的形式是:.tar, .tar.bz ...

  2. js合并压缩 java_Java Web程序使用wro4j合并、压缩js、css等静态资源

    在Web项目中,js.css合并压缩,不仅有利于减少Http请求数量.减少宽带资源占用,还能有效的管理各种js.css的引入,使整个项目更加有序.而对于访问用户来说,其更大的好处是增加了页面的打开速度 ...

  3. Linux中压缩、解压缩(tar/zip/bzip2/gz/gzip/zip)

    源码包一般都是以压缩形式存储的,所以,在获得软件包之后,要进行解压缩.  压缩包也有两种形式,一种是tar.gz包(.tgz包也是这种),一种是tar.bz2包.  tar.gz包的解压方法:tar ...

  4. html文件压缩成gzip,前端性能优化成神之路-HTTP压缩开启gzip

    什么是HTTP压缩css HTTP压缩是指: Web服务器和浏览器之间压缩传输的"文本内容"的方法. HTTP采用通用的压缩算法,好比gzip来压缩HTML,Javascript, ...

  5. Nginx开启Gzip压缩配置详解

    Nginx开启Gzip压缩配置详解 最近生产上发生了一些问题,原先所有的静态资源文件都是经过gzip压缩的,然而这几天突然都没有压缩了,经过一顿排查,发现是Nginx的配置有问题,借此机会详细了解了N ...

  6. html压缩原理,webpack--前端性能优化与Gzip原理

    目录 前言 前不久看过掘金小册<前端性能优化原理与实践>,受益匪浅."我深感性能优化实在是前端知识树中特别的一环--当你需要学习前端框架时,文档和源码几乎可以告诉你所有问题的答案 ...

  7. node js并发加载页面缓慢_详解如何利用前端Node模块zlib开启gzip压缩使页面加载速度更快...

    前言 这篇文章我们来聊一聊Node的原生模块zlib,它的主要作用是压缩和解压缩数据,我们都知道数据在压缩后可以减小体积,在网络传输时提高传输速度和节约带宽! API用法 zlib这个模块提供了很多的 ...

  8. linux压缩文件命令_24.gzip、unzip命令详解 - 钟桂耀

    gzip命令 减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间.gzip是在Linux系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用. 注 ...

  9. Linux gzip压缩/解压 *.gz文件详解

    gzip 是linux中常见的压缩/解压工具,最常见的使用对象是*.gz格式的文件,这里简单介绍下它最常见的用法, GZIP(1) General Commands Manual GZIP(1) NA ...

最新文章

  1. 【配置映射】—Entity Framework实例详解
  2. asp.net窗体操作总结
  3. 未解决oracle错误12505、01034、27101
  4. android context.java_Android / Java类范围和Context
  5. 马云融资80亿美金的“资本”
  6. html5距离底部的距离代码,如何使距离为HTML5
  7. tensorflow之relu
  8. 图片滚动js 实现图片无缝滚动
  9. HD 2177(威佐夫博弈 入门)
  10. 3DMM(人脸3D形变统计模型)
  11. 卸载Microsoft Edge浏览器
  12. 尤大大(尤雨溪)的年度总结、预期
  13. Python爬虫教程(一):爬虫
  14. DataQL之语法-万能查询执行步骤
  15. java文件后缀_java源文件名的后缀是什么?
  16. py语言和php,php和python什么区别
  17. 谷歌研究总监Peter Norvig赴斯坦福任教,著有《人工智能:一种现代方法》
  18. scrapy爬取京东笔记本电脑数据并进行简单处理和分析
  19. e最著名的形容美女的词语
  20. 百度之星2018初赛游记

热门文章

  1. git地址变更 vs code 如何修改本地地址并查看
  2. linux centos7 rpm 安装nginx
  3. 许巍:顺其自然地从另类音乐走向主流(SB的標題,那不是另類,那是真正的音樂,現在的主流只是白開水)...
  4. 说是一种能力,不说是一种智慧
  5. Python 的应用领域有哪些呢
  6. Python基于AIML智能聊天机器人实战视频教程-张子良-专题视频课程
  7. 说说 Google 软件测试开发工程师的工作内容
  8. 路由器及其相关命令配置
  9. 提取公积金所需材料及注意事项
  10. MyBatis工作原理