雲原生時代崛起的程式語言Go常用標準庫實戰

2023-05-06 06:01:17

@

基礎標準庫

簡述

Go語言的標準庫覆蓋網路、系統、加密、編碼、圖形等各個方面,可以直接使用標準庫的 http 包進行 HTTP 協定的收發處理;網路庫基於高效能的作業系統通訊模型(Linux 的 epoll、Windows 的 IOCP);所有的加密、編碼都內建支援,無需要再從第三方開發者處獲取;Go 語言的編譯器也是標準庫的一部分,通過詞法器掃描原始碼,使用語法樹獲得原始碼邏輯分支等;Go 語言的周邊工具也是建立在這些標準庫上。在標準庫上可以完成幾乎大部分的需求,Go 語言的標準庫以包的方式提供支援,下表是 Go 語言標準庫中常見的包及其功能。

Go語言標準庫包名 功 能
bufio 帶緩衝的 I/O 操作
bytes 實現位元組操作
container 封裝堆、列表和環形列表等容器
crypto 加密演演算法
database 資料庫驅動和介面
debug 各種偵錯檔案格式存取及偵錯功能
encoding 常見演演算法如 JSON、XML、Base64 等
flag 命令列解析
fmt 格式化操作
go Go 語言的詞法、語法樹、型別等。可通過這個包進行程式碼資訊提取和修改
html HTML 跳脫及模板系統
image 常見圖形格式的存取及生成
io 實現 I/O 原始存取介面及存取封裝
log 用於紀錄檔記錄和控制檯輸出
math 數學庫
net 網路庫,支援 Socket、HTTP、郵件、RPC、SMTP 等
os 作業系統平臺不依賴平臺操作封裝
path 相容各作業系統的路徑操作實用函數
plugin Go 1.7 加入的外掛系統。支援將程式碼編譯為外掛,按需載入
reflect 語言反射支援。可以動態獲得程式碼中的型別資訊,獲取和修改變數的值
regexp 正規表示式封裝
runtime 執行時介面
sort 排序介面
strings 字串轉換、解析及實用函數
sync 提供同步原語,如互斥鎖和條件變數(上篇文章有專門講解)
time 時間介面
text 文字模板及 Token 詞法器

字串-string

底層結構

標準庫的strings包提供了許多有用的與字串相關的函數。Go字串底層的資料結構在runtime/strings.go中定義如下:

從上面的stringStruct結構體得知其包含兩個欄位,一個是8個位元組的萬能指標,指向一個陣列,陣列裡面儲存就是實際的字元,另一個則是一個8個位元組表示其長度,因此不管anyStrings多長通過unsafe.Sizeof("anyStrings")最終獲取大小都是固定的16個位元組。

函數

下面是一些常見函數舉例,可以到strings包檔案中找到更多的函數

package main

import (
	"fmt"
	s "strings"
	"unsafe"
)

var p = fmt.Println

func main() {
	p(unsafe.Sizeof("anyStrings"))
	p(unsafe.Sizeof("anyStringsMoreThenLength"))
	p("Contains:  ", s.Contains("test", "es"))
	p("Count:     ", s.Count("test", "t"))
	p("HasPrefix: ", s.HasPrefix("test", "te"))
	p("HasSuffix: ", s.HasSuffix("test", "st"))
	p("Index:     ", s.Index("test", "e"))
	p("Join:      ", s.Join([]string{"a", "b"}, "-"))
	p("Repeat:    ", s.Repeat("a", 5))
	p("Replace:   ", s.Replace("foo", "o", "0", -1))
	p("Replace:   ", s.Replace("foo", "o", "0", 1))
	p("Split:     ", s.Split("a-b-c-d-e", "-"))
	p("ToLower:   ", s.ToLower("TEST"))
	p("ToUpper:   ", s.ToUpper("test"))
}

長度

  • Go 語言的內建函數 len(),可以用來獲取切片、字串、通道(channel)等的長度,
  • Go 語言的字串都以 UTF-8 格式儲存,每個中文佔用 3 個位元組,因此使用 len() 獲得兩個中文文字對應的 6 個位元組;
  • 針對ASCII 字串長度使用 len() 函數,Unicode 字串長度使用 utf8.RuneCountInString() 函數。如果沒有使用 Unicode,漢字則顯示為亂碼。
  • ASCII 字串遍歷直接使用下標;Unicode 字串遍歷用 for range
  • bytes.Buffer實現字串拼接。
package main

