Try   HackMD

引言

String Handling在程式中非常常見,也是學習過程的必修課之一。本文記錄了使用Golang處理String的常見方法。

String是什麼

string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.

本質上,string是一串byte (byte slice, 1 byte=8 bits),而Golang的bytes類等於uint8。bytes按照UTF-8 encode後產生對應的character (字元)。

由於每個1 byte只有8 bits,因此並不能代表所有UTF-8 encoded字元,例如中文字、emoji等,所以golang另外有rune type處理這些字元。

runtime/string.go 可見Golang runtime對string的定義是由byte pointer跟一個int組成。

type stringStruct struct {
	str unsafe.Pointer // underlying bytes
	len int            // number of bytes
}

// Variant with *byte pointer type for DWARF debugging.
type stringStructDWARF struct {
	str *byte
	len int
}

String Handling

nil

Golang有自己一套對null (空值) 的處理。對於String type而言,是沒有nil的,只有預設值""(empty string)。

str := "hello"
fmt.Println(str == nil)

這段code會報錯:

invalid operation: str == nil (mismatched types string and untyped nil)

雖然可以通過 str=="" 檢查null string,但是對於某些情況而言,empty string跟null string有其各自的意義,並不對等。因此會使用*string (pointer of string) 處理需要null string的情況。

var strp *string
fmt.Println(strp == nil) // true
// fmt.Println(*strp)
// runtime error: invalid memory address or nil pointer dereference
var str = ""
strp = &str
fmt.Println(strp == nil) // false
fmt.Println(*strp) // 

len()

因為string是一串bytes,即byte slice。可以通過len(string)找出string的長度。

var str = "Hello\n"
fmt.Println(len(str)) // 6

String Literals

跨行的string有兩種表達方式,分別是使用""``

var str = "Hello1\n2World3\n4!"

等於

var str = `Hello1
2World3
4!`

String concatenation

operator +

s := "Hello" + "World!"
fmt.Println(s) // HelloWorld!

fmt.Sprint, fmt.Sprintln, fmt.Sprintf

fmt.Sprintf

s := fmt.Sprint("HelloWorld!")
fmt.Println(s) // HelloWorld!
s = fmt.Sprintln("Bye", "World", "~")
fmt.Println(s) // ByeWorld~
s = fmt.Sprintf("%s", "NiceWorld!")
fmt.Println(s) // NiceWorld!

Sprint亦可以將不同類型的variable轉換成String:

sli := []int{1,2,3}
str := fmt.Sprint(sli)
fmt.Println(str) // [1 2 3]

strings.Join()

strings.Join

ss := []string{"Hello", "World", "~"}
s := strings.Join(ss, "")
fmt.Println(s) // HelloWorld~

bytes.Buffer

bytes.Buffer

var b bytes.Buffer
b.WriteString("Hello")
b.WriteString("World!")
fmt.Println(b.String()) // HelloWorld!

String convertion

使用strconv package

str := "1234"
v, _ := strconv.Atoi(str)
fmt.Printf("%T\n", v) // int
// Or, use "reflect", fmt.Println(reflect.TypeOf(v))
s := strconv.Itoa(v)
fmt.Printf("%T\n", s) // string
u, _ := strconv.ParseUint(str, 10, 32)
fmt.Printf("%T\n", u) // uint64

Convert int slice into string

a := []int{1,2,3,4}
str := strings.Trim(strings.Replace(fmt.Sprint(a), " ", ",", -1), "[]")
fmt.Print(str) // 1,2,3,4

特殊情況

Remove the last character from a string

ss := []string{"Hello", "World", "Peter", "Tom"}
var s string
for _, v := range ss{
  s = s + v + ", "
}
// s == "Hello, World, Peter, Tom, "
s = s[:i] + strings.Replace(s[i:], ", ", "", 1)
fmt.Println(s) // Hello, World, Peter, Tom

如果你知道要刪除的substring是什麼,可以使用bytes.Buffer:

b.WriteString("Hello")
b.WriteString("World!")
b.Truncate(b.Len() - len("rld!"))
fmt.Println(b.String()) // HelloWo

Create a random string

letterRunes := []rune("3456789ABCEFGHJKLMNPQRSTXY")
func RandStringRunes(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

Handle full space character

使用rune可以處理更多的UTF-8字元,包含中文字、emoji等全形字。
使用byte slice去表示中文時可以發現,golang會使用多於一個byte去表示中文字元,例如:

fmt.Println([]byte("Hello, 世界"))
// [72 101 108 108 111 44 32 228 184 150 231 149 140]
// 228 184 150 世
// 231 149 140 界

一個例子:validateComment 會用 * 取代comment中的特定字眼,其中comment可以包含全形字。strings.Builder.WriteRune就可以直接寫入全形字元。

func validateComment(comment string) string {
    lowerComment := sensitive.Filter.Replace(strings.ToLower(comment), '*')
    var sb strings.Builder
    var runeCount int
    for _, runeValue := range lowerComment {
        if runeValue != '*' {
            _, err := sb.WriteRune([]rune(comment)[runeCount])
            if err != nil {
                log.Error(err)
            }
        } else {
            _, err := sb.WriteRune(runeValue)
            if err != nil {
                log.Error(err)
            }
        }
        runeCount++
    }
    defer sb.Reset()
    return sb.String()
}
// sensitive: https://github.com/importcjj/sensitive

這個例子同時展示了,如何還原ToLower後的String。

Reference