本文翻译自golang官方 ,英文文章原地址 https://blog.golang.org/strings    ,主要介绍了 go中的 strings 、bytes、 runes 、characters。

Author: 岳东卫

Email: usher.yue@gmail.com

介绍

之前的文章介绍了go中的切片是如何工作的,我们使用了大量的例子来解释其背后实现的原理和机制. 在这个背景下, 我们在这篇文章讨论go中的字符串.首先 ,字符串对于一个博客文章的主题来说似乎比较简单, 但是为了更好的使用它们不仅需要理解它们是如何工作的, 还要知道他和字节、字符、rune之间的区别,UTF-8编码和Unicode编码之间的区别, 一个字符串和一个字符串字面量的区别, 以及更多细微的区别。

解决问题的一个方法就是将这个问题当成常见问题的答案: "当我用下标索引字符串的时候,为什么不能获取到对应的字符?" 正如你所看到的, 这个问题引导我们去了解更多细节有关于当今世界上的文字在go语言中是如何工作的。

Joel Spolsky的博客 ,有一些关于这些问题的很好的介绍,这些介绍独立于go语言, The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!). 他提出的很多观点我们可以在这里回应.

什么是字符串?

我们从一些基础开始。

在go中,字符串实际上是一个只读的片段,如果你完全不了解什么是一个字节切片,或者他的工作原理, 请阅读 切片 这篇文章; 我们假设你理解这些.

理解一个字符串包含任意字节是非常重要的.不需要保存unicode文本, UTF-8编码的文本, 或则其他任何预定义格式的文本. 就字符串的内容而言,他完全等同一个字节切片。

这里有一个字符串, 它使用 \xNN 符号 去定义一个包含特殊字节值的字符串常量。 (当然, 字节范围从十六进制的0x00到0xFF.)

    const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"

打印字符串

由于我们例子字符串中的一些字节不是有效的ASCII字符, 甚至没有有效的UTF-8码, 直接打印将会产生一些丑陋的输出. 简单的打印声明

    fmt.Println(sample)

产生这种混乱的输出 (准确的外观随着环境的变化而变化):

��=� ⌘

为了找出这个字符串真正的额含义, 我们需要将字符串分开并且检查每一个片段. 有几种方法可以做到这些. 最显然的是遍历字符串并且逐个提取单个字节, 就像下面的for循环:

    for i := 0; i < len(sample); i++ {fmt.Printf("%x ", sample[i])}

正如前面所述  ,  索引一个字符串访问的是单个字节而不是字符. 我们将在下面介绍这个主题,现在, 让我们坚持使用字符串。 下面是逐字节的循环输出:

bd b2 3d bc 20 e2 8c 98

注意 定义字符串时候各个字节是如何匹配十六进制转义.

一个简单的方式就是通过使用 fmt.Println的%X(十六进制)格式化动词可以将混乱的字符串生成可呈现的输出。 fmt.Printf. 他只是将字符串的顺序字节转储为十六进制, 每个字节由两个组成.

    fmt.Printf("%x\n", sample)

将其输出与上面输出进行比较:

bdb23dbc20e28c98

一个好的技巧是在格式化字符转中使用空格标志,放置一个空格在% 和 X之间. 对比此处的格式化字符串和上面所使用的,

    fmt.Printf("% x\n", sample)

并且注意输出字节的时候在他们之间是怎样伴随空格输出的。

bd b2 3d bc 20 e2 8c 98

还有更多 ,%q(引用)动词将转义字符串中任何不可打印的字节序列,因此输出是明确的。

    fmt.Printf("%q\n", sample)

当大多数的字符串可以理解为文本时,这种技术是很方便的,但有特殊性要根除;它产生:

"\xbd\xb2=\xbc ⌘"

如果我们斜眯着眼睛看这个字符串,我们可以看到,隐藏在乱码中的是一个ASCII等于符号,以及一个常规的空格,最后出现了着名的瑞典“兴趣点”的标志。 该符号具有Unicode值U + 2318,空格(十六进制值20)之后的UTF-8字节编码为:e2 8c 98。

