Golang——rune和byte

本文详细介绍Golang中的两种字符类型rune和byte,介绍他们的区别,编码方式和简单的使用。

文章目录

      • [`byte` 类型](#byte 类型)
      • [`rune` 类型](#rune 类型)
      • [UTF-8 与 Unicode 的关系](#UTF-8 与 Unicode 的关系)
      • byte和rune的主要区别
      • Go的默认编码方式
      • 遍历方式
        • [遍历 `byte`](#遍历 byte)
        • [遍历 `rune`](#遍历 rune)
        • 补充
      • 字符还原
        • [从 `byte` 序列还原字符串](#从 byte 序列还原字符串)
        • [从 `rune` 序列还原字符串](#从 rune 序列还原字符串)

在Go语言中,runebyte都是表示单个字符的类型,但它们有一些关键的区别。

byte 类型

byteuint8 的别名,即一个 8 位无符号整数,表示一个字节,范围是 0 到 255。

  • byte 用于表示 UTF-8 编码中的 字节,适合处理字节流和 ASCII 字符。

字符占用字节数:

  • ASCII 字符(0-127)占用 1 字节。
  • 常见的字符,如拉丁字母、标点符号,占用 1 字节。
  • 中文等非 ASCII 字符会占用 3 字节。

byte 表示:字符串 "你",它在 Go 中的 UTF-8 编码是 0xE4, 0xBD, 0xA0(十六进制)。

go 复制代码
s := "你"
for i := 0; i < len(s); i++ {
    fmt.Printf("byte at index %d: %d\n", i, s[i])
}

输出:

复制代码
byte at index 0: 228
byte at index 1: 189
byte at index 2: 160

rune 类型

runeint32 的别名,即一个 32 位有符号整数,用于表示一个 Unicode 字符。Go中所有字符(包括 ASCII 和 Unicode 字符)都是以 rune 类型表示的,范围是 0 到 0x10FFFF。

  • rune 用于表示 Unicode 字符 ,它表示字符的 编码点,适合处理字符操作,尤其是涉及 Unicode 字符(如中文、表情符号等)。

rune 表示:

go 复制代码
s := "你"
for _, c := range s {
    fmt.Printf("rune: %c, rune value: %d\n", c, c)
}

输出:

复制代码
rune: 你, rune value: 20320

这表示 "你" 的 Unicode 编码点(20320,即 0x4F60)被 rune 类型存储。

UTF-8 与 Unicode 的关系

  • Unicode 是字符集 ,而 UTF-8 是 Unicode 字符集的编码方式之一。Unicode 定义了所有字符的编码点,但它并没有规定字符如何存储和传输。为了实现跨平台和跨语言的兼容,UTF-8 被定义为一种将 Unicode 编码点转换为字节序列的方式。除了 UTF-8 外,还有 UTF-16 和 UTF-32。
  • 联系
    • Unicode 为每个字符分配一个编码点(一个数字)。
    • UTF-8 通过不同长度的字节序列来编码这些 Unicode 编码点,使得它们可以被存储在文件中、传输通过网络、显示在屏幕上等。

byte和rune的主要区别

特性 byte rune
类型 uint8 (8-bit unsigned int) int32 (32-bit signed int)
用途 处理 ASCII 或字节数据 处理 Unicode 字符
表示范围 0 到 255 0 到 0x10FFFF
常见应用 字节流、ASCII字符 Unicode字符(包括多字节字符)
存储大小 1 字节 4 字节
字符集支持 仅支持 ASCII 字符 支持所有 Unicode 字符

Go的默认编码方式

Go 字符串默认的编码方式是 UTF-8 。所以默认使用 byte 序列来表示字符串中的每个字符。

具体来说,Go 中的字符串(string 类型)是由 UTF-8 编码的字节序列 组成的。因此:

  • 一个 Go 字符串是由多个字节(byte)组成的,每个字节都是 UTF-8 编码的一个字符。
  • 这些字节遵循 UTF-8 编码,Go 字符串既可以包含 ASCII 字符(这些字符在 UTF-8 中占用 1 个字节),也可以包含多字节的 Unicode 字符(如中文字符,这些字符在 UTF-8 中通常占用 3 个字节)。
go 复制代码
s := "a"
fmt.Print("占用字节数:", len(s))
fmt.Printf(";类型:%T  ", s[0])
fmt.Println()
s1 := "你"
fmt.Print("占用字节数:", len(s1))
fmt.Printf(";类型:%T  ", s1[0])

输出:

复制代码
占用字节数:1;类型:uint8  
占用字节数:3;类型:uint8  

遍历方式

遍历 byte

bytes := []byte(s)可以直接将字符串转为byte,当然也可以遍历:

  • 使用 for i := 0; i < len(s); i++,每次迭代都可以访问字符串中的每个字节。
  • len(s) 返回字符串的 字节数(byte) ,即字符串中包含的字节总数,而不是字符的数量。对于一个包含多字节字符(如中文字符)的字符串,len(s) 返回的是字符串所占用的字节数。
go 复制代码
package main

import "fmt"

func main() {
	s := "你" // 包含中文字符

	// 按字节遍历字符串
	fmt.Println("按字节遍历字符串:")
	for i := 0; i < len(s); i++ {
		fmt.Printf("s[%d] = %v (类型: %T)\n", i, s[i], s[i]) // 输出每个字节的值
	}
}

输出:

复制代码
按字节遍历字符串:
s[0] = 228 (类型: uint8)
s[1] = 189 (类型: uint8)
s[2] = 160 (类型: uint8)
遍历 rune

runes := []rune(s)可以直接将字符串转为rune,当然也可以遍历:

  • 使用 for _, c := range s 遍历字符串时,Go 会自动将字符串 s 中的每个字符解码成 rune 类型,这样即使字符是多字节的,也能正确处理。

  • range 遍历字符串时,按 字符(rune) 进行迭代。每次迭代返回一个 Unicode 码点(rune) 和该字符在字符串中的索引。对于多字节字符,range 会自动跳过这些字节,按字符来迭代。

go 复制代码
package main

import "fmt"

func main() {
	s := "你"

	// len(s) 返回字节数
	fmt.Println("len(s) =", len(s)) // 输出:3,因为"你"是由 3 个字节表示

	// 使用 range 遍历字符串,按字符(rune)遍历
	fmt.Println("使用 range 遍历字符串,按字符(rune)遍历:")
	for i, r := range s {
		fmt.Printf("i = %d, r = %v (类型: %T)\n", i, r, r)
	}
}

输出:

复制代码
len(s) = 3
使用 range 遍历字符串,按字符(rune)遍历:
i = 0, r = 20320 (类型: int32)
补充

for i := range s 的 s[i] 其实也是byte,但是处理中文时候会存在问题。

  • 当你使用for i := range s 处理英文字符串的时候,可能不会有问题,因为英文字符(ASCII 字符)在 UTF-8 编码中是单字节表示的,所以每个字符正好对应一个字节。

  • 但是如果字符串中包含非英文字符(如中文、表情符号等),它们通常会占用多个字节。在这种情况下。使用for i := range s 就会发现问题,range 会按照字符(rune)进行遍历,统计的数量是字符数(rune)【如下只有1个】,而不是字节数(byte)【一个中文,应该是对应3个字节】。

go 复制代码
package main

import "fmt"

func main() {
	s := "你" // 字符串包含中文字符

	// 使用 range 遍历字符串
	fmt.Println("使用 range 遍历字符串:")
	for i := range s {
		fmt.Printf("s[%d] = %v (类型: %T)\n", i, s[i], s[i]) // 打印每个字节的值
	}
}

输出:

复制代码
使用 range 遍历字符串:
s[0] = 228 (类型: uint8)

字符还原

要从 byte 序列或 rune 序列还原回原始字符串,你可以通过以下方式进行操作:

  • byte 序列还原字符串 :可以直接使用 string(byteSlice)
  • rune 序列还原字符串 :可以直接使用 string(runeSlice)
byte 序列还原字符串
go 复制代码
package main

import "fmt"

func main() {
	s := "你好" // 字符串 "你好"

	// 将字符串转换成 rune 切片
	bytes := []byte(s)

	fmt.Println("bytes:", bytes)
	// 将 rune 切片转换回字符串
	s1 := string(bytes)
	fmt.Println("还原的字符串:", s1)
}
bash 复制代码
bytes: [228 189 160 229 165 189]
还原的字符串: 你好
rune 序列还原字符串
go 复制代码
package main

import "fmt"

func main() {
	s := "你好" // 字符串 "你好"

	// 将字符串转换成 rune 切片
	runes := []rune(s)

	fmt.Println("runes编码:", runes)
	// 将 rune 切片转换回字符串
	s1 := string(runes)
	fmt.Println("还原的字符串:", s1)
}
bash 复制代码
runes编码: [20320 22909]
还原的字符串: 你好
相关推荐
用户68545375977692 小时前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo2 小时前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
LSTM973 小时前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack3 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo3 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊3 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说3 小时前
基于Spark的配置化离线反作弊系统
后端
Java编程爱好者4 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
后端
苏三说技术4 小时前
Spring AI 和 LangChain4j ,哪个更好?
后端