乐趣区

关于golang:有趣的面试题Go语言字符串的字节长度和字符个数

背景

哈喽,大家好,我是 asong。明天咱们一起来看看Go 语言中的 rune 数据类型,首先从一道面试题动手,你能很快说出上面这道题的答案吗?

func main()  {
    str := "Golang 梦工厂"
    fmt.Println(len(str))
    fmt.Println(len([]rune(str)))
}

运行后果是 1515还是 159呢?先思考一下,一会揭晓答案。

其实这并不是一道面试题,是我在日常开发中遇到的一个问题,过后场景是这样的:后端要对前端传来的字符串做字符校验,产品的需要是限度为 200 字符,而后我在后端做校验时间接应用 len(str) > 200 来做判断,后果呈现了 bug,前端字符校验没有超过200 字符,调用后端接口确始终是参数谬误,改成应用 len([]rune(str)) > 200 胜利解决了这个问题。具体起因咱们在文中揭晓。

Unicode和字符编码

在介绍 rune 类型之前,咱们还是要从一些基础知识开始。—— Unicode和字符编码。

  • 什么是Unicode

咱们都晓得计算机只能解决数字,如果想要解决文本须要转换为数字能力解决,早些时候,计算机在设计上采纳 8bit 作为一个 byte,一个byte 示意的最大整数就是 255,想示意更大的整数,就须要更多的byte。显然,一个字节示意中文,是不够的,至多须要两个字节,而且还不能和 ASCII 编码抵触,所以,我国制订了GB2312 编码,用来把中文编进去。然而世界上有很多语言,不同语言制订一个编码,就会不可避免地呈现抵触,所以 unicode 字符就是来解决这个痛点的。Unicode把所有语言都对立到一套编码里。总结来说:”unicode 其实就是对字符的一种编码方式,能够了解为一个字符 — 数字的映射机制,利用一个数字即可示意一个字符。

  • 什么是字符编码?

尽管 unicode 把所有语言对立到一套编码里了,然而他却没有规定字符对应的二进制码是如何存储。以汉字“汉”为例,它的 Unicode 码点是 0x6c49,对应的二进制数是 110110001001001,二进制数有 15 位,这也就阐明了它至多须要 2 个字节来示意。能够设想,在 Unicode 字典中往后的字符可能就须要 3 个字节或者 4 个字节,甚至更多字节来示意了。

这就导致了一些问题,计算机怎么晓得你这个 2 个字节示意的是一个字符,而不是别离示意两个字符呢?这里咱们可能会想到,那就取个最大的,如果 Unicode 中最大的字符用 4 字节就能够示意了,那么咱们就将所有的字符都用 4 个字节来示意,不够的就往前面补 0。这样的确能够解决编码问题,然而却造成了空间的极大节约,如果是一个英文文档,那文件大小就大出了 3 倍,这显然是无奈承受的。

于是,为了较好的解决 Unicode 的编码问题,UTF-8 UTF-16 两种以后比拟风行的编码方式诞生了。UTF-8 是目前互联网上应用最宽泛的一种 Unicode 编码方式,它的最大特点就是可变长。它能够应用 1 - 4 个字节示意一个字符,依据字符的不同变换长度。在 UTF-8 编码中,一个英文为一个字节,一个中文为三个字节。

Go语言中的字符串

基本概念

先来看一下官网对 string 的定义:

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

人工翻译:

string8 位字节的汇合,通常但不肯定代表 UTF-8 编码的文本。string能够为空,但不能为 nilstring 的值是不能扭转的

说得艰深一点,其实字符串实际上是只读的字节切片,对于字符串底层而言就是一个 byte 数组,不过这个数组是只读的,不容许批改。

写个例子验证一下:

func main()  {byte1 := []byte("Hl Asong!")
    byte1[1] = 'i'

    str1 := "Hl Asong!"
    str1[1] = 'i'
}

对于 byte 的操作是可行,而 string 操作会间接报错:

cannot assign to str1[1]

所以说 string 批改操作是不容许的,仅仅反对替换操作。

依据后面的剖析,咱们也能够得出咱们将字符存储在字符串中时,也就是按字节进行存储的,所以最初存储的其实是一个数值。

Go语言的字符串编码

下面咱们介绍了字符串的基本概念,接下来咱们看一下 Go 语言中的字符串编码是怎么的。

Go 源代码为 UTF-8 编码格局的,源代码中的字符串间接量是 UTF-8 文本。所以 Go 语言中字符串是 UTF-8 编码格局的。

