前一阵做活动有一个分享文案总是分享错误,排除法之后发现是一个字符的编解码问题。好几次遇到这种问题都是懵过去的,这次有时间写篇文章大概梳理一下。
字符集?字符编码?
首先我们知道,计算机能接收的指令和能处理的信息都最后都是要转化成二进制的数据的。而在很早以前,我们需要处理的是文本,这就衍生了 ASCII 字符集,规定了打印字符(例如大写小写英文字母、数字,和一些其他字符),控制字符(例如回车,上下左右)和编号的对应关系,这个编号就是像 URI 一样给字符的唯一标识。而字符编码则是根据这个编号,对应写入字节流。
为了方便大家理解,放出一个 ASCII 表再熟悉一下:
我们后面说到的表的原理都大同小异,就是把一个独一无二的编号和一个字符对应起来,而字符编码的过程就是让计算机拿到这个编号之后知道这个东西应该展示/存储成什么样子。
但是我们刚才说到的字符集是基于单字节编码,什么叫单字节编码?看到上图的左上角低四位/高四位没?一共8位即一个字节就可以表示一个字符,这就是单字节编码。掐指一算8位表示一个字符,这一共也才能表示256个字符啊,太少了!发现 ASCII 字符集远远不够用,多用几位表示不就行了?所以有了多字节编码方式。例如中国使用的就是双字节字符编码(使用最多两个字节来编码),并且我们有自己的字符集 GB2312,GBK 是在前者的基础上增加了繁体字等的编码。
到此为止,我们的字符集都不能包含人类所用的全部字符。然后就衍生了 Unicode 字符集。它为每个字符统一编号,分配唯一的字符码。
上面字符集和编码方式是直接绑定在一起的,想扩展是很复杂的事情;而 unicode 是要涵盖所有字符,包括未来不可预知的字符。为了提高可扩展性,我们把字符集和字符编码分开,虽然 编号(unicode 码)相同,但是最终决定字节流的是你的字符编码方案。举几个我们相当熟悉的字符编码,UTF-8和 UTF-16,见图。
所以说到这里其实我们也知道了,unicode 编码和 unicode 字符集不是一回事。 unicode 编码是能对 unicode 字符码转换成字节流的字节编码方式的统称,包含 urf-8等实现方式。
关于乱码?
我不知道你在说什么,就没法解析你的话。我用codepage936的字符编码去解你用 utf-8编码出的字节流,自然不知道你在说什么。
网页指定字符编码的位置一般就是,后端返回的响应头的 content-type;网页 meta:http-equiv;
在 Javascript 中的应用?
我能想到的就一下这些方法,大家可以补充。
encodeURI:是按 utf-8 进行编码,除了:; , / ? : @ $ = + &
(前面这些是保留字符,保留的原因不再赘述) 字母 数字 _ - · ! ~ * ' ( ) #
encodeURIComponent:也是按 utf-8 进行编码,除了:字母 数字 _ - · ! ~ * ' ( ) #
btoa:将 ASCII 字符串或者二进制数据转为 base64编码过的字符串,该方法不能直接作用于 Unicode 字符串
atob:反过来
JSON.parse: 经历两次转义。第一次调用 toString()方法(注意在这个方法中已经对内部的\n 等进行一次转义),第二次把JSON字符串转义为 JSON 对象。
eval:同上,并且和 parse 一样可以把符合 json 标准的字符串转化为 json 对象,但是因为设计安全问题所以不推荐使用
有时候直接 console 是对的,但是放在 html 不对,是因为 console 其实是自带一次 toString()的过程的,而 toString()会转义。
反斜线到底什么用?首先是展示一些不能展示的ASCII字符,例如回车\n
,tab键\t
等。其次是展示一些特殊用途的字符,双引号\"
等等。本质上他的作用是替代一些不能表示的东西。
有时候反斜线展示在页面上,不能成功显示为应该显示的字符,是因为没有经历解码的过程。
为什么在 控制台的HTML 里直接改成\n 不能用,因为直接在控制台里写是不经过转义的。。
为啥innertext="\n"显示结果是回车,innerHTML="\n"显示结果不是回车(也不直接展示\n,因为这两个方法都会先转义)?因为前者是把你给他的东西做基础转义之后原封不动的放进去,而后者会经过一次 HTML 转义,你在代码里直接写回车也不会搭理你的。。。(是 HTML 文本中空白符的默认处理方式,可以修改,比如你 css 中的 white-space
)
下面贴一段转义的定义(来自百度百科):
转义字符串(Escape Sequence)也称字符实体(Character Entity)。在HTML中,定义转义字符串的原因有两个:第一个原因是像“<”和“>”这类符号已经用来表示HTML标签,因此就不能直接当做文本中的符号来使用。为了在HTML文档中使用这些符号,就需要定义它的转义字符串。当解释程序遇到这类字符串时就把它解释为真实的字符。在输入转义字符串时,要严格遵守字母大小写的规则。第二个原因是,有些字符在ASCII字符集中没有定义,因此需要使用转义字符串来表示。
其实所有编程语言,拥有转义字符的原因基本上是两点:一、使用转义字符来表示字符集中定义的字符,比如ASCll里面的控制字符及回车换行等字符,这些字符都没有现成的文字代号。所以只能用转义字符来表示 。二、某一些特定的字符在编辑语言中被定义为特殊用途的字符。这些字符由于被定义为特殊用途,它们失去了原有的意义。比如说HTML中,<被HTML定义为标签的开始,所以当我们转入<时,HTML就会把它当作为开始,而不是当作一个<来看待。再如PHP 的双引号("),被PHP定义为字符串的外围标签,所以如果你在一对双引号里面,还想要使用双引号,只能使用转义字符了。不然PHP就会报错了。
从上面也可以看出转义无非是两种情况:1:将普通字符转为特殊用途,一般是编程语言中,用于表示不能直接显示的字符,比如后退键,回车键,等。2:用来将特殊意义的字符转换回它原来的意义。一般用在正则表达式中。还有有些脚本语言是弱类型,有些语言比如HTML 并不是编程语言,而是标记语言,有些语言只有一种类型 比如shell 脚本语言,这些语言中字符串都不加引号” ” ,或者可以不加引号“ ”,所以有时候需要转义字符说明某字符此时的身份是普通字符,而不是有特殊意义的元字符。
base64?用武之地?编码过程?
是一种基于64个可打印字符来表示二进制数据的方法,是用于传输8bit 字节码的编码方式之一。
是从二进制到文本数据的过程。
具有不可读性,需要解码后才能阅读。
因为 base64可以表示所有二进制数据,所以基本上意味着所有数据都可以转换成 base64的格式。
拿我们最熟悉的 ASCII 举例子,我们知道在 ASCII 码中,128~255是不可见字符。而这些字符在传输过程中,如果直接传输会在中间过程被处理错误,且这个过程是不可逆的(原有的字符会被替换丢失),所以我们要保证传输过程中没有不可见字符,让数据符合传输协议的要求。相当于 base64就像我们为了数据传输准确而带的通行证。
但是注意 base64编码过后的数据大概会编程原来的4/3,因为有填补空位的过程。
我们常用的场景就是,图片传输,根证书,邮件附件等。
编码过程:对于非二进制数据,是先将其转换成二进制形式,然后每连续6比特(2的6次方=64)计算其十进制值,根据该值在上面的索引表中找到对应的字符,最终得到一个文本字符串。
%是啥意思?为啥 encode 完了都带上这个,但是明明字符编码完不是这样的?
而且为啥我在 HTML 里直接写\n 不行?
在 c 语言中,转义用\
表示,在 URI 协议中,转义字符是百分号%
。并且其他语言的转义字符可能各不相同。
这也就解答了为什么你直接在HTML 里用\n
是不行的,因为HTML 中转义字符不是\
,他只当这是两个正常的字符,一个反斜线一个字母 n。只是在 Javasciprt 这门语言里(当然还有很多其他语言例如 C)会转义成回车。
参考文章:
https://segmentfault.com/q/1010000009768523/a-1020000009774588
https://stackoverflow.com/questions/12694110/rendering-newlines-in-escaped-html