日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Go 学习笔记(31)— 字符串 string、字符 rune、字节 byte、UTF-8 和 Unicode 区别以及获取字符串长度

發布時間:2023/11/27 生活经验 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 学习笔记(31)— 字符串 string、字符 rune、字节 byte、UTF-8 和 Unicode 区别以及获取字符串长度 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 字符串 string 類型

Go 語言中字符串的內部實現使用 UTF-8 編碼,通過 rune 類型,可以方便地對每個 UTF-8 字符進行訪問。當然, Go 語言也支持按照傳統的 ASCII 碼方式逐字符進行訪問。

  1. 字符串是常量,可以通過類似數組索引訪問其字節單元,但是不能修改某個字節的值;
var a string = "hello,world"
b := a[0]
a[1] = "a" // error

內置的 len 函數可以返回一個字符串中的字節數目(不是 rune 字符數目)。

s := "hello, world"
fmt.Println(len(s))     // "12"
fmt.Println(s[0], s[7]) // "104 119" ('h' and 'w')

注意:第 i 個字節并不一定是字符串的第 i 個字符,因為對于非 ASCII 字符的 UTF8 編碼會要兩個或多個字節。

  1. 字符串轉換為切片 []byte(s) 要慎用,每轉換一次需要復制一份內存,尤其是字符串數據量較大時;

  2. 字符串末尾不包含 NULL 字符,與 C/C++ 不一樣;

  3. 字符串類型底層實現是一個二元的數據結構,一個是指針指向字節數組的起點,另一個是字符串的長度;

// runtime/string.gotype stringStruct struct {str unsafe . Pointer //指向底層字節數組的指針len int //字節數組長度
}
  1. 基于字符串創建的切片和原字符串指向相同的底層字符數組,一樣不能修改,對字符串的切片操作返回的子串仍是 string ,而非 slice

  2. 字符串和切片的轉換:字符串可以轉換為字節數組,也可以轉換為 Unicode 數組;

a := "hello , 世界!"
b := []byte(a)
c :=[]rune(a)
  1. 字符串支持連接運算,可以通過 len() 獲取字符串的長度; lenGolang 內置的一個函數,這個函數返回字符串的長度,切片長度,數組長度,通道長度。如果用于獲取字符串長度時,當字符串為空, len 返回值是 0。即判斷字符串是否為空,有以下兩種方式:
var str string	//string 類型變量在定義后默認的初始值是空,不是 nil。
if str == "" {// str 為空
}

或者

var str string
if len(str) == 0 {// str 為空
}

對于簡單而少量的拼接,使用運算符+和+=的效果雖然很好,但隨著拼接操作次數的增加,這種做法的效率并不高。如果需要在循環中拼接字符串,則使用空的字節緩沖區來拼接的效率更高。

func main() {var buffer bytes.Bufferfor i := 0; i < 500; i++ {buffer.WriteString("hello,world")}fmt.Println(buffer.String())	 // 對緩沖區調用函數String() 以字符串的方式輸出結果。
}
  1. 獲取字符串中某個字節的地址屬于非法行為,例如 &str[i]

  2. 使用雙引號""表示字符串時不能跨行,如果想要在源碼中嵌入一個多行字符串時,就必須使用 ` 反引號(Esc 下面的字符),代碼如下:

package mainimport "fmt"func main() {str := `first line,second line,third line,\r	\n`
// 在這種方式下,反引號間換行將被作為字符串中的換行,但是所有的轉義字符均無效,文本將會原樣輸出。fmt.Println(str)
}

輸出:

			first line,second line,third line,\r\n

示例代碼如下:

package mainimport "fmt"func main() {var s string = "hello"var a string = ",world"s1 := s[0]s2 := s[:3]s3 := []byte(s)s4 := []rune(s)sLength := len(s)c := s + afmt.Printf("s1 is %v, s1 type is %T\n", s1, s1)fmt.Printf("s2 is %v, s2 type is %T\n", s2, s2)fmt.Printf("s3 is %v, s3 type is %T\n", s3, s3)fmt.Printf("s4 is %v, s4 type is %T\n", s4, s4)fmt.Printf("sLength is %v \n", sLength)fmt.Printf("c is %v \n", c)
}

輸出結果:

s1 is 104, s1 type is uint8
s2 is hel, s2 type is string
s3 is [104 101 108 108 111], s3 type is []uint8
s4 is [104 101 108 108 111], s4 type is []int32
sLength is 5 
c is hello,world 

字符串值也可以用字符串面值方式編寫,只要將一系列字節序列包含在雙引號內即可:

"hello, 世界"

因為 Go 語言源文件總是用 UTF8 編碼,并且 Go 語言的文本字符串也以 UTF8 編碼的方式處理,因此我們可以將 Unicode 碼點也寫到字符串面值中。

在一個雙引號包含的字符串面值中,可以用以反斜杠\開頭的轉義序列插入任意的數據。下面的換行、回車和制表符等是常見的 ASCII 控制代碼的轉義方式:

\a      響鈴
\b      退格
\f      換頁
\n      換行
\r      回車
\t      制表符
\v      垂直制表符
\'      單引號(只用在 '\'' 形式的rune符號面值中)
\"      雙引號(只用在 "..." 形式的字符串面值中)
\\      反斜杠

可以通過十六進制或八進制轉義在字符串面值中包含任意的字節。

  • 一個十六進制的轉義形式是\xhh,其中兩個 h 表示十六進制數字(大寫或小寫都可以);
  • 一個八進制轉義形式是\ooo,包含三個八進制的 o 數字(0 到 7),但是不能超過\377(譯注:對應一個字節的范圍,十進制為255);

len 用來獲取字符串、切片、數組、通道、字典類型變量的內容長度,不同的數據類型,長度計算規則不一樣。

  • 對于切片、字典、數組、通道類型的變量,它們中每一個元素就是一個長度;
  • 對于 string 類型變量,它們每一個字節是一個長度;
  • 對于 rune 類型切片變量,它們每一個字符是一個長度,rune 類型變量中的內容采用 UTF-8 編碼,一個字符可能對應 4 個字節;

2. 字符 rune 類型

Go 默認的字符編碼就是 UTF-8 類型。Go 語言的字符有以下兩種:

  • 一種是 uint8 類型,或者叫 byte 型( byteunit8 的別名),代表了 ASCII 碼的一個字符,占用 1 個字節。

  • 一種是 rune 類型,代表一個 UTF-8 字符,當需要處理中文、日文或者其他復合字符時,則需要用到 rune 類型。 rune 類型等價于 int32 類型,占用 4 個字節。

rune 類型是 int32 的一個別名, rune 主要用于表示 UTF-8 編碼時的字符類型。

通常情況下一個字符就是一個字節,在 Golang 中用 byte 關鍵字來表示字節,而 UTF-8 編碼的字符,可能會存在一個字符用三個字節表示。如果使用 byte 類型來存儲 UTF-8 編碼的字符串,就會導致讀取單個字節時值沒有意義的情況。

所以 Golang 中使用 rune 來存儲 UTF-8 編碼的字符。

package mainimport "fmt"func main() {var str string = "中國"rangeRune([]rune(str))rangeStr(str)
}func rangeRune(arg []rune) {fmt.Println("rune type arg length is ", len(arg))for i := 0; i < len(arg); i++ {fmt.Printf("i is %d, value is %c\n", i, arg[i])}
}func rangeStr(arg string) {fmt.Println("str type arg length is ", len(arg))for i := 0; i < len(arg); i++ {fmt.Printf("i is %d, value is %c\n", i, arg[i])}
}

輸出結果:

rune type arg length is  2
i is 0, value is 中
i is 1, value is 國
str type arg length is  6
i is 0, value is ?
i is 1, value is ?
i is 2, value is -
i is 3, value is ?
i is 4, value is ?
i is 5, value is ?

可以看到,當使用 byte 來讀取字符時,出現了亂碼現象,主要原因是一個漢字不是一個字節。所以當字符串中有漢字時,采用 rune 類型能夠比較方便地存儲和讀取。

rune 類型變量默認初始值是 0。

3. 字節 byte 類型

byte 用來表示字節,一個字節是 8 位。定義一個字節類型變量的語法是:

var b1 byte
var b2 = 'c'
var b3 byte = 'c'
b4 := 'c'

byte 類型變量默認初始值是 0。byte 類型是 uint8 類型的一個別名。

func main() {s := "hello 世界"runeSlice := []rune(s) // len = 8byteSlice := []byte(s) // len = 12// 打印每個rune切片元素for i:= 0; i < len(runeSlice); i++ {fmt.Println(runeSlice[i])// 輸出104 101 108 108 111 32 19990 30028}fmt.Println()// 打印每個byte切片元素for i:= 0; i < len(byteSlice); i++ {fmt.Println(byteSlice[i])// 輸出104 101 108 108 111 32 228 184 150 231 149 140}
}

我們可以看到,因為Go中的字符串采用UTF-8編碼,且由于rune類型是4個字節,所以切片[]rune中,一個rune切片中的單個元素(4個字節),就能夠完整的容納一個UTF-8編碼的中文字符(3個字節);而在[]byte中,由于每個byte切片元素只有1個字節,所以需要3個byte切片元素來表示一個中文字符。這樣,用[]byte表示的字符串就要比[]rune表示的字符串,切片長度多4(6 - 2),打印結果符合預期。
所以,我個人認為設計rune類型的目的,就是為了更方便的表示類似中文的非英文字符,處理起來更加方便;而byte類型則對英文字符的處理更加友好。

4. UTF-8 和 Unicode 有何區別

UnicodeASCII 類似,都是一種字符集。

Unicode 收集了這個世界上所有的符號系統,包括重音符號和其它變音符號,制表符和回車符,還有很多神秘的符號,每個符號都分配一個唯一的 Unicode 碼點, Unicode 碼點對應 Go 語言中的 rune 整數類型( runeint32 等價類型)。

我們可以將一個符號序列表示為一個 int32 序列。這種編碼方式叫 UTF-32UCS-4 ,每個 Unicode 碼點都使用同樣大小的 32bit 來表示。這種方式比較簡單統一,但是它會浪費很多存儲空間,因為大多數計算機可讀的文本是 ASCII 字符,本來每個 ASCII 字符只需要 8bit 或 1 字節就能表示。而且即使是常用的字符也遠少于 65,536 個,也就是說用 16bit 編碼方式就能表達常用字符。但是,還有其它更好的編碼方法嗎?

UTF8 是一個將 Unicode 碼點編碼為字節序列的變長編碼。 UTF8 編碼使用 1 到 4 個字節來表示每個 Unicode 碼點, ASCII 部分字符只使用 1 個字節,常用字符部分使用 2 或 3 個字節表示。

UTF-16編碼存在一定的問題:無論是ASCII中定義的英文字符,還是復雜的中文字符,它都采用2個字節來存儲。如果嚴格按照2個字節存儲,編碼號比較小的(如英文字母)的許多高位都為0(如字母t:00000000 01110100)。

UTF-16、UTF-8、還有其他五花八門的編碼存儲方式,都是Unicode的底層存儲實現。用編程范式的語言來描述:Unicode是接口,定義了有哪些映射規則;而UTF-8、UTF-16則是Unicode這個接口的實現,它們在計算機底層實現了這些映射規則。

每個符號編碼后第一個字節的高端 bit 位用于表示編碼總共有多少個字節。如果第一個字節的高端 bit為 0,則表示對應 7bit 的 ASCII 字符, ASCII 字符每個字符依然是一個字節,和傳統的 ASCII 編碼兼容。如果第一個字節的高端 bit 是 110,則說明需要 2 個字節;后續的每個高端 bit 都以 10 開頭。更大的 Unicode 碼點也是采用類似的策略處理。

0xxxxxxx                             runes 0-127    (ASCII)
110xxxxx 10xxxxxx                    128-2047       (values <128 unused)
1110xxxx 10xxxxxx 10xxxxxx           2048-65535     (values <2048 unused)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  65536-0x10ffff (other values unused)

變長的編碼無法直接通過索引來訪問第 n 個字符,但是 UTF8 編碼獲得了很多額外的優點:

  1. 首先 UTF8 編碼比較緊湊,完全兼容 ASCII 碼,并且可以自動同步;
  2. 它可以通過向前回朔最多 3 個字節就能確定當前字符編碼的開始字節的位置。它也是一個前綴編碼,所以當從左向右解碼時不會有任何歧義也并不需要向前查看(譯注:像 GBK 之類的編碼,如果不知道起點位置則可能會出現歧義)。
  3. 沒有任何字符的編碼是其它字符編碼的子串,或是其它編碼序列的字串,因此搜索一個字符時只要搜索它的字節編碼序列即可,不用擔心前后的上下文會對搜索結果產生干擾。同時 UTF8 編碼的順序和 Unicode 碼點的順序一致,因此可以直接排序 UTF8 編碼序列。同時因為沒有嵌入的 NULL (0)字節,可以很好地兼容那些使用 NULL 作為字符串結尾的編程語言。

Go 語言的源文件采用 UTF8 編碼,并且 Go 語言處理 UTF8 編碼的文本也很出色。 unicode 包提供了諸多處理 rune 字符相關功能的函數(比如區分字母和數字,或者是字母的大寫和小寫轉換等), unicode/utf8 包則提供了用于 rune 字符序列的 UTF8 編碼和解碼的功能。

Go 語言字符串面值中的 Unicode 轉義字符讓我們可以通過 Unicode 碼點輸入特殊的字符。有兩種形式:

  1. \uhhhh對應 16bit 的碼點值;
  2. \Uhhhhhhhh對應 32bit 的碼點值;

其中 h 是一個十六進制數字,一般很少需要使用 32bit 的形式。每一個對應碼點的 UTF8 編碼。例如:下面的字母串面值都表示相同的值:

"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"

上面三個轉義序列都為第一個字符串提供替代寫法,但是它們的值都是相同的。

Unicode 轉義也可以使用在 rune 字符中。下面三個字符是等價的:

'世' '\u4e16' '\U00004e16'

對于小于 256 的碼點值可以寫在一個十六進制轉義字節中,例如\x41對應字符 'A' ,但是對于更大的碼點則必須使用\u\U轉義形式。因此,\xe4\xb8\x96并不是一個合法的 rune 字符,雖然這三個字節對應一個有效的 UTF8 編碼的碼點。

我們看下包含了中西兩種字符。如下所示字符串包含 13 個字節,以 UTF8 形式編碼,但是只對應 9 個 Unicode 字符:

import "unicode/utf8"
s := "Hello, 世界"
fmt.Println(len(s))                    // "13"
fmt.Println(utf8.RuneCountInString(s)) // "9"

為了處理這些真實的字符,我們需要一個 UTF8 解碼器。 unicode/utf8 包提供了該功能,我們可以這樣使用:

for i := 0; i < len(s); {r, size := utf8.DecodeRuneInString(s[i:])fmt.Printf("%d\t%c\n", i, r)i += size
}

字符集為每個字符分配一個唯一的 ID,我們使用到的所有字符在 Unicode 字符集中都有一個唯一的 ID,例如上面例子中的 a 在 Unicode 與 ASCII 中的編碼都是 97。漢字“你”在 Unicode 中的編碼為 20320,在不同國家的字符集中,字符所對應的 ID 也會不同。而無論任何情況下,Unicode 中的字符的 ID 都是不會變化的。

UTF-8 是編碼規則,將 Unicode 中字符的 ID 以某種方式進行編碼,UTF-8 的是一種變長編碼規則,從 1 到 4 個字節不等。編碼規則如下:

  • 0xxxxxx 表示文字符號 0~127,兼容 ASCII 字符集。
  • 從 128 到 0x10ffff 表示其他字符。

根據這個規則,拉丁文語系的字符編碼一般情況下每個字符占用一個字節,而中文每個字符占用 3 個字節。

廣義的 Unicode 指的是一個標準,它定義了字符集及編碼規則,即 Unicode 字符集和 UTF-8、UTF-16 編碼等。

5. golang 中獲取字符串長度

5.1 不同編碼符串定義

因為不同字符具有不同的編碼格式。拉丁字母一個字符只要一個字節就行,而中文則可能需要兩到三個字節;UNICODE 把所有字符設置為 2 個字節,UTF-8 格式則把所有字符設置為 1–3 個字節。

因此,字符串長度的獲得,不等于按字節數查找,而要根據不同字符編碼查找。

5.2 獲取字符串長度的方法

golang 有自己的默認判斷長度函數 len() ;但遺憾的是,len() 函數判斷字符串長度的時候,是判斷字符的字節數而不是字符長度。因此,在中文字符下,應該采用如下方法:

  1. 使用 bytes.Count() 統計
  2. 使用 strings.Count() 統計
  3. 將字符串轉換為 []rune 后調用 len 函數進行統計
  4. 使用 utf8.RuneCountInString()統計

由于中文字符可能占用 1-3 個字節,所以 len() 獲取的長度會比其它的大一些。

package mainimport ("bytes""fmt""strings""unicode/utf8"
)func main() {s := "hello,您好"s_length := len(s)fmt.Println(s_length)       // 12fmt.Println(len([]byte(s))) // 12byte_length := f1(s)fmt.Println(byte_length) // 8string_length := f2(s)fmt.Println(string_length) // 8rune_length := f3(s)fmt.Println(rune_length) // 8utf_length := f4(s)fmt.Println(utf_length) // 8
}func f1(s string) int {return bytes.Count([]byte(s), nil) - 1
}func f2(s string) int {return strings.Count(s, "") - 1
}func f3(s string) int {return len([]rune(s))
}func f4(s string) int {return utf8.RuneCountInString(s)
}

6. 字符串和Byte切片

Go 語言中,一個 string 類型的值既可以被拆分為一個包含多個字符的序列,也可以被拆分為一個包含多個字節的序列。前者可以由一個以 rune 為元素類型的切片來表示,而后者則可以由一個以 byte 為元素類型的切片代表。

runeGo 語言特有的一個基本數據類型,它的一個值就代表一個字符,即:一個 Unicode 字符。比如,‘G’、‘o’、‘愛’、‘好’、'者’代表的就都是一個 Unicode 字符。我們已經知道,UTF-8 編碼方案會把一個 Unicode 字符編碼為一個長度在[1, 4]范圍內的字節序列。所以,一個 rune 類型的值也可以由一個或多個字節來代表。

package mainimport ("fmt"
)func main() {str := "Go愛好者"fmt.Printf("The string: %q\n", str)// %q 表示 該值對應的雙引號括起來的 go 語法字符串字面值// 一個rune類型的值在底層其實就是一個 UTF-8 編碼值fmt.Printf("  => runes(char): %q\n", []rune(str))// %x 表示按照 16 進制數來顯示fmt.Printf("  => runes(hex): %x\n", []rune(str))// 把每個字符的 UTF-8 編碼值都拆成相應的字節序列fmt.Printf("  => bytes(hex): [% x]\n", []byte(str))
}

輸出結果:

The string: "Go愛好者"=> runes(char): ['G' 'o' '愛' '好' '者']=> runes(hex): [47 6f 7231 597d 8005]=> bytes(hex): [47 6f e7 88 b1 e5 a5 bd e8 80 85]

注意,對于一個多字節的 UTF-8 編碼值來說,我們可以把它當做一個整體轉換為單一的整數,也可以先把它拆成字節序列,再把每個字節分別轉換為一個整數,從而得到多個整數。

這兩種表示法展現出來的內容往往會很不一樣。比如,對于中文字符’愛’來說,它的 UTF-8 編碼值可以展現為單一的整數7231,也可以展現為三個整數,即:e7、88和b1。

總之,一個 string 類型的值會由若干個 Unicode 字符組成,每個 Unicode 字符都可以由一個rune 類型的值來承載。這些字符在底層都會被轉換為 UTF-8 編碼值,而這些 UTF-8 編碼值又會以字節序列的形式表達和存儲。

因此,一個 string 類型的值在底層就是一個能夠表達若干個 UTF-8 編碼值的字節序列。

package mainimport "fmt"func main() {str := "Go愛好者"for i, c := range str {fmt.Printf("%d: %q [% x]\n", i, c, []byte(string(c)))}
}

輸出結果:

0: 'G' [47]
1: 'o' [6f]
2: '愛' [e7 88 b1]
5: '好' [e5 a5 bd]
8: '者' [e8 80 85]

由此可以看出,這樣的 for 語句可以逐一地迭代出字符串值里的每個 Unicode 字符。但是,相鄰的 Unicode 字符的索引值并不一定是連續的。這取決于前一個 Unicode 字符是否為單字節字符。正因為如此,如果我們想得到其中某個 Unicode 字符對應的 UTF-8 編碼值的寬度,就可以用下一個字符的索引值減去當前字符的索引值。

6.1 字符串和Byte 切換

首先:一個值在從 string 類型向 []byte 類型轉換時代表著以 UTF-8 編碼的字符串會被拆分成零散、獨立的字節。

除了與 ASCII 編碼兼容的那部分字符集,以 UTF-8 編碼的某個單一字節是無法代表一個字符的。

string([]byte{'\xe4', '\xbd', '\xa0', '\xe5', '\xa5', '\xbd'}) // 你好

比如,UTF-8 編碼的三個字節\xe4、\xbd和\xa0合在一起才能代表字符’你’,而\xe5、\xa5和\xbd合在一起才能代表字符’好’。

其次:一個值在從 string 類型向 []rune 類型轉換時代表著字符串會被拆分成一個個 Unicode 字符。

string([]rune{'\u4F60', '\u597D'}) // 你好

完整示例代碼:

package mainimport ("fmt"
)func main() {srcStr := "你好"fmt.Printf("The string: %q\n", srcStr)            // The string: "你好"fmt.Printf("The hex of %q: %x\n", srcStr, srcStr) // The hex of "你好": e4bda0e5a5bdfmt.Printf("The byte slice of %q: % x\n", srcStr, []byte(srcStr))//	The byte slice of "你好": e4 bd a0 e5 a5 bdfmt.Printf("The string: %q\n", string([]byte{'\xe4', '\xbd', '\xa0', '\xe5', '\xa5', '\xbd'}))// The string: "你好"fmt.Printf("The rune slice of %q: %U\n", srcStr, []rune(srcStr))// The rune slice of "你好": [U+4F60 U+597D]fmt.Printf("The string: %q\n", string([]rune{'\u4F60', '\u597D'}))// The string: "你好"
}

6.2 字符串處理標準包

標準庫中有四個包對字符串處理尤為重要: bytesstringsstrconvunicode 包。

  • strings 包提供了許多如字符串的查詢、替換、比較、截斷、拆分和合并等功能。
  • bytes 包也提供了很多類似功能的函數,但是針對和字符串有著相同結構的 []byte 類型。因為字符串是只讀的,因此逐步構建字符串會導致很多分配和復制。在這種情況下,使用 bytes.Buffer 類型將會更有效。
  • strconv 包提供了布爾型、整型數、浮點數和對應字符串的相互轉換,還提供了雙引號轉義相關的轉換。
  • unicode 包提供了 IsDigitIsLetterIsUpperIsLower 等類似功能,它們用于給字符分類。每個函數有一個單一的 rune 類型的參數,然后返回一個布爾值。而像 ToUpperToLower 之類的轉換函數將用于 rune 字符的大小寫轉換。所有的這些函數都是遵循 Unicode 標準定義的字母、數字等分類規范。 strings 包也有類似的函數,它們是 ToUpperToLower ,將原始字符串的每個字符都做相應的轉換,然后返回新的字符串。

下面例子的 basename 函數靈感源于 Unix shell 的同名工具。在我們實現的版本中, basename(s) 將看起來像是系統路徑的前綴刪除,同時將看似文件類型的后綴名部分刪除:

fmt.Println(basename("a/b/c.go")) // "c"
fmt.Println(basename("c.d.go"))   // "c.d"
fmt.Println(basename("abc"))      // "abc"

pathpath/filepath 包提供了關于文件路徑名更一般的函數操作。使用斜杠分隔路徑可以在任何操作系統上工作。斜杠本身不應該用于文件名,但是在其他一些領域可能會用于文件名,例如 URL 路徑組件。

相比之下, path/filepath 包則使用操作系統本身的路徑規則,例如 POSIX 系統使用 /foo/bar ,而 Microsoft Windows 使用c:\foo\bar等。

一個字符串是包含只讀字節的數組,一旦創建,是不可變的。相比之下,一個字節 slice 的元素則可以自由地修改。
字符串和字節 slice 之間可以相互轉換:

s := "abc"
b := []byte(s)
s2 := string(b)

從概念上講,一個 []byte(s) 轉換是分配了一個新的字節數組用于保存字符串數據的拷貝,然后引用這個底層的字節數組。編譯器的優化可以避免在一些場景下分配和復制字符串數據,但總的來說需要確保在變量 b 被修改的情況下,原始的 s 字符串也不會改變。將一個字節 slice 轉換到字符串的 string(b) 操作則是構造一個字符串拷貝,以確保 s2 字符串是只讀的。

為了避免轉換中不必要的內存分配, bytes 包和 strings 同時提供了許多實用函數。下面是 strings 包中的六個函數:

func Contains(s, substr string) bool
func Count(s, sep string) int
func Fields(s string) []string
func HasPrefix(s, prefix string) bool
func Index(s, sep string) int
func Join(a []string, sep string) string

bytes 包中也對應的六個函數:

func Contains(b, subslice []byte) bool
func Count(s, sep []byte) int
func Fields(s []byte) [][]byte
func HasPrefix(s, prefix []byte) bool
func Index(s, sep []byte) int
func Join(s [][]byte, sep []byte) []byte

它們之間唯一的區別是字符串類型參數被替換成了字節 slice 類型的參數。

bytes 包還提供了 Buffer 類型用于字節 slice 的緩存。一個 Buffer 開始是空的,但是隨著 stringbyte[]byte 等類型數據的寫入可以動態增長,一個 bytes.Buffer 變量并不需要初始化,因為零值也是有效的:

// intsToString is like fmt.Sprint(values) but adds commas.
func intsToString(values []int) string {var buf bytes.Bufferbuf.WriteByte('[')for i, v := range values {if i > 0 {buf.WriteString(", ")}fmt.Fprintf(&buf, "%d", v)}buf.WriteByte(']')return buf.String()
}
func main() {fmt.Println(intsToString([]int{1, 2, 3})) // "[1, 2, 3]"
}

當向 bytes.Buffer 添加任意字符的 UTF8 編碼時,最好使用 bytes.BufferWriteRune 方法,但是 WriteByte 方法對于寫入類似 '['']'ASCII 字符則會更加有效。

7. 字符串和數字的轉換

除了字符串、字符、字節之間的轉換,字符串和數值之間的轉換也比較常見。由 strconv 包提供這類轉換功能。
將一個整數轉為字符串有兩種方法:

  • 一種方法是用 fmt.Sprintf 返回一個格式化的字符串;
  • 另一個方法是用 strconv.Itoa(“整數到ASCII”)
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"

FormatIntFormatUint 函數可以用不同的進制來格式化數字:

fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"

fmt.Printf 函數的 %b%d%o%x 等參數提供功能往往比 strconv 包的 Format 函數方便很多,特別是在需要包含有附加額外信息的時候:

s := fmt.Sprintf("x=%b", x) // "x=1111011"

如果要將一個字符串解析為整數,可以使用 strconv 包的 AtoiParseInt 函數,還有用于解析無符號整數的 ParseUint 函數:

x, err := strconv.Atoi("123")             // x is an int
y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits

ParseInt 函數的第三個參數是用于指定整型數的大小;例如 16 表示 int16 ,0 則表示 int 。在任何情況下,返回的結果 y 總是 int64 類型,你可以通過強制類型轉換將它轉為更小的整數類型。

有時候也會使用 fmt.Scanf 來解析輸入的字符串和數字,特別是當字符串和數字混合在一行的時候,它可以靈活處理不完整或不規則的輸入。

參考:
Go 語言圣經

總結

以上是生活随笔為你收集整理的Go 学习笔记(31)— 字符串 string、字符 rune、字节 byte、UTF-8 和 Unicode 区别以及获取字符串长度的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。