如果我们对字符串中的奇怪值不熟悉或混淆, 我们可以给%q动词使用+标志. 该标志导致输出不仅可以转义不可打印的字符, 还可以转义任何非ASCII字节, 并且同时解释UTF-8.  结果是它暴露了在字符串中表示非ASCII数据的正确格式的UTF-8的Unicode值:

    fmt.Printf("%+q\n", sample)

使用这个格式,瑞典符号的unicode的值显示为\u 转义:

"\xbd\xb2=\xbc \u2318"

当调试字符串的内容的时候这些打印技术很容易被使用,而且在后续的讨论中会非常方便. 值得指出的是所有这些方法对于字节切片和字符串的行为完全一致.

下面是我们列出的全部打印选项, 作为可以在浏览器中运行 (和编辑)的完整程序呈现。

package mainimport "fmt"func main() {const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"fmt.Println("Println:")fmt.Println(sample)fmt.Println("Byte loop:")for i := 0; i < len(sample); i++ {fmt.Printf("%x ", sample[i])}fmt.Printf("\n")fmt.Println("Printf with %x:")fmt.Printf("%x\n", sample)fmt.Println("Printf with % x:")fmt.Printf("% x\n", sample)fmt.Println("Printf with %q:")fmt.Printf("%q\n", sample)fmt.Println("Printf with %+q:")fmt.Printf("%+q\n", sample)
}
Run

[练习: 修改上面的示例使用字节切片而不是一个字符串。 提示:可以通过类型转换来创建切片.]

[练习: 通过%q格式遍历每个字符串. 输出结果告诉你了什么?]

UTF-8和字符串文字

正如我们看到的 ,索引一个字符串索引到的是他的字节而不是字符,:一个字符串只是一堆字节. 这意味着我们在字符串中存储一个字符的时候, 我们是存储的他的字节表示. 让我们来看看更多地例子,看一下为什么会发生这样的事情。

这是一个简单的程序他通过三种不同的方式打印字符串, 一次是纯字符串, 一次是ASCII引用的字符串, 一次是逐个字节打印十六进制. 为了避免混淆,我们创建一个原始字符串, 用引号引起来, 因此他只能包含字面文本. (常规字符串, 用双引号引起来, 可以包含上面展示的转义序列.)

func main() {const placeOfInterest = `⌘`fmt.Printf("plain string: ")fmt.Printf("%s", placeOfInterest)fmt.Printf("\n")fmt.Printf("quoted string: ")fmt.Printf("%+q", placeOfInterest)fmt.Printf("\n")fmt.Printf("hex bytes: ")for i := 0; i < len(placeOfInterest); i++ {fmt.Printf("%x ", placeOfInterest[i])}fmt.Printf("\n")
}
Run

输出:

plain string: ⌘
quoted string: "\u2318"
hex bytes: e2 8c 98

这提醒我们,Unicode字符值U + 2318(“兴趣点”)⌘表示为字节e2 8c 98,这些字节是十六进制值2318的UTF-8编码。.

根据您对UTF-8的熟悉程度,这可能是显而易见的,或者可能是微妙的,请花费一点时间看一下如何创建字符串的UTF-8表示形式。 最简单的事实就是:它是在源代码写入时创建的。

go的源代码文件定义为UTF-8格式; 其他任何格式都不允许. 这意味在源代码中,我们编写we

`⌘`

用于创建程序的文本编辑器将符号⌘的UTF-8编码放入源文本. 当我们打印出十六进制字节时,我们只是将编辑器中的数据转储到文件中。

简单说,go语言的源代码是UTF-8所以go源代码中的字符串也是UTF-8. 如果该字符串文字不包含原始字符串不能的转义序列,则构造的字符串将准确地保留引号之间的源文本. 因此,通过定义和构造,原始字符串将始终包含其内容的有效的UTF-8表示. 类似地,除非它包含像上一节那样的UTF-8终止转义,否则常规字符串文字也将始终包含有效的UTF-8。.

一些人认为go的字符串一直是UTF-8类型,但是并不是这样,它仅仅是字符串字面量是这样. 就像我们在前面小节展示的一样,字符串可以包含任意字节; 正如我们在这里所示,字符串文字总是包含UTF-8文本,只要它们没有字节级转义。

总而言之,字符串可以包含任意字节,但是当从字符串字面量构造字符串时,这些字节(几乎总是)为UTF-8格式。.

Code points, characters, and runes

