FangChannel的GoLang學習筆記

LOGO.jpg



繼[[深入淺出 Gp - O'REILLY]]之後依照FangChannelGolang學習記來復習 ,會再次記錄我認為需要記下的地方。


![[Pasted image 20220904185635.png]]
Q:為什麼 import要使用"gonote/note"而不是"note"才能使用到note.go的內容?
A:因為go.mod裡面寫著moudle gonote意思是這整個模組是gonote


Go大項目標準架構


api: API相關
assets: 圖片等資源
build: 建置好的文件
cmd: 子項目
configs: 配置文件
pkg: 存放編譯過的套件程式碼


慣例


// 單行註釋 -> 一般用來描述函數的目的與結構
一般多行註釋也建議使用//


...


// 用 [...] 自動讓GO判斷陣列長度
var a = [...] int {
1,
2,
3,
}

range


// 簡易歷遍陣列的方式
for index, value := range a {
}
// 若是不想要其中一個值可以用_代替
for _, value := range a {
}

切片


// 切片是對陣列的引用,所以會改變陣列的值
arr := [5]int{1, 2, 3, 4, 5} // 這是一個[1,2,3,4,5]的陣列
x := arr[1:4] // 這是一個[2,3,4]的切片(Slices) (引用自arr)
x[0] = 0 // 改片切片第0個值
fmt.Println("arr=", arr) // arr= [1 0 3 4 5]
fmt.Println("x=", x) // arr= [0 3 4]
// PS 切片也可以引用自切片,但最終都會指向原始的陣列
// PS 切片不引用的話,初值是nil
fmt.Printf("len(x)=%v", len(x)) // 3
fmt.Printf("cap(x)=%v\n", cap(x)) // 4 // 容量是由父陣列被切的開始到父陣列最後一個元素

var x2 []int
if isOverArrIdx := false; isOverArrIdx { // 改變此值來決定之後append的長度會不會超出父陣列
x2 = arr[0:2] // 超出父陣列容量 所以另外建立陣列
} else {
x2 = arr[0:1] // 沒超出父陣列容量 所以沿用
}
x = append(x, x2...)
fmt.Println("x=", x) // arr= [0 3 4 1 0]
fmt.Println("x2=", x2) // arr= [1 0]
fmt.Println("arr=", arr) // arr= [1 0 3 4 5] // 注意arr會因為append使用x2的長度而影響值

轉型


// []byte(xxx) 的解讀方法
str := "ab"
arr := ([]byte)(str) // equal []byte(str)
fmt.Println(arr)

// string to []byte
str := "ab"
arr := ([]byte)(str) // 從str轉換至[]byte,因為類型不相同所以會另外建立記憶體空間
arr[0] = 'z'
fmt.Printf("%s\n", str) // ab
fmt.Printf("%s\n", arr) // zb

// []byte to string
arr2 := []byte{'a', 'b'}
str2 := string(arr2) // 從[]byte轉換至string,因為類型不相同所以會另外建立記憶體空間

arr2[0] = 'z'
fmt.Printf("%s\n", str2) // ab
fmt.Printf("%s\n", arr2) // zb

ia := []int{1, 2, 3}
ia_t := ([]int)(ia) // 從int轉換至int,因為類型相同,所以共用記憶體空間。這行等同於 ia_t := ia
ia_t[1] = 0
fmt.Println(ia) // [1 0 3]
fmt.Println(ia_t) // [1 0 3]

[]byte(str) 其實是 ([]byte)(str) => 把(str)轉型成[]byte
同理
interface{}(typeA)其實是(interface{})(typeA) => 把(typeA)轉型成interface{} => 這樣空介面內的.(type)就會指向到typeA的類型


Map


// delete map
m1 := map[string]string{
"一": "星期一",
"二": "星期二",
"三": "星期三",
}
fmt.Println(m1) // map[一:星期一 三:星期三 二:星期二]
delete(m1, "一")
fmt.Println(m1) // map[三:星期三 二:星期二]
// 兩種清空map的方式
m1 = nil // 第一種
m1 = make(map[string]string) // 第二種
fmt.Println(m1) // map[]

Type


// type 建立類型
type mesType uint16 // 現在mesType也是一個型別,等同於uint16的格式,但是屬於不同類型,彼此之間賦值一樣需要類型轉換
var textMes mesType = 1000
fmt.Printf("textMes=%v, Type of textMes=%T\n", textMes, textMes) // textMes=1000, Type of textMes=note.mesType

// type 建立別名
type myUint16 = uint16
var myu16 myUint16 = u1000
fmt.Printf("myu16=%v, Type of myu16=%T\n", myu16, myu16) // myu16=1000, Type of myu16=uint16

// 結j構
// var u2 *User = new(User) // 同下行,但只是建立指標
var u2 *User = &User{
Name: "李四",
}
(*u2).Id = 10001
fmt.Println("u2", *u2) // u2 {李四 10001}
u2.Id = 10002 // 這樣也可以,但這樣是Go裡面內建的語法糖
fmt.Println("u2", *u2) // u2 {李四 10002}

interface


// 從interface還原struct
func Foo(m Mes) { // 假設Mes是你有設好的interface
switch mptr := m.(type) { // 利用 interface.(type),type指向到struct類型
case *textMes: // 如果struct是*textMes
mptr.doTextMesSomeFuc() // 執行textMes Struct獨有的方法
case *textImg: // 如果struct是*textImg
mptr.doTextImgSomeFuc() // 執行textImg Struct獨有的方法
}

}


// 單個interface還原struct
var n1 int = 1
n1i := interface{}(n1) // 現在有一個interface他指向的類型是int
switch mptr := n1i.(type) {
case int:
fmt.Println(mptr) // 1
}
n2 := n1i.(int) // n1i是interface, n2則是int
fmt.Println(n2) // 1
switch mptr := n2.(type) { } // 此行會報錯,因為n2已經是int而不是interface,所以n2是不能用.(type)這個方法的
// PS 可以用isOk判斷.(type)成功 val, isOk = n1i.(type)

channel


// channel 緩衝區
var c1 chan int = make(chan int, 100) // 緩衝區100
c1 <- 1 // 若沒有緩衝區此行會堵塞
// close
close(c1) // 如果沒有這行,外面又是無窮迴圈接收管道資料,外面沒辦法透過isOk來判斷是否資料已經傳輸完畢了
// 透過for range取得管道資料
for v := range c1 {
}
// select case 用於處理沒辦法確認管道何時關閉的情況
Print:
for {
select { // 檢查每個case的管道是否有值
case v:= <-c2: // c2有值可取
...
default: // 所有管道都已經無值可取
...
break Print
}
}
// 直接丟棄管道的值
<- c1

// 單向channel
func foo(a <-chan int, b chan<- int) {
... // a是唯讀channel,b是唯寫channel
}

套件


math/rand


rand.Seed(time.Now().UnixNano()) // 亂數種子
fmt.Println(rand.Intn(10) + 1) // 亂數產生

進制轉換


str := strconv.FormatInt(123, 4)         // 參數(數字,進制)
val, err := strconv.ParseUint(str, 4, 0) // 參數(字串,進制,精度)

strings 字串


// len輸出是字節數(byte),如果包含非ascii的話可以轉換成[]rune再進行len
len([]rune(str))

strings.Contains
strings.Count
strings.Index
strings.LastIndex
strings.Replace
strings.ReplaceAll
strings.Repeat
strings.HasPrefix
strings.HasSuffix
strings.EqualFold // 不分大小寫的 ==
strings.Fields // 以空格拆字串 \t \n \v \f \r U+0085(NEL), U+00A0(NBSP)
strings.Split
strings.SplitAfter // 拆字串,sep留下
strings.SplitN // 拆N次
strings.SplitAfterN
strings.Trim
strings.TrimLeft
strings.TrimRight
strings.TrimPrefix
strings.TrimSuffix
strings.TrimSpace


中文字


utf8.RuneCount(bytes) // 統計字數
utf8.RuneCountInString(str) // 統計字數


時間


時段的類型 => Duration // 一段時間 (d)


time.Sleep(time.Second * 10) 休息十秒
time.ParseDuration // 從字串解析時段 (字串需是google定義的格式)
time.Since == time.Now().Sub(t) // 過去到現在此刻的時段
time.Until == t.Sub(time.Now()) // 現在到未來時刻的時段


時刻 => Time // 某時間當下那一刻


時區 => Location


time.LoadLocation("Asia/Taipei")
time.Now
time.Format
time.NewTicker // 週期計數器


//每0.1秒印出一次'.',在一秒後停止
intChan := make(chan int) // 通道
go func() { // goroutines 子程序 每一秒會給一個值
time.Sleep(time.Second)
intChan <- 1
}()
TickerFor: // 用於跳出無窮迴圈
for {
select { // 檢查通道是否有資料
case <-intChan:
fmt.Println()
break TickerFor

// 每過N time.NewTicker().C會給通道一個值
case <-time.NewTicker(100 * time.Millisecond).C:
fmt.Printf(".")
}
}

time.NewTimer // 一次計數器


Error


一般宣告在文件最上面(import之後),使用駝峰命名


package some

import (
"fmt",
"errors",
)

var ( // 提前寫好的Error讓可預期的錯誤一目瞭然
ErrRequestFail = errors.New("Request is failed")
...
...
)

測試


testing.T 測Bug
testing.B 測速度
t.Log/Logf // 紀錄
t.Fail // 標示此測試失敗,繼續下個測試
t.Error // t.Fail + t.Log
t.Errorf // t.Fail + t.Logf
t.FailNow // 標記此測試失敗,直接停止測試
t.Fatal // t.FailNow + t.Log
t.Fatalf // t.FailNow + t.Logf
t.SkipNow // 跳過當前測試
t.Skip // t.SkipNow + t.Log
t.Skipf // t.SkipNow + t.Logf
t.Skipped // 當前測試是否被跳過


命令列參數


os.Args
flag // 命令裂參數解析


vPtr := flag.Bool("v", false, "版本")
var userName string
flag.StringVar(&userName, "u", "", "用戶名")
flag.Parse()
if *vPtr {
fmt.Println("版本是0.0.1")
}
fmt.Println("當前用戶為:", userName)

Sync


WaitGroup


var wg sync.WaitGroup
waitGroup.Add // +n
waitGroup,Done // -1
waitGroup.Wait // 阻塞等到歸零


Cond 多協程阻塞


將一個for用多協程阻塞並喚醒,因為是多協程所以順序不是0~9


var mutex sync.Mutex // 需要一個互斥鎖
cond := sync.NewCond(&mutex)
for i := 0; i < 10; i++ {
go func(n int) {
cond.L.Lock() // 鎖定協程
cond.Wait() // 等待直到協程被喚醒
fmt.Printf("協程%v被喚醒了\n", n)
cond.L.Unlock() // 喚醒之後要解鎖,否則後面無法繼續喚醒
}(i)
}
for i := 0; i < 15; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Print(".")
if i == 4 {
fmt.Println()
cond.Signal() // 先喚醒一個協程
}
if i == 9 {
fmt.Println()
cond.Broadcast() // 把剩下的協程憶起喚醒
}
}
// .....
// 協程0被喚醒了
// .....
// 協程3被喚醒了
// 協程2被喚醒了
// 協程1被喚醒了
// 協程4被喚醒了
// 協程9被喚醒了
// 協程5被喚醒了
// 協程6被喚醒了
// 協程7被喚醒了
// 協程8被喚醒了
// .....

Once 確保一個函數只會被執行一次


once.Do(func)


Map


在多個goroutines下併發使用可以不需要額外的控制或互斥鎖


Pool


同sync.Map,但是是無序的


sort


sort.Slice(any, func(i, j) bool) // 照指定規則排序,i j 會是 any中提取出的兩個值


JSON


json.Marshal // 將結構體編碼成JSON
json.Indent // 格式化JSON字串



  • 結構體可以加入 json:"keyName"來在使用json.Marshal時改變鍵名

  • 結構體可以加入 json:"omitempty"來在使用json.Marshal時跳過零值(false, 0, nil ...)

  • 結構體可以加入 json:"-"來在使用json.Marshal時直接跳過
    json.Unmmarshal // 將JSON解碼成結構體
    json.Valid


TCP


TCP的流對應GO的[]byte


l, err := net.Listen("tcp", "ip:port") // 監聽...
conn, err := l.Accept() // 一但有連入,取的連接

conn, err := net.Dial("tcp", "ip:port") // TCP到...


LevelDB 內置


可以隨GO自帶的key:value資料庫


Redis


github
redis.Options // 結構體,redis連結設定
redis.Client // 結構體,redis客戶端
context.Context // 接口,用於在任務中斷後,再繼續執行 (context.Background())
redis.Cmd // 結構體,代表一個命令



留言

這個網誌中的熱門文章

成人剪舌繫帶聽過嗎?我剪了!!

Scp - ssh 的遠端檔案傳輸指令

睡覺使你更有效率