一个go语言编码的例子

本文涉及的产品
可观测可视化 Grafana 版,10个用户账号 1个月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【7月更文挑战第2天】本文介绍Go语言使用Unicode字符集和UTF-8编码。Go中,`unicode/utf8`包处理编码转换,如`EncodeRune`和`DecodeRune`。`golang.org/x/text`库支持更多编码转换,如GBK到UTF-8。编码规则覆盖7位至21位的不同长度码点。

1 go语言的字符集和编码方案

Go 语言源码默认使用unicode字符集,并采用UTF8编码方案。 Go还提供了rune原生类型表示unicode字符。
在大多数场景,并不需要深入了解字符集和字符编码方案。

但是在涉及不同字符集的转换或同一字符集不同编码方案之间的转换时,
了解字符原理和字符编码方案就显得非常必要了。 这里介绍 Go的Unicode字符表示以及如何使用Go进行字符编码方案转换。

string类型在golang中以utf-8的编码形式存在,而string的底层存储结构,划分到字节即byte,划分到字符即rune。本文将会介绍字符编码的一些基础概念,详细讲述三者之间的关系,并提供部分字符串相关的操作实践。

    byte是uint8类型的别名,通常用于表示一个字节(8bit)。

    rune是int32类型的别名,通常用于表示一个字符(32bit)。

    string是8bit字节的集合,通常是表示UTF-8编码的字符串。

从官方概念来看,string表示的是byte的集合,即八位的一个字节的集合,通常情况下使用UTF-8的编码方式,但不绝对。

2 rune字符

而rune表示用四个字节组成的一个字符,rune值为字符的Unicode编码。

string类型在golang中以utf-8的编码形式存在,而string的底层存储结构,划分到字节即byte,划分到字符即rune。

unicode 为 ASCII 的扩展集,是字符与二进制的一一对应

unicode/utf8
encode rune 支持将 rune 函数接受一个rune值,通过UTF-8的编码规则。
将其转化为[]byte并写入p,同时返回写入的字节数。

对于字符串来说,如果使用Unicode进行存储,则每个字符使用的存储长度是不固定的,而且是无法进行精确分割的。

如中文字符“南”使用的Unicode编码为0x5357,对于该编码可以整体理解为一个字符“南”,也可以理解为0x53(S)和0x57(W)。

因而单纯使用Unicode是无法进行字符串编码的,因为计算机无法去识别要在几个字节处做分割,哪几个字节要组成一个字符。

所以需要一种Unicode之上,存在部分冗余位的编码方式,以准确表示单个字符,并在多个字符进行组合的时候,能够正确进行分割,即UTF-8。

UTF-8是针对Unicode的一种可变长度字符编码,它可以用来表示Unicode标准中的任何字符。

因而UTF-8是Unicode字符编码的一种实现方式,Unicode强调单个字符的一一对应关系,UTF-8是Unicode的组合实现方式,此外还有UTF-16,UTF-32等类似编码,普适性较UTF-8稍弱。

不同国家,地区,甚至行业使用的字符和字符总数将有所不同。

当时美国人只用到128个字符,预留128个备用。 要表示256个字符,使用8个比特就可以了。
这就是ASCII码 美国标准信息交换代码,American Standard Code for Information Interchange

这8比特恰好等于计算机的基本存储数据单元 --- 字节的比特数相同,这样一个字节就恰好表示ASCII字符集的一个字符。

计算机字符集的每隔字符都有两个属性: 码点 code point 和表示这个码点的内存编码 位模式,表示这个字符码点的二进制比特串。

所谓码点 --- 这里使用了Unicode字符集的码点概念 --- 是指将字符集全部字符排成一队,字符在队伍中的唯一序号值。
以ASCII字符集的字符为例:

      码点               字符                     含义                      内存编码表示,位模式,二进制
      0                 不可见                   空字符                      0000 0000
      ...
      65                A                        字符A                       01000001
      ...
      127               不可见                    DEL                       1111 1111

我们看到ASCII字符集的每个字符码点和内存编码表示一致。例如ASCII字符A在ASCII码表的码点序号 为65,内存编码值也为 0100 0001