到目前为止我们使用字节和字符一直非常谨慎 .一部分是因为字符串保存的是字节, 另一部分就是字符的含义很难定义. Unicode标准使用术语“代码点”来表示由单个值表示的项.代码点U + 2318(十六进制值2318)表示符号⌘。 (有关该代码点的更多信息,请参阅其Unicode页面。)

选择一个更简单的例子,Unicode代码点U+0061表示的是小写拉丁字母“A”:a

但是,小写字母“A”,à? 这是一个字符,它也是一个代码点(U + 00E0),但它有其他表示。 例如,我们可以使用“组合”重音符号代码点U + 0300,并将其附加到小写字母a,U + 0061,以创建相同的字符à。 一般来说,字符可以由多个不同的代码点序列表示,因此UTF-8字节的序列不同。

因此,计算中字符的概念是模糊的,至少令人困惑,所以我们应该谨慎使用它。 为了使一切变得可靠,有一些规范技术可以保证指定的字符始终使用相同的代码点来表示,但是这个问题现在使我们离主题太远。 稍后的博文将解释Go库如何解决规范化问题。.

Go中代码点的术语是 rune. 该术语出现在库和源代码中,并且意味着与“代码点”完全相同,还有一个有趣的补充。

go语言中将rune定义为 int32的别名,因此当整数值表示代码点时,程序可以清除。此外,你可能会认为是一个字符常量在Go中称为rune常数。 表达式的类型和值是rune类型,整数值为0x2318。

总而言是,这里有几个要点:

  • Go源代码总是UTF-8.
  • 一个字符串保存任意字节.
  • 一个字符串字面量,没有字节级转义,始终保存有效的UTF-8序列。
  • T这些序列表示Unicode代码点,称为runes。
  • 在go中并不保证字符串四正常的.

Range loops

除了Go源代码是UTF-8的公开细节外,Go只有一种方法可以特别处理UTF-8,也就是在字符串上使用range循环。

我们已经看到常规for循环会发生什么. range 循环每次循环的时候解码UTF8编码的Rune. 每次循环的时候, 循环的索引是当前rune的起始字节位置, 以字节为单位, 并且代码点就是他的值。 这里的shili使用了另一个Printf格式%#U, 其中显示了代码点的Unicode值及其打印值

    const nihongo = "日本語"for index, runeValue := range nihongo {fmt.Printf("%#U starts at byte position %d\n", runeValue, index)}
Run

输出显示每个代码点如何占用多个字节:

U+65E5 '日' starts at byte position 0
U+672C '本' starts at byte position 3
U+8A9E '語' starts at byte position 6

