阅读 212

Js 实现 Unicode 编码转 UTF-8 编码

前言

阅读本文,需要同学们已经对 Unicode 和 UTF-8 有了一定的了解。若是还未了解,这里推荐同学们阅读阮一峰大佬的相关文章(也是本文参考来源):

  1. 字符编码笔记:ASCII,Unicode 和 UTF-8

  2. Unicode 与 JavaScript 详解

UTF-8 的编码规则

UTF-8 是一种变长的编码方法,字符长度从1个字节到4个字节不等。越是常用的字符,字节越短,最前面的128个字符,只使用1个字节表示,与ASCII码完全相同。

UTF-8 编码规则:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

  2. 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

下表是对编码规则的总结,字母x表示可用编码的位。

Unicode符号范围(十六进制)UTF-8编码方式(二进制)字节数
0000 0000——0000 007F0xxxxxxx1
0000 0080——0000 07FF110xxxxx 10xxxxxx2
0000 0800——0000 FFFF1110xxxx 10xxxxxx 10xxxxxx3
0001 0000——0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx4

现在,我们依据上表,来解读一下 UTF-8 编码。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

经过解读,想必同学们对 UTF-8 编码规则已有所了解。下面,我们就以汉字为例,演示如何实现 UTF-8 编码。

  • 的 Unicode 是597d(十六进制)

  • 597d 转二进制是101100101111101,转十进制是22909

根据上表,可以得知597d处在0000 0800 - 0000 FFFF(十进制,2048-65535)范围内,所以的 UTF-8 编码需要3个字节,也就是说它的格式是1110xxxx 10xxxxxx 10xxxxxx

然后,从的最后一个二进制位开始,依次从后向前替换掉格式中的x,多出的位补0。这样,我们就得到了的 UTF-8 编码:11100101 10100101 10111101,转换成十六进制就是e5a5bd

代码实现

下面的就是 Unicode 编码转 UTF-8 编码的代码实现。它是根据 UTF-8 编码规则来实现的,在阅读时,建议结合上面的表格和规则一起看。

function toByte(data) {     let parsedData = [];     for (let i = 0, l = data.length; i < l; i++) {         let byteArray = [];         // charCodeAt() 方法可返回指定位置的字符的 Unicode 编码,返回值是 0 - 65535          // 之间的整数,表示给定索引处的 UTF-16 代码单元。         let code = data.charCodeAt(i);     // 十六进制转十进制:0x10000 ==> 65535  0x800 ==> 2048  0x80 ==> 128     if (code > 0x10000) { // 4个字节         // 0xf0 ==> 11110000          // 0x80 ==> 10000000         byteArray[0] = 0xf0 | ((code & 0x1c0000) >>> 18); // 第 1 个字节         byteArray[1] = 0x80 | ((code & 0x3f000) >>> 12); // 第 2 个字节         byteArray[2] = 0x80 | ((code & 0xfc0) >>> 6); // 第 3 个字节         byteArray[3] = 0x80 | (code & 0x3f); // 第 4 个字节     } else if (code > 0x800) { // 3个字节         // 0xe0 ==> 11100000         // 0x80 ==> 10000000         byteArray[0] = 0xe0 | ((code & 0xf000) >>> 12);         byteArray[1] = 0x80 | ((code & 0xfc0) >>> 6);         byteArray[2] = 0x80 | (code & 0x3f);     } else if (code > 0x80) { // 2个字节         // 0xc0 ==> 11000000         // 0x80 ==> 10000000         byteArray[0] = 0xc0 | ((code & 0x7c0) >>> 6);         byteArray[1] = 0x80 | (code & 0x3f);     } else { // 1个字节         byteArray[0] = code;     }         parsedData.push(byteArray);     }     parsedData = Array.prototype.concat.apply([], parsedData);          console.log('输出结果:', parsedData);     console.log('转二进制:',         parseInt(parsedData[0].toString(2)),         parseInt(parsedData[1].toString(2)),         parseInt(parsedData[2].toString(2)),     ); } 复制代码

为了大家易于理解代码,我们还以为例进行解读。

  • 的 Unicode 是597d(十六进制),二进制是101100101111101,十进制是22909

  • 处在0000 0800 - 0000 FFFF(十进制,2048-65535)范围内,它的的 UTF-8 编码需要3个字节,格式是1110xxxx 10xxxxxx 10xxxxxx

下面,是以为例时,进入的逻辑代码。

// 代码要结合 UTF-8 规则一起看,特别是如何用二进制补位的规则 // 格式:1110xxxx 10xxxxxx 10xxxxxx // code二进制 ==>  1011001 01111101 // 各字节分别的补位: // 第一个字节:101 // 第二个字节:100101 // 第三个字节:111101 // 0xf000 ==>    11110000 00000000 // 0xfc0 ==>         1111 11000000 // 0x3f ==>                 111111                   // 0xe0 ==> 11100000 // 0x80 ==> 10000000 // 按位与运算通常用来对某些位清 0 或保留某些位(取特定位)。 // 按位或可用来给指定位设定为 1 byteArray[0] = 0xe0 | ((code & 0xf000) >>> 12); byteArray[1] = 0x80 | ((code & 0xfc0) >>> 6); byteArray[2] = 0x80 | (code & 0x3f); 复制代码

运算分析:

  • byteArray[0] 对应第一个字节,也就是格式中的:1110xxxx,最大编码位1111,在其之前的12个编码位用0补,所以得11110000 00000000,即 0xf000

    (code & 0xf000) 按位与,得1010000 00000000,在 >>> 12(无符号右移12位),得101,最后进行按位或0xe0 | 101,得11100101

  • byteArray[1] 对应第二个字节,也就是格式中的:10xxxxxx,最大编码位111111,在其之前的6个编码位用0补,所以得1111 11000000,即 0xfc0

    (code & 0xfc0) 按位与,得1001 01000000,在 >>> 6(无符号右移6位),得100101,最后进行按位或0xe0 | 101,得10100101

  • byteArray[2] 对应第三个字节,也就是格式中的:10xxxxxx,最大补位111111,在其之前无编码位,所以得111111,即 0x3f

    (code & 0x3f) 按位与,得111101,最后进行按位或0xe0 | 101,得10111101

结果展示:

截屏2021-10-26 11.15.43.png

通过按位运算得到的二进制会被自动转为十进制,我们可以利用Number 对象的toString()方法将其转为二进制。

另外,同学们是否注意到,若是将上面算出的三个值 101100101111101 拼接起来,它就是code的二进制 1011001 01111101

结束语

还是那句话,如若同学们发现文章内有说错的地方,还请不吝赐教,共同进步。最后,再说一句:阅读代码案例时,一定要结合 UTF-8 编码规则看。

规则很重要!

规则很重要!

规则很重要!


作者:夜听风雨知花落
链接:https://juejin.cn/post/7023214891959844871

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