import (
	"bytes"
	"fmt"
	"unicode/utf8"
)

var p = fmt.Println

func main() {
	str1 := "Hello World!"
	str2 := "你好"
	fmt.Println(len(str1))                          // 12
	fmt.Println(len(str2))                          // 6
	fmt.Println(utf8.RuneCountInString(str2))       // 2
	fmt.Println(utf8.RuneCountInString("你好,world")) // 8

	// 宣告位元組緩衝
	var stringBuilder bytes.Buffer
	// 把字串寫入緩衝
	stringBuilder.WriteString(str1)
	stringBuilder.WriteString(str2)
	// 將緩衝以字串形式輸出
	fmt.Println(stringBuilder.String())

	theme := "狙擊 start"
	for i := 0; i < len(theme); i++ {
		fmt.Printf("ascii: %c  %d\n", theme[i], theme[i])
	}

	for _, s := range theme {
		fmt.Printf("Unicode: %c  %d\n", s, s)
	}
}

格式化輸出

Go為傳統的printf字串格式化提供了很好的支援,Go提供了若干個列印「動詞」,用於格式化一般Go值,下面是一些常見的字串格式化任務的範例。

package main

import (
	"fmt"
	"os"
)

type point struct {
	x, y int
}

func main() {

	p := point{1, 2}
	fmt.Printf("struct1: %v\n", p)

	fmt.Printf("struct2: %+v\n", p)

	fmt.Printf("struct3: %#v\n", p)

	fmt.Printf("type: %T\n", p)

	fmt.Printf("bool: %t\n", true)

	fmt.Printf("int: %d\n", 123)

	fmt.Printf("bin: %b\n", 14)

	fmt.Printf("char: %c\n", 33)

	fmt.Printf("hex: %x\n", 456)

	fmt.Printf("float1: %f\n", 78.9)

	fmt.Printf("float2: %e\n", 123400000.0)
	fmt.Printf("float3: %E\n", 123400000.0)

	fmt.Printf("str1: %s\n", "\"string\"")

	fmt.Printf("str2: %q\n", "\"string\"")

	fmt.Printf("str3: %x\n", "hex this")

	fmt.Printf("pointer: %p\n", &p)

	fmt.Printf("width1: |%6d|%6d|\n", 12, 345)

	fmt.Printf("width2: |%6.2f|%6.2f|\n", 1.2, 3.45)

	fmt.Printf("width3: |%-6.2f|%-6.2f|\n", 1.2, 3.45)

	fmt.Printf("width4: |%6s|%6s|\n", "foo", "b")

	fmt.Printf("width5: |%-6s|%-6s|\n", "foo", "b")

	s := fmt.Sprintf("sprintf: a %s", "string")
	fmt.Println(s)

	fmt.Fprintf(os.Stderr, "io: an %s\n", "error")
}

模版-template

text/template

Golang中的標準庫template就像是一個「指令碼語言解析器」,其中涉及到變數賦值、函數/方法呼叫和各種條件/迴圈控制結構等。template包實現了資料驅動的用於生成文字輸出的模板,簡單來說就是將一組文字嵌入另一組文字模版中,返回一個期望的文字。Go為模板操作提供了豐富的支援。巢狀模板,匯入函數,表示變數,迭代資料等等都很簡單。如果需要比CSV資料格式更復雜的東西,模板可能是一個不錯的解決方案。模板的另一個應用是網站的頁面渲染;當我們想要將伺服器端資料呈現給使用者端時,模板可以很好地滿足要求。

Go提供了text/template和html/template這兩個模板包,這兩個包的部分函數看起來非常相似,實際功能也確實如此

package main

import (
	"os"
	"text/template"
)

type Inventory struct {
	Username string
	Phone    uint
	Tag      bool
	Sex      string
}