[练习:将无效的UTF-8字节序列放入字符串。循环的迭代会发生什么?

Libraries

go语言标准库提供了对utf-8的强大的支持. 如果for循环不能满足您的需求,您可以使用选择golang库中相关的包。.

最重要的这个包是unicode / utf8,它包含帮助程序来验证,反汇编和重新组合UTF-8字符串。 这是一个等同于上面范围范例的程序,但是使用该包中的DecodeRuneInString函数来完成工作。 函数的返回UTF-8编码字节中的符文及其宽度。

    const nihongo = "日本語"for i, w := 0, 0; i < len(nihongo); i += w {runeValue, width := utf8.DecodeRuneInString(nihongo[i:])fmt.Printf("%#U starts at byte position %d\n", runeValue, i)w = width}
Run

运行它将看到相同的执行结果. 定义 for循环和DecodeRuneInString 产生相同的迭代序列。

看看unicode / utf8包的文档,看看它提供了什么其他的功能。

结论

要回答起始提出的问题:字符串是从字节构建的,因此索引它们产生字节,而不是字符。 一个字符串可能不会保存字符。实际上字符的定义是模糊的,通过字符来定义字符串,尝试解决歧义是不正确的。

还有更多关于UTF-8、多语言文本处理, 可以等到另一篇文章讨论. 现在,我们希望您更好地了解Go字符串的行为,尽管它们可能包含任意字节,但UTF-8是其设计的核心部分。.

By Rob Pike

Strings, bytes, runes and characters in Go相关推荐

  1. Go1.18 新特性:高效复制,strings, bytes 库新增 Clone 功能

    大家好,期盼已久的 Go1.18 上周已经发布,今天给大家带来一个 1.18 版本新特性中的优化相关的内容,是与 strings 和 bytes 标准库有关. 背景 想要更快捷复制 在日常编程中,字节 ...

  2. Strings and Runes

    字符串方法 字符串是否包含子字符串 fmt.Println("Contains:", strings.Contains("test", "es&quo ...

  3. Go 开发关键技术指南 | 敢问路在何方?(内含超全知识大图)

    作者 | 杨成立(忘篱) 阿里巴巴高级技术专家 Go 开发关键技术指南文章目录: 为什么你要选择 Go? Go 面向失败编程 带着服务器编程金刚经走进 2020 年 敢问路在何方? Go 开发指南大图 ...

  4. Golang的单引号、双引号与反引号用法

    看了目前国内网络上一些参考数榜首的几篇类似本标题的文章,觉得说得不够简明扼要直击本质,甚至有谬误.特此写下本文,以便参考. 单引号single quote: ' go语言中使用单引号给rune(gol ...

  5. C# dotnet 使用 OpenXml 解析 PPT 元素的坐标和宽度高度

    在阅读本文之前,我期望你能了解基础的 PPT 解析内容,或看我的入门级博客.本文将告诉大家如何从 PPT 里面解析出通用元素的 x 和 y 的值,以及元素的宽度和高度的值 在开始之前请看 C# dot ...

  6. 字符串编码(utf8)

    文章 Things about Unicode everyone needs to know golang: Strings, bytes, runes and characters in Go 编码 ...

  7. 官方教程:Go fuzzing模糊测试

    前言 Go 1.18在go工具链里引入了fuzzing模糊测试,可以帮助我们发现Go代码里的漏洞或者可能导致程序崩溃的输入.Go官方团队也在官网发布了fuzzing入门教程,帮助大家快速上手. 本人对 ...

  8. Go的简单入门:开始使用模糊测试

    开始使用模糊测试 文章目录 开始使用模糊测试 一.介绍 二.准备 三.实践 3.1 为你的代码创建一个目录 3.2 添加代码用于测试 写代码 运行代码 3.3 添加单元测试 写代码 运行代码 3.4 ...

  9. golang for语句完全指南

    golang for语句完全指南 Posted on January 13, 2018 以下所有观点都是个人愚见,有不同建议或补充的的欢迎emialaboutme 原文章地址 关于for语句的疑问 f ...

最新文章

  1. Centos6.5下docker 环境搭建
  2. RabbitMQ 高可用之如何确保消息成功消费
  3. Javascript绝句欣赏
  4. 团队章程---促进团队更合作和更高效
  5. POJ 2251 Dungeon Master(三维BFS求最短路径)
  6. 关于计算机软件系统分类能够匹配的有,以下关于计算机软件系统分类能够匹配的有:...
  7. 查看某个进程的线程在干什么_有了多线程,为什么还要有协程?
  8. python的书籍推荐_python 书籍推荐
  9. 【翻译】BCGControlBar Professional Edition for MFC v 29.0重大更新
  10. 操作SDO_GEOMETRY字段
  11. opencv二值化代码实现
  12. java 万年历_java实现万年历
  13. python实现seo疯狂外链发送工具
  14. 在不被限制的前提下,企业微信一天加多少好友(主动+被动)
  15. js实现开平方 Math.sqrt
  16. VBS 请求WebAPI接口_C#进阶系列——WebApi 路由机制剖析:你准备好了吗?
  17. 手写数字图片数据之python读取保存、二值化、灰度化图片+opencv处理图片的方法
  18. 微信头像存储mysql数据库
  19. JSP 【基础】连接数据库的登录及验证
  20. WIN Vista系统汇总!!![讯雷下载]

热门文章

  1. 在OTFS学习中的一些总结
  2. C语言_钩子函数(回调函数)
  3. nginx配置禁止访问目录或禁止访问目录下的文件
  4. 2022年前端面试集锦
  5. PDF怎么修改,如何删除PDF水印
  6. 2020面试准备之Java集合
  7. Unity游戏开发客户端面经——lua(初级)
  8. 智能家居新体验:“小摩凳”足部按摩器上架小米有品
  9. Skleran-线性模型-最小角回归(LARS)
  10. 万象优鲜生鲜配送系统源码 团队开发