对于象形文字,使用ASCII字符集就不够了,因此亚洲国家字符串为 GB18030 BIG5 SHIFT_JIS 等。
字符集多了,容易造成混乱,乱码问题就出现了,比如 地球 二字,在网站存储和传输时使用 GB18030字符集编码,位模式 0xB5D8,0xC7F2
而在使用BIG5的浏览器中加载 0xB5D8,0xC7F2 分别表示 崋,⑩

乱码主要原因是字符集不兼容,一个内存编码表示在不同字符集中表示不同字符。

比如从 0 到 127 的代码是 ASCII,它们在两个代码页中是相同的。
128 及以上的一些代码是不同的。
如果不知道正在使用的代码页,就无法理解 128 及以上的代码。 亚洲语言有超过 1000+ 个字母,无法放入单个字节。因此,DBCS 使用了双字节字符集。在这种情况下,一些字母使用 1 个字节,而另一些字母使用 2 个字节。为了简化所有这些复杂性,我们需要一个包含所有书写系统的字符集。Unicode联盟就这样诞生了。

在 Unicode 中,字母映射到称为 Code Point 的东西。例如,A 映射到 U+0041(数字为十六进制),即十进制 65。事实上,前 128 个 ASCII 字符在 Unicode 中保留了相同的码位。一些最大的优点是

3 编码转换方案

日常编码中,我们经常涉及在不同字符集的字符编码方案间进行转换,以满足字符在不同字符编码环境的解析,处理,展示,存储的需求。

这里以UTF8编码为例,使用Go实现这两个字符编码环境下字符编码的转换。

image.png

GB18030 全称是信息技术中文编码字符集。

是国家标准所规定的变长多字节字符集,是计算计系统必须遵守的基础标准之一。

该字符集采用变长多字节编码,每个字符可以由1,2,4字节编码表示,因此编码空间庞大,可以定义160多万个字符。

Go语言默认源码文件字符采用UTF8编码方案的Unicode字符。

在Go中,每一个rune对应一个Unicode字符码点,而Unicode字符在内存编码表示则放在 []byte 类型中。

从rune类型转换为 []byte 类型称为 编码。 encode,而反过来称之为 decode 解码。

    unicode码点                               码点在内存编码表示 位模式
    rune           ------编码----->           []byte
                   <----解码-----

一般概念中,字符串包含字节,码点表示单个Unicode值表示的个体。 具有16进制值。

在go语言中,码点就是rune,其类型为int32的别名。

Go 源代码始终为utf8

字符串可以包含任意字节

字符集文字不包括字节级转义符时,字符串始终包含有效utf8序列

代码Unicode码点的字节序被称为rune

在go语言不会保证字符串的字符被规范化。

4 在go中转换编码的例子

每个rune对应一个unicode 码点。 我们通过标准库提供的unicode/utf8 对rune进行解码操作,如下:

    func encodeRune() {
        var r rune = 0x4E2D  //在unicode编码的码点0x4E2D 表示 中
        buf := make([]byte, 3)
        n := utf8.EncodeRune(buf, r)

        fmt.Printf("the byte slice after encoding rune 0x4E2D is:\n")
        fmt.Printf("[")
        for i := 0; i < n; i++{
            fmt.Printf("0x%X", buf[i])
        }
        fmt.Printf("]\n")
        fmt.Printf("the unicode charactor is %s\n", string(buf)) 
    }

    //[]byte -> rune
    func decodeRune() {
        var buf = []byte{0xE4, 0xB8, 0xAD}
        r, _ := utf8.DecodeRune(buf)
        fmt.Printf("the rune after decoding [0xE4,0xB8, 0xAD] is 0x%X \n", r )
    }  

    fund main() {
        encodeRune()
        decodeRune()
    }

运行示例:

    go run .\main.go
    the byte slice after encoding rune 0x4E2D is:
    [0xE40xB80xAD]
    the unicode charactor is 中
    the rune after decoding [0xE4,0xB8, 0xAD] is 0x4E2D

我们再通过打印字符字面量底层的内存空间内容验证示例输出结果的正确性:

    func BaseCache() {
        var s = "中"
        fmt.Printf("Unicode 字符:%s => 其 UTF-8 内存编码表示为:", s)
        for _, v := range []byte(s) {
            fmt.Printf("0x%X ", v)
        }
        fmt.Printf("\n")
    }

