存储单位
数据存储是以“字节”(Byte)为单位,数据传输大多是以“位”(bit,又名“比特”)为单位,一个位就代表一个0或1(即二进制),每8个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小数据存储单位。
1B = 8bit
1KB = 1024B = 8192bit
1MB = 1024KB = 1048576B = 8388608bit
1GB = 1024MB = 1048576KB = 1073741824B = 8589934592bit
1TB = 1024GB = 1048576MB = 1073741824KB = 1099511627776B = 8796093022208bit
字符编码
ASCII 码
我们知道,计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000到11111111。上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。
ASCII 码一共规定了128个字符的编码,比如空格SPACE是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0。
Unicode
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名字都表示的,这是一种所有符号的编码。
UTF-8
Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。
js 字符的Unicode表示法
js允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的Unicode码点(16进制)。
但是,这种表示法只限于码点在\u0000~\uFFFF
之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。
// a 10进制:[97] 16进制:[0x0061]
// 𠮷
// 32位 10进制:[134071] 16进制:[0x20BB7]
// 16位 10进制:[55362 57271] 16进制:[0xD842 0xDFB7]
// 汉字“𠮷”(注意,这个字不是“吉祥”的“吉”)的码点是0x20BB7(十进制为134071),
// UTF-16 编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。
// 对于这种4个字节的字符,js不能正确处理,字符串长度会误判为2,
// 而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。
console.log('\u0061'); //'a'
console.log("\uD842\uDFB7"); //𠮷
console.log("\u20BB7"); //₻7
// 如果直接在\u后面跟上超过0xFFFF的数值(比如\u20BB7),JavaScript 会理解成\u20BB+7。
// 由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。
ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
console.log('\u{20BB7}'); //'𠮷'
console.log('\u{41}\u{42}\u{43}'); //'ABC'
console.log('hell\u{6F}'); //'hello'
// 大括号表示法与四字节的 UTF-16 编码是等价的
console.log('\u{1F680}' === '\uD83D\uDE80'); //true
console.log('z' === '\z'); //true
console.log('z' === '\172'); //true
console.log('z' === '\x7A'); //true
console.log('z' === '\u007A'); //true
console.log('z' === '\u{7A}'); //true
console.log('z' === String.fromCharCode(0x007A) ); //true
ES5: charCodeAt、fromCharCode
var str = "𠮷a";
// 获取 16 位的 UTF-16 字符的码点
console.log(str.charCodeAt(0), str.charCodeAt(1), str.charCodeAt(2)); //55362 57271 97
// 获取 32 位的 UTF-16 字符的码点
// 在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点 134071(即十六进制的20BB7)。
// 在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt 方法的结果与 charCodeAt 方法相同。
console.log(str.codePointAt(0), str.codePointAt(1), str.codePointAt(2)); //134071 57271 97
ES6: codePointAt、fromCodePoint
// fromCharCode 不能识别 32 位的 UTF-16 字符(Unicode 码点大于 0xFFFF 的字符)
console.log(String.fromCharCode(0x20BB7)); //'ஷ'
// fromCodePoint 可以识别大于 0xFFFF 的字符
console.log(String.fromCodePoint(0x20BB7)); //'𠮷'
console.log(String.fromCodePoint(134071)); //'𠮷'
console.log(String.fromCodePoint(55362, 57271)); //'𠮷'
Runes
Runes 对象是一个 32位 字符对象,用来表示一个字。这样设计也是考虑兼容 UTF-16 四个字节的情况。
// a 10进制:[97] 16进制:[0x0061]
// 𠮷
// 32位 10进制:[134071] 16进制:[0x20BB7]
// 16位 10进制:[55362 57271] 16进制:[0xD842 0xDFB7]
codeUnits、codeUnitAt 返回16位码点
var s1 = '𠮷';
print(s1.length); //2 表示占2个16位字符
print('a'.codeUnits); //[97] 转化成16位[0x0061]
print(s1.codeUnits); //[55362, 57271] 转化成16位[0xd842, oxdfb7] 转换成32位[134071]
print(s1.codeUnitAt(0)); //55362
print(s1.codeUnitAt(1)); //57271
runes 返回32位 码点的 Runes
var s1 = '𠮷';
print(s1.runes); //(134071)
print(s1.runes.runtimeType); //Runes
print(s1.runes.length); //1 表示占1个32位字符
fromCharCode、fromCharCodes 码点、Runes转为字符
var s1 = '𠮷';
var s2 = s1.runes;
print(String.fromCharCodes(s2)); //𠮷
print(String.fromCharCode(134071)); //𠮷
print('\ud842\udfb7'); //𠮷
print('\u{20BB7}'); //𠮷
print('\u0061'); //a