1. 程式人生 > >Go語言---strings包(字串操作)

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 &lt;b&gt;HTML&lt;/b&gt;!
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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 兩個屬性來控制。