Go语言中的UTF-8实现


Posted in Golang onApril 26, 2021

计算机刚诞生的时候,计算机内的字符可以全部由 ASCII 来表示,ASCII 字符的长度是 7 位,可以表示 128 个字符,对于美国等国家来说是够了,但是对于世界上的其他国家,特别是东亚国家,文字不是由字母组成,汉字就有几万个,ASCII 码根本不够用。

字符本质就是对应计算机中的一个数值,既然不够用,那么解决方法就是把这个范围扩大,Unicode 的出现就解决了这个问题,它包括了世界上所有的字符,每一个字符都对应一个数值,这个数值被称之为 Unicode 码点。

但是 Unicode 也不是没有缺点,因为表示的范围大,所以每一个 Unicode 都需要 4 个字节来表示,但是对于原本的 ASCII 编码,本来只需要一个字节,现在也需要 4个字节,这样会浪费很多存储。

UTF-8 的出现解决了这个问题,它解决问题的思路是让每个字符选择自己的大小,需要多少字节就用多少。对于占不同字节的字符,有不同的表示格式:

  • 1 字节:0xxxxxxx
  • 2 字节:110xxxxx 10xxxxxx
  • 3 字节:1110xxxx 10xxxxxx 10xxxxxx
  • 4 字节:11110xxx 10 xxxxxx 10xxxxxx 10xxxxxx

通过识别每个字符串的头部来判断占几个字节。

每个 Unicode 字符都对应一个码点,在字符串中,可以对码点进行转义,使用 \uhhhh 表示 16 位码点,使用 \Uhhhhhhhh 来表示 32 位码点,每一个 h 都代表一个十六进制的数字。

这里有一点比较特殊,对于码点值小于 256 的文字符号可以使用单个十六进制的数字来表示,比如 'A' 可以使用 '\x41' 来表示,对于大于 256 的码点,就必须使用 \u 或者 \U 来转义。

Go 语言对于 UTF-8 的支持很好,这里有一点很有意思,Go 语言的两位作者 Ken Thompson 和 Rob Pike 同时也是 UTF-8 的发明者,Go 语言对 UTF-8 的支持赢在起跑线。

Go 语言总是使用 UTF-8 来处理源文件,同时也是优先使用 UTF-8 来处理字符串。所以上面说到的那些 Unicode 字符的转义被 Go 直接处理,比如下面三个字符串在 Go 语言中是等价的:

"世界"
"\u4e16\u754c"
"\U00004e16\U0000u754c"

Go 字符串使用只读的 []byte 来存储,所以字符串值是不变的,这样做更安全,效率也很高:

s := "left root"
t := s
s += ", right root"

fmt.Println(s) // left root, right root
fmt.Println(t) // left root

在上面的例子中, s 的值出现了变化,但是 t 的值还是旧的字符串。由于是 [] byte 是 slice 类型,所以字符串的截取操作效率很高,但是在字符串截取的过程中,就会出现一些坑。
Go 中的字符串底层使用了只读的 []byte 来存储,所以**本质上 Go 语言中的字符串是使用字节来表示,而不是字符表示,**理解这一点很重要。

str := "hello world"
fmt.Println(str[:2]) // he

str = "你好,世界"
fmt.Println(str[:2]) // ��,这个符号用来表示 UTF-8 里面的未知字符,码点是

非 ASCII 码的字符一般占用的字节会超过一个,如果直接截取,就会导致截取不到正确的位置,从而乱码。在上面的例子中,一个中文字符占 3 个字节,只有严格按照字节数来截取才能获取到显示正常的字符:

str = "你好,世界"
fmt.Println(str[:3]) // 你

那么在这个时候,如果要按照字符截取,就需要把字符串转成 []rune,每个 rune 都代表一个 UTF-8 中的码点,对 []rune 按照字符截取就不会出现乱码:

str = "你好,世界"
runeStr := []rune(str)
fmt.Println(string(runeStr[:1])) // 你

把字符串转成 []rune,就是把字符串转成 UTF-8 码点,而不是 []byte,rune 其实就是 int32 类型。

Go 语言中有一个专门 unicode/utf8 包来处理 utf8 字符。由于每个字符占据的字节可能不一样,所以字符数和字节数大小是两回事:

s := "Hello, 世界" // 逗号是半角符号
fmt.Println(len(s))                    // 13
fmt.Println(utf8.RuneCountInString(s)) // 9

如果要获取字符占据的总字节数,就使用 len 方法,如果需要计算字符的个数,那就需要使用 utf8.RuneCountInString 方法。
这个包里面还提供了其他常用函数:

// 判断是否符合 utf8 编码:
func Valid(p []byte) bool
func ValidRune(r rune) bool
func ValidString(s string) bool
// 判断 rune 所占的字节数
func RuneLen(r rune) int
// 判断字节串或者字符串中的 rune 字符数
func RuneCount(p []byte) int
func RuneCountInString(s string) int
// 对 rune 的编码和解码
func EncodeRune(p []byte, r rune) int
func DecodeRune(p []byte) (r rune, size int)
func DecodeRuneInString(s string) (r rune, size int)
func DecodeLastRune(p []byte) (r rune, size int)
func DecodeLastRuneInString(s string) (r rune, size int)

除了 utf8 包之外, unicode 包对提供了一系列 IsXX 函数来 rune 的检查:

func Is(rangeTab *RangeTable, r rune) bool // 是否是 RangeTable 类型的
func In(r rune, ranges ...*RangeTable) bool  // 是否是 ranges 中任意一个类型的字符
func IsControl(r rune) bool  // 是否是控制字符
func IsDigit(r rune) bool  // 是否是阿拉伯数字字符,即 0-9
func IsGraphic(r rune) bool // 是否是图形字符
func IsLetter(r rune) bool // 是否是字母
func IsLower(r rune) bool // 是否是小写字符
func IsMark(r rune) bool // 是否是符号字符
func IsNumber(r rune) bool // 是否是数字字符,包含罗马数字
func IsOneOf(ranges []*RangeTable, r rune) bool // 是否是 RangeTable 中的一个
func IsPrint(r rune) bool // 是否是可打印字符
func IsPunct(r rune) bool // 是否是标点符号
func IsSpace(r rune) bool // 是否是空格
func IsSymbol(r rune) bool // 是否符号字符
func IsTitle(r rune) bool // 字符串中的每个单词的第一个字符是否是大写
func IsUpper(r rune) bool // 是否是大写字符

RangeTable 是对所有 Unicode 字符的分类,比如验证一个字符是否是汉字:

r := '中'
result := unicode.Is(unicode.Han, r)
fmt.Println(result) // true

其中 unicode.Han 就是 RangeTable 类型,表示汉字。

到此这篇关于Go语言中的UTF-8实现的文章就介绍到这了,更多相关Go语言UTF-8内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Golang 相关文章推荐
一文读懂go中semaphore(信号量)源码
Apr 03 Golang
go语言map与string的相互转换的实现
Apr 07 Golang
解决Golang中ResponseWriter的一个坑
Apr 27 Golang
Go标准容器之Ring的使用说明
May 05 Golang
Go遍历struct,map,slice的实现
Jun 13 Golang
Go 语言结构实例分析
Jul 04 Golang
golang中的struct操作
Nov 11 Golang
Go语言实现一个简单的并发聊天室的项目实战
Mar 18 Golang
Golang 1.18 多模块Multi-Module工作区模式的新特性
Apr 11 Golang
Golang获取List列表元素的四种方式
Apr 20 Golang
Golang日志包的使用
Apr 20 Golang
Golang入门之计时器
May 04 Golang
golang中实现给gif、png、jpeg图片添加文字水印
Apr 26 #Golang
Go语言带缓冲的通道实现
Apr 26 #Golang
go语言求任意类型切片的长度操作
Apr 26 #Golang
golang如何去除多余空白字符(含制表符)
Apr 25 #Golang
用golang如何替换某个文件中的字符串
Apr 25 #Golang
Golang 正则匹配效率详解
golang正则之命名分组方式
Apr 25 #Golang
You might like
php4与php5的区别小结(配置异同)
2011/12/20 PHP
php字符串截取函数用法分析
2014/11/25 PHP
PHP封装返回Ajax字符串和JSON数组的方法
2017/02/17 PHP
让ie运行js时提示允许阻止内容运行的解决方法
2010/10/24 Javascript
用最通俗易懂的代码帮助新手理解javascript闭包 推荐
2012/03/01 Javascript
仿谷歌主页js动画效果实现代码
2013/07/14 Javascript
利用javascript实现禁用网页上所有文本框,下拉菜单,多行文本域
2013/12/14 Javascript
jQuery结合AJAX之在页面滚动时从服务器加载数据
2015/06/30 Javascript
jquery控制页面的展开和隐藏实现方法(推荐)
2016/10/15 Javascript
自定义事件解决重复请求BUG的问题
2017/07/11 Javascript
JS实现简单的点赞与踩功能示例
2018/12/05 Javascript
微信小程序map组件结合高德地图API实现wx.chooseLocation功能示例
2019/01/23 Javascript
微信小游戏之使用three.js 绘制一个旋转的三角形
2019/06/10 Javascript
jquery实现垂直无限轮播的方法分析
2019/07/16 jQuery
jquery validate 实现动态增加/删除验证规则操作示例
2019/10/28 jQuery
vue滑动吸顶及锚点定位的示例代码
2020/05/10 Javascript
微信小程序收藏功能的实现代码
2020/06/19 Javascript
[03:40]DOTA2英雄梦之声_第01期_炼金术士
2014/06/23 DOTA
在漏洞利用Python代码真的很爽
2007/08/26 Python
python使用xauth方式登录饭否网然后发消息
2014/04/11 Python
Python使用正则匹配实现抓图代码分享
2015/04/02 Python
python中urllib.unquote乱码的原因与解决方法
2017/04/24 Python
Django使用详解:ORM 的反向查找(related_name)
2018/05/30 Python
Python实现二维曲线拟合的方法
2018/12/29 Python
python 实现视频流下载保存MP4的方法
2019/01/09 Python
python读取与处理netcdf数据方式
2020/02/14 Python
解决Keras中Embedding层masking与Concatenate层不可调和的问题
2020/06/18 Python
scrapy-redis分布式爬虫的搭建过程(理论篇)
2020/09/29 Python
HTML5去掉输入框type为number时的上下箭头的实现方法
2020/01/03 HTML / CSS
比利时香水网上商店:NOTINO
2018/03/28 全球购物
2014元旦晚会策划方案
2014/02/19 职场文书
离婚协议书范本样本
2014/08/19 职场文书
医院领导班子查摆问题对照检查材料思想汇报
2014/10/08 职场文书
纪律委员竞选稿
2015/11/19 职场文书
高一英语教学反思
2016/03/03 职场文书
2016年党支部公开承诺书
2016/03/25 职场文书