一个go语言编码的例子

本文涉及的产品
可观测可视化 Grafana 版,10个用户账号 1个月
可观测监控 Prometheus 版,每月50GB免费额度
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
简介: 【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。

目录
相关文章
|
7月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
7月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
1月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
1月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
2月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
2月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
242 0
|
3月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
4月前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
111 10
|
7月前
|
存储 缓存 监控
企业监控软件中 Go 语言哈希表算法的应用研究与分析
在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
120 3
|
7月前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。