Unicode
Unicode是目前绝大多数程序使用的字符编码,定义也很简单,用一个码点(code point)映射一个字符。码点值的范围是从U+0000到U+10FFFF,可以表示超过110万个符号。
字符集 | 字数 | Unicode 编码 |
基本汉字 | 20902字 | 4E00-9FA5 |
基本汉字补充 | 74字 | 9FA6-9FEF |
扩展A | 6582字 | 3400-4DB5 |
扩展B | 42711字 | 20000-2A6D6 |
扩展C | 4149字 | 2A700-2B734 |
扩展D | 222字 | 2B740-2B81D |
扩展E | 5762字 | 2B820-2CEA1 |
扩展F | 7473字 | 2CEB0-2EBE0 |
扩展G | 4939字 | 30000-3134A |
康熙部首 | 214字 | 2F00-2FD5 |
部首扩展 | 115字 | 2E80-2EF3 |
兼容汉字 | 477字 | F900-FAD9 |
兼容扩展 | 542字 | 2F800-2FA1D |
PUA(GBK)部件 | 81字 | E815-E86F |
部件扩展 | 452字 | E400-E5E8 |
PUA增补 | 207字 | E600-E6CF |
汉字笔画 | 36字 | 31C0-31E3 |
汉字结构 | 12字 | 2FF0-2FFB |
汉语注音 | 43字 | 3105-312F |
注音扩展 | 22字 | 31A0-31BA |
UTF(Unicode transformation format)
UTF是Unicode转换格式,是服务于Unicode的,用于将一个Unicode码点转换为特定的字节序列。常见的UTF有
UTF-8 可变字节序列,用1到4个字节表示一个字符
UTF-16 可变字节序列,用2或4个字节表示一个字符
UTF-32 固定字节序列,用4个字节表示一个字符
UTF-8对ASCⅡ编码是兼容的,都是一个字节,超过U+07FF的部分则用了复杂的转换方式来映射Unicode,具体不再详述。
UTF-16对于BMP的码点,采用2个字节进行编码,用4个字节来表示。其中前两个字节范围是U+D800到U+DBFF,后两个字节范围是U+DC00到U+DFFF,通过以下公式完成映射(H:高字节 L:低字节 c:码点)
H = Math.floor((c-0x10000) / 0x400)+0xD800
L = (c – 0x10000) % 0x400 + 0xDC00
UCS(Universal Character Set)
UCS通用字符集,是一个ISO标准,目前与Unicode可以说是等价的。
相对于UTF,UCS也有自己的转换方法(编码)。如
UCS-2 用2个字节表示一个字符
UCS-4 用4个字节表示一个字符
UCS-2是一个过时的编码方式,因为它只能编码基本平面(BMP)的码点,在BMP的编码上,与UTF-16是一致的,所以可以认为是UTF-16的一个子集。
UCS-4则与UTF-32等价,都是用4个字节来编码Unicode。
JavaScript的字符处理
Js到底是用的啥编码呢?答案是UCS-2。咦,刚刚不是说UCS-2过时了吗?首先看下年表
1990 UCS-2 诞生
1995.5 Java 诞生
1996.7 UTF-16 诞生
也就是说,Brendan Eich在写JS的时候,UTF-16还没问世,所以只能用UCS-2的方式来处理字符,也因此留下了隐患。
隐患1:length问题
console.log("𠂒".length);//2
解决办法:
console.log(Array.from("𠂒").length);//1
隐患2:reverse的问题
function reverse(str) {
return str.split('').reverse().join('');
}
console.log(reverse("𠂒是告的一部分"));
//分部一的告是��
解决办法:
function reverse(str) {
//return str.split('').reverse().join('');
return Array.from(str).reverse().join('');
}
console.log(reverse("𠂒是告的一部分"));
//分部一的告是𠂒
隐患3:unicode转码
//中文转Unicode
function encodeUnicode(str) {
let res = [];
for ( let i=0; i<str.length; i++ ) {
let v = ( "00" + str.charCodeAt(i).toString(16) ).slice(-4)
res[i] = v.toUpperCase();
}
return "\\u" + res.join("\\u");
}
console.log(encodeUnicode("告"))//\u544A,正确
console.log(encodeUnicode("𠂒"))//\uD840\uDC92,错误!实际编码:(0x)20092
//Unicode转中文
console.log(String.fromCharCode(0x544A));//告
console.log(String.fromCharCode(0xD840,0xDC92));//𠂒
console.log(String.fromCharCode(0x20092));//□ 无法识别的字符
解决办法:
//中文转Unicode
function encodeUnicode2(str) {
let res = [];
let i = 0;
for(const char of str) {//不能使用length
res[i++] = char.codePointAt(0).toString(16).toUpperCase();
}
return "\\u" + res.join("\\u");
}
console.log(encodeUnicode2("𠂒"))//\u20092
//Unicode转中文
console.log(String.fromCodePoint(0x20092));//𠂒
总结
长度判断:Array.from("𠂒").length
字符串循环遍历:for(const char of str)
反转:Array.from(str).reverse().join('')
中文转Unicode:char.codePointAt(0).toString(16) //代替char.charCodeAt(i).toString(16)
Unicode转中文:String.fromCodePoint(0x20092) //代替String.fromCharCode(0x20092)