func main() {
	t1 := template.New("t1")
	t1, err := t1.Parse("Value is {{.}}\n")
	if err != nil {
		panic(err)
	}

	t1 = template.Must(t1.Parse("Value: {{.}}\n"))

	t1.Execute(os.Stdout, "some text")
	t1.Execute(os.Stdout, 5)
	t1.Execute(os.Stdout, []string{
		"Go",
		"Rust",
		"C++",
		"C#",
	})

	Create := func(name, t string) *template.Template {
		return template.Must(template.New(name).Parse(t))
	}

	t2 := Create("t2", "Name: {{.Name}}\n")

	t2.Execute(os.Stdout, struct {
		Name string
	}{"Jane Doe"})

	t2.Execute(os.Stdout, map[string]string{
		"Name": "Mickey Mouse",
	})

	t3 := Create("t3",
		"{{if . -}} yes {{else -}} no {{end}}\n")
	t3.Execute(os.Stdout, "not empty")
	t3.Execute(os.Stdout, "")

	t4 := Create("t4",
		"Range: {{range .}}{{.}} {{end}}\n")
	t4.Execute(os.Stdout,
		[]string{
			"Go",
			"Rust",
			"C++",
			"C#",
		})

	sweaters := Inventory{"移動", 10086, false, "難"}

	content := `{{.Phone}} of {{.Username}} {{if .Tag }} tag=true {{else}}   tag=false {{end}}`
	tmpl, err := template.New("test").Parse(content)
	//{{.Phone}}獲取的是struct物件中的Phone欄位的值
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, sweaters) // 10086 of 移動  tag=true
	if err != nil {
		panic(err)
	}
}

html/template

使用html/template來呈現網站,模板是純文字,但變數和函數可以在大括號塊內使用,模板包還提供了處理檔案的便捷方法。html/template包是對text/template包的包裝,因此能同於text/template基本都對html/template包同樣適用,除了import語句無需其他任何修改。HTML模板提供了上下文感知安全性的額外好處,也可以防止諸如JavaScript注入之類的事情。如果要生成HTML格式的輸出,參見html/template包,該包提供了和本包相同的介面,但會自動將輸出轉化為安全的HTML格式輸出,可以抵抗一些網路攻擊。

package main

import (
	"html/template"
	"net/http"
)

func tmpl(w http.ResponseWriter, r *http.Request) {
	t1, err := template.ParseFiles("test.html")
	if err != nil {
		panic(err)
	}
	t1.Execute(w, "hello world")
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/tmpl", tmpl)
	server.ListenAndServe()
}

建立test.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Go Web</title>
</head>
<body>
{{ . }}
</body>
</html>

存取測試地址http://localhost:8080/tmpl

正規表示式-regexp

Go提供了對正規表示式的內建支援,其由regexp包實現了正規表示式搜尋;Go標準庫使用RE2語法,RE2語法也是Python、C和Perl使用的正規表示式語法,常見函數:

  • MatchString:regexp.MatchString()用來匹配子字串。下面這個例子是檢查字串是否以Golang開頭。我們使用^來匹配字串中以文字的開始。我們使用^Golang作為正規表示式進行匹配。
  • Compile:Compile() 或者 MustCompile()建立一個編譯好的正規表示式物件。假如正規表示式非法,那麼Compile()方法會返回error,而MustCompile()編譯非法正規表示式時不會返回error,而是會panic。如果你想要很好的效能,不要在使用的時候才呼叫Compile()臨時進行編譯,而是預先呼叫Compile()編譯好正規表示式物件。
  • FindString:FindString()用來返回第一個匹配的結果。如果沒有匹配的字串,那麼它會返回一個空的字串,當然如果你的正規表示式就是要匹配空字串的話,它也會返回空字串。使用 FindStringIndex 或者 FindStringSubmatch可以區分這兩種情況。
    • FindStringIndex:FindStringIndex()可以得到匹配的字串在整體字串中的索引位置。如果沒有匹配的字串,它會返回nil值。
    • FindStringSubmatch:FindStringSubmatch() 除了返回匹配的字串外,還會返回子表示式的匹配項。如果沒有匹配項,則返回nil值。
  • FindAllString:FindString方法的All版本,它返回所有匹配的字串的slice。如果返回nil值代表沒有匹配的字串。
  • ReplaceAllString :用來替換所有匹配的字串,返回一個源字串的拷貝。

Go中與regexp相關的一些常見範例

package main

import (
	"bytes"
	"fmt"
	"regexp"
)

func main() {

	match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
	fmt.Println(match)

	r, _ := regexp.Compile("p([a-z]+)ch")

	fmt.Println(r.MatchString("peach"))

	fmt.Println(r.FindString("peach punch"))

	fmt.Println("idx:", r.FindStringIndex("peach punch"))

	fmt.Println(r.FindStringSubmatch("peach punch"))

	fmt.Println(r.FindStringSubmatchIndex("peach punch"))

	fmt.Println(r.FindAllString("peach punch pinch", -1))

	fmt.Println("all:", r.FindAllStringSubmatchIndex(
		"peach punch pinch", -1))

	fmt.Println(r.FindAllString("peach punch pinch", 2))

	fmt.Println(r.Match([]byte("peach")))

	r = regexp.MustCompile("p([a-z]+)ch")
	fmt.Println("regexp:", r)

	fmt.Println(r.ReplaceAllString("a peach", "<fruit>"))

	in := []byte("a peach")
	out := r.ReplaceAllFunc(in, bytes.ToUpper)
	fmt.Println(string(out))
}

