Go 语言进制转换与字符编码详解:二进制、ASCII、Unicode 与 UTF-8
title: description: keywords: slug: tags: title = “Go 语言进制转换与字符编码详解:二进制、ASCII、Unicode 与 UTF-8” description = “深入讲解计算机进制转换原理、数据存储单位换算,以及 ASCII、Unicode、UTF-8 字符编码的工作机制。结合 Go 语言代码实例,帮助你彻底搞懂从二进制到字符编码的底层逻辑。” keywords = “Go 语言编码, 进制转换, 二进制, ASCII 编码, Unicode 字符集, UTF-8 编码, 字节单位换算, Go 字符串底层” categories = [“编程开发”] tags = [“Go 语言”,“进制转换”,“字符编码”,“UTF-8”,“Unicode”,“ASCII”,“计算机基础”] slug = “golang-binary-encoding-ascii-unicode-utf8” date = “2026-04-17” lastmod = “2026-04-17” summary = "" draft = false type = “posts” weight = 0 include_toc = false show_comments = true
Go 语言进制转换与字符编码详解
在学习任何编程语言之前,搞清楚进制和编码这两个概念非常有必要。如果你正在学 Go 语言,这些基础知识会直接影响你对字符串处理、网络传输、文件读写等核心操作的理解深度。这篇文章会从零开始,把进制转换和字符编码讲透彻。
为什么要了解进制和编码
很多初学者觉得进制和编码"太底层、用不上",但实际开发中经常会碰到这些场景:
- 处理中文字符串时出现乱码,不知道怎么排查
- 读取文件或网络数据时,不理解字节和字符的区别
- 看到
\xe6\xad\xa6这样的输出一头雾水
把底层原理搞明白,遇到这些问题就不会慌了。
进制:计算机如何表达数字
从生活场景理解二进制
想象一排灯泡,每个灯泡只有两种状态——亮或灭。用 1 代表亮,0 代表灭,那么 6 个灯泡的状态就可以写成类似 110010 这样的序列。
计算机内部的晶体管和二极管,工作原理与灯泡非常相似:高电平为 1,低电平为 0。所以计算机底层所有的数据,归根结底都是一串 0 和 1 的组合,这就是二进制。
二进制的计数规则很简单——逢 2 进 1:
| 十进制 | 二进制 |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 2 | 10 |
| 3 | 11 |
| 4 | 100 |
| 5 | 101 |
| 10 | 1010 |
常用进制对照
日常开发中最常打交道的有四种进制,它们之间的关系并不复杂,核心区别就在于"满几进 1":
| 进制 | 基数 | 使用场景 |
|---|---|---|
| 二进制 | 2 | 计算机底层存储、位运算 |
| 八进制 | 8 | Linux 文件权限(如 0755) |
| 十进制 | 10 | 日常生活、大部分业务计算 |
| 十六进制 | 16 | 内存地址、颜色值(如 #FF5733)、编码表示 |
Go 语言中的进制表示
在 Go 中,可以直接用不同前缀来声明各种进制的整数字面量:
package main
import "fmt"
func main() {
a := 10 // 十进制
b := 0b1010 // 二进制,前缀 0b
c := 012 // 八进制,前缀 0
d := 0xA // 十六进制,前缀 0x
fmt.Println(a, b, c, d) // 输出: 10 10 10 10
}四个变量值相同,只是书写形式不同。理解这一点后,你在阅读源码时遇到 0x 或 0b 开头的数字就不会陌生了。
数据存储单位:从 bit 到 TB
核心换算关系
计算机中衡量数据大小有一套标准单位体系,相邻单位之间以 1024(即 2 的 10 次方)为换算倍率:
| 单位 | 全称 | 与上一级的关系 |
|---|---|---|
| b(bit) | 位 | 最小单位,一个 0 或 1 |
| B(Byte) | 字节 | 1 B = 8 b |
| KB(Kilobyte) | 千字节 | 1 KB = 1024 B |
| MB(Megabyte) | 兆字节 | 1 MB = 1024 KB |
| GB(Gigabyte) | 千兆字节 | 1 GB = 1024 MB |
| TB(Terabyte) | 太字节 | 1 TB = 1024 GB |
更大的单位还有 PB、EB、ZB 等,不过在日常开发中接触较少,了解即可。
为什么是 1024 而不是 1000? 因为计算机使用二进制,2^10 = 1024 是最接近 1000 的 2 的整数次幂,所以被选作换算基数。
生活中的实际参照
理解了这套单位,日常接触的这些数字就有了具体含义:
- 手机内存 8 GB ≈ 约 85 亿个二进制位
- 笔记本硬盘 512 GB ≈ 可以存大约 500 部高清电影
- 手机月流量 30 GB ≈ 可以刷大约 60 小时的标清短视频
- 一张普通照片大约 3 ~ 5 MB
字符编码:让计算机读懂文字
计算机只认识 0 和 1,那文字、符号这些人类语言怎么存到计算机里?答案就是编码——建立一套"字符 ↔ 二进制"的映射规则。
ASCII 编码:英文世界的起点
ASCII(American Standard Code for Information Interchange)是最早的字符编码标准,诞生于 1963 年。它用 1 个字节(8 位) 来表示一个字符,理论上可以映射 2^8 = 256 种符号,实际定义了 128 个常用字符。
几个常见的映射举例:
| 字符 | 十进制 | 二进制 |
|---|---|---|
| A | 65 | 01000001 |
| Z | 90 | 01011010 |
| a | 97 | 01100001 |
| 0 | 48 | 00110000 |
| 空格 | 32 | 00100000 |
ASCII 的优点是简洁高效,缺点也很明显:128 个位置根本不够用。光英文字母、数字和标点就快占满了,中文、日文、韩文、阿拉伯文等其他语言完全无法表示。
Unicode 字符集:把全球文字装进一张表
为了让全世界的文字都能在计算机中统一表达,Unicode(统一码,也叫万国码)应运而生。它的目标很明确:给每一个字符分配一个唯一的编号(码位)。
Unicode 有两种常见的编码方案:
UCS-2:用 2 个字节(16 位)表示一个字符,最多能容纳 2^16 = 65,536 个字符。对于中文、日文、韩文等常见文字足够了,但覆盖不了一些生僻字和特殊符号(如 Emoji)。
UCS-4:用 4 个字节(32 位)表示一个字符,最多能容纳 2^32 ≈ 43 亿个字符,足以涵盖人类已知的所有书写系统。
几个 Unicode 码位示例:
| 字符 | Unicode 码位(十六进制) |
|---|---|
| % | U+0025 |
| A | U+0041 |
| 武 | U+6B66 |
| 沛 | U+6C9B |
实际项目中,UCS-2 和 UCS-4 的选择取决于你的业务场景。如果需要支持 Emoji 表情或罕见字符,UCS-4 是更稳妥的选择。
UTF-8 编码:最流行的 Unicode 实现方式
Unicode 定义了"字符对应哪个编号",但没有规定这个编号在计算机中具体怎么存储。如果所有字符都用 4 个字节存储,一篇纯英文文章的体积会膨胀 4 倍,这显然不合理。
UTF-8 的做法非常巧妙——变长编码。不同范围的码位使用不同长度的字节来存储,英文 1 个字节,中文 3 个字节,既兼容 ASCII,又不浪费空间。
第一步:根据码位选择编码模板
| 码位范围(十六进制) | 字节数 | 编码模板 |
|---|---|---|
| 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 |
根据这张表可以快速判断:
- 字母
B的码位是 U+0042,落在第一行范围内,用 1 个字节 - 字母
ǣ的码位是 U+01E3,落在第二行范围内,用 2 个字节 - 汉字
武的码位是 U+6B66,落在第三行范围内,用 3 个字节
这就解释了为什么 UTF-8 编码下,一个中文字符占 3 个字节。
第二步:将码位填入模板
以汉字"武"为例,完整推导一遍:
- “武"的 Unicode 码位是
6B66,转为二进制:0110 1011 0110 0110 - 码位在 U+0800 ~ U+FFFF 范围,选第三个模板:
1110XXXX 10XXXXXX 10XXXXXX - 把二进制位从高到低依次填入模板中的
X位置:
码位二进制:0110 101101 100110
↓ ↓ ↓
模板填充: 1110 0110 10 101101 10 100110最终结果:11100110 10101101 10100110,对应十进制就是 230 173 166。
除了 UTF-8,还有 UTF-16 和 UTF-32 等编码方式。UTF-16 在 Windows 系统和 Java 内部使用较多,UTF-32 则比较"奢侈”,每个字符固定占 4 字节。但在互联网领域,UTF-8 是当之无愧的主流,全球超过 98% 的网页都采用 UTF-8 编码。
Go 语言中验证 UTF-8 编码
Go 语言的字符串在底层就是以 UTF-8 编码存储的,这一点可以通过代码直接验证:
package main
import (
"fmt"
"strconv"
)
func main() {
name := "武沛齐"
// "武" 占 3 个字节,索引 0、1、2
fmt.Println(name[0], strconv.FormatInt(int64(name[0]), 2)) // 230 11100110
fmt.Println(name[1], strconv.FormatInt(int64(name[1]), 2)) // 173 10101101
fmt.Println(name[2], strconv.FormatInt(int64(name[2]), 2)) // 166 10100110
// "沛" 占 3 个字节,索引 3、4、5
fmt.Println(name[3], strconv.FormatInt(int64(name[3]), 2)) // 230 11100110
fmt.Println(name[4], strconv.FormatInt(int64(name[4]), 2)) // 178 10110010
fmt.Println(name[5], strconv.FormatInt(int64(name[5]), 2)) // 155 10011011
// "齐" 占 3 个字节,索引 6、7、8
fmt.Println(name[6], strconv.FormatInt(int64(name[6]), 2)) // 233 11101001
fmt.Println(name[7], strconv.FormatInt(int64(name[7]), 2)) // 189 10111101
fmt.Println(name[8], strconv.FormatInt(int64(name[8]), 2)) // 144 10010000
}可以看到,字符串 "武沛齐" 在内存中总共占了 9 个字节,每个汉字 3 个字节,和我们前面推导的 UTF-8 编码规则完全吻合。
在 Go 中通过下标访问字符串时,拿到的是单个字节而不是字符。如果需要按字符遍历中文字符串,应该使用 for range 或者将字符串转换为 []rune:
package main
import "fmt"
func main() {
name := "武沛齐"
// 使用 for range 按字符遍历
for i, ch := range name {
fmt.Printf("索引: %d, 字符: %c, Unicode 码位: U+%04X\n", i, ch, ch)
}
// 转换为 rune 切片后按索引访问
runes := []rune(name)
fmt.Printf("第 1 个字符: %c\n", runes[0]) // 武
fmt.Printf("第 2 个字符: %c\n", runes[1]) // 沛
fmt.Printf("第 3 个字符: %c\n", runes[2]) // 齐
}常见问题
Q1:二进制和十六进制之间怎么快速转换?
每 4 位二进制恰好对应 1 位十六进制。比如二进制 1010 1111 就是十六进制 AF。记住 A=10、B=11、C=12、D=13、E=14、F=15 即可。
Q2:为什么 Go 语言中 len("中文") 返回 6 而不是 2?
因为 len() 返回的是字节数,而 UTF-8 编码下每个中文字符占 3 个字节,所以 2 个汉字就是 6 个字节。如果要获取字符数量,可以使用 utf8.RuneCountInString("中文"),返回值为 2。
Q3:什么时候会遇到乱码问题?
乱码的根本原因是编码和解码使用了不同的规则。例如一段文字用 UTF-8 编码后存储,但读取时却用 GBK 解码,显示出来就是乱码。解决方法就是确保编码和解码保持一致,在 Go 项目中建议统一使用 UTF-8。
Q4:ASCII 和 UTF-8 有什么关系?
UTF-8 完全兼容 ASCII。也就是说,所有的 ASCII 字符在 UTF-8 中的编码方式完全相同,都只占 1 个字节。一个纯英文的 ASCII 文件,同时也是合法的 UTF-8 文件。
Q5:UCS-2 和 UTF-16 有什么区别?
UCS-2 是固定 2 字节编码,只能表示基本多语言平面(U+0000 ~ U+FFFF)的字符。UTF-16 在 UCS-2 基础上增加了代理对机制,可以表示 U+10000 以上的字符(如 Emoji),所以 UTF-16 是 UCS-2 的超集。
Q6:为什么网页开发推荐使用 UTF-8 而不是 UTF-16?
主要原因有三点:UTF-8 兼容 ASCII,处理纯英文内容时体积最小;UTF-8 是字节流友好的编码,不存在字节序问题;全球互联网已经形成了以 UTF-8 为标准的生态共识。
总结
总结核心要点:
- 计算机底层全是二进制。所有数据(文字、图片、视频、程序代码)最终都要转换成 0 和 1 才能被计算机处理。
- 进制之间的转换本质上是"满几进 1"的规则不同。日常开发中二进制、八进制、十进制、十六进制各有用武之地。
- 数据单位换算以 1024 为进率:8 b = 1 B,1024 B = 1 KB,1024 KB = 1 MB,依此类推。
- ASCII 编码用 1 个字节映射 128 个英文字符,是最早也是最简单的编码标准。
- Unicode 字符集为全世界每个字符分配了唯一编号(码位),解决了多语言共存的问题。
- UTF-8 是 Unicode 的变长编码实现,英文占 1 字节,中文占 3 字节,既兼容 ASCII 又节省空间,是当前互联网的主流编码。
- 在 Go 语言中,字符串底层存储的就是 UTF-8 字节序列,用
len()得到的是字节数,用[]rune转换后才能正确处理多字节字符。
掌握了进制和编码这些基础知识,后续学习 Go 语言的字符串操作、文件 I/O、网络编程等内容时,理解起来会顺畅很多。
如果你对进制转换或字符编码还有什么疑问,或者在实际开发中遇到了乱码相关的问题,欢迎在评论区留言交流,我们一起讨论解决~~~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/golang-binary-encoding-ascii-unicode-utf8/
备用原文链接: https://blog.fiveyoboy.com/articles/golang-binary-encoding-ascii-unicode-utf8/