Strings, bytes, runes and characters in Go

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

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

Author: 岳东卫

Email:

介绍

之前的文章介绍了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 main

import "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)
}

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

[练习: 通过%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")
}

输出:

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)
    }

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

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
    }

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

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

结论

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

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

By Rob Pike




目录
相关文章
|
4月前
|
Go
Go string bytes、strings、strconv和unicode包相关方法
Go string bytes、strings、strconv和unicode包相关方法
26 0
|
5月前
|
XML 编解码 数据格式
python报错 ‘utf-8‘ codec can‘t encode characters in position xxxx-xxxx: surrogates not allowed
python报错 ‘utf-8‘ codec can‘t encode characters in position xxxx-xxxx: surrogates not allowed
156 0
|
6月前
|
JSON 数据格式
JsonParseException: Unexpected character (‘ï‘ (code 239)): was expecting comma to separate Object
JsonParseException: Unexpected character (‘ï‘ (code 239)): was expecting comma to separate Object
53 0
|
8月前
|
API
JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Invalid escape sequence at line
JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Invalid escape sequence at line
84 0
成功解决ValueError: ‘usecols‘ must either be list-like of all strings, all unicode, all integers or a ca
成功解决ValueError: ‘usecols‘ must either be list-like of all strings, all unicode, all integers or a ca
|
JSON 数据格式
遇到【Unexpected character (‘“‘ (code 34)): was expecting comma to separate Object entries】的解决办法
遇到【Unexpected character (‘“‘ (code 34)): was expecting comma to separate Object entries】的解决办法
遇到【Unexpected character (‘“‘ (code 34)): was expecting comma to separate Object entries】的解决办法
TestRange.cs error CS0104: `Range' is an ambiguous reference between `System.Range' and Gtk.Range
TestRange.cs error CS0104: `Range' is an ambiguous reference between `System.Range' and Gtk.Range
136 0
Python typeError: a bytes-like object is required, not ‘str’ Solution
Python typeError: a bytes-like object is required, not ‘str’ Solution
|
IDE 开发工具
C - error: converting to execution character set:Illegal byte sequence
C - error: converting to execution character set:Illegal byte sequence
1052 0
C - error: converting to execution character set:Illegal byte sequence
Leetcode-Easy 806. Number of Lines To Write String
Leetcode-Easy 806. Number of Lines To Write String
62 0