編碼-encoding

encoding 包是 Go 標準庫中的一個重要包,主要用於資料編碼和解碼。encoding 包中包含了許多常用的資料編碼和解碼演演算法,如 JSON、XML、CSV、Base64 等,這些演演算法可以幫助我們將資料從一種格式轉換為另一種格式,便於在不同的系統之間傳輸和處理。

Base64

Go提供對base64編碼/解碼的內建支援。

package main

import (
	b64 "encoding/base64"
	"fmt"
)

func main() {

	data := "abc123!?$*&()'-=@~"

	sEnc := b64.StdEncoding.EncodeToString([]byte(data))
	fmt.Println(sEnc)

	sDec, _ := b64.StdEncoding.DecodeString(sEnc)
	fmt.Println(string(sDec))
	fmt.Println()

	uEnc := b64.URLEncoding.EncodeToString([]byte(data))
	fmt.Println(uEnc)
	uDec, _ := b64.URLEncoding.DecodeString(uEnc)
	fmt.Println(string(uDec))
}

JSON

Go提供了對JSON編碼和解碼的內建支援,包括來自內建和自定義資料型別的支援。需要使用encoding/json包進行json實現序列化與反序列化,go的json解析主要是編碼和解碼兩個函數,序列化也就是由結構體轉化為json string字串,使用json.Marshal函數;反序列化就是將json string字串轉化為結構體,使用函數json.Unmarshal函數完成。

~~~go
package main

import (
	"encoding/json"
	"fmt"
	"os"
)

type response1 struct {
	Page   int
	Fruits []string
}

type response2 struct {
	Page   int      `json:"page"`
	Fruits []string `json:"fruits"`
}

func main() {

	bolB, _ := json.Marshal(true)
	fmt.Println(string(bolB))

	intB, _ := json.Marshal(1)
	fmt.Println(string(intB))

	fltB, _ := json.Marshal(2.34)
	fmt.Println(string(fltB))

	strB, _ := json.Marshal("gopher")
	fmt.Println(string(strB))

	slcD := []string{"apple", "peach", "pear"}
	slcB, _ := json.Marshal(slcD)
	fmt.Println(string(slcB))

	mapD := map[string]int{"apple": 5, "lettuce": 7}
	mapB, _ := json.Marshal(mapD)
	fmt.Println(string(mapB))

	res1D := &response1{
		Page:   1,
		Fruits: []string{"apple", "peach", "pear"}}
	res1B, _ := json.Marshal(res1D)
	fmt.Println(string(res1B))

	res2D := &response2{
		Page:   1,
		Fruits: []string{"apple", "peach", "pear"}}
	res2B, _ := json.Marshal(res2D)
	fmt.Println(string(res2B))

	byt := []byte(`{"num":6.13,"strs":["a","b"]}`)

	var dat map[string]interface{}

	if err := json.Unmarshal(byt, &dat); err != nil {
		panic(err)
	}
	fmt.Println(dat)

	num := dat["num"].(float64)
	fmt.Println(num)

	strs := dat["strs"].([]interface{})
	str1 := strs[0].(string)
	fmt.Println(str1)

	str := `{"page": 1, "fruits": ["apple", "peach"]}`
	res := response2{}
	json.Unmarshal([]byte(str), &res)
	fmt.Println(res)
	fmt.Println(res.Fruits[0])

	enc := json.NewEncoder(os.Stdout)
	d := map[string]int{"apple": 5, "lettuce": 7}
	enc.Encode(d)
}

XML

Go通過encoding.xml包提供了對XML和類XML格式的內建支援。Go語言內建的 encoding/xml 包可以用在結構體和 XML 格式之間進行編解碼,其方式跟 encoding/json 包類似;然而與 JSON 相比 XML 的編碼和解碼在功能上更苛刻得多,這是由於 encoding/xml 包要求結構體的欄位包含格式合理的標籤,而 JSON 格式卻不需要。

package main

import (
	"encoding/xml"
	"fmt"
)

type Plant struct {
	XMLName xml.Name `xml:"plant"`
	Id      int      `xml:"id,attr"`
	Name    string   `xml:"name"`
	Origin  []string `xml:"origin"`
}

