前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于编码的那些事

关于编码的那些事

作者头像
Bruce Li
发布2019-07-30 16:55:02
6960
发布2019-07-30 16:55:02
举报

引言

之前做一个POC的时候,Vicky同学遇到一个关于编码的问题,问到我,我觉得当时没有解释得很清楚,于是决定查阅相关的资料文档,写一篇文章,记录这个问题及对背后的原因、原理的理解。

问题

关于这个问题,为了简化起见,我会做一些假设。问题原型是有一个Web application,后台用Java实现,前端Javascript。前端页面上有一个下载文件的功能,这个功能实现的基本逻辑是:后台用Java API读取一个文件成字节流 -> 用Java API将字节流转成Base64 encoded string -> 后台将这个string返回给前端 -> 前端AJAX Call接收到后台返回的string -> 前端调用Javascript API将Encoded string做decode,得到decoded string -> 调用Javascript API将string写入文件 - > 最后前端页面出现下载提示,用户选择下载。

后台代码的基本逻辑如下:

代码语言:javascript
复制
String a = "a";Base64.getEncoder().encodeToString(a.getBytes())

最开始用这个逻辑实现文本文件(xml)的下载,没有问题,下载下来的文件能够正常打开并且显示正确。之后用同样的逻辑实现二进制文件(pdf)的下载,结果下载下来的文件不能打开。这是什么原因呢?

此外,在研究这个问题的过程中发现另外一个编码问题:之前的文本文件全都是英文字符,当我加入中文字符以后,这些中文字符在下载下来的文件中也是乱码,如下图。这又是什么原因呢?

编码相关

以解释上面两个问题为出发点,我查阅了相关资料文档,以下是我对常见术语的理解。

二进制文件:计算机系统里面所有文件都是二进制文件,即一个字节一个字节排列而成,文本文件也是二进制文件。

文本文件:采用特定编码表示常见文字符号的文件,这种文件会将文字符号转换成指定编码对应的code,然后以二进制的方式存储。

编码:编码是信息从一种形式或格式转换为另一种形式的过程。

ASCII: 第一代的编码标准,美国人提出,英文全称是American Standard Code for Information Interchange,中文翻译是“美国信息互换标准代码”,等同于国际标准ISO/IEC 646。 简单讲,计算机一个字节的八位可以组合出256中不同的状态,将前128种状态分别代表128个英文字符(其中包括大小写字母、数字、空格、标点符号以及一些特殊的控制字符)。这就是计算机专业同学刚上大学要了解的ASCII码表,比如小写的a是97,大写A是65,等等。

由于这种编码只定义了所有的英语字符,所以如果世界上所有电脑都采用英语系统,也就没有下面编码什么事了。但是现实是残酷的,世界上各个国家,甚至民族都有自己的语言符号,将这些语言文字符号在计算机系统中显示存储,随着计算机的普及,是一件水到渠成的必须要解决的问题,于是就有了以下各种编码方式的出现。

ISO-8859-1:ASCII码只用到一个字节的前128个状态,这个标准扩展了ASCII,将后面128个状态(128-255)利用了起来,增加了对一些西欧拉丁系语言特殊字符的支持。比如,德语的元音字符ü对应252。

以上两种标准都是单字节编码。单字节编码能够最多支持256个字符,计算机要世界上如此多的语言,显然是不够的,于是应运而生,就出现了多字节编码。最开始各个主流语言都出现了自己的编码规则,比如简体中文的GB2312,繁体中文的BIG5,日语的JIS等。

ANSI: 默认的编码方式,对于英文系统是ASCII编码,对于简体中文系统是GB2312编码,对于繁体中文系统是Big5码。

GB2312: 用两个字节代表一个汉字字符。这种编码包含了六千多个常用汉字。比如中文的“严”字用D1CF代表。

GBK: GB2312编码基本上能够满足常用需求,但是对于古文里偏僻的汉字,少数民族的文字等是没有对应的编码的,于是就出现了GBK。这种编码扩展了GB2312,增加了偏僻汉字,少数民族文字的支持。

这里GB是国标的意思,K是扩展的意思。

JIS: 日语文字的编码标准。

以上标准都是双字节标准,即都是用计算机两个字节代表一个字符。

