Strings, bytes, runes and characters in Go

本文翻译自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 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)
}

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

时间: 2024-10-03 12:20:50

Strings, bytes, runes and characters in Go的相关文章

算法题之UVA 11081 Strings(dp)

string by combining two subsequences from the first two strings. After deleting 0 or more characters from a string we can get its subsequence. For example "a", "b", "c", "ab", "ac", "bc" and &quo

使用rpclib进行Python网络编程时的注释问题

       这篇文章主要介绍了使用rpclib进行Python网络编程时的注释问题,作者讲到了自己在编写服务器时要用unicode注释等需要注意的地方,需要的朋友可以参考下            rpclib 是一个非常好用的 python webservice 库,可以动态的生成 wsdl, 不过这个项目已经基本停止,并被一个新的项目取代 spyne,由于旧的项目 工作已经比较稳定,所以我没有贸然升级到 spyne.         我在 rpclib 编写 service 方法时,遇到一个

深入Go语言文本类型

Go的作者Ken Thompson是UTF-8的发明人(也是C,Unix,Plan9等的创始人),因此在关于字符编码上,Go有着独到而周全的设计.本文介绍了Go语言中的三种内置文本类型:string, byte,rune的内部表示与相互转换. 1. 概览 Go中,字符串string是内置类型,与文本处理相关的内置类型还有符文rune和字节byte. UTF-8编码在Go语言中有着特殊的位置,无论是源代码的文本编码,还是字符串的内部编码都是UTF-8.Go绕开前辈语言们踩过的坑,使用了UTF8作为

iOS 中正则表达式使用方法汇总

iOS 中正则表达式使用方法汇总 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 某种语言中的正则工具算是木桶,而这个工具处理的是正则表达式,算是水,那么水很多,无论是淡水还是咸水,或是雨水,至

W3C 用于在 web 应用中处理文件的 API(W3C File API for representing file objects in web applications)

File API W3C Last Call Working Draft 12 September 2013 This Version: http://www.w3.org/TR/2013/WD-FileAPI-20130912/ Latest Published Version: http://www.w3.org/TR/FileAPI/ Latest Editor's Draft: http://dev.w3.org/2006/webapi/FileAPI/ Previous Version

C++字符串完全指引之一 —— Win32 字符编码

C++字符串完全指引之一 -- Win32 字符编码 原著:Michael Dunn 翻译:Chengjie Sun 原文出处:CodeProject:The Complete Guide to C++ Strings, Part I  引言 毫无疑问,我们都看到过像 TCHAR, std::string, BSTR 等各种各样的字符串类型,还有那些以 _tcs 开头的奇怪的宏.你也许正在盯着显示器发愁.本指引将总结引进各种字符类型的目的,展示一些简单的用法,并告诉您在必要时,如何实现各种字符串

【OH】Glossary Oracle词汇表(上)

Glossary [OH]Glossary Oracle词汇表(上) Oracle? Multimedia DICOM Developer's Guide 11g Release 2 (11.2) E10778-03 Glossary ● anonymity document An XML document that specifies the set of attributes to be made anonymous, and defines the actions required to

RS-232, RS-422, RS-485 Serial Communication General Concepts(转载)

前面转载的几篇文章重点介绍了UART及RS-232.在工控领域除了RS-232以外,常用的串行通信还有RS-485.本文转载的文章重点介绍了RS-232.RS-422和RS-485. Overview This article explains the general concepts of the serial communication protocols RS-232, RS-422, and RS-485, including basic concepts like baud rate,

Java Thread Programming 1.8.4 - Inter-thread Communication

Streaming Data Between Threads Using Pipes The java.io package provides many classes for writing and reading data to and from streams. Most of the time, the data is written to or read from a file or network connection. Instead of streaming data to a