前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Golang】深究字符串——从byte rune string到Unicode与UTF-8

【Golang】深究字符串——从byte rune string到Unicode与UTF-8

作者头像
DDGarfield
发布2022-06-23 19:27:45
2.4K0
发布2022-06-23 19:27:45
举报
文章被收录于专栏:加菲的博客

Go语言使用UTF-8编码,因此任何字符都可以用Unicode表示。为此,Go在代码中引入了一个新术语,称为 rune。rune是int32的类型别名:

代码语言:javascript
复制
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

另外,字符串经常被转换为[]byte使用,要详细说清楚runebyte、字符串之间的关系,必须得从人和宇宙的关系说起,呸!是必须得从字符编码说起。

1. ASCII码

通过数字电路的知识,我们知道使用二进制对信息进行编码与度量。最初现代计算机由美国人发明使用,自然而然就考虑把英语进行编码,所以ASCII码就是英语字符对应的二进制位,而且一直沿用至今,ASCII码占用1个字节,最高位统一规定为0,所以只使用了7位,一共可以表示27=128个字符,包括32个不能打印的字符。

2.Unicode

现代计算机早已不是美国一家独大,互联网更是让世界互联互通。但是文字确实多种多样,各个国家拥有一套编码规则,同一个二进制数会被不同编码解释为不同符号。如果每次不把编码方式勾兑清楚,谁也不知道该怎么解码。有没有不需要勾兑的方式?有,就是抛开各个国家独有的编码方式,统一使用一个编码方式Unicode

3.UTF-8

Unicode规定了字符的二进制代码,但是却没有规定如何存储。而且,各个字符占的字节是可能不同的,比如汉字很多都有10几位二进制,可能需要2个字节,3个字节,甚至4个字节。虽有unicode对应,肯定是该多少字节就存多少字节,而不是每个字符都存相同大小字节,毕竟unicode有100多万,全存相同大小字节,肯定浪费空间。但是就有了最终要解决的问题:什么时候该读3个字节以表示1个字符,什么时候该读1个字节以表示字符?

UTF-8就是存储Unicode的方式,但不是唯一的,其他utf-16,utf-32交给童鞋们自己探索,我们主要深究一下utf-8。来看下UTF-8是如何解决上面的问题:

什么时候读1个字节的字符?

字节的第一位为0,后面7位为符号的unicode码。所以这样看,英语字母的utf-8和ascii一致。

什么时候读多个字节的字符?

对于有n个字节的字符,(n>1)....其中第一个字节的高n位就为1,换句话说:

  • 第一个字节读到0,那就是读1个字节
  • 第一个字节读到n个1,就要读n个字节

然后第一个字高n位后1位设为0,后续其他字节前两位都设为10

代码语言:javascript
复制
0xxxxxxx # 读1个字节
110xxxxx 10xxxxxx # 读两个字节
1110xxxx 10xxxxxx 10xxxxxx #读3个字节
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx #读4个字节