UNICODE: 上面的编码标准是互不兼容的,ISO为了这种各自为政的局面,决定制定一套统一的标准能够代表世界上所有的文字,这个标准就叫做UNICODE。 UNICODE又叫做UCS,是Universal Multiple-Octet Coded Character Set的缩写。

UTF-8: UTF是UCS Transfer Format的缩写。可变长的UNICODE标准的实现,举个例子,UTF-8表示英文字符用一个字节表示(与ASCII兼容),表示汉字通常是三个字节,比如e6b189代表中文的“汉”字,e5ad97代表中文的“字”字。

UTF-16/32:通常不用。

对于问题的解释

回过头来解释上面遇到的两个问题。

第一个问题,为什么xml文件的下载没有问题,而pdf文件的下载却是打开乱码呢?

首先,前端调用Javascript API将Encoded string做decode,得到decoded string的代码如下:

代码语言:javascript
复制
var decodedStr = atob(data);

atob这个方法输入一个encoded的string,输入一个decoded的string。其实现逻辑主要分三步: 第一步将encoded的string采用默认的编码转换成byte array,第二步将对byte array做base64 decode转换,得到转换后的byte array,第三步,将byte array采用默认的编码转换成string。这里默认的编码是ISO-8859-1。

然后,调用如下代码实现文件的save功能:

代码语言:javascript
复制
var blob = new Blob([atob(decodedStr)], {type: "application/pdf"});var link=document.createElement('a');link.href=window.URL.createObjectURL(blob);link.download="downloadedFile.pdf";link.click();

Javascript的Blob实现下载功能会默认采用utf-8编码。由于utf-8跟ASCII兼容,但是不跟ISO-8859-1兼容,ISO-8859-1编码里面的后127个字符在utf-8里面会有另外一个code对应。举个例子:decodedStr中的一个字符"?"在ISO-8859-1编码里面code是e2,当存储成文件的时候应用utf-8的编码,其对应的code是c3a2,所有对应于ISO-8859-1编码后127位的字节都会转成utf-8码,通常都变成了两个字节。如下图所示(注:上半部分是正常可打开的pdf的十六进制视图,下半部分是打不开的pdf的十六进制视图):

但是由于这个文件是二进制文件,不应该有此转换,所以就出现了这个问题。

有两个解决方案:第一种方案,存储文件的时候指定编码,我做了以下尝试,但是不生效,暂时还没找到如何指定编码。

代码语言:javascript
复制
var blob = new Blob([atob(decodedStr)], {type: "application/pdf"; charset: "iso-8859-1"});

第二种方案,先将decodedStr手动转成byte array,然后再构造Blob,这种情况下Blob就不会再做转换,下载下来的文件就

能够正确打开。

代码语言:javascript
复制
var decodedStr = atob(data);
var bytes = new Uint8Array( decodedStr.length );
for(var i=0; i<decodedStr.length; i++){
bytes[i] = decodedStr.charCodeAt(i);
}
var blob = new Blob([bytes.buffer], { type: 'application/pdf' });

有同学可能会问,为什么xml文件下载下来就可以正常打开?这是因为xml文件里面全都是英文字符和符号,都是ASCII码可以表示的(ISO-8859-1前128个,ISO-8859-1兼容ASCII),所以在上面提到的下载过程中转码成utf-8没有问题。

第二个问题,当我在xml文件里加入中文字符以后,这些中文字符在下载下来的文件中也是乱码。这又是什么原因呢?

同样的,我们先看正常显示和乱码显示文件的十六进制视图对比(注:下图是正常显示文件,上图是乱码显示文件):

从图上可以看出,字节e6被转成了utf-8对应的码c3a6。其实,下图本来已经是utf-8编码(e6b189代表中文的“汉”字,e5ad97代表中文的“字”字),所以再经过一次转换就会出现乱码。

解决方案同上,直接写入byte array。

最后的话:在对字符串、文本、文件做处理的时候一定要注意编码方式,不然很可能就会出现意想不到的乱码问题。

Rerefences:

  • https://en.wikipedia.org/wiki/ASCII
  • https://en.wikipedia.org/wiki/ISO/IEC_8859-1
  • https://en.wikipedia.org/wiki/UTF-8
  • http://www.fileformat.info/info/unicode/char/00E6/index.htm
  • https://en.wikipedia.org/wiki/Base64
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-05-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 天马行空布鲁斯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档