讲清楚Go字符串和utf8编码

unicode 字符集

unicode 字符集给世界上每个字符做了编码,每个字符对应一个码点(code pooint)。unicode字符集将所有的字符按平面划分,分为1个基础平面+16个补充平面,参考:unicode字符集

码点范围:

  • 基础平面:U+0000 - U+FFFF
  • 补充平面:U+10000 - U+10FFFF

注意:这里码点用16进制写法,每个16进制位等于4个二进制位,2个16进制位就等于1字节。

utf8

unicode码点不好被计算机直接存储,假如每个字符都按最长码点(U+10FFFF),用3字节存储,对基础平面的字符是极大的浪费,而我们日常用的最多的就是基础平面的字符。

出于这个原因,utf8被发明出来。utf8是一种编码方式,它规定每个unicode码点如何被编码。简单来说,它的规则很简单:

  1. 根据码点范围,决定需要的字节数
  2. 前缀+码点二进制

下面的表格具体展示了码点范围和对应的编码规则

分类 范围 Byte1 Byte2 Byte3 Byte4
基本平面 U+0000, U+007F 0xxxxxxx
U+0080, U+07FF 110xxxxx 10xxxxxx
U+0800, U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
补充平面 U+10000, U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

1个基本平面的字符被细分成了三个范围,16个补充片面算一个范围,具体来说:

  • U+0000,U+007F: 这之间的码点,用1个字节编码,前缀是0,剩下的7位就是码点的二进制位
  • U+0080,U+07FF:这之间的码点,用2个字节表示,第一个字节前缀是110,第二个字节前缀是10,一二字节剩余的位置就是码点二进制位原样写进来
  • U+0800, U+FFFFU+10000,U+10FFFF: 以此类推

举个例子:

中文"世"的码点是:4E16

码点写成二进制:0100,1110,0001,0110

码点落在第3区间,utf8编码为:11100100 ,10111000 ,10010110

utf8编码用16进制表示:E4B896


中文"界"的码点是:754C

码点写成二进制:0111,0101,0100,1100

码点落在第3区间,utf8编码为:11100111 ,10010101 ,10001100

utf8编码用16进制表示:E7958C

Go 字符串

Go 中的字符串就是byte slice,Go 对于string literal采用utf8编码,每个byte存的就是utf8编码的结果。

go 复制代码
word := "世界"
fmt.Printf("% x\n", word)  // output: e4 b8 96 e7 95 8c
fmt.Printf("% x\n", []byte(word)) // same output: e4 b8 96 e7 95 8c

要强调的是,string本身就是一个byte slice,没有任何特殊。只是 Go 在处理 string literal 时,默认将 utf8 编码存到这个byte slice中。除此以外,我们可以随意往里面插入字节,甚至是无法用utf8解析的字节。

go 复制代码
utf8Bytes := []byte{0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c}
fmt.Printf("%s\n", string(utf8Bytes)) // output: 世界

utf8Bytes = append(utf8Bytes, 0xff) // 插入一个不合法的utf8字节
fmt.Printf("%s\n", string(utf8Bytes)) // output: 世界�

在打印时,对于无法用 utf8 解析的字符,默认输出�,这叫replacement character。当渲染失败时,默认展示这个字符,它的码点是U+FFFD。

Go 字符

Go 中单个字符直接存储unicode码点,不经过utf8编码。一个很直观的例子

go 复制代码
s := "世"
c := '世'
fmt.Printf("%x\n", s) // output: E4B896
fmt.Printf("%x\n", c) // output: 4E16

双引号"世"被当做字符串,存储 utf8 编码,单引号'世'被当做单个字符,直接存储 unicode 码点。

Go 给 unicode 码点取了新名字: rune。从定义来看:type rune = int32runeint32完全等价。这也说明码点在Go中永远都是用4字节存储。

我们可以把 string 的每个utf8字符解码成码点(rune)输出。

go 复制代码
world := "世界"
fmt.Printf("%x\n", []rune(world)) // output: [4e16 754c]
相关推荐
红尘散仙2 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记4 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆4 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪4 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6165 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364575 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao5 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒6 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰7 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理
Apifox7 小时前
Apifox 5 月更新|Postman 导入优化、Runner 支持非 root 运行、请求代码自动带鉴权
前端·后端·安全