目录

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
}

四个变量值相同,只是书写形式不同。理解这一点后,你在阅读源码时遇到 0x0b 开头的数字就不会陌生了。

数据存储单位:从 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 个字节

第二步:将码位填入模板

以汉字"武"为例,完整推导一遍:

  1. “武"的 Unicode 码位是 6B66,转为二进制:0110 1011 0110 0110
  2. 码位在 U+0800 ~ U+FFFF 范围,选第三个模板:1110XXXX 10XXXXXX 10XXXXXX
  3. 把二进制位从高到低依次填入模板中的 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 为标准的生态共识。

总结

总结核心要点:

  1. 计算机底层全是二进制。所有数据(文字、图片、视频、程序代码)最终都要转换成 0 和 1 才能被计算机处理。
  2. 进制之间的转换本质上是"满几进 1"的规则不同。日常开发中二进制、八进制、十进制、十六进制各有用武之地。
  3. 数据单位换算以 1024 为进率:8 b = 1 B,1024 B = 1 KB,1024 KB = 1 MB,依此类推。
  4. ASCII 编码用 1 个字节映射 128 个英文字符,是最早也是最简单的编码标准。
  5. Unicode 字符集为全世界每个字符分配了唯一编号(码位),解决了多语言共存的问题。
  6. UTF-8 是 Unicode 的变长编码实现,英文占 1 字节,中文占 3 字节,既兼容 ASCII 又节省空间,是当前互联网的主流编码。
  7. 在 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/