Go语言字符串循环

Go语言中字符串能够应用 range 循环和下标循环。咱们写一个例子,看一下两种形式循环有什么区别:

func main()  {
    str := "Golang 梦工厂"
    for k,v := range str{fmt.Printf("v type: %T index,val: %v,%v \n",v,k,v)
    }
    for i:=0 ; i< len(str) ; i++{fmt.Printf("v type: %T index,val:%v,%v \n",str[i],i,str[i])
    }
}

运行后果:

v type: int32 index,val: 0,71 
v type: int32 index,val: 1,111 
v type: int32 index,val: 2,108 
v type: int32 index,val: 3,97 
v type: int32 index,val: 4,110 
v type: int32 index,val: 5,103 
v type: int32 index,val: 6,26790 
v type: int32 index,val: 9,24037 
v type: int32 index,val: 12,21378 
v type: uint8 index,val:0,71 
v type: uint8 index,val:1,111 
v type: uint8 index,val:2,108 
v type: uint8 index,val:3,97 
v type: uint8 index,val:4,110 
v type: uint8 index,val:5,103 
v type: uint8 index,val:6,230 
v type: uint8 index,val:7,162 
v type: uint8 index,val:8,166 
v type: uint8 index,val:9,229 
v type: uint8 index,val:10,183 
v type: uint8 index,val:11,165 
v type: uint8 index,val:12,229 
v type: uint8 index,val:13,142 
v type: uint8 index,val:14,130

依据运行后果咱们能够得出如下论断:

应用下标遍历获取的是 ASCII 字符,而应用 Range 遍历获取的是 Unicode 字符。

什么是 rune 数据类型

官网对 rune 的定义如下:

// 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

人工翻译:

runeint32 的别名,在所有方面都等同于int32,依照约定,它用于辨别字符值和整数值。

说的艰深一点就是 rune 一个值代表的就是一个 Unicode 字符,因为一个 Go 语言中字符串编码为 UTF-8,应用1-4 字节就能够示意一个字符,所以应用 int32 类型范畴就能够完满适配。

答案揭晓

后面说了这么多知识点,的确有点乱了,咱们当初就依据开始的那道题来做一个总结。为了不便查看,在贴一下这道题:

func main()  {
    str := "Golang 梦工厂"
    fmt.Println(len(str))
    fmt.Println(len([]rune(str)))
}

这道题的正确答案是 159

具体起因:

len()函数是用来获取字符串的字节长度,rune一个值代表的就是一个 Unicode 字符,所以求 rune 切片的长度就是字符个数。因为在 utf-8 编码中,英文占 1 个字节,中文占 3 个字节,所以最终后果就是 159

贴个图,不便了解:

unicode/utf8

如果大家对 rune 的应用不是很明确,能够学习应用一下 Go 规范库 unicode/utf8,其中提供了多种对于rune 的应用办法。比方下面这道题,咱们就能够应用 utf8.RuneCountInString 办法获取字符个数。更多库函数应用办法请自行解锁,本篇就不做过多介绍了。

总结

针对全文,咱们做一个总结:

  • Go 语言源代码始终为UTF-8
  • Go语言的字符串能够蕴含任意字节,字符底层是一个只读的 byte 数组。
  • Go语言中字符串能够进行循环,应用下表循环获取的 acsii 字符,应用 range 循环获取的 unicode 字符。
  • Go语言中提供了 rune 类型用来辨别字符值和整数值,一个值代表的就是一个 Unicode 字符。
  • Go语言中获取字符串的字节长度应用 len() 函数,获取字符串的字符个数应用 utf8.RuneCountInString 函数或者转换为 rune 切片求其长度,这两种办法都能够达到预期后果。

好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者继续创作更多优质内容的能源!

创立了一个 Golang 学习交换群,欢送各位大佬们踊跃入群,咱们一起学习交换。入群形式:关注公众号获取。更多学习材料请到公众号支付。

我是 asong,一名普普通通的程序猿,让咱们一起缓缓变强吧。欢送各位的关注,咱们下期见~~~

举荐往期文章:

  • Go 看源码必会常识之 unsafe 包
  • 源码分析 panic 与 recover,看不懂你打我好了!
  • 空构造体引发的大型打脸现场
  • Leaf—Segment 分布式 ID 生成零碎(Golang 实现版本)
  • 面试官:两个 nil 比拟后果是什么?
  • 面试官:你能用 Go 写段代码判断以后零碎的存储形式吗?
  • 如何平滑切换线上 Elasticsearch 索引
退出移动版