func (p Plant) String() string {
	return fmt.Sprintf("Plant id=%v, name=%v, origin=%v",
		p.Id, p.Name, p.Origin)
}

func main() {
	coffee := &Plant{Id: 27, Name: "Coffee"}
	coffee.Origin = []string{"Ethiopia", "Brazil"}

	out, _ := xml.MarshalIndent(coffee, " ", "  ")
	fmt.Println(string(out))

	fmt.Println(xml.Header + string(out))

	var p Plant
	if err := xml.Unmarshal(out, &p); err != nil {
		panic(err)
	}
	fmt.Println(p)

	tomato := &Plant{Id: 81, Name: "Tomato"}
	tomato.Origin = []string{"Mexico", "California"}

	type Nesting struct {
		XMLName xml.Name `xml:"nesting"`
		Plants  []*Plant `xml:"parent>child>plant"`
	}

	nesting := &Nesting{}
	nesting.Plants = []*Plant{coffee, tomato}

	out, _ = xml.MarshalIndent(nesting, " ", "  ")
	fmt.Println(string(out))
}

時間-time

在程式設計中經常會遭遇八小時時間差問題,這是由時區差異引起的,為了能更好地解決它們,需要先理解幾個時間定義標準。

  • GMT(Greenwich Mean Time):格林威治時間;GMT 根據地球的自轉和公轉來計算時間,它規定太陽每天經過位於英國倫敦郊區的皇家格林威治天文臺的時間為中午12點;GMT 是前世界標準時。
  • UTC(Coordinated Universal Time),協調世界時間,又稱世界統一時間;UTC 比 GMT 更精準,它根據原子鐘來計算時間,適應現代社會的精確計時。在不需要精確到秒的情況下,可以認為 UTC=GMT;UTC 是現世界標準時。

適應現代社會的精確計時從格林威治本初子午線起,往東為正,往西為負,全球共劃分為 24 個標準時區,相鄰時區相差一個小時;如何獲取自Unix紀元以來的秒數、毫秒數或納秒數和時間格式化。

package main

import (
	"fmt"
	"time"
)

func main() {
	p := fmt.Println

	now := time.Now()
	p(now)

	then := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	p(then)

	p(then.Year())
	p(then.Month())
	p(then.Day())
	p(then.Hour())
	p(then.Minute())
	p(then.Second())
	p(then.Nanosecond())
	p(then.Location())

	p(then.Weekday())

	p(then.Before(now))
	p(then.After(now))
	p(then.Equal(now))

	diff := now.Sub(then)
	p(diff)

	p(diff.Hours())
	p(diff.Minutes())
	p(diff.Seconds())
	p(diff.Nanoseconds())

	p(then.Add(diff))
	p(then.Add(-diff))
	p("-----------------------")
	fmt.Println(now.Unix())
	fmt.Println(now.UnixMilli())
	fmt.Println(now.UnixNano())

	fmt.Println(time.Unix(now.Unix(), 0))
	fmt.Println(time.Unix(0, now.UnixNano()))
	p("-----------------------")
	t := time.Now()
	p(t.Format(time.RFC3339))

	t1, e := time.Parse(
		time.RFC3339,
		"2012-11-01T22:08:41+00:00")
	p(t1)

	p(t.Format("3:04PM"))
	p(t.Format("Mon Jan _2 15:04:05 2006"))
	p(t.Format("2006-01-02T15:04:05.999999-07:00"))
	form := "3 04 PM"
	t2, e := time.Parse(form, "8 41 PM")
	p(t2)

	fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
		t.Year(), t.Month(), t.Day(),
		t.Hour(), t.Minute(), t.Second())

	ansic := "Mon Jan _2 15:04:05 2006"
	_, e = time.Parse(ansic, "8:41PM")
	p(e)
}

網路-net

網路程式設計是go語言使用的一個核心模組;golang的網路封裝使用對於底層socket或者上層的http,甚至是web服務都很友好。net包提供了可移植的網路I/O介面,包括TCP/IP、UDP、域名解析和Unix域socket等方式的通訊。其中每一種通訊方式都使用 xxConn 結構體來表示,諸如IPConn、TCPConn等,這些結構體都實現了Conn介面,Conn介面實現了基本的讀、寫、關閉、獲取遠端和本地地址、設定timeout等功能。

URL

url提供了一種統一的方式來定位資源,Go中解析url需要使用到其net包。

  • URL格式: