BuBu

Zwj`Blog

My decentralized blog
github
email

Go#

基礎#

命令行參數解析#

package main

import (
   // 需在此處添加代碼。[1]
   "flag"
   "fmt"
)

var name string

func init() {
   // 需在此處添加代碼。[2]
   flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
   // 需在此處添加代碼。[3]
   flag.Parse()
   fmt.Printf("Hello, %s!\n", name)
}

執行

go run main.go -name="Robert"

輸出

Hello, Robert!

變量#

定義#

//變量的聲明
var a int = 10

簡短聲明#

//簡短聲明
b := 20
fmt.Println(a, b)

多重賦值#

//多重賦值
c, d, e := 30, 40, 50
fmt.Println(c, d, e)

交換變量值#

//交換變量值
f, g := 99, 88
fmt.Println(f, g)//99 88
f, g = g, f
fmt.Println(f, g)//88 99

下劃線忽略值#

//忽略值/返回值
h, _ := 10, 20
fmt.Println(h)

格式化輸出#

//格式輸出
fmt.Printf("%d\n", 1)         //%d 1 以整數輸出
fmt.Printf("%f\n", 1.1)       //%f 1.100000 以浮點型輸出
fmt.Printf("%.2f\n", 1.11111) //%.2f 1.11 以浮點型輸出,保留2位小數
fmt.Printf("%t\n", false)     //%t false 以布爾型數據輸出
fmt.Printf("%s\n", "false")   //%s false 以字符串型數據輸出
fmt.Printf("%c\n", 'a')       //%c 'a' 以字符型數據輸出
fmt.Printf("%p\n", &a)        //%p 0x1400012c008 以指針數據輸出地址
fmt.Printf("%T\n", a)  				//%T 輸出該變量的類型

獲取輸入#

//獲取用戶輸入
fmt.Scan(&a)   //輸入88
fmt.Println(a) //輸出88

字符和字節#

var n byte = 'a' //byte只能存單個字符
var m rune = '' //rune相當於其他語言裡的char,存儲單個Unicode字符/中文等等
fmt.Println(n,m)

常量#

定義#

常量存儲一直不會發生變化的數據

//常量的定義
const a = false

iota 枚舉#

常量聲明可以使用 iota 常量生成器初始化,它用於生成一組以相似規則初始化的常量,但是不用每行都寫一遍初始化表達式。注意:在一個 const 聲明語句中,在第一個聲明的常量所在的行,iota 將會被置為 0,然後在每一個有常量聲明的行加一。

const (
   b = iota
   c = iota
   d = iota
)
fmt.Println(b, c, d) //0 1 2

const (
   f = iota
   g
   h
)
fmt.Println(f, g, h) //0 1 2

如果定義枚舉時,常量寫在同一行值是相同的,換一行就加一

const (
   i    = iota
   j, k = iota, iota
)
fmt.Println(i, j, k) //0 1 1

運算符#

算數運算符#

運算符描述
+相加
-相減
*相乘
/相除
%求余

注意: ++(自增)和–(自減)在 Go 語言中是單獨的語句,並不是運算符。

關係運算符#

運算符描述
==檢查兩個值是否相等,如果相等返回 True 否則返回 False。
!=檢查兩個值是否不相等,如果不相等返回 True 否則返回 False。
>檢查左邊值是否大於右邊值,如果是返回 True 否則返回 False。
>=檢查左邊值是否大於等於右邊值,如果是返回 True 否則返回 False。
<檢查左邊值是否小於右邊值,如果是返回 True 否則返回 False。
<=檢查左邊值是否小於等於右邊值,如果是返回 True 否則返回 False。

邏輯運算符#

運算符描述
&&邏輯 AND 運算符。 如果兩邊的操作數都是 True,則為 True,否則為 False。
ll邏輯 OR 運算符。 如果兩邊的操作數有一個 True,則為 True,否則為 False。
!邏輯 NOT 運算符。 如果條件為 True,則為 False,否則為 True。

位運算符#

位運算符對整數在內存中的二進制位進行操作。

運算符描述
&參與運算的兩數各對應的二進位相與。(兩位均為 1 才為 1)
l參與運算的兩數各對應的二進位相或。(兩位有一個為 1 就為 1)
^參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果為 1。(兩位不一樣則為 1)
<<左移 n 位就是乘以 2 的 n 次方。“a<<b” 是把 a 的各二進位全部左移 b 位,高位丟棄,低位補 0。
>>右移 n 位就是除以 2 的 n 次方。“a>>b” 是把 a 的各二進位全部右移 b 位。

賦值運算符#

運算符描述
=簡單的賦值運算符,將一個表達式的值賦給一個左值
+=相加後再賦值
-=相減後再賦值
*=相乘後再賦值
/=相除後再賦值
%=求余後再賦值
<<=左移後賦值
>>=右移後賦值
&=按位與後賦值
l=按位或後賦值
^=按位異或後賦值

類型轉換#

//類型轉換
c := 3
d := float64(c)
fmt.Println(c, d)

流程控制#

If 語句 (Go 不支持三目)#

• 可省略條件表達式括號。
• 持初始化語句,可定義代碼塊局部變量。 
• 代碼塊左 括號必須在條件表達式尾部。

if 布爾表達式 {
  /* 在布爾表達式為 true 時執行 */
} 

可以在表達式中聲明變量

x := 0
if n := "abc"; x > 0 { // 初始化語句未必就是定義變量, 如 println("init") 也是可以的。
   println(n[2])
} else if x < 0 { // 注意 else if 和 else 左大括號位置。
   println(n[1])
} else {
   println(n[0])
}

Switch 語句#

語法#

Go 中默認帶 Break, 如果不需要 Break 可以用 fallthrough 關鍵字

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

變量 var1 可以是任何類型,而 val1 和 val2 則可以是同類型的任意值。類型不被局限於常量或整數,但必須是相同的類型;或者最終結果為相同類型的表達式。
可以同時測試多個可能符合條件的值,使用逗號分割它們,例如:case val1, val2, val3。

package main

import "fmt"

func main() {
   /* 定義局部變量 */
   var grade string = "B"
   var marks int = 90

   switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"
      default: grade = "D"  
   }

   switch {
      case grade == "A" :
         fmt.Printf("優秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" )
   }
   fmt.Printf("你的等級是 %s\n", grade )
}    

以上代碼執行結果為:

優秀!
你的等級是 A  
Type Switch#
switch x.(type){
    case type:
       statement(s)      
    case type:
       statement(s)
    /* 你可以定義任意個數的case */
    default: /* 可選 */
       statement(s)
}  
實例#
package main

import "fmt"

func main() {
    var x interface{}
    //寫法一:
    switch i := x.(type) { // 帶初始化語句
    case nil:
        fmt.Printf(" x 的類型 :%T\r\n", i)
    case int:
        fmt.Printf("x 是 int 型")
    case float64:
        fmt.Printf("x 是 float64 型")
    case func(int) float64:
        fmt.Printf("x 是 func(int) 型")
    case bool, string:
        fmt.Printf("x 是 bool 或 string 型")
    default:
        fmt.Printf("未知型")
    }
    //寫法二
    var j = 0
    switch j {
    case 0:
    case 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }
    //寫法三
    var k = 0
    switch k {
    case 0:
        println("fallthrough")
        fallthrough
        /*
            Go的switch非常靈活,表達式不必是常量或整數,執行的過程從上至下,直到找到匹配項;
            而如果switch沒有表達式,它會匹配true。
            Go裡面switch默認相當於每個case最後帶有break,
            匹配成功後不會自動向下執行其他case,而是跳出整個switch,
            但是可以使用fallthrough強制執行後面的case代碼。
        */
    case 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }
    //寫法三
    var m = 0
    switch m {
    case 0, 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }
    //寫法四
    var n = 0
    switch { //省略條件表達式,可當 if...else if...else
    case n > 0 && n < 10:
        fmt.Println("i > 0 and i < 10")
    case n > 10 && n < 20:
        fmt.Println("i > 10 and i < 20")
    default:
        fmt.Println("def")
    }
}   

以上代碼執行結果為:

    x 的類型 :<nil>
    fallthrough
    1
    1
    def

For 語句#

for 循環是一個循環控制結構,可以執行指定次數的循環。

語法#

Go 語言的 For 循環有 3 中形式,只有其中的一種使用分號。

    for init; condition; post { }
    for condition { }
    for { }
    init: 一般為賦值表達式,給控制變量賦初值;
    condition: 關係表達式或邏輯表達式,循環控制條件;
    post: 一般為賦值表達式,給控制變量增量或減量。
    for語句執行過程如下:
    ①先對表達式 init 賦初值;
    ②判別賦值表達式 init 是否滿足給定 condition 條件,若其值為真,滿足循環條件,則執行循環體內語句,然後執行 post,進入第二次循環,再判別 condition;否則判斷 condition 的值為假,不滿足條件,就終止for循環,執行循環體外語句。   
s := "abc"

for i, n := 0, len(s); i < n; i++ { // 常見的 for 循環,支持初始化語句。
    println(s[i])
}

n := len(s)
for n > 0 {                // 替代 while (n > 0) {}
    n-- 
    println(s[n])        // 替代 for (; n > 0;) {}
}

for {                    // 替代 while (true) {}
    println(s)            // 替代 for (;;) {}
}  

不要期望編譯器能理解你的想法,在初始化語句中計算出全部結果是個好主意。

package main

func length(s string) int {
    println("call length.")
    return len(s)
}

func main() {
    s := "abcd"

    for i, n := 0, length(s); i < n; i++ {     // 避免多次調用 length 函數。
        println(i, s[i])
    } 
}  

輸出:

    call length.
    0 97
    1 98
    2 99
    3 100 
實例#
package main

import "fmt"

func main() {

   var b int = 15
   var a int

   numbers := [6]int{1, 2, 3, 5}

   /* for 循環 */
   for a := 0; a < 10; a++ {
      fmt.Printf("a 的值為: %d\n", a)
   }

   for a < b {
      a++
      fmt.Printf("a 的值為: %d\n", a)
      }

   for i,x:= range numbers {
      fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
   }   
} 

以上實例運行輸出結果為:

    a 的值為: 0
    a 的值為: 1
    a 的值為: 2
    a 的值為: 3
    a 的值為: 4
    a 的值為: 5
    a 的值為: 6
    a 的值為: 7
    a 的值為: 8
    a 的值為: 9
    a 的值為: 1
    a 的值為: 2
    a 的值為: 3
    a 的值為: 4
    a 的值為: 5
    a 的值為: 6
    a 的值為: 7
    a 的值為: 8
    a 的值為: 9
    a 的值為: 10
    a 的值為: 11
    a 的值為: 12
    a 的值為: 13
    a 的值為: 14
    a 的值為: 15
    第 0 位 x 的值 = 1
    第 1 位 x 的值 = 2
    第 2 位 x 的值 = 3
    第 3 位 x 的值 = 5
    第 4 位 x 的值 = 0
    第 5 位 x 的值 = 0  
循環嵌套#

在 for 循環中嵌套一個或多個 for 循環

語法

以下為 Go 語言嵌套循環的格式:

for [condition |  ( init; condition; increment ) | Range]
{
   for [condition |  ( init; condition; increment ) | Range]
   {
      statement(s)
   }
   statement(s)
}  
實例

以下實例使用循環嵌套來輸出 2 到 100 間的素數:

package main

import "fmt"

func main() {
   /* 定義局部變量 */
   var i, j int

   for i=2; i < 100; i++ {
      for j=2; j <= (i/j); j++ {
         if(i%j==0) {
            break // 如果發現因子,則不是素數
         }
      }
      if(j > (i/j)) {
         fmt.Printf("%d  是素數\n", i)
      }
   }  
}  

以上實例運行輸出結果為:

    2  是素數
    3  是素數
    5  是素數
    7  是素數
    11  是素數
    13  是素數
    17  是素數
    19  是素數
    23  是素數
    29  是素數
    31  是素數
    37  是素數
    41  是素數
    43  是素數
    47  是素數
    53  是素數
    59  是素數
    61  是素數
    67  是素數
    71  是素數
    73  是素數
    79  是素數
    83  是素數
    89  是素數
    97  是素數  
無限循環#

如過循環中條件語句永遠不為 false 則會進行無限循環,我們可以通過 for 循環語句中只設置一個條件表達式來執行無限循環:

package main

import "fmt"

func main() {
    for true  {
        fmt.Printf("這是無限循環。\n");
    }
}  

Range 語句#

語法#

Golang range 類似迭代器操作,返回 (索引,值) 或 (鍵,值)。

for 循環的 range 格式可以對 slice、map、數組、字符串等進行迭代循環。格式如下:

for key, value := range oldMap {
    newMap[key] = value
}   
1st value2nd value
stringindexs[index]unicode, rune
array/sliceindexs[index]
mapkeym[key]
channelelement

可忽略不想要的返回值,或 "_" 這個特殊變量。

package main

func main() {
    s := "abc"
    // 忽略 2nd value,支持 string/array/slice/map。
    for i := range s {
        println(s[i])
    }
    // 忽略 index。
    for _, c := range s {
        println(c)
    }
    // 忽略全部返回值,仅迭代。
    for range s {

    }

    m := map[string]int{"a": 1, "b": 2}
    // 返回 (key, value)。
    for k, v := range m {
        println(k, v)
    }
}   

輸出結果:

    97
    98
    99
    97
    98
    99
    a 1
    b 2  
重要注意#

*注意,range 會複製對象。

package main

import "fmt"

func main() {
    a := [3]int{0, 1, 2}

    for i, v := range a { // index、value 都是從複製品中取出。

        if i == 0 { // 在修改前,我們先修改原數組。
            a[1], a[2] = 999, 999
            fmt.Println(a) // 確認修改有效,輸出 [0, 999, 999]。
        }

        a[i] = v + 100 // 使用複製品中取出的 value 修改原數組。

    }

    fmt.Println(a) // 輸出 [100, 101, 102]。
}   

輸出結果:

    [0 999 999]
    [100 101 102]   

建議改用引用類型,其底層數據不會被複製。

package main

func main() {
    s := []int{1, 2, 3, 4, 5}

    for i, v := range s { // 複製 struct slice { pointer, len, cap }。

        if i == 0 {
            s = s[:3]  // 對 slice 的修改,不會影響 range。
            s[2] = 100 // 對底層數據的修改。
        }

        println(i, v)
    }
}   

輸出結果:

    0 1
    1 2
    2 100
    3 4
    4 5

另外兩種引用類型 map、channel 是指針包裝,而不像 slice 是 struct。

for 和 range 區別#

主要是使用場景不同

for 可以 遍歷 array 和 slice || 遍歷 key 為整型遞增的 map || 遍歷 string

for range 可以完成所有 for 可以做的事情,卻能做到 for 不能做的,包括

遍歷 key 為 string 類型的 map 並同時獲取 key 和 value || 遍歷 channel

函數#

定義#

函數特點#
    • 無需聲明原型。
    • 支持不定 變參。
    • 支持多返回值。
    • 支持命名返回參數。 
    • 支持匿名函數和閉包。
    • 函數也是一種類型,一個函數可以賦值給變量。

    • 不支持 嵌套 (nested) 一個包不能有兩個名字一樣的函數。
    • 不支持 重載 (overload) 
    • 不支持 默認參數 (default parameter)。 
函數聲明#

函數聲明包含一個函數名,參數列表, 返回值列表和函數體。如果函數沒有返回值,則返回列表可以省略。函數從第一條語句開始執行,直到執行 return 語句或者執行函數的最後一條語句。

函數可以沒有參數或接受多個參數。

注意類型在變量名之後 。

當兩個或多個連續的函數命名參數是同一類型,則除了最後一個類型之外,其他都可以省略。

函數可以返回任意數量的返回值。

使用關鍵字 func 定義函數,左大括號依舊不能另起一行。

func test(x, y int, s string) (int, string) {
    // 類型相同的相鄰參數,參數類型可合併。 多返回值必須用括號。
    n := x + y          
    return n, fmt.Sprintf(s, n)
}

函數是第一類對象,可作為參數傳遞。建議將複雜簽名定義為函數類型,以便於閱讀。

package main

import "fmt"

func test(fn func() int) int {
    return fn()
}
// 定義函數類型。
type FormatFunc func(s string, x, y int) string 

func format(fn FormatFunc, s string, x, y int) string {
    return fn(s, x, y)
}

func main() {
    s1 := test(func() int { return 100 }) // 直接將匿名函數當參數。

    s2 := format(func(s string, x, y int) string {
        return fmt.Sprintf(s, x, y)
    }, "%d, %d", 10, 20)

    println(s1, s2)
}

輸出結果:

    100 10, 20

有返回值的函數,必須有明確的終止語句,否則會引發編譯錯誤。

參數#

普通形參#

函數定義時指出,函數定義時有參數,該變量可稱為函數的形參。形參就像定義在函數體內的局部變量。

但當調用函數,傳遞過來的變量就是函數的實參,函數可以通過兩種方式來傳遞參數:

值傳遞

指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。

func swap(x, y int) int {
       ... ...
  }
引用傳遞

是指在調用函數時將實際參數的地址傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數。

package main

import (
    "fmt"
)

/* 定義相互交換值的函數 */
func swap(x, y *int) {
    var temp int

    temp = *x /* 保存 x 的值 */
    *x = *y   /* 將 y 值賦給 x */
    *y = temp /* 將 temp 值賦給 y*/

}

func main() {
    var a, b int = 1, 2
    /*
        調用 swap() 函數
        &a 指向 a 指針,a 變量的地址
        &b 指向 b 指針,b 變量的地址
    */
    swap(&a, &b)

    fmt.Println(a, b)
}

輸出結果:

    2 1

在默認情況下,Go 語言使用的是值傳遞,即在調用過程中不會影響到實際參數。

注意 1

無論是值傳遞,還是引用傳遞,傳遞給函數的都是變量的副本,不過,值傳遞是值的拷貝。引用傳遞是地址的拷貝,一般來說,地址拷貝更為高效。而值拷貝取決於拷貝的對象大小,對象越大,則性能越低。

注意 2

map、slice、chan、指針、interface 默認以引用的方式傳遞。

不定參數#

不定參數傳值就是函數的參數不是固定的,後面的類型是固定的。(可變參數)

Golang 可變參數本質上就是 slice。只能有一個,且必須是最後一個。

在參數賦值時可以不用用一個一個的賦值,可以直接傳遞一個數組或者切片,特別注意的是在參數後加上 “…” 即可。

func main() {
   //不定參函數
   test(1, 2, 3, 4)//[1 2 3 4]
}

func test(args ...int) {
   fmt.Println(args)
}
Slice 傳入不定參方法

使用 slice 對象做變參時,必須展開。(slice...)

package main

import (
    "fmt"
)

func test(s string, n ...int) string {
    var x int
    for _, i := range n {
        x += i
    }

    return fmt.Sprintf(s, x)
}

func main() {
    s := []int{1, 2, 3}
    res := test("sum: %d", s...)    // slice... 展開slice
    println(res)
}

返回值#

"_"標識符,用來忽略函數的某個返回值

Go 的返回值可以被命名,並且就像在函數體開頭聲明的變量那樣使用。

返回值的名稱應當具有一定的意義,可以作為文檔使用。

沒有參數的 return 語句返回各個返回變量的當前值。這種用法被稱作 “裸” 返回。

直接返回語句僅應當用在像下面這樣的短函數中。在長的函數中它們會影響代碼的可讀性。

package main

import (
    "fmt"
)

func add(a, b int) (c int) {
    c = a + b
    return
}

func calc(a, b int) (sum int, avg int) {
    sum = a + b
    avg = (a + b) / 2

    return
}

func main() {
    var a, b int = 1, 2
    c := add(a, b)
    sum, avg := calc(a, b)
    fmt.Println(a, b, c, sum, avg)
}

輸出結果:

    1 2 3 3 1 

Golang 返回值不能用容器對象接收多返回值。只能用多個變量,或 "_" 忽略。

package main

func test() (int, int) {
    return 1, 2
}

func main() {
    // s := make([]int, 2)
    // s = test()   // Error: multiple-value test() in single-value context

    x, _ := test()
    println(x)
}

輸出結果:

    1 

多返回值可直接作為其他函數調用實參。

package main

func test() (int, int) {
    return 1, 2
}

func add(x, y int) int {
    return x + y
}

func sum(n ...int) int {
    var x int
    for _, i := range n {
        x += i
    }

    return x
}

func main() {
    println(add(test()))
    println(sum(test()))
}

輸出結果:

    3
    3

命名返回參數可看做與形參類似的局部變量,最後由 return 隱式返回。

package main

func add(x, y int) (z int) {
    z = x + y
    return
}

func main() {
    println(add(1, 2))
}

輸出結果:

    3  

命名返回參數可被同名局部變量遮蔽,此時需要顯式返回。

func add(x, y int) (z int) {
    { // 不能在一個級別,引發 "z redeclared in this block" 錯誤。
        var z = x + y
        // return   // Error: z is shadowed during return
        return z // 必須顯式返回。
    }
}

命名返回參數允許 defer 延遲調用通過閉包讀取和修改。

package main

func add(x, y int) (z int) {
    defer func() {
        z += 100
    }()

    z = x + y
    return
}

func main() {
    println(add(1, 2)) 
}

輸出結果:

    103

顯式 return 返回前,會先修改命名返回參數。

package main

func add(x, y int) (z int) {
    defer func() {
        println(z) // 輸出: 203
    }()

    z = x + y
    return z + 200 // 執行順序: (z = z + 200) -> (call defer) -> (return)
}

func main() {
    println(add(1, 2)) // 輸出: 203
}

輸出結果:

    203
    203

匿名函數#

匿名函數是指不需要定義函數名的一種函數實現方式。1958 年 LISP 首先采用匿名函數。

在 Go 裡面,函數可以像普通變量一樣被傳遞或使用,Go 語言支持隨時在代碼裡定義匿名函數。

匿名函數由一個不帶函數名的函數聲明和函數體組成。匿名函數的優越性在於可以直接使用函數內的變量,不必申明。

package main

import (
    "fmt"
    "math"
)

func main() {
    getSqrt := func(a float64) float64 {
        return math.Sqrt(a)
    }
    fmt.Println(getSqrt(4))
}

輸出結果:

 2
閉包#

因為函數在調用結束後會進行銷毀,所有函數內部的變量值沒有進行保存

func test1(a int) {
   a++
   fmt.Println(a)
}
func main() {
   a := 1
   for i := 0; i < 10; i++ {
      test1(a)
   }
}

輸出結果

2
2
2
2
2
2
2
2
2
2

返回值也是函數的函數稱為閉包

可以通過匿名函數和閉包 實現函數在棧區實現持久化

func main(){
  a := 1
  f := test2(a)
  for i := 0; i < 10; i++ {
     fmt.Println(f())
  }
}
func test2(a int) func() int {
   return func() int {
      
      return a
   }
}

輸出結果

2
3
4
5
6
7
8
9
10
11

遞歸函數#

遞歸,就是在運行的過程中調用自己。
一個函數調用自己,就叫做遞歸函數。

構成遞歸需具備的條件:

    1.子問題須與原始問題為同樣的事,且更為簡單。
    2.不能無限制地調用本身,須有個出口,化簡為非遞歸狀況處理。
數字階乘#
package main

import "fmt"

func factorial(i int) int {
    if i <= 1 {
        return 1
    }
    return i * factorial(i-1)
}

func main() {
    var i int = 7
    fmt.Printf("Factorial of %d is %d\n", i, factorial(i))
}

輸出結果:

    Factorial of 7 is 5040
斐波那契數列#

這個數列從第 3 項開始,每一項都等於前兩項之和。

package main

import "fmt"

func fibonaci(i int) int {
    if i == 0 {
        return 0
    }
    if i == 1 {
        return 1
    }
    return fibonaci(i-1) + fibonaci(i-2)
}

func main() {
    var i int
    for i = 0; i < 10; i++ {
        fmt.Printf("%d\n", fibonaci(i))
    }
}

輸出結果:

    0
    1
    1
    2
    3
    5
    8
    13
    21
    34

defer 延遲調用#

defer 特性#
    1. 關鍵字 defer 用於註冊延遲調用。
    2. 這些調用直到 return 前才被執。因此,可以用來做資源清理。
    3. 多個defer語句,按先進後出的方式執行。
    4. defer語句中的變量,在defer聲明時就決定了。
defer 用途#
    1. 關閉文件句柄
    2. 鎖資源釋放
    3. 數據庫連接釋放

defer 是先進後出的

package main

import "fmt"

func main() {
    var whatever [5]struct{}

    for i := range whatever {
        defer fmt.Println(i)
    }
} 

輸出結果:

    4
    3
    2
    1
    0
defer 和閉包#

image-20220416190037239

defer 會延遲函數的執行,雖然立即調用了一匿名函數,但是該匿名函數不會執行,等整個 main ( ) 函數結束之前在去調用執行匿名函數

image-20220416190056827

由於匿名函數前面加上了 defer 所以,匿名函數沒有立即執行。但是問題是,程序從上開始執行當執行到匿名函數時,雖然沒有立即調用執行匿名函數,但是已經完成了參數的傳遞。

defer 和異常#

多個 defer 註冊,按 FILO 次序執行 (先進後出)。哪怕函數或某個延遲調用發生錯誤,這些調用依舊會被執行。

package main

func test(x int) {
    defer println("a")
    defer println("b")

    defer func() {
        println(100 / x) // div0 異常未被捕獲,逐步往外傳遞,最終終止進程。
    }()

    defer println("c")
}

func main() {
    test(0)
} 

輸出結果:

    c
    b
    a
    panic: runtime error: integer divide by zero
重要#

*延遲調用參數在註冊時求值或複製,可用指針或閉包 “延遲” 讀取。

package main

func test() {
    x, y := 10, 20

    defer func(i int) {
        println("defer:", i, y) // y 閉包引用
    }(x) // x 被複製 <<<<<------這裡閉包將參數傳遞進來

    x += 10
    y += 100
    println("x =", x, "y =", y)
}

func main() {
    test()
}  

輸出結果:

    x = 20 y = 120
    defer: 10 120
濫用 defer#

*濫用 defer 可能會導致性能問題,尤其是在一個 “大循環” 裡。

package main

import (
    "fmt"
    "sync"
    "time"
)

var lock sync.Mutex

func test() {
    lock.Lock()
    lock.Unlock()
}

func testdefer() {
    lock.Lock()
    defer lock.Unlock()
}

func main() {
    func() {
        t1 := time.Now()

        for i := 0; i < 10000; i++ {
            test()
        }
        elapsed := time.Since(t1)
        fmt.Println("test elapsed: ", elapsed)
    }()
    func() {
        t1 := time.Now()

        for i := 0; i < 10000; i++ {
            testdefer()
        }
        elapsed := time.Since(t1)
        fmt.Println("testdefer elapsed: ", elapsed)
    }()

}

輸出結果:

    test elapsed:  223.162µs
    testdefer elapsed:  781.304µs
defer 陷阱#
defer 與 closure
package main

import (
    "errors"
    "fmt"
)

func foo(a, b int) (i int, err error) {
    defer fmt.Printf("first defer err %v\n", err)
    defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err)
    defer func() { fmt.Printf("third defer err %v\n", err) }()
    if b == 0 {
        err = errors.New("divided by zero!")
        return
    }

    i = a / b
    return
}

func main() {
    foo(2, 0)
}  

輸出結果:

    third defer err divided by zero!
    second defer err <nil>
    first defer err <nil>

解釋:如果 defer 後面跟的不是一個 closure 最後執行的時候我們得到的並不是最新的值。

defer 與 return
package main

import "fmt"

func foo() (i int) {

    i = 0
    defer func() {
        fmt.Println(i)
    }()

    return 2
}

func main() {
    foo()
}

輸出結果:

    2

解釋:在有具名返回值的函數中(這裡具名返回值為 i),執行 return 2 的時候實際上已經將 i 的值重新賦值為 2。所以 defer closure 輸出結果為 2 而不是 1。

defer nil 函數
package main

import (
    "fmt"
)

func test() {
    var run func() = nil
    defer run()
    fmt.Println("runs")
}

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    test()
} 

輸出結果:

runs
runtime error: invalid memory address or nil pointer dereference

解釋:名為 test 的函數一直運行至結束,然後 defer 函數會被執行且會因為值為 nil 而產生 panic 異常。然而值得注意的是,run () 的聲明是沒有問題,因為在 test 函數運行完成後它才會被調用。

IO 流#

package main

import (
   "bufio"
   "fmt"
   "io"
   "log"
   "os"
)

func main01() {
   file := Create("a.txt")
   defer func(file *os.File) {
      err := file.Close()
      if err != nil {
         log.Fatal("文件關閉失敗")
      }
   }(file)
   Write(file)

   fp := Create("b.txt")
   defer func(file *os.File) {
      err := file.Close()
      if err != nil {
         log.Fatal("文件關閉失敗")
      }
   }(fp)
   //b := []byte{'h', 'e', 'l', 'l', 'o'}
   b := []byte("HelloWorld")
   _, _ = fp.Write(b)
}

func Write(file *os.File) {
   n, _ := file.WriteString("文件寫入")
   fmt.Println(n)
   n1, _ := file.WriteString("文件hhh")
   fmt.Println(n1)
}

// Create 創建文件
func Create(path string) (file *os.File) {
   //創建文件 文件路徑
   file, err := os.Create(path)
   if err != nil {
      log.Fatal(err.Error())
   }
   fmt.Println("文件創建成功")
   return file
}

func main02() {
   //打開文件
   file, err := os.OpenFile("a.txt", os.O_RDWR, 6)
   defer file.Close()
   if err != nil {
      fmt.Println(err.Error())
   }
   //覆蓋文件原來的內容進行寫入
   _, err = file.WriteAt([]byte("芜湖"), 3)
   if err != nil {
      fmt.Errorf(err.Error())
   }
}

func main03() {
   //打開文件
   file, err := os.OpenFile("a.txt", os.O_RDWR, 6)
   defer file.Close()
   if err != nil {
      fmt.Println(err.Error())
   }
   //將偏移量移動到文件末尾
   seek, err := file.Seek(0, io.SeekEnd)
   if err != nil {
      fmt.Errorf(err.Error())
   }
   _, err = file.WriteAt([]byte("芜湖"), seek)
   if err != nil {
      fmt.Errorf(err.Error())
   }
}

func main04() {
   //打開文件
   file, err := os.Open("b.txt")
   if err != nil {
      fmt.Errorf(err.Error())
   }
   b := make([]byte, 1024)
   for i := 0; i < len(b); i++ {
      file.Read(b)
   }
   fmt.Println(b)
}

func main05() {
   //打開文件
   file, err := os.Open("b.txt")
   if err != nil {
      fmt.Errorf(err.Error())
   }
   defer func(file *os.File) {
      err := file.Close()
      if err != nil {
         fmt.Println("文件關閉失敗")
      }
   }(file)
   r := bufio.NewReader(file)
   b, _ := r.ReadBytes('\n')
   fmt.Println(string(b))
   b, _ = r.ReadBytes('\n')
}

//文件拷貝
func main() {
   file, err := os.Open("labuladong.pdf")
   if err != nil {
      fmt.Println("文件打開失敗")
   }
   defer func(file *os.File) {
      err := file.Close()
      if err != nil {
         fmt.Println("關閉失敗")
      }
   }(file)

   fp, err := os.Create("labuladonghhhh.pdf")
   if err != nil {
      fmt.Println("文件創建失敗")
   }
   defer func(fp *os.File) {
      err := fp.Close()
      if err != nil {
         fmt.Println("關閉失敗")
      }
   }(fp)

   data := make([]byte, 1*1024*1024)
   for {
      n, err := file.Read(data)
      if err == io.EOF {
         break
      }
      fp.Write(data[:n])
   }
}

Test 測試#

例如測試這個方法

func calcTriangle(a, b int) int {
   var c int
   c = int(math.Sqrt(float64(a*a + b*b)))
   return c
}

可以在同目錄下創建對應的 包名_test.go

image-20220418005055464

使用表格驅動測試

func Test_calcTriangle(t *testing.T) {
   tests := []struct {
      a, b, c int
   }{
      {3, 4, 5},
      {8, 15, 17},
      {6, 8, 10},
      {12, 35, 37},
      {30000, 40000, 50000},
   }
   for _, tt := range tests {
      if got := calcTriangle(tt.a, tt.b); got != tt.c {
         t.Errorf("calcTriangle() = %v, want %v", got, tt.c)
      }
   }
}

testing.B 是性能測試

func BenchmarkName(b *testing.B) {
   for i := 0; i < b.N; i++ {
      arr := []int{1, 9, 10, 30, 2, 5, 45, 8, 63, 234, 12}
      for i := 0; i < len(arr); i++ {
         for j := i + 1; j < len(arr); j++ {
            if arr[i] > arr[j] {
               arr[i], arr[j] = arr[j], arr[i]
            }
         }
      }
   }
}

標準庫#

strings#

package main

import (
   "fmt"
   "strconv"
   "strings"
)

func main01() {
   //判斷字符串是否包含在目標字符串裡
   str := "hello world"
   if isCont := strings.Contains(str, "hello"); isCont {
      fmt.Println("存在")
   } else {
      fmt.Println("不存在")
   }

   //字符串拼接
   s := []string{"1234", "3876", "9017", "1859"}
   str = strings.Join(s, "-")
   fmt.Println(str)

   //返回目標字符串第一次出現的字符
   str1 := "日照香爐生紫煙"
   fmt.Println(strings.Index(str1, "生"))

   //字符串重複
   str2 := "日照香爐生紫煙"
   repeat := strings.Repeat(str2, 2)
   fmt.Println(repeat)

   //字符串替換
   str3 := "日照香爐生紫煙煙"
   //源字符,替換字符,替換成的字符,替換次數 -1代表全部替換
   replace := strings.Replace(str3, "煙", "hhh", -1)
   fmt.Println(replace)

   //字符串分割
   str4 := "日,照,香,爐,生,紫,煙"
   split := strings.Split(str4, ",")
   fmt.Println(split)

   //去除前後空格
   str5 := "           日照香爐生紫煙              "
   trim := strings.Trim(str5, " ")
   fmt.Println(trim)

   str6 := "           日 照 香   爐  生 紫 烟              "
   fields := strings.Fields(str6)
   fmt.Println(fields)
}

strconv#

func main02() {
   //字符串轉換
   formatBool := strconv.FormatBool(true)
   fmt.Println(formatBool)

   //目標值 進制
   s := strconv.FormatInt(123, 10)
   fmt.Println(s)
   s = strconv.FormatInt(3, 3)
   fmt.Println(s)

   //數字轉成10進制
   itoa := strconv.Itoa(10)
   fmt.Println(itoa)
   //數據 'f' 小數點後幾位保留 以幾位處理
   //會四捨五入
   float := strconv.FormatFloat(3.1232224, 'f', 3, 64)
   fmt.Println(float)
}

func main() {
   //將字符串轉成其他類型
   str := "false"
   parseBool, _ := strconv.ParseBool(str)
   fmt.Println(parseBool)
   str1 := "111"
   fmt.Println(strconv.ParseInt(str1, 10, 64))

   atoi, _ := strconv.Atoi(str1)
   fmt.Println(atoi)

   //將整數轉成字符後添加到切片裡
   b := make([]byte, 0, 1024)
   b = strconv.AppendBool(b, false)
   b = strconv.AppendInt(b, 10, 10)
   fmt.Println(string(b))
}

進階#

異常處理#

如果遇到底層的異常,可以使用第三方異常處理包 pkg/errors 進行包裝 wrap,

image-20220512220428303

然後直接返回即可,而不是每個錯誤產生都去打日誌,這樣會導致層層日誌一直打印,重複打印,在上層調用函數展示錯誤的堆棧信息,和上下文信息等,上層可以使用 % v+,err 進行展示堆棧信息

image-20220512220628645

第一條的意思是:如果當前寫的這個方法或者項目被很多其他的項目引用或者使用,最好直接返回錯誤本身,不要進行包裝,因為有可能其他的項目調用本項目會產生錯誤,然後進行了 warp 包裝,導致多重 wrap 嵌套報錯堆棧信息,造成冗余

並發#

互斥鎖#

互斥鎖 Mutex 就提供兩個方法 Lock 和 Unlock:進入臨界區之前調用 Lock 方法, 退出臨界區的時候調用 Unlock 方法

  func(m *Mutex)Lock()
  func(m *Mutex)Unlock()

當一個 goroutine 通過調用 Lock 方法獲得了這個鎖的擁有權後, 其它請求鎖的 goroutine 就會阻塞在 Lock 方法的調用上,直到鎖被釋放並且自己獲取到了這個鎖的擁有權。

實例#
func main() {
   var count = 0
   // 使用WaitGroup等待10個goroutine完成
   var wg sync.WaitGroup
   var lock sync.Mutex
   wg.Add(10)
   for i := 0; i < 10; i++ {
      go func() {
         defer wg.Done()
         // 對變量count執行10次加1
         for j := 0; j < 100000; j++ {
            lock.Lock()
            count++
            lock.Unlock()
         }
      }()
   }
   // 等待10個goroutine完成
   wg.Wait()
   fmt.Println(count)
}

可以使用 Google 提供的工具對代碼進行檢查,就有可能發現並發問題。

go run -race main.go
內部結構實現#
type WaitGroup struct {
    // 避免複製使用的技巧,可以告訴vet工具違反了複製使用的規則
    noCopy noCopy
    // 64bit(8bytes)的值分成兩段,高32bit是計數值,低32bit是waiter的計數
    // 另外32bit是用作信號量的
    // 因為64bit值的原子操作需要64bit對齊,但是32bit編譯器不支持,所以數組中的元素在不同的架構中不一樣,具體處理看下面的方法
    // 總之,會找到對齊的那64bit作為state,其余的32bit做信號量
    state1 [3]uint32
}
// 得到state的地址和信號量的地址
func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {
    if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
        // 如果地址是64bit對齊的,數組前兩個元素做state,后一個元素做信號量
        return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]
    } else {
        // 如果地址是32bit對齊的,數組后兩個元素用來做state,它可以用來做64bit的原子操作,第一个元素32bit用來做信號量
        return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]
    }
}
WaitGroup 的方法實現#
Add/Done 方法
func (wg *WaitGroup) Add(delta int) {
    statep, semap := wg.state()
    // 高32bit是計數值v,所以把delta左移32,增加到計數上
    state := atomic.AddUint64(statep, uint64(delta)<<32)
    v := int32(state >> 32) // 當前計數值
    w := uint32(state) // waiter count
    if v > 0 || w == 0 {
        return
    }
    // 如果計數值v為0並且waiter的數量w不為0,那麼state的值就是waiter的數量
    // 將waiter的數量設置為0,因為計數值v也是0,所以它們俩的組合*statep直接設置為0即可。此時需要並喚醒所有的waiter
    *statep = 0
    for ; w != 0; w-- {
        runtime_Semrelease(semap, false, 0)
    }
}
Done 方法

Done 方法實際就是計數器減 1

func (wg *WaitGroup) Done() {
    wg.Add(-1)
}
Wait 方法
func (wg *WaitGroup) Wait() {
    statep, semap := wg.state()
    
    for {
        state := atomic.LoadUint64(statep)
        v := int32(state >> 32) // 當前計數值
        w := uint32(state) // waiter的數量
        if v == 0 {
            // 如果計數值v為0, 調用這個方法的goroutine不必再等待,繼續執行它後面的邏輯即可
            return
        }
        // 否則把waiter數量加1。期間可能有並發調用Wait的情況,所以最外層使用了一个for循環
        if atomic.CompareAndSwapUint64(statep, state, state+1) {
            // 阻塞休眠等待
            runtime_Semacquire(semap)
            // 被喚醒,不再阻塞,返回
            return
        }
    }
}

常見問題#

計數器設置為負值 / 或調用 Done 次數大於 Add
func main() {
    var wg sync.WaitGroup
    wg.Add(10)
    wg.Add(-10)//將-10作為參數調用Add,計數值被設置為0
    wg.Add(-1)//將-1作為參數調用Add,如果加上-1計數值就會變為負數。這是不對的,所以會觸發panic
}
func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    wg.Done()
    wg.Done()
}
不期望的 Add 時機

必須等所有的 Add 方法調用之後再調用 Wait,否則就可能導致 panic 或者不期望的結果。

func main() {
    var wg sync.WaitGroup
    go dosomething(100, &wg) // 啟動第一個goroutine
    go dosomething(110, &wg) // 啟動第二個goroutine
    go dosomething(120, &wg) // 啟動第三個goroutine
    go dosomething(130, &wg) // 啟動第四個goroutine
    wg.Wait() // 主goroutine等待完成
    fmt.Println("Done")
}
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
    duration := millisecs * time.Millisecond
    time.Sleep(duration) // 故意sleep一段時間
    wg.Add(1)
    fmt.Println("後台執行, duration:", duration)
    wg.Done()
}

原本想的是,等四個 goroutine 都執行完畢後輸出 Done 的信息,但是它的錯誤之處在於,將 WaitGroup.Add 方法的調用放在了子 gorotuine 中。等主 goorutine 調用 Wait 的時候,因為四個任務 goroutine 一開始都休眠,所以可能 WaitGroup 的 Add 方法還沒有被調用,WaitGroup 的計數還是 0,所以它並沒有等待四個子 goroutine 執行完畢才繼續執行,而是立刻執行了下一步。導致這個錯誤的原因是,沒有遵循先完成所有的 Add 之後才 Wait。

正確寫法


func main() {
    var wg sync.WaitGroup
    wg.Add(4) // 預先設定WaitGroup的計數值
    go dosomething(100, &wg) // 啟動第一個goroutine
    go dosomething(110, &wg) // 啟動第二個goroutine
    go dosomething(120, &wg) // 啟動第三個goroutine
    go dosomething(130, &wg) // 啟動第四個goroutine
    wg.Wait() // 主goroutine等待
    fmt.Println("Done")
}
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
    duration := millisecs * time.Millisecond
    time.Sleep(duration)
    fmt.Println("後台執行, duration:", duration)
    wg.Done()
}

前一個 Wait 還沒結束就重用 WaitGroup

因為 WaitGroup 是可以重用的。只要 WaitGroup 的計數值恢復到零值的狀態,那麼它就可以被看作是新創建的 WaitGroup,被重複使用。

實現線程安全的 map#

自己使用讀寫鎖#

使用讀寫鎖自己實現一個 map

type RWMap struct { // 一個讀寫鎖保護的線程安全的map
    sync.RWMutex // 讀寫鎖保護下面的map字段
    m map[int]int
}
// 新建一個RWMap
func NewRWMap(n int) *RWMap {
    return &RWMap{
        m: make(map[int]int, n),
    }
}
func (m *RWMap) Get(k int) (int, bool) { //從map中讀取一個值
    m.RLock()
    defer m.RUnlock()
    v, existed := m.m[k] // 在鎖的保護下從map中讀取
    return v, existed
}
func (m *RWMap) Set(k int, v int) { // 設置一個鍵值對
    m.Lock()              // 鎖保護
    defer m.Unlock()
    m.m[k] = v
}
func (m *RWMap) Delete(k int) { //刪除一個鍵
    m.Lock()                   // 鎖保護
    defer m.Unlock()
    delete(m.m, k)
}
func (m *RWMap) Len() int { // map的長度
    m.RLock()   // 鎖保護
    defer m.RUnlock()
    return len(m.m)
}
func (m *RWMap) Each(f func(k, v int) bool) { // 遍歷map
    m.RLock()             //遍歷期間一直持有讀鎖
    defer m.RUnlock()
    for k, v := range m.m {
        if !f(k, v) {
            return
        }
    }
}
使用第三方的分片鎖 map#

github/orcaman/concurrent-map

特定場景的 map#
sync.Map

1. 只會增長的緩存系統中,一個 key 只寫入一次而被讀很多次;

2. 多個 goroutine 為不相交的鍵集讀、寫和重寫鍵值對。

Store 方法

Store 方法,它是用來設置一個鍵值對,或者更新一個鍵值對的。

func (m *Map) Store(key, value interface{}) {
    read, _ := m.read.Load().(readOnly)
    // 如果read字段包含這個項,說明是更新,cas更新項目的值即可
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return
    }
    // read中不存在,或者cas更新失敗,就需要加鎖訪問dirty了
    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok { // 雙檢查,看看read是否已經存在了
        if e.unexpungeLocked() {
            // 此項目先前已經被刪除了,通過將它的值設置為nil,標記為unexpunged
            m.dirty[key] = e
        }
        e.storeLocked(&value) // 更新
    } else if e, ok := m.dirty[key]; ok { // 如果dirty中有此項
        e.storeLocked(&value) // 直接更新
    } else { // 否則就是一個新的key
        if !read.amended { //如果dirty為nil
            // 需要創建dirty對象,並且標記read的amended為true,
            // 說明有元素它不包含而dirty包含
            m.dirtyLocked()
            m.read.Store(readOnly{m: read.m, amended: true})
        }
        m.dirty[key] = newEntry(value) //將新值增加到dirty對象中
    }
    m.mu.Unlock()
}

可以看出,Store 既可以是新增元素,也可以是更新元素。如果運氣好的話,更新的是已存在的未被刪除的元素,直接更新即可,不會用到鎖。如果運氣不好,需要更新(重用)刪除的對象、更新還未提升的 dirty 中的對象,或者新增加元素的時候就會使用到了鎖,這個時候,性能就會下降。

所以從這一點來看,sync.Map 適合那些只會增長的緩存系統,可以進行更新,但是不要刪除,並且不要頻繁地增加新元素。

新加的元素需要放入到 dirty 中,如果 dirty 為 nil,那麼需要從 read 字段複製出來一個 dirty 對象:

func (m *Map) dirtyLocked() {
    if m.dirty != nil { // 如果dirty字段已經存在,不需要創建了
        return
    }
    read, _ := m.read.Load().(readOnly) // 獲取read字段
    m.dirty = make(map[interface{}]*entry, len(read.m))
    for k, e := range read.m { // 遍歷read字段
        if !e.tryExpungeLocked() { // 把非punged的鍵值對複製到dirty中
            m.dirty[k] = e
        }
    }
}

Load 方法

Load 方法用來讀取一個 key 對應的值。它也是從 read 開始處理,一開始並不需要鎖。


func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    // 首先從read處理
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    if !ok && read.amended { // 如果不存在並且dirty不為nil(有新的元素)
        m.mu.Lock()
        // 雙檢查
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        if !ok && read.amended {//依然不存在,並且dirty不為nil
            e, ok = m.dirty[key]// 從dirty中讀取
            // 不管dirty中存不存在,miss數都加1
            m.missLocked()
        }
        m.mu.Unlock()
    }
    if !ok {
        return nil, false
    }
    return e.load() //返回讀取的對象,e既可能是從read中獲得的,也可能是從dirty中獲得的
}

如果運氣好的話,我們從 read 中讀取到了這個 key 對應的值,那麼就不需要加鎖了,性能會非常好。但是,如果請求的 key 不存在或者是新加的,就需要加鎖從 dirty 中讀取。所以,讀取不存在的 key 會因為加鎖而導致性能下降,讀取還沒有提升的新值的情況下也會因為加鎖性能下降。

其中,missLocked 增加 miss 的時候,如果 miss 數等於 dirty 長度,會將 dirty 提升為 read,並將 dirty 置空。

func (m *Map) missLocked() {
    m.misses++ // misses計數加一
    if m.misses < len(m.dirty) { // 如果沒達到閾值(dirty字段的長度),返回
        return
    }
    m.read.Store(readOnly{m: m.dirty}) //把dirty字段的內存提升為read字段
    m.dirty = nil // 清空dirty
    m.misses = 0  // misses數重置為0
}

Delete 方法

sync.map 的第 3 個核心方法是 Delete 方法。 Delete 方法的核心改在了對 LoadAndDelete 中實現了。Delete 方法是先從 read 操作開始

func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    if !ok && read.amended {
        m.mu.Lock()
        // 雙檢查
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        if !ok && read.amended {
            e, ok = m.dirty[key]
            // 這一行長坤在1.15中實現的時候忘記加上了,導致在特殊的場景下有些key總是沒有被回收
            delete(m.dirty, key)
            // miss數
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。