很多人搞不清楚有了UTF,为什么还需要有GBK

内容分享9小时前发布
0 0 0

最近罗永浩在社交媒体上吐槽上海电信,说自己办的千兆宽带,测速却只有不到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"   -> 0x411字节)
"中"  -> 0xD6 0xD02字节)
"A中" -> 0x41 0xD6 0xD03字节)

GBK:GB2312 的扩展

GB2312 收录的汉字还是不够,许多人名、地名用字都没有。1995年发布的 GBK 进行了扩展:

  • 收录了 21003 个汉字
  • 完全兼容 GB2312
  • 依旧使用 2 个字节 表明汉字

GB18030:最新的国家标准

2000年发布的 GB18030 更进一步:

  • 收录了 70244 个汉字(包括繁体字、少数民族文字等)
  • 使用 1、2 或 4 个字节 表明字符
  • 是中国的强制标准

GBK 系列编码的特点

  1. 专门为中文设计,存储中文效率高(每个汉字 2 字节)
  2. 兼容 ASCII
  3. 只适用于中文环境,无法表明日文、韩文、阿拉伯文等

Unicode:大一统的野心

各国编码的混乱

GBK 解决了中国的问题,但世界上还有许多语言:

  • 日本有 Shift_JISEUC-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 的优点

  1. 完全兼容 ASCII:英文字符只需 1 字节,和 ASCII 编码完全一致
  2. 无字节序问题:不像 UTF-16/32 需要思考大端小端
  3. 自同步:从任意位置开始读取,都能正确识别字符边界
  4. 节省空间:对于英文为主的文本超级高效

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字节                       
└─────────────────────────────────────────────┘

核心要点

  1. bit 和 Byte:1 Byte = 8 bits,网速用 bit,存储用 Byte,千兆宽带测速 100MB/s 是正常的
  2. 字符集 vs 编码:字符集定义”有哪些字符”,编码定义”怎么存储”
  3. GBK vs UTF-8:GBK 存中文更省空间(2字节 vs 3字节),但 UTF-8 支持所有语言,是现代项目的首选
  4. 乱码本质:编码和解码方式不一致
  5. 最佳实践:新项目统一使用 UTF-8,遇到老系统再思考 GBK

下次再有人问你”千兆宽带为什么只有一百兆”或者”UTF-8 和 GBK 有什么区别”,信任你已经能够给出清晰的解答了。

许多人搞不清楚有了UTF,为什么还需要有GBK

© 版权声明

相关文章

暂无评论

none
暂无评论...