最近罗永浩在社交媒体上吐槽上海电信,说自己办的千兆宽带,测速却只有不到100兆,怀疑被运营商”坑”了。评论区一片热闹,有人跟着骂运营商,也有人出来科普:千兆宽带的”千兆”是1000Mbps(兆比特每秒),而测速软件显示的是MB/s(兆字节每秒),1字节=8比特,所以1000Mbps÷8≈125MB/s,再算上各种损耗,如果有100多兆,那是完全正常的。不过罗永浩反馈的是不到百兆……那是另外一个话题了。
这个事件,恰好引出了一个许多程序员都容易混淆的基础概念:bit(比特)和 Byte(字节)到底是什么关系? 而这个问题,又和另一个经典困惑紧密相连:有了UTF-8,为什么还需要GBK?
今天我们就从这个”千兆变百兆”的故事出发,把bit、字节、字符、字符集这些概念彻底讲清楚。
从千兆宽带说起:bit 和 Byte 的区别
什么是 bit(比特)
bit(比特)是计算机中最小的数据单位,它只有两个值:0 或 1。
计算机的一切数据,无论是文字、图片、视频还是程序,最终都要转换成 0 和 1 的序列来存储和传输。一个 bit 就是这个序列中的一个位置。
为什么是 0 和 1?由于计算机的物理基础是电子元件,电路只有”通电”和”断电”两种状态,用 0 和 1 来表明最简单直接。
什么是 Byte(字节)
Byte(字节)= 8 个 bit
这是一个约定俗成的标准。为什么是 8 个?历史缘由许多,但最实用的解释是:8 个 bit 可以表明 2^8 = 256 种不同的状态,刚好足够表明英文字母(大小写)、数字、标点符号和一些控制字符。
1 Byte = 8 bits
1 KB = 1024 Bytes
1 MB = 1024 KB
1 GB = 1024 MB
为什么网速用 bit,存储用 Byte?
这就是罗永浩被”坑”的缘由:
- 网络带宽习惯用 bit 作为单位:1000Mbps = 1000兆比特每秒
- 文件大小和下载速度习惯用 Byte 作为单位:100MB/s = 100兆字节每秒
所以:
1000 Mbps ÷ 8 = 125 MB/s(理论最大值)
思考到网络协议开销、线路损耗等因素,实际能达到 100-120 MB/s 就已经是正常水平了。
小技巧:区分 Mb 和 MB 很简单——小写 b 是 bit,大写 B 是 Byte。
从 bit 到字符:编码的诞生
字符是什么?
字符(Character)是人类可读的最小文本单位,列如字母 A、数字 1、汉字 中、标点 , 都是字符。
但计算机只认识 0 和 1,不认识”A”或”中”。所以我们需要一套规则,把字符转换成二进制数字,这套规则就叫字符编码(Character Encoding)。
ASCII:一切的起点
1963年,美国制定了 ASCII(American Standard Code for Information Interchange) 编码标准:
- 使用 7 个 bit 表明一个字符
- 可以表明 2^7 = 128 个字符
- 包括:大小写英文字母(52个)、数字(10个)、标点符号、控制字符
字符 ASCII码(十进制) 二进制
A 65 01000001
a 97 01100001
0 48 00110000
空格 32 00100000
ASCII 用 1 个字节存储(实际只用了 7 位,最高位为 0),对于英语国家来说完全够用。
但问题来了:128 个字符,中文怎么办? 常用汉字就有几千个,加上生僻字有好几万。
GBK:中国人的解决方案
GB2312:最早的中文编码
1980年,中国发布了 GB2312 编码标准:
- 收录了 6763 个汉字 和 682 个其他符号
- 使用 2 个字节 表明一个汉字
- 兼容 ASCII(英文字符仍用 1 个字节)
原理很简单:ASCII 只用了 0-127,那 128-255 这个区间就空着。GB2312 规定:当第一个字节大于 127 时,就和下一个字节一起组成一个汉字。
"A" -> 0x41(1字节)
"中" -> 0xD6 0xD0(2字节)
"A中" -> 0x41 0xD6 0xD0(3字节)
GBK:GB2312 的扩展
GB2312 收录的汉字还是不够,许多人名、地名用字都没有。1995年发布的 GBK 进行了扩展:
- 收录了 21003 个汉字
- 完全兼容 GB2312
- 依旧使用 2 个字节 表明汉字
GB18030:最新的国家标准
2000年发布的 GB18030 更进一步:
- 收录了 70244 个汉字(包括繁体字、少数民族文字等)
- 使用 1、2 或 4 个字节 表明字符
- 是中国的强制标准
GBK 系列编码的特点:
- 专门为中文设计,存储中文效率高(每个汉字 2 字节)
- 兼容 ASCII
- 只适用于中文环境,无法表明日文、韩文、阿拉伯文等
Unicode:大一统的野心
各国编码的混乱
GBK 解决了中国的问题,但世界上还有许多语言:
- 日本有 Shift_JIS、EUC-JP
- 韩国有 EUC-KR
- 俄罗斯有 KOI8-R
- 欧洲各国也有自己的编码…
这就造成了巨大的混乱:
- 同一个字节序列,用不同编码解读,会得到不同的字符(乱码的根源)
- 一个文档里无法同时包含中文、日文、阿拉伯文
Unicode 的诞生
1991年,Unicode(统一码) 应运而生,目标是:为世界上所有字符分配一个唯一的编号。
Unicode 不是一种编码,而是一个字符集(Character Set)——它只负责给每个字符分配一个数字编号(称为码点,Code Point),不管具体怎么存储。
字符 Unicode码点 含义
A U+0041 拉丁字母A
中 U+4E2D 汉字"中"
U+1F600 笑脸表情
截至 2023 年,Unicode 已经收录了超过 14 万个字符,涵盖了世界上几乎所有的书写系统。
字符集 vs 字符编码
这里要区分两个概念:
|
概念 |
含义 |
例子 |
|
字符集(Character Set) |
字符的集合,每个字符有唯一编号 |
Unicode、ASCII |
|
字符编码(Character Encoding) |
把字符编号转换成字节序列的规则 |
UTF-8、UTF-16、GBK |
Unicode 是字符集,UTF-8、UTF-16、UTF-32 是基于 Unicode 的不同编码方式。
UTF-8:Unicode 的最佳实践
为什么需要 UTF-8?
Unicode 给每个字符分配了一个码点,但码点最大可以到 0x10FFFF(十进制 1114111),直接存储需要 3-4 个字节。
如果每个字符都用 4 个字节存储(UTF-32),那英文文本的体积会变成原来的 4 倍——这太浪费了!
UTF-8 的设计目标就是:用尽可能少的字节来存储字符。
UTF-8 的编码规则
UTF-8 是一种变长编码,根据字符的 Unicode 码点大小,使用 1-4 个字节:
|
Unicode 范围 |
UTF-8 字节数 |
字节格式 |
|
U+0000 ~ U+007F |
1 字节 |
0xxxxxxx |
|
U+0080 ~ U+07FF |
2 字节 |
110xxxxx 10xxxxxx |
|
U+0800 ~ U+FFFF |
3 字节 |
1110xxxx 10xxxxxx 10xxxxxx |
|
U+10000 ~ U+10FFFF |
4 字节 |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
举例:
字符 "A"
Unicode: U+0041 = 65
二进制: 1000001
UTF-8: 01000001(1字节,和 ASCII 完全一样!)
字符 "中"
Unicode: U+4E2D = 20013
二进制: 100 111000 101101
UTF-8: 11100100 10111000 10101101(3字节)
= 0xE4 0xB8 0xAD
UTF-8 的优点
- 完全兼容 ASCII:英文字符只需 1 字节,和 ASCII 编码完全一致
- 无字节序问题:不像 UTF-16/32 需要思考大端小端
- 自同步:从任意位置开始读取,都能正确识别字符边界
- 节省空间:对于英文为主的文本超级高效
UTF-8 的”缺点”
对于中文来说,UTF-8 需要 3 个字节 表明一个汉字,而 GBK 只需要 2 个字节。
这意味着:纯中文文本用 UTF-8 存储,体积比 GBK 大 50%。
回答核心问题:有了 UTF-8,为什么还需要 GBK?
目前我们可以正式回答这个问题了:
1. 历史遗留
GBK(1995年)比 UTF-8 普及(2000年代后期)要早得多。大量的老系统、老数据库、老文件都是 GBK 编码的,不可能一夜之间全部转换。
2. 存储效率
|
编码 |
英文字符 |
中文字符 |
|
UTF-8 |
1 字节 |
3 字节 |
|
GBK |
1 字节 |
2 字节 |
对于中文内容为主的系统(列如中文论坛、中文数据库),GBK 可以节省约 33% 的存储空间。在存储成本较高或带宽受限的场景下,这个差异是有意义的。
3. 特定场景的兼容性
- 某些老旧的硬件设备只支持 GBK
- 某些政府或企业的内部系统要求使用国标编码
- 与老系统对接时必须使用 GBK
4. 现实中的选择
新项目应该优先使用 UTF-8,缘由是:
- 国际化支持:可以混合存储任何语言
- 生态支持:几乎所有现代工具、框架都默认 UTF-8
- 网络传输:HTTP 协议推荐 UTF-8
- 避免乱码:统一编码可以避免大部分乱码问题
但在以下场景,GBK 仍有存在价值:
- 维护老系统
- 对接遗留接口
- 存储空间极度敏感的中文系统
- 某些特定行业的合规要求
乱码是怎么产生的?
理解了编码原理,乱码问题就很好解释了:
乱码 = 用错误的编码方式解读字节序列
"中文" 的 GBK 编码:0xD6 0xD0 0xCE 0xC4
如果用 UTF-8 解读这 4 个字节:
0xD6 不是有效的 UTF-8 起始字节 -> 显示 �
0xD0 不是有效的 UTF-8 起始字节 -> 显示 �
...
结果:乱码 ����
如果用 Latin-1 (ISO-8859-1) 解读:
0xD6 = Ö
0xD0 = Ð
0xCE = Î
0xC4 = Ä
结果:ÖÐÎÄ(看起来像乱码的欧洲字符)
解决乱码的关键:确保编码和解码使用一样的字符编码。
常见的乱码场景和解决方案:
|
场景 |
缘由 |
解决方案 |
|
网页乱码 |
HTML 未声明编码或声明错误 |
添加 <meta charset=”UTF-8″> |
|
数据库乱码 |
连接编码与数据编码不一致 |
统一数据库、连接、客户端编码 |
|
文件乱码 |
文件编码与编辑器设置不一致 |
用正确编码打开或转换文件编码 |
|
API 乱码 |
请求/响应编码不一致 |
统一使用 UTF-8,正确设置 Content-Type |
实战:Java 中的编码处理
public class EncodingDemo {
public static void main(String[] args) throws Exception {
String text = "中文ABC";
// 获取不同编码的字节数组
byte[] utf8Bytes = text.getBytes("UTF-8");
byte[] gbkBytes = text.getBytes("GBK");
System.out.println("UTF-8 字节数: " + utf8Bytes.length); // 9 (中文3*2 + ABC3)
System.out.println("GBK 字节数: " + gbkBytes.length); // 7 (中文2*2 + ABC3)
// 打印字节内容
System.out.print("UTF-8: ");
for (byte b : utf8Bytes) {
System.out.printf("%02X ", b);
}
// E4 B8 AD E6 96 87 41 42 43
System.out.print("
GBK: ");
for (byte b : gbkBytes) {
System.out.printf("%02X ", b);
}
// D6 D0 CE C4 41 42 43
// 错误解码导致乱码
String wrong = new String(gbkBytes, "UTF-8");
System.out.println("
用UTF-8解码GBK字节: " + wrong); // 乱码
// 正确解码
String correct = new String(gbkBytes, "GBK");
System.out.println("用GBK解码GBK字节: " + correct); // 中文ABC
}
}
实战:MySQL 中的编码配置
-- 查看数据库编码设置
SHOW VARIABLES LIKE 'character_set%';
-- 创建 UTF-8 数据库(推荐 utf8mb4,支持 emoji)
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建表时指定编码
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100)
) CHARACTER SET utf8mb4;
-- 修改已有表的编码
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4;
-- 连接时指定编码
SET NAMES 'utf8mb4';
-- 或在连接字符串中指定
-- jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8
MySQL 编码踩坑提示:
- utf8 在 MySQL 中是”假的” UTF-8,最多只支持 3 字节,存不了 emoji
- 要存储完整的 Unicode(包括 emoji),必须使用 utf8mb4
总结
让我们用一张图总结今天的内容:
bit (比特)
└── 最小数据单位,0 或 1
└── 网络带宽单位:Mbps(兆比特每秒)
│
│ 8 bits = 1 Byte
▼
Byte (字节)
└── 基本存储单位
└── 文件大小/下载速度单位:MB/s(兆字节每秒)
│
│ 编码规则
▼
字符 (Character)
└── 人类可读的最小文本单位
└── 需要通过编码转换为字节
│
│ 字符集定义编号,编码定义存储方式
▼
┌─────────────────────────────────────────────┐
│ 字符集 (Character Set) │
│ └── ASCII: 128 个字符 │
│ └── Unicode: 14万+ 字符(持续增加) │
└─────────────────────────────────────────────┘
│
│ 不同的编码实现
▼
┌─────────────────────────────────────────────┐
│ 字符编码 (Character Encoding) │
│ ├── ASCII: 1字节/字符,仅英文 │
│ ├── GBK: 1-2字节,中文2字节,仅中英文 │
│ ├── UTF-8: 1-4字节,中文3字节,支持所有语言 │
│ ├── UTF-16: 2-4字节,中文2字节 │
│ └── UTF-32: 固定4字节 │
└─────────────────────────────────────────────┘
核心要点:
- bit 和 Byte:1 Byte = 8 bits,网速用 bit,存储用 Byte,千兆宽带测速 100MB/s 是正常的
- 字符集 vs 编码:字符集定义”有哪些字符”,编码定义”怎么存储”
- GBK vs UTF-8:GBK 存中文更省空间(2字节 vs 3字节),但 UTF-8 支持所有语言,是现代项目的首选
- 乱码本质:编码和解码方式不一致
- 最佳实践:新项目统一使用 UTF-8,遇到老系统再思考 GBK
下次再有人问你”千兆宽带为什么只有一百兆”或者”UTF-8 和 GBK 有什么区别”,信任你已经能够给出清晰的解答了。