Unicode符号范围     |        UTF-8编码方式
(十六进制)        |              (二进制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

怎样完成UTF-8最终编码?

解决了读几个字节的问题,还有一个问题:Unicode怎么填充UTF-8的各个字节?

比如 字,unicode编码5F20,对应的十六进制处于0000 0800-0000 FFFF中,也就是3个字节。

  • 1110xxxx 10xxxxxx 10xxxxxx
  • 的unicode对应的二进制:101 111100 100000
  • 从后向前填充,高位不够的补0
    • 010000 填充至第三个字节 10xxxxxx → 10100000
    • 111100 填充至第二个字节 10xxxxxx → 10111100
    • 101 填充至第一个字节 1110xxxx → 1110x101
    • 高位补0 1110x101 → 11100101
    • 最终结果:11100101 10111100 10100000 16进制 E5BCA0

4.go语言的字符串

字符串是Go 语言中最常用的基础数据类型之一,实际上字符串是一块连续的内存空间,一个由字符组成的数组,既然作为数组来说,它会占用一片连续的内存空间,这片连续的内存空间就存储了多个字节整个字节数组组成了字符串。

5.rune与byte的使用

Ascii码字符

代码语言:javascript
复制
package main

import (
 "fmt"
 "unsafe"
)

func main() {
 s := 'a'       //rune
 fmt.Println(s) // 97
 t := unsafe.Sizeof(s) 
 fmt.Println(t) // 4
}

a是Ascii码字符,单引号' '包裹的字符,go语言会将其视为rune类型,rune类型为int32,所以占4个字节。

全为Ascii码的字符串

代码语言:javascript
复制
package main

import (
 "fmt"
 "unsafe"
)

func main() {
 b := "golang"
 fmt.Println(b)
 s_rune := []rune(b)
 s_byte := []byte(b)
 fmt.Println(s_byte) // [103 111 76 97 110 103]
 fmt.Println(s_rune) // [103 111 76 97 110 103]
}
  • []rune()将字符串转换为rune切片
  • []byte()将字符串转换为byte切片
  • 由于都是Ascii码字符串,所以输出的整数都一致

包含非ascii码的字符串

代码语言:javascript
复制
package main

import (
 "fmt"
 "unicode/utf8"
 "unsafe"
)

func main() {
 c := "go语言"
 s_rune_c := []rune(c)
 s_byte_c := []byte(c)
 fmt.Println(s_rune_c) // [103 111 35821 35328]  
 fmt.Println(s_byte_c) // [103 111 232 175 173 232 168 128]
 fmt.Println(utf8.RuneCountInString(c))  //4
  fmt.Println(len(c))        //8
 fmt.Println(len(s_rune_c))     //4
}
  • 汉字占3个字节,所以转换的[]byte长度为8
  • 由于已经转换为[]rune,所以长度为4
  • utf8.RuneCountInString()获取UTF-8编码字符串的长度,所以跟[]rune一致

6.汉字的输出详解

代码语言:javascript
复制
package main

import (
 "fmt"
 "unsafe"
)

func main() {
 f := "张"
 s_byte_f := []byte(f)
 s_rune_f := []rune(f)
 t := unsafe.Sizeof(s_byte_f) 
 fmt.Println(s_byte_f) // [299 188 160]
 t = unsafe.Sizeof(s_rune_f) 
 fmt.Println(s_rune_f) // [24352]
  e := '张'
 s_byte_e := byte(e)
  t = unsafe.Sizeof(s_byte_e) 
 fmt.Println(t) // 1
  fmt.Println(s_byte_e) // 张32?
}

24352?[299 188 160] ? 32???

  • 输出的值24352是unicode
    • 十六进制 5F20
    • 十进制 24352
    • 二进制101111100100000
  • 存储方式是utf-8
    • uft-8编码:11100101 10111100 10100000
    • 11100101 - 299
    • 10111100 - 188
    • 10100000 - 160
  • 这就解释了为什么转换后的[]byte是[299 188 160]

在go语言中,byte其实是uint8的别名,byteuint8 之间可以直接进行互转,只能将0~255范围的int转成byte。超出这个范围,go在转换的时候,就会把多出来数据砍掉;但是rune转byte,又有些不同:会先把rune从UTF-8转换为Unicode,由于Unicode依然超出了byte表示范围,所以取低8位,其余的全部扔掉 101111100100000,就能解释为什么是输出32(这里有专门的汉字对应表,可以用其他做验证。)

7.总结

  • Go 语言中的字符串是一个只读的字节切片
  • 声明的任何单个字符,go语言都会视其为rune类型
  • []rune()可以把字符串转换为一个rune数组(即unicode数组)
    • 一个rune就表示一个Unicode字符
    • 每个Unicode字符,在内存中是以utf-8的形式存储
    • Unicode字符,输出[]rune,会把每个UTF-8转换为Unicode后再输出
  • []byte()可以把字符串转换为一个byte数组
    • Unicode字符,按[]byte输出,就会把UTF-8的每个字节单个输出
    • 输出[]byte,会按字符串在内存中实际存储形式(UTF-8)输出
  • 而Unicode字符做强制转换时,会优先计算出Unicode值,再做转换
  • 对于Ascii码字符,runebyte值是一样的
    • 这是因为Ascii码字符的Unicode也只需要1个字节,且一致
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 加菲的博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. ASCII码
  • 2.Unicode
  • 3.UTF-8
    • 什么时候读1个字节的字符?
      • 什么时候读多个字节的字符?
        • 怎样完成UTF-8最终编码?
        • 4.go语言的字符串
        • 5.rune与byte的使用
          • Ascii码字符
            • 全为Ascii码的字符串
              • 包含非ascii码的字符串
              • 6.汉字的输出详解
              • 7.总结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档