输出结果:

    nicode 字符:中 => 其 UTF-8 内存编码表示为:0xE4 0xB8 0xAD

可以发现,unicode字符的中 底层内存空间内容与其UTF8编码后的切片内容是一样的。

5 更多一点的

将UTF8编码环境的中文转换为 GB18030编码环境的编码表示 位模式,

并验证转换后的结果在GB18030能否被正确解析和呈现。

将 中 字符从UTF8表示,转换为GB18030编码

    unicode码点                   utf8 内存编码表示
    U+4E2D    中    =====>       E4 B8 AD
        |                            |
        |                             |
    GB18030码点表              GB18030内存表示
    D6D0      中    =====>     D6 D0  中

Go标准库没有直接提供简体中文编码与UTF8编码之间的转换实现,

但是Go标准库依赖的 golang.org/x/text模块提供了相关转换实现。

 golang.org/x/text 同样是Go核心团队维护的工具包。 

我们可以将该模块的包作为标准库,只是Go1兼容性不保证这些包对外提供的API稳定性。

代码如下:

    package main
    ...

真正执行UTF8到GB18030编码形式转换的是 simplifiedchinese.GB18030.NewEncode方法,它读取以UTF8编码表示形式存在的字节流[]byte

并将其转换为以GB18030编码表示的字节流返回。

如果Macos输出为乱码,需要在系统环境变量全部设置为 GB18030

    locale
    LANG= "zh_CH.GB18030"
    LC_COLLATE="zh_CH.GB18030"
    ...

这样可以支持中文的内存编码输出到系统控制台。

使用Go标准库及其依赖库 golang.org/x/text 的包,

我们不仅可以实现Go默认字符编码UTF-8与其他字符集编码的相互转换,
还可以实现任意字符集编码之间的相互转换。

也可以将GB18030编码数据转换为 UTF-16和UTF-32。

其转换逻辑如下

                中
    数据源    0xD6 0xD0                                         0x4E 0x2D  转换结果 中
    gb18030                                                     

            |                                                    |
            |                                                    |
 reader chain bytes Reader  --->  transform.Reader   ---->   transform.Reader

                                      |                        rune -> []byte
                                  []byte -> rune
                                  Go 以 utf8编码表示码点    

我们使用golang.org/x/text 的 transform.Reader 链实现任意字符编码间的转换。

从上图可以看出,我们使用了一个惯用的Reader链结构完成了数据从gb18030编码到UTF-16和UTF-32的转换。

第一个 transform.Reader 在GB18030编码的源数据 []byte 转换为 rune,即unicode码点,

并以Go默认的UTF-8编码格式保存在内存中。

第二个 transform.Reader 在UTF16.Encoder帮助下,将rune再编码转换为最终数据。

  • 编码规则

      • ASCII字符(不包含扩展128+)0000 0000-0000 007F (0~7bit)
    
      • 0xxxxxxx
    
      • 0000 0080-0000 07FF (8~11bit)
    
      • 110xxxxx 10xxxxxx
    
      • 0000 0800-0000 FFFF (12~16bit)
    
      • 1110xxxx 10xxxxxx 10xxxxxx
    
      • 0001 0000-0010 FFFF (17~21bit)
    
      • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    

    5 小结

rune代表Unicode字符,等价于int32
字符串底层是字节byteuint8别名)集合,以UTF-8存储。
Unicode码点是字符的唯一标识,
UTF-8是一种变长编码,确保不同字符集兼容。
ASCII是最早的7位字符集,最多表示128个内容。
Unicode扩展了ASCII。

目录
相关文章
|
10天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
20 2
|
2天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
1天前
|
Go 索引
go语言按字符(Rune)遍历
go语言按字符(Rune)遍历
11 3
|
5天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
22 4
|
5天前
|
缓存 监控 前端开发
在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统
本文深入探讨了在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统。
35 1
|
8天前
|
Go
go语言中的continue 语句
go语言中的continue 语句
19 3
|
9天前
|
安全 Go 调度
探索Go语言的并发模型:goroutine与channel
在这个快节奏的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你深入了解Go语言的goroutine和channel,这两个核心特性如何协同工作,以实现高效、简洁的并发编程。
|
10天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
20 4
|
10天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
48 1
|
3天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
13 0
下一篇
无影云桌面