越努力,越幸运,做个ccode~

0%

计算机编码与js字符串

前言

世界上只有10种人, 认识二进制的和不认识的
计算机只能存储0和1, 所有信息在新计算内部最终都是以二进制的形式存储。

每一个二进制位(bit)都只有0和1两种状态,八位二进制(一个字节byte)可以表示2**8 === 256种状态,从00000000~11111111。

既然计算机只能存储0和1,它是怎么存储数字以及各种字符呢?先从简单的数字说起吧

如何存储数字

存储数字很容易,直接转为二进制就好了

15 => 0b1111
-15 => -0b1111
0.75 => 0b0.11

计算机只能存储1和0
负数以补码的形式存储
小数以浮点数的形式存储

如何存储字符

上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。

比如 字符a的ascii码对应的十进制是97, 二进制是0110 0001; 字符5的ascii码对应的十进制是53, 二进制是0011 0101。

ASCII 码一共规定了128个字符的编码,只用了一个字节。

如何存储中文字符

ASCII码只能存储128个英文字符, 不能存储中文字符,所以在ASCII的基础上,中国推出了GB2321编码

GB2312 共收录 6763 个汉字,同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。

后来为了存储生僻字、繁体字、日语、朝鲜语等,微软推出了 GBK 字符集

如何存储所有字符

为了存储全球字符,将全球字符编号,有了unicode编码,包括中日韩文字、藏文、盲文、楔形文字、 颜文字:-)、 绘文字😂

比如 汉字的 Unicode 是十六进制数4E25,转换成二进制数10011100 00100101 汉字的 Unicode 是十六进制数4f60, 转换成二进制数01001111 01100000,

Unicode只规定了每个字符的码点,到底用什么样的字节序表示这个码点,就涉及到编码方法。

最直观的编码方法是,每个码点使用四个字节表示,字节内容一一对应码点。这种编码方法就叫做UTF-32。

4个字节表示一个字符
完全对应unicode编码

比如 a的utf-32为0x00000061 即二进制 00000000 00000000 00000000 01100001

如何节省空间 (UTF-8编码)

虽然utf-32编码简单直观,查找起来很方便,但是浪费空间,比如字符’a’,ascii码只用了一个字节存储,utf-32却用了四个字节
所以我们需要一种节省空间的表示方法,这导致了utf-8的诞生

UTF-8是一种变长的编码方法,字符长度从1个字节到4个字节不等。

所以这里我们知道,unicode是一种字符集,而utf-8是为了节省存储空间的编码方法

以下是utf-8编码规则

00000000 00000000 00000000 01111111 即 0x0000007F 以下
0XXXXXXX

00000000 00000000 00000111 11111111 即 0x000007FF 以下
110XXXXX 10XXXXXX

00000000 00000000 11111111 11111111 即 0x0000FFFF 以下
1110XXXX 10XXXXXX 10XXXXXX

00000000 00011111 11111111 11111111 即 0x001FFFFF 以下
11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

比如 字符’你’的unicode编码是 0100 1111 0110 00000x4f60 在0x000007FF~0x0000FFFF之间,所以需要三个字节
格式是1110XXXX 10XXXXXX 10XXXXXX ,从’你’的二进制最后一位开始,从右向左替换’X’,高位补0 会得到
11100100 10111101 10100000E4BDA0,所以’你’的UTF-8编码是E4BDA0

还有更多的unicode编码方式,请参考

UTF-8 可变字节序列,用 1 到 4 个字节表示一个码点
UTF-16 可变字节序列,用 2 或 4 个字节表示一个码点
UTF-32 固定字节序列,用 4 个字节表示一个码点

Javascript采用的编码方式 (USC-2编码)

由于js诞生的时候只有UCS-2编码, 所以js采用的编码是UCS-2编码

USC-2 1990年发布 使用2个字节表示已经有码点的字符
js是1995年诞生的
UTF-16 1996年7月发布

由于js采用的是USC-2编码,只能读取2个字节的字符(即范围在0x0000~0xFFFF),这会导致很多问题

字符的unicode表示法

这里请参考字符的unicode表示法

ES6 加强了对 Unicode 的支持, 允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。

'\u4f60' // '你'

但是在读取超过两个字节的字符时,就会只读取前两个字节

'\u20bb7'  // "₻7"

而大于两字节的字符的length也会显示错误

"𠮷".length  // 2

ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符,以UTF-16的编码方式。

"\u{20BB7}"    // "𠮷"


"\u{41}\u{42}\u{43}"     // "ABC"


let hello = 123;
hell\u{6F}  // 123   因为'\u{6f}'表示字符'o'

'\u{1F680}' === '\uD83D\uDE80' // true  都表示字符"🚀"

javaScript 共有 6 种方法可以表示一个字符。

'\z' === 'z'  // true    
'\172' === 'z' // true     这里是unicode码的八进制
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

将字符串转为数组

('𠮷𠮷𠮷').split('')  //  ["�", "�", "�", "�", "�", "�"] 超过两个字节的字符无法正确识别

解决办法: ES6推出了Array.from方法

Array.from('𠮷𠮷𠮷')  //  ["𠮷", "𠮷", "𠮷"]

码点与字符互换

String.prototype.charCodeAt() 返回 0 到 65535 之间的整数(十进制),表示给定索引处的 UTF-16 代码单元
String.fromCharCode() 返回由指定的UTF-16代码单元序列创建的字符串

'z'.charCodeAt()              // 122
String.fromCharCode('122','0o172','0x7a')    // "zzz"

但是对于超过两个字节的字符ES5无法正确转换

String.fromCharCode(0x20BB7)   // "ஷ"  乱码  因为0x20BB7超过两个字节,无法正确解读

ES6的两个方法可以解决这一bug

String.codePointAt() 返回UTF-16的完整码点
String.fromCodePoint() 返回由指定的UTF-16代码单元序列创建的字符串

"🚀".codePointAt()              // 128640
String.fromCodePoint(128640)    // "🚀"
"𠮷".codePointAt()              // 134071
String.fromCodePoint(0x20BB7,134071)   // "𠮷𠮷"

// 对于小于两个字节的也完全适用
"a".codePointAt()               // 97
String.fromCodePoint(97)        // "a"
"z".codePointAt()               // 122
String.fromCodePoint('122','0o172','0x7a')   // "zzz"

另外一些bug,请参考javascript有个unicode的天坑