编码字符集
字符是不能直接存入计算机的。
起初,美国人将需要的字符(英文字母、数字、标点符号、特殊字符)挨个从 0 开始到 127 进行编号,总共 128 个字符。
然后再将 0 到 127 转换成对应的二进制数字。0 到 127 就称为码点,对应的二进制就是编码,再通过 字符集 映射成对应的字符。
这里的 字符集 就是 ASCII 字符集。

ASCII 字符集
ASCII(American Standard Code for Information Interchange):美国信息交换标准代码,包括了英文、符号等。
标准ASCII字符集使用1个字节存储一个字符,首位是 0,总共可表示 128 个字符,对美国人来说完全够用。
但对于中文的话,ASCII 字符集无法表示,因此,我们需要使用其他字符集来表示中文。
GBK
GBK 是一种国标的汉字编码字符集,包含了2万多个汉字等字符,其中一个中文字符编码成两个字节的形式存储。
注意:GBK兼容了ASCII字符集。也就是GBK中0到127这128个码点需要和ASCII字符集的码点对应。因此,在GBK中,英文和数字占1个字节,中文占2个字节。
对于字符串“我a你”,用GBK的方式存入计算机,编码后的形式如下:

GBK规定,汉字的第一个字节的第一位必须是1,如上图所示。
在解码时,计算机还是会一个一个字节读取,读取第一个字节时,发现第一个字节的第一位是1,那么计算机会连着读取后面一个字节,作为一个整体读取。读取到中间的字节时,发现是0开头,就认为是ASCII字符集,只需要读一个字节。读取到第四个字节时,发现第一位是1,那么就将第4和第5个字节作为整体读取。
因此,GBK最大能表示的范围就只有 15 位二进制位。
Unicode字符集(统一码,也叫万国码)
GBK 只满足汉字编码,不能满足世界上其他国家的字符需求。因此,我们需要更大的字符集,Unicode 字符集来表示。
Unicode是国际组织制定的,可以容纳世界上所有文字、符合的字符集。
Unicode字符集也提供了很多编码方案,比如:
- UTF-32。最早提供的方案之一。使用4个字节表示一个字符。字节数固定,编解码方便简单。但是浪费存储空间,通信效率低。比如,ASCII字符集只需要1个字节,但如果采用UTF-32编码方案,就需要往前面手动补3个字节。
- UTF-8
UTF-8的编码规则是:
- 对于单字节的字符,直接使用一个字节表示,规定ASCII字符直接以一个字节的形式编码,不做任何处理
- 对于双字节的字符,使用两个字节表示, 第一个字节必须以110开头,第二个字节必须以10开头。
- 对于三字节的字符,使用三个字节表示, 第一个字节必须以1110开头,第二个和第三个字节必须以10开头
- 对于四字节的字符,使用四个字节表示, 第一个字节必须以11110开头,后面三个字节都是以10开头。
英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节。
因此“我”的UTF-8编码就是:11100110 10001000 10010001
综上,字符串"a我m"对应的UTF-8编码为:
01100001 11100110 10001000 10010001 01101101
function strToUtf8Binary(s) {
// 1. 编码为UTF-8字节序列(Uint8Array)
const encoder = new TextEncoder();
const utf8Bytes = encoder.encode(s);
// 2. 每个字节转为8位二进制,拼接
let binaryStr = '';
for (const b of utf8Bytes) {
binaryStr += b.toString(2).padStart(8, '0'); // 补前导0至8位
}
return binaryStr;
}
// 示例
console.log(strToUtf8Binary("a")); // 01100001
console.log(strToUtf8Binary("中")); // 111001001011100010101101
function utf8BinaryToStr(binaryStr) {
// 1. 验证二进制字符串合法性(仅包含0/1,且长度为8的倍数)
if (!/^[01]+$/.test(binaryStr) || binaryStr.length % 8 !== 0) {
throw new Error("无效的UTF-8二进制编码(必须仅包含0和1,且长度为8的倍数)");
}
// 2. 按8位拆分二进制字符串,得到每个字节的二进制
const byteBinaries = [];
for (let i = 0; i < binaryStr.length; i += 8) {
byteBinaries.push(binaryStr.slice(i, i + 8));
}
// 3. 将每个字节的二进制转为十进制数值(0-255)
const bytes = new Uint8Array(byteBinaries.map(bin => parseInt(bin, 2)));
// 4. 用TextDecoder解码UTF-8字节序列
const decoder = new TextDecoder('utf-8');
return decoder.decode(bytes);
}
console.log(utf8BinaryToStr("01100001")); // a
console.log(utf8BinaryToStr("111001001011100010101101")); // 中function strToUtf8Binary(s) {
// 1. 编码为UTF-8字节序列(Uint8Array)
const encoder = new TextEncoder();
const utf8Bytes = encoder.encode(s);
// 2. 每个字节转为8位二进制,拼接
let binaryStr = '';
for (const b of utf8Bytes) {
binaryStr += b.toString(2).padStart(8, '0'); // 补前导0至8位
}
return binaryStr;
}
// 示例
console.log(strToUtf8Binary("a")); // 01100001
console.log(strToUtf8Binary("中")); // 111001001011100010101101
function utf8BinaryToStr(binaryStr) {
// 1. 验证二进制字符串合法性(仅包含0/1,且长度为8的倍数)
if (!/^[01]+$/.test(binaryStr) || binaryStr.length % 8 !== 0) {
throw new Error("无效的UTF-8二进制编码(必须仅包含0和1,且长度为8的倍数)");
}
// 2. 按8位拆分二进制字符串,得到每个字节的二进制
const byteBinaries = [];
for (let i = 0; i < binaryStr.length; i += 8) {
byteBinaries.push(binaryStr.slice(i, i + 8));
}
// 3. 将每个字节的二进制转为十进制数值(0-255)
const bytes = new Uint8Array(byteBinaries.map(bin => parseInt(bin, 2)));
// 4. 用TextDecoder解码UTF-8字节序列
const decoder = new TextDecoder('utf-8');
return decoder.decode(bytes);
}
console.log(utf8BinaryToStr("01100001")); // a
console.log(utf8BinaryToStr("111001001011100010101101")); // 中乱码问题
假设我们使用GBK对字符串"a我m"进行编码存储,然后使用UTF-8进行读取,就会出现乱码:

显然,当我们使用GBK对"a我m"进行编码时,"a"占用1个字节,"我"占用2个字节,"m"占用一个字节。GBK编码后大概是这样子:
0xxxxxxx 1xxxxxxx xxxxxxxx 0xxxxxxx
如果我们使用UTF-8进行解码时,第一个字节可以正常解码为"a"。读取第二个字节时,发现不符合UTF-8的编码规则,因此打印一个?号。读取第三个字节时,发现也不符合UTF-8编码规则,因此打印一个?号。读取第四个字节时,发现符合UTF-8编码规则,因此打印"m"
注意:
- 字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码
- 英文、数字一般不会乱码,因为很多字符集都兼容了ASCII编码。