Go語言---strings包(字串操作)
strings標準庫包主要涉及字串的基本操作。
常見字串的操作有:
- 字串求長度
- 求子串
- 是否存在某個字元或者子串
- 子串出現的次數(字串匹配)
- 字串分割(切分)成[]string
- 字串是否存在某個字首或字尾
- 字元或者子串在字串中首次出現的位置或最後一次出現的位置
- 通過某個字串將[]string進行拼接
- 字串重複次數
- 字串中子串替換
- 大小寫轉換
- ......................等等一些基本操作。
由於string型別可以看成是一種特殊的slice型別,因此獲取長度可以用內建的函式len;同時支援 切片 操作,因此,子串獲取很容易。
說明:這裡說的字元是rune型別,即一個UTF-8字元(Unicode程式碼點)。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
子串
是否存在某個字元或子串
// 子串substr在s中,返回true
func Contains(s, substr string) bool
// chars中任何一個Unicode程式碼點在s中,返回true
func ContainsAny(s, chars string) bool
// Unicode程式碼點r在s中,返回true
func ContainsRune(s string, r rune) bool
示例程式碼:ContainsAny 函式說明
fmt.Println(strings.ContainsAny("team", "i")) //false fmt.Println(strings.ContainsAny("failure", "u & i")) //true fmt.Println(strings.ContainsAny("in failure", "s g")) //true fmt.Println(strings.ContainsAny("foo", "")) //false fmt.Println(strings.ContainsAny("", "")) //false
第二個引數 chars 中任意一個字元(Unicode Code Point)如果在第一個引數 s 中存在,則返回true。
上述三個函式的原始碼都是呼叫函式Index(子串出現的位置函式),然後和0比較返回true或false。如,contains函式:
func Contains(s, substr string) bool {
return Index(s, substr) >= 0
}
字元或子串在字串中出現的位置
//返回子串sep在字串s中第一次出現的索引值,不在的話返回-1. func Index(s, sep string) int //chars中任何一個Unicode程式碼點在s中首次出現的位置,不存在返回-1 func IndexAny(s, chars string) int //查詢字元 c 在 s 中第一次出現的位置,其中 c 滿足 f(c) 返回 true func IndexFunc(s string, f func(rune) bool) int //rune型別是int32別名,UTF-8字元格式編碼。 //返回字元c在s中第一次出現的位置 func IndexByte(s string, c byte) int //byte是位元組型別 // Unicode 程式碼點 r 在 s 中第一次出現的位置 func IndexRune(s string, r rune) int
//查詢最後一次出現的位置
func LastIndex(s, sep string) int
func LastIndexByte(s string, c byte) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int
示例:IndexFunc 的例子。
fmt.Printf("%d\n", strings.IndexFunc("studygolang", func(c rune) bool {
if c > 'u' {
return true
}
return false
})) //輸出:4 匿名函式傳遞 因為 y 的 Unicode 程式碼點大於 u 的程式碼點。帶入匿名函式進行比較
子串出現次數
func Count(s, sep string) int //子串在s字串中出現的次數
(1)、特別說明一下的是當 sep 為空時,Count 的返回值是:utf8.RuneCountInString(s) + 1
(2)、Count 是計運算元串在字串中出現的無重疊的次數
字串是否有某個字首或字尾
// s 中是否以 prefix 開始
func HasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}
// s 中是否以 suffix 結尾
func HasSuffix(s, suffix string) bool {
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}
golang語言中的rune型別
Rune 是int32 的別名。用UTF-8 進行編碼。這個型別在什麼時候使用呢?例如需要遍歷字串中的字元。可以迴圈每個位元組(僅在使用US ASCII 編碼字串時與字元等價,而它們在Go中不存在!)。因此為了獲得實際的字元,需要使用rune型別。在UTF-8 世界的字元有時被稱作runes。通常,當人們討論字元時,多數是指8 位字元。UTF-8 字元可能會有32 位,稱作rune。
例如 s:="Go程式設計" fmt.Println(len(s)) 輸出結果應該是8 因為中文字元是用3個位元組存的。
len(string(rune('編'))) 的結果是3如果想要獲得我們想要的情況的話,需要先轉換為rune切片再使用內建的len函式
fmt.Println(len([]rune(s))) 結果就是 4 了。
所以用string儲存 unicode 的話,如果有中文,按下標是訪問不到的,因為你只能得到一個 byte 。 要想訪問中文的話,還是要用rune切片,這樣就能按下表訪問。----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
字串轉換
func ToUpper(s string) string
func ToLower(s string) string
func ToTitle(s string) string
func ToUpperSpecial(_case unicode.SpecialCase, s string) string
func ToLowerSpecial(_case unicode.SpecialCase, s string) string
func ToTitleSpecial(_case unicode.SpecialCase, s string) string
func Title(s string) string
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------比較
func Compare(a, b string) int //返回不相等-1或者 相等0
func EqualFold(s, t string) bool
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------清理
func Trim(s string, cutset string) string //去除字串的頭尾子字串
func TrimLeft(s string, cutset string) string
func TrimRight(s string, cutset string) string
func TrimFunc(s string, f func(rune) bool) string //函式
func TrimLeftFunc(s string, f func(rune) bool) string
func TrimRightFunc(s string, f func(rune) bool) string
func TrimSpace(s string) string //字串前後空格 fmt.Println(strings.TrimSpace(" \t\n a lone gopher \n\t\r\n")) 輸出:a lone gopher
func TrimPrefix(s, prefix string) string
func TrimSuffix(s, suffix string) string
程式碼示例:
func main(){
var s = "aaasddfgaaaa"
fun := func (c rune) bool {
if c != 'a'{
return false
}
return true
}
fmt.Println(strings.TrimFunc(s, fun)) //輸出 sddfg
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------拆合函式
Fields
func Fields(s string) []string
func FieldsFunc(s string, f func(rune) bool) []string
Fields 用一個或多個連續的空格分隔字串 s,返回子字串的陣列(slice)。如果字串 s 只包含空格,則返回空列表([]string的長度為0)。其中,空格的定義是 unicode.IsSpace,之前已經介紹過。
由於是用空格分隔,因此結果中不會含有空格或空子字串。
FieldsFunc 用這樣的Unicode程式碼點 c 進行分隔:滿足 f(c) 返回 true。該函式返回[]string。如果字串 s 中所有的程式碼點(unicode code points)都滿足f(c)或者 s 是空,則 FieldsFunc 返回空slice。也就是說,我們可以通過實現一個回撥函式來指定分隔字串 s 的字元。
示例:
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
fmt.Println(strings.FieldsFunc(" foo bar baz ", unicode.IsSpace))
split
這四個函式放在一起講,是因為是通過同一個內部函式實現的。
func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }
它們都呼叫了 genSplit 函式。這四個函式都是通過 sep 進行分割,返回[]string。如果 sep 為空,相當於分成一個個的 UTF-8 字元,如 Split("abc",""),得到的是[a b c]。
Split(s, sep) 和 SplitN(s, sep, -1) 等價;SplitAfter(s, sep) 和 SplitAfterN(s, sep, -1) 等價。
那麼,Split 和 SplitAfter 有啥區別呢?通過這兩句程式碼的結果就知道它們的區別了:
fmt.Printf("%q\n", strings.Split("foo,bar,baz", ",")) // ["foo" "bar" "baz"]
fmt.Printf("%q\n", strings.SplitAfter("foo,bar,baz", ",")) // ["foo," "bar," "baz"]
也就是說,Split 會將 s 中的 sep 去掉,而 SplitAfter 會保留 sep。
帶 N 的方法可以通過最後一個引數 n 控制返回的結果中的 slice 中的元素個數,當 n < 0 時,返回所有的子字串;當 n == 0 時,返回的結果是 nil;當 n > 0 時,表示返回的 slice 中最多隻有 n 個元素,其中,最後一個元素不會分割,比如:
fmt.Printf("%q\n", strings.SplitN("foo,bar,baz", ",", 2)) // ["foo" "bar,baz"]
Join
func Join(a []string, sep string) string
將字串陣列(或slice)連線起來可以通過 Join 實現。
假如沒有這個庫函式,我們自己實現一個,我們會這麼實現:
func Join(str []string, sep string) string {
// 特殊情況應該做處理
if len(str) == 0 {
return ""
}
if len(str) == 1 {
return str[0]
}
buffer := bytes.NewBufferString(str[0])
for _, s := range str[1:] {
buffer.WriteString(sep)
buffer.WriteString(s)
}
return buffer.String()
}
使用了 bytes 包的 Buffer 型別,避免大量的字串連線操作(因為 Go 中字串是不可變的)。
標準庫的實現:
func Join(a []string, sep string) string {
if len(a) == 0 {
return ""
}
if len(a) == 1 {
return a[0]
}
n := len(sep) * (len(a) - 1)
for i := 0; i < len(a); i++ {
n += len(a[i])
}
b := make([]byte, n) //藉助 位元組切片實現
bp := copy(b, a[0])
for _, s := range a[1:] {
bp += copy(b[bp:], sep)
bp += copy(b[bp:], s)
}
return str
標準庫的實現沒有用 bytes 包,當然也不會簡單的通過 + 號連線字串。Go 中是不允許迴圈依賴的,標準庫中很多時候會出現程式碼拷貝,而不是引入某個包。這裡 Join 的實現方式挺好,我個人猜測,不直接使用 bytes 包,也是不想依賴 bytes 包(其實 bytes 中的實現也是 copy 方式)。
字串重複次數
func Repeat(s string, count int) string
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------替換
// 用 new 替換 s 中的 old,一共替換 n 個。
// 如果 n < 0,則不限制替換次數,即全部替換
func Replace(s, old, new string, n int) string
func Map(mapping func(rune) rune, s string) string //滿足函式實現的進行替換
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Replacer
這是一個結構,沒有匯出任何欄位(欄位值小寫),例項化通過
// A Replacer replaces a list of strings with replacements.
type Replacer struct {
r replacer //介面型別
}
// replacer is the interface that a replacement algorithm needs to implement.
type replacer interface {
Replace(s string) string
WriteString(w io.Writer, s string) (n int, err error)
}
func NewReplacer(oldnew ...string) *Replacer
函式進行,其中不定引數 oldnew 是 old-new 對,即進行多個替換。
type Replacer struct { ... }
// 建立一個替換規則,引數為“查詢內容”和“替換內容”的交替形式。
// 替換操作會依次將第 1 個字串替換為第 2 個字串,將第 3 個字串
// 替換為第 4 個字串,以此類推。
// 替換規則可以同時被多個例程使用。
func NewReplacer(oldnew ...string) *Replacer
// 使用替換規則對 s 進行替換並返回結果。
func (r *Replacer) Replace(s string) string
// 使用替換規則對 s 進行替換並將結果寫入 w。
// 返回寫入的位元組數和遇到的錯誤。
func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)
程式示例:
r := strings.NewReplacer("<", "<", ">", ">")
fmt.Println(r.Replace("This is <b>HTML</b>!")) // 輸出結果是 This is <b>HTML</b>!
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Reader
看到名字就能猜到,這是實現了 io 包中的介面。它實現了 io.Reader(Read 方法),io.ReaderAt(ReadAt 方法),io.Seeker(Seek 方法),io.WriterTo(WriteTo 方法),io.ByteReader(ReadByte 方法),io.ByteScanner(ReadByte 和 UnreadByte 方法),io.RuneReader(ReadRune 方法) 和 io.RuneScanner(ReadRune 和 UnreadRune 方法)。
Reader 結構如下:
type Reader struct {
s string // Reader 讀取的資料來源
i int // current reading index(當前讀的索引位置)
prevRune int // index of previous rune; or < 0(前一個讀取的 rune 索引位置)
}
可見 Reader 結構沒有匯出任何欄位,而是提供一個例項化方法:
func NewReader(s string) *Reader
該方法接收一個字串,返回的 Reader 例項就是從該引數字串讀資料。在後面學習了 bytes 包之後,可以知道 bytes.NewBufferString 有類似的功能,不過,如果只是為了讀取,NewReader 會更高效。
func (r *Reader) Read(b []byte) (n int, err error)
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
func (r *Reader) WriteTo(w io.Writer) (n int64, err error)
func (r *Reader) Seek(offset int64, whence int) (int64, error)
func (r *Reader) ReadByte() (byte, error)
func (r *Reader) UnreadByte() error
func (r *Reader) ReadRune() (ch rune, size int, err error)
func (r *Reader) UnreadRune() error
func (r *Reader) Len() int
func (r *Reader) Size() int64
func (r *Reader) Reset(s string)
其他方法不介紹了,都是之前介面的實現,有興趣的可以看看原始碼實現比較簡單易懂,大部分都是根據 i、prevRune 兩個屬性來控制。