上一节了解了 Base64 的编码原理和过程,这一节了解一下 Javascript 的字符集和字符编码,为后面的编解码处理做好准备。
ASCII码
ASCII 码是美国针对英语字符制定的一套字符编码(或者叫字符集)。ASCII 是单字节编码,用 8 位二进制表示一个英文字符,其最高位总为 0,字符范围为 00000000 ~ 0fffffff(128个)。比如空格 "SPACE" 是32(二进制00100000),大写的字母 A 是 65(二进制01000001)。
非 ASCII 码
因为 ASCII 码表示的字符数量太少了,所以各个国家会针对 ASCII 码进行重新编码,利用其闲置的最高位来进行扩展,最多能表示 256 个字符;但到了东南亚国家,256 个字符仍然不够用,这时会用两个字节表示一个字符,比如 GB2312,其能表示的字符达到了 256 * 256 的数量。
UNICODE码
因为上面出现的不同编码,会导致一个非 ASCII 码,如果不知道编码规则的情况下,在不同的地区会表示成不同的字符,比如我们经常出现打开文件乱码。为了解决这个问题,我们需要制定统一标准,让全世界所有的字符都有唯一的编码,这就是Unicode,它是一种所有符号的编码。
但是,unicode 只是一种字符集,并没有说明编码格式,也就是说一个字符所对应的 unicode 十六进制值,我可以以任何形式进行存储,这也是造成 unicode 码一开始没有大规模流行的原因。
UTF-8
互联网的普及,强烈需要一种统一的编码方式。UTF-8 就是现在使用最广的一种 Unicode 的编码实现。其他编码还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。
UTF-8 是一种变长的编码方式。它可以使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8 的编码规则很简单,只有二条:
- 对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
- 对于 n 字节的符号(n>1),第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。
Unicode 字符范围(十六进制) |
Utf-8 编码(二进制) |
---|---|
0000 0000 ~ 0000 007F | 0xxxxxxx |
0000 0080 ~ 0000 07FF | 110xxxxx 10xxxxxx |
0000 0800 ~ 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000 ~ 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF-8 编码非常简单。如果一个字节的第一位是 0,则这个字节单独就是一个字符;如果第一位是 1,则连续有多少个 1,就表示当前字符占用多少个字节。
比如“风”,其 unicode 码为 98CE,对应的二进制码为 1001 1000 1100 1110,按照上面规则转换后的二进制码为 11101001 10100011 10001110,对应的十六进制码为 E9A38E,其正对应着“风”的 utf-8 码。
UCS-2
JavaScript 语言采用 Unicode 字符集,但是只支持一种编码方法。这种编码既不是 UTF-16,也不是 UTF-8,更不是 UTF-32,而是 UCS-2。
什么是 UCS-2?这就需要讲一点历史。
在很久以前,曾经有两个团队同时想搞统一字符集,一个是 1988 年成立的 Unicode 团队,另一个是 1989 年成立的 UCS 团队。等到他们发现了对方的存在时,他们达成一致:世界上不需要两套统一字符集。于是在 1991 年 10 月,两个团队决定合并字符集,从今以后只发布一套字符集,就是 Unicode,并且修订此前发布的字符集,UCS 的码点将与 Unicode 完全一致。
但是 UCS 的开发进度快于 Unicode,1990 年就公布了第一套编码方法 UCS-2,而 Unicode 在 1996 年 7 月才公布第一套编码 UTF-16。而 Javascript 是 1995 年才出现的,所以,不是它不想采用别的编码方案,而是只有 UCS-2 这一套编码方案。
Javascript 实现 Unicode 转 UTF-8
因为 Javascript 的字符集是 unicode,而且 Javascript 中提供了读取字符 unicode 值的方法 charCodeAt,所以我们可以一个一个字符地读取 unicode 值,然后按照上面的规则进行转换。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Javascipt 实现 Utf-8 编解码</title>
</head>
<body>
<div>
<span>输入要编码的字符串</span>
<input type="text" id="input">
</div>
<div>
<span>utf-8 编码:</span>
<span id="utf-8"></span>
</div>
<div>
<span>utf-8 解码:</span>
<span id="unicode"></span>
</div>
<script>
function unicode2utf8(str) {
var charCode,
outStr = "",
len = str.length;
for (var i = 0; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode <= 0x7F) { // 单字节
outStr += str.charAt(i);
} else if (charCode <= 0x7FF) { // 双字节
outStr += String.fromCharCode(0xC0 | ((charCode >> 6) & 0x1F));
outStr += String.fromCharCode(0x80 | ((charCode >> 0) & 0x3F));
} else if (charCode <= 0xFFFF) { // 三字节
outStr += String.fromCharCode(0xE0 | ((charCode >> 12) & 0xF));
outStr += String.fromCharCode(0x80 | ((charCode >> 6) & 0x3F));
outStr += String.fromCharCode(0x80 | ((charCode >> 0) & 0x3F));
} else { // 四字节
outStr += String.fromCharCode(0xF0 | ((charCode >> 18) & 0x7));
outStr += String.fromCharCode(0x80 | ((charCode >> 12) & 0x3F));
outStr += String.fromCharCode(0x80 | ((charCode >> 6) & 0x3F));
outStr += String.fromCharCode(0x80 | ((charCode >> 0) & 0x3F));
}
}
return outStr;
}
function utf82unicode(str) {
var len = str.length,
outStr = "",
charCode,
h1, h2, h3, h4;
for (var i = 0; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode < 0xC0) { // 单字节
outStr += str.charAt(i);
} else if (charCode < 0xE0) { // 双字节
h1 = (charCode & 0x1F) << 6;
h2 = (str.charCodeAt(++i) & 0x3F) << 0;
outStr += String.fromCharCode(h1 | h2);
} else if (charCode < 0xF0) { // 三字节
h1 = (charCode & 0xF) << 12;
h2 = (str.charCodeAt(++i) & 0x3F) << 6;
h3 = (str.charCodeAt(++i) & 0x3F) << 0;
outStr += String.fromCharCode(h1 | h2 | h3);
} else { // 四字节
h1 = (charCode & 0x7) << 18;
h2 = (str.charCodeAt(++i) & 0x3F) << 12;
h3 = (str.charCodeAt(++i) & 0x3F) << 6;
h4 = (str.charCodeAt(++i) & 0x3F) << 0;
outStr += String.fromCharCode(h1 | h2 | h3);
}
}
return outStr;
}
document.querySelector("#input").addEventListener("input", function () {
var utf8 = unicode2utf8(this.value);
document.querySelector("#utf-8").innerText = utf8.split('').map(s => s.codePointAt().toString(16)).join(' ');
document.querySelector("#unicode").innerText = utf82unicode(utf8);
})
</script>
</body>
</html>