Go string

  • 字符串表示不可改变的字节序列
  • 字符串虽然可以包含任意数据,但通常指用来包含人类可读的文本。
  • 字符串是一种值类型且值不可变,即字节的定长数组,因此创建文本后将无法再次修改内容。

概念

  • 字节

字节即byte由8个比特位组成,即1byte = 8bit,字节是计算机中基本的计量单位。Golang中存在byte类型,其本质是uint8的别名。

  • 字符

字符的概念比较模糊,在Unicode中通常使用code point码元来表示,简单来说字符是一种信息单元,比如符号、字母等。

  • rune

rune实际上是int32的别名,为了区分将字符和整数值分开,而增加了rune类型代表一个字符。

  • UTF-8

Golang中string是Unicode兼容的,是UTF-8编码的。

Golang中string的内部实现使用UTF-8编码。由于采用UTF-8编码,string可以包含文本,而文本是世界上任何语言的混合,因而不会造成页面的混乱和限制。

UTF-8编码格式是文本的标准编码,对占用的字节长度具有不定性。string是UTF-8字符的一个序列,当字符为ASCII码表上的字符时则会占用1个字节,其它字符会根据需要占用2到4个字节。Golang为减少内存和磁盘占用空间,string可能会根据需要占用1到4个字节。

Golang中string不同于其他语言,它是一个变宽字符序列,每个字符使用UTF-8编码的一个或多个字节表示。string是任意字节(包括值为零的字节)的不可变链。

Golang中的string的字节采用UTF-8编码来标识Unicode文本,统一使用UTF-8编码以解决中文乱码问题。

string

Golang标准库builtin给出了所有内置类型的定义

type string string
  • string是一系列8位字节的集合,但并不一定代表UTF-8编码的文本。
  • string可以为空(长度为0),但不能为nil
  • string对象不可修改

例如:使用var关键字声明字符串变量

var str string

stringStruct

Golang的string是一种数据类型,占16字节空间。stringruntime包中就是stringStruct对外呈现才叫做string

  • 前8字节是一个指针指向字符串值(字节切片)的地址
  • 后8个字节是一个整数,用于标识字符串的长度。
type stringStruct struct{
  str unsafe.Pointer //字符串的首地址
  len int //字符串的长度
}

string数据结构跟切片类似,只不过切片还有一个表示容量的成员。事实上,string和切片准确来说是byte切片,经常会发生转移。因此string本质上可看作是一个只读的字节切片。

string的结构上看实际上是一个指针str,指向某个字节切片的首地址,字节切片中存放着字符串的真正内容。

例如:字符串“你好”在内存中的表示

var str string
str = "你好"
内存表示

由于Golang源码默认采用UTF-8编码,因此string字面量也是UTF-8编码的。中文汉字的“你”会被编码为\xe4\xbd\xa0,中文汉字的“好”会被编码为\xe5\xa5\xbd

例如:将UTF-8编码转换为字符串

bs := []byte{0xe4, 0xbd, 0xa0}
fmt.Printf("%s", string(bs))//你

虽然string并非切片,但支持切片操作。

对于同一个string字面量,不同的string变量指向相同的底层数组,因为string是只读的,为了节省内存,相同字面量的string通常对应同一个string产量。

str1 := "hello"
str2 := "hello"
str3 := "hello "

data1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)).Data
fmt.Printf("str1 data = %d\n", data1) //str1 data = 7858269

data2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)).Data
fmt.Printf("str2 data = %d\n", data2) //str2 data = 7858269

data3 := (*reflect.StringHeader)(unsafe.Pointer(&str3)).Data
fmt.Printf("str3 data = %d\n", data3) //str3 data = 7858734

Golang中string内部并非以\0作为结尾,而是通过一个长度域来表示字符串的长度。

字面量

Golang中string的值使用双引号包裹内容(使用单引号包裹的是字符),因此可在Golang的源码中直接添加非ASCII码字符。string以原生数据类型出现,使用string就像使用其他原生数据类型一样。

str := "hello world"

Golang中string字面量可以通过两种方式创建

  • 使用双引号包裹

使用双引号创建的字符串支持转义字符,但不能跨行。

  • 使用反引号包裹

使用反引号创建的字符串又称为原始文本(raw literals),原始文本不支持转义字符,但可以跨越多行。两个反引号之间包裹的字符串会被原样输出,因此在反引号中使用转义字符将会是无效的。在反引号之间的所有代码均不会被编译器识别,只会作为字符串的一部分。

  • 不可变性

Golang中string一旦创建则不可变,即无法更改string的值,也就是说string是只读的。若尝试更改则编译器会引发错误。这是由于字符串字面量的存储位置并不在堆和栈上,通常编译器会分配到只读段(.rodata)上,因此对应的地址是不可修改的。

Golang的实现中string不包含内存空间,只有一个内存的指针,这样做的好处是string变得轻量,可以方便地进行传递且无需担心内存拷贝。由于string通常指向字符串字面量,而字符串字面量存储位置是只读段,而不是堆或栈上,所有string才不可修改。

string是一个只读字节片,string的字节可以使用UTF-8编码在Unicode文本中表示。

转义字符

使用双引号包裹的字符串支持转义字符

转义字符 含义
\r 回车符,返回行首。
\n 换行符,直接跳到下一行的同列位置。
\t 制表符
\' 单引号
\" 双引号
\\ 反斜杠

长度

  • len()获取字符串占用字节的长度(字节数)
  • utf8.RuneCountInString()获取字符串的长度(符文数)
var str string
str = "hell?我"
fmt.Printf("len = %d, RuneCount = %d\n", len(str), utf8.RuneCountInString(str)) //len = 8, RuneCount = 6

拼接

  • 多个字符串可使用+字符串拼接符来进行追加形成新的字符串
  • 由于编译器会自动在行尾补全分号,因此字符串拼接符必须放在行尾。连接跨行字符串时+必须在上一行末尾否则会导致编译错误。
var str string
str = "hello " +
    "world"
fmt.Println(str)
  • 可使用+=来对字符串进行拼接
  • 可使用fmt.Sprintf进行拼接
var str string
str = fmt.Sprintf("id = %d, name = %s\n", 1, "admin")
fmt.Println(str)

字节

var str string
str = "h1你"

i := 0
fmt.Printf("str[%d] = %q, val = %v, hex = %x, unicode = %U\n", i, str[i], str[i], str[i], str[i])
str[0] = 'h', val = 104, hex = 68, unicode = U+0068
占位符 描述
%q 字符字面量,字节对应的字符字面量。
%v 默认格式,字节对应的编码值。
%x 小写十六进制
%U Unicode格式

字节切片

Golang中string是一个字节切片,而非字符切片。通过标准索引的方式获取的是字节,即在方括号[]中指定索引值,索引值从0开始计数。

var str string
str = "hello"
fmt.Printf("char = %c, byte = %d\n", str[0], str[0]) //char = h, byte = 104

通过rune类型可方便地对每个UTF-8字符进行访问

var str string
str = "hello"

for i:=0; i<len(str); i++{
    char := str[i]
    fmt.Printf("char = %c, byte = %d\n", char, char)
}
char = h, byte = 104
char = e, byte = 101
char = l, byte = 108
char = l, byte = 108
char = o, byte = 111
  • 获取string中某个字节的地址属于非法行为
var str string
str = "hello"

for i, v := range str {
    fmt.Printf("index = %d, value = %c, byte = %d\n", i, v, v)
}
index = 0, value = h, byte = 104
index = 1, value = e, byte = 101
index = 2, value = l, byte = 108
index = 3, value = l, byte = 108
index = 4, value = o, byte = 111

比较

  • 比较运算符在内存中会按字节比较来实现字符串的比较,因此比较的结果是字符串自然编码的顺序。

遍历

  • 使用forbyte形式遍历字符串
var str string
str = "h1你"
for i := 0; i < len(str); i++ {
    fmt.Printf("i = %d, v = %v, char = %c\n", i, str[i], str[i])
}
i = 0, v = 104, char = h
i = 1, v = 49, char = 1
i = 2, v = 228, char = ä
i = 3, v = 189, char = ½
i = 4, v = 160, char =  
  • 使用for...rangerune方式遍历字符串

当使用for...range迭代字符串时,每次迭代Golang都会使用UTF-8解码出一个rune类型的字符,且索引为当前rune的起始位置(以字节为最小单位)。

var str string
str = "h1你"

fmt.Println(reflect.TypeOf(str))    //string
fmt.Println(reflect.TypeOf(str[0])) //uint8 = byte
for _, v := range str {
    fmt.Println(reflect.TypeOf(v)) // int32 = rune
}

实际上使用for...range迭代string时,会将string中的byte重新转换为UTF-8字符,对于错误的编码则会使用一个字节的占位符来替代。

for i,v := range []rune(str){

}

例如:

var str string
str = "h1你"
for i, v := range str {
    fmt.Printf("i = %d, v = %v, char = %c\n", i, v, v)
}
i = 0, v = 104, char = h
i = 1, v = 49, char = 1
i = 2, v = 20320, char = 你

转换

要修改字符串,可先将其转换为[]byte[]rune,然后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容