go基礎入門

goroot和gopath

  1. Goroot是存放go編譯器的位置
  2. Gopath是存放源代碼的位置。gopath下必須有三個目錄,src, pkg, bin如果是從外部導入的代碼,需要放在src目錄下,比如$gopath/src/github.com

go面向對象

  • 封裝
  • 繼承
  • 多態

匿名字段:只提供類型,而不寫字段名的字段

http服務器

package main

import "net/http"

func HandleCon(w http.ResponseWriter, req *http.Request){
	w.Write([]byte("hello go"))
}
func main(){
	http.HandleFunc("/", HandleCon)  //註冊回調函數
	http.ListenAndServe(":8000",nil)  //綁定端口

}

在服務端打斷點才能看到實際運行

init函數

init函數有一下幾點特性:

  • init函數在main執行之前,自動被調用執行的,不能顯示調用
  • 每個包的init函數在包被引用時,自動被調用
  • 每個包可以有多個init函數
  • 同一個文件中可定義多個init()函數

map轉json

//map轉json
	m := map[string][]string{
		"level":{"debug"},
		"message":{"File not found", "stack overflow"},
	}
	//將map解析成Json格式
	if data, err := json.Marshal(m); err == nil{
		fmt.Printf("%s\n",data)
	}

結構體轉json

package main

import (
	"encoding/json"
	"fmt"
)
//變量名必須大寫
type IT struct {
	Company string `json:"company"`
	Subject []string`json:"subjects"`
	Isok bool `json:"-"`  //不輸出
	Price float64 `json:",string"`
}

func main() {
	//定義一個結構體變量 同時初始化
	s :=IT{"itcast", []string{"GO","C++","Python","Java"}, true, 666.66}
	//編碼 根據內容生成Json文本
	//buf, err := json.Marshal(s)
	buf, err := json.MarshalIndent(s,"","	")

	if err != nil{
		fmt.Printf("error ", err)
	}
	fmt.Printf("%+v", string(buf))
}

map轉json

func main() {
	//創建一個map
	m := make(map[string]interface{}, 4)
	m["company"]="itcast"
	m["subjects"]=[]string{"GO","C++","Java"}
	m["isok"]=true
	m["price"]=666.666

	//編碼成json
	//result, err := json.Marshal(m)
	result, err := json.MarshalIndent(m,"","	")
	if err != nil{
		fmt.Printf("error",err)
		return
	}
	fmt.Printf("result = %v", string(result))
}

方法和函數

黑馬視頻
方法是一個包含了接收者的函數,指定了調用者,對象或指針,方法的名字可以相同,但是接收者必須不同。

對比函數:
A:意義
方法:某個類別的行爲功能,需要指定的接收者調用。
函數:一段獨立功能的代碼,可以直接調用。
B:語法:
方法:方法名可以相同,只要接收者不同。
函數:命名不能衝突

package main

import "fmt"
//定義結構體
type Worker struct{
	name string
	age int
	sex string
}
//定義結構體
type Cat struct{
	name string
	age int
}
//定義方法
func (w Worker) work(){
	fmt.Println(w.name, "在工作")
}
//定義方法
func (p *Worker) rest(){
	fmt.Println(p.name, "在休息")
}

func(p * Worker) printInfo(){
	fmt.Printf( "%+v\n",p)
}
func(p * Cat) printInfo(){
	fmt.Printf( "%+v\n",p)
}
func main() {
	w := Worker{name: "yao jun", age: 14, sex: "male"}
	w.work()
	w.rest()
	w.printInfo()
	c := Cat{"tian tian", 18}
	c.printInfo()
}

繼承

使用結構體嵌套,模擬繼承。

package main

import "fmt"

//1. 父類
type Person struct{
	name string
	age int
}
//2. 子類
type Student struct{
	Person //結構體嵌套 模擬繼承性
	school string
}
//3. 父類方法
func (p *Person) eat(){
	fmt.Println("父類喫窩窩頭...")
}
//4. 子類新增方法
func (s *Student) study(){
	fmt.Println("子類學習...")
}
//5. 子類重寫父類方法
func (s *Student) eat(){
	fmt.Println("子類喫炸雞...")
}
func main(){
	p := Person{"yao jun", 19}
	p.eat()

	s := Student{p, "重慶大學"}
	s.eat() //重寫父類方法
	fmt.Println(s.name) //s.Person.name 子類直接訪問父類的字段 提升字段
	s.study()
}

接口

功能的定義和實現分類,解耦合。

package main

import "fmt"
//1. 定義接口
type USB interface {
   start()
   end()
}
//2. 定義實現類
type Mouse struct{
   name string
}
type FlaskDisk struct{
   name string
}
//這裏只能是對象 不能是指針
func (m Mouse) start(){
   fmt.Println("鼠標開始")
}
func (m Mouse) end(){
   fmt.Println("鼠標結束")
}
func (f FlaskDisk) start(){
   fmt.Println("U盤開始")
}
func (f FlaskDisk) end(){
   fmt.Println("U盤結束")
}
//3. 測試方法
func testInterface(usb USB){
   usb.start()
   usb.end()
}
func main(){
   m := Mouse{"羅技"}
   f := FlaskDisk{"閃迪"}
   m.start()
   m.end()

   f.start()
   f.end()
   fmt.Println(m.name)
   testInterface(m)
   testInterface(f)
   var usb USB
   usb = m
   testInterface(usb)
   usb.start()
   usb.end()
   //fmt.Println(usb.name) 接口對象不能方位實現類中的屬性
}

多態

  1. 子類是一個特殊的父類類型,可以看作父類對象
  2. 子類可以看作子類對象

一個函數如果接受接口類型作爲參數,那麼實際上可以傳入接口的任意實現類型對象作爲參數。定義一個類型爲接口類型,實際上可以賦值爲任意實現類的對象。

鴨子類型:dock type

www.json.cn檢查json格式

  1. 通過結構體生成json
  2. 通過map生成json

結構體變量名首字母必須大寫

函數

package main

import "fmt"
//不定參  函數
func ff(args ...int){
	for i := 0; i < len(args); i++{
		fmt.Printf("args[%d] = %d\n", i, args[i])
	}
}
func main(){
	ff(1,3,5)
}

println和printf的區別

a := 100
fmt.Println("a = ",a) //一段一段處理 自動換行
fmt.Printf("a = %d\n", a) //格式化處理

匿名變量 配合函數返回值 纔有優勢

函數類型(對應於C函數指針)

package main

import "fmt"

func add(a, b int) int{
	return a+b
}
func sub(a, b int) int{
	return a - b
}
type FuncType func(a, b int) int
func main(){
	//函數也是一種數據類型,通過type給一個函數類型起名 必須要有同樣的參數和返回值
	var fTest FuncType //聲明一個函數類型的變量
	//指向加法
	fTest = add
	res := fTest(4, 6)
	fmt.Println(res)
	//指向減法  實現多態
	fTest = sub
	res = fTest(88,66)
	fmt.Println(res)
}

回調函數:函數有一個參數是函數類型

package main

import (
	"fmt"
)

func add(a, b int) int{
	return a+b
}
func sub(a, b int) int{
	return a - b
}
type FuncType func(a, b int) int
func calc(a, b int, fTest FuncType)(result int){
	fmt.Printf("calc = ")
	result = fTest(a, b)
	fmt.Println(result)
	return
}
func mul(a, b int)int{
	return a*b
}
func div(a, b int)int{
	if b==0{
		panic("除數爲0")
	}
	return a/b
}
func main(){
	calc(5,6,add)
	calc(5,2, sub)
	calc(4,3, mul)
	calc(6,2, div)
}

匿名函數:不需要定義函數名的一種函數實現

閉包:一個函數捕獲了和它在同一作用域的其他常量和變量。

func main(){
	a := 10
	b := 20
	//匿名函數
	f := func()int {
		fmt.Println(a+b)
		return a+b
	}
	f()
	//定義匿名函數 同時調用
	func(c int) int{
		fmt.Println(a-b+c)
		return a - b + c
	}(5)      //立即執行函數
}

閉包以引用方式捕獲外部變量 內部變 外部也跟着變。只要閉包還在使用,捕獲的變量就會一直存在,閉包裏面變量的生命週期不是由它的作用域決定的。

package main

import "fmt"

func test() func()int{
	var x int
	return func() int {
		x++
		return x*x
	}
}
func main(){
	f := test()
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
}

字符串常用API

package main

import (
	"fmt"
	"strings"
)

func main(){
	//Contains包含
	fmt.Println(strings.Contains("hellogo", "hello")) //判斷是否包含
	//Join拼接
	s := []string{"abc", "hello", "world", "go"}
	fmt.Println(strings.Join(s, "_"))
	//Index索引
	fmt.Println(strings.Index("helloWorld","World"))
	//Repeat重複拼接
	fmt.Println(strings.Repeat("abc", 3))
	//Split分割
	fmt.Println(strings.Split("hello go world"," "))
	//Trim去掉兩頭空格
	fmt.Println(strings.Trim("   hello world  ", " "))
	//Fields提取單詞 空格分割
	fmt.Println(strings.Fields("hello world are you ok!"))
}

其他類型轉換爲字符串

package main

import (
	"fmt"
	"strconv"
)

func main(){
	var str string
	str = strconv.FormatInt(44,32)
	fmt.Println(str)
	//整型轉字符串
	str = strconv.Itoa(19)
	fmt.Println(str)
	//字符串轉整形
	a,_ := strconv.Atoi("234")
	fmt.Println(a)
}

正則表達式

package main

import (
	"fmt"
	"regexp"
)

func main(){
	buf := "abc azc atc a2c a4c at"
	//解釋器
	reg := regexp.MustCompile(`a.c`)
	if reg == nil{
		panic("reg error")
	}
	//根據規則提取信息
	res := reg.FindAllStringSubmatch(buf,-1)
	fmt.Println(res)
}

反轉字符串

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main(){
	//反轉字符串
	bf := bufio.NewReader(os.Stdin)
	ss, _ := bf.ReadString('\n')
	res := strings.Fields(ss)
	n := len(res)
	for i := 0; i < n/2; i++{
		res[i], res[n-1-i] = res[n-1-i], res[i]
	}
	for i := 0; i < n; i++{
		if i!= n-1{
			fmt.Printf("%s ", res[i])
		}else{
			fmt.Printf("%s\n", res[i])
		}
	}
}

資源管理和出錯處理
defer是一個棧,後出現的defer先執行,先進後出。

測試

go test .

package awesomeProject

import "testing"

/*傳統測試
1. 測試數據和測試邏輯混在一起
2. 出錯信息不明確

表格驅動測試
1. 測試數據和邏輯分離
2. 明確的出錯信息
3. 可以部分失敗
4. GO語言的語法使表格驅動測試更適合
*/
func TestAdd(t *testing.T){
	tests := [] struct{a, b, c int}{
		{3, 4, 7},
		{5, 7, 12},
		{8, 11, 19},
	}

	for _, tt := range tests{
		if actual := add(tt.a, tt.b); actual != tt.c{
			t.Errorf("add(%d, %d); actual is %d, expected %d\n", tt.a, tt.b, actual,tt.c)
		}
	}
}

//同目錄下的函數
package awesomeProject
func add(a, b int)int{
	return a+b
}

duck typing

描述事物的外部行爲而非內部結構。像鴨子走路,長得像鴨子,那就是鴨子。
接口由使用者定義。使用者download,實現者retriever。實現者不需要說我實現了哪個接口,只要實現接口裏的方法。由使用者規定實現者必須有某個方法。

package main

import (
	"fmt"
	"net/http"
	"net/http/httputil"
)

type Retriever interface {  //定義一個接口 和一個方法
	Get(url string) string
}
func download(r Retriever) string{ //這是一個函數 參數是接口 必須要事先實現接口中的方法
	return r.Get("http://www.imooc.com")
}
type Retrieve struct { //這是一個結構體 讓這個結構體來實現接口中的方法
	content string
}

func (r Retrieve) Get(url string) string{ //實現的方法
	resp, err := http.Get(url)
	if err != nil{
		panic(err)
	}
	result, err := httputil.DumpResponse(resp, true)
	defer resp.Body.Close()
	if err!= nil{
		panic(err)
	}
	return string(result[:100])
}

func main() {
	var r Retrieve
	r = Retrieve{"fasdf"}
	fmt.Println(download(r))
}

可以把實現類結構體賦值給對應的接口。即使實現的方法是值類型,但是傳遞的時候可以傳值也可以傳指針,語法糖。但是指針類型實現的方法,不能傳值類型。
接口是種類型。任何類型都實現了空接口。
子類接口可以轉換爲父類接口,反之不行。

package main

import (

	"fmt"
)

type Human interface {  //父
	hello()
}

type Person interface { //子
	Human
	sing(lrc string)
}

type Student struct {
	name string
	age int
}

func main() {
	var ip Person
	var ih Human
	ih = ip //子接口 可以轉換爲 父接口
	fmt.Println(ih)
}

空接口的類型斷言

if斷言

package main

import "fmt"

type Student struct {
	name string
	age int
}

func main() {
	m := make([] interface{}, 3)
	m[0] = 1 //int
	m[1] = "hello go" // string
	m[2] = Student{"mike", 666} //Student
	//類型斷言 第一個返回下標 第二個返回下標對應的值
	for index, data := range m{
		//第一個返回值 本身
		//第二個返回值 真假
		if value, ok := data.(int); ok==true{
			fmt.Printf("m[%d] = %d\n", index, value)
		} else if value, ok := data.(string); ok==true{
			fmt.Printf("m[%d] = %s\n", index, value)
		} else if value, ok := data.(Student); ok{
			fmt.Printf("m[%d] = %v\n", index, value)
		}
	}

}

switch斷言

package main

import "fmt"

type Student struct {
	name string
	age int
}

func main() {
	m := make([] interface{}, 3)
	m[0] = 1 //int
	m[1] = "hello go" // string
	m[2] = Student{"mike", 666} //Student
	//類型斷言 第一個返回下標 第二個返回下標對應的值
	for index, data := range m{
		switch value := data.(type) {
		case  int:
				fmt.Printf("m[%d] = %d\n", index, value)
		case string:
			fmt.Printf("m[%d] = %s\n", index, value)
		case Student:
			fmt.Printf("m[%d] = %v\n", index, value)
		}

	}

}

實現接口

同一個結構體可以實現多個接口

接口可以嵌套

package main

import "fmt"

type animal interface {
	mover
	eater
}
type mover interface {
	move()
}
type eater interface {
	eat()
}
type Cat struct {
	name string
	feet int8
}
//Cat實現了mover接口
func (c *Cat) move(){
	fmt.Printf("走貓步...")
}
//Cat實現了eater接口
func (c * Cat) eat(){
	fmt.Println("貓喫魚...")
}

func main() {
	
}

go語言的接口的獨特之處在於,它是隱式實現。對於一個具體的類型,無須聲明它實現了哪些接口,只要提供接口所必須的方法即可。

如果一個類型實現了一個接口要求的所有方法,那麼這個類型實現了這個接口。

接口賦值:僅當一個表達式實現了一個接口, 這個表達式纔可以賦給該接口。

Go語言中,還可以使用new關鍵字對類型(包括結構體、整型、浮點數、字符串等)進行實例化,結構體在實例化後會形成指針類型的結構體。

我們在實現File的時候,可能並不知道上面4個接口的存在,但不管怎樣,File實現了上面所有的4個接口。我們可以將File對象賦值給上面任何一個接口。

File在實現的時候,並不需要指定實現了哪個接口,它甚至根本不知道這4個接口的存在。

我們可以將一個實現接口的對象實例賦值給接口,也可以將另外一個接口賦值給接口。

sort自定義排序

  1. 排序字符串數組string
package main

import (
	"fmt"
	"sort"
)

type StringSlice []string
func(p StringSlice) Len()int{
	return len(p)
}
func(p StringSlice) Less(i, j int)bool{
	return p[i] < p[j]
}

func(p StringSlice) Swap(i, j int){
	p[i],p[j] = p[j],p[i]
}
func main(){
	names :=[]string{"yao","jun","ni","hao"}
	//sort.Sort(StringSlice(names))
	sort.Strings(names)  //字符串slice的排序
	fmt.Println(names)
}
  1. 排序整型數組int
package main

import (
	"fmt"
	"sort"
)

type intSlice []int
func(p intSlice) Len()int{
	return len(p)
}
func(p intSlice) Less(i, j int)bool{
	return p[i] < p[j]
}

func(p intSlice) Swap(i, j int){
	p[i],p[j] = p[j],p[i]
}
func main(){
	ints := []int{1,4,3,2,3,6,9,10}
	//sort.Ints(ints) ^^+R
	sort.Sort(intSlice(ints))
	fmt.Println(ints)
}
  1. 結構體排序struct

最核心是實現比較函數

package main

import (
	"fmt"
	"sort"
)
type Student struct{
	name string
	age int
}
type stuSlice []Student
func(p stuSlice) Len()int{
	return len(p)
}
func(p stuSlice) Less(i, j int)bool{
	return  p[i].age < p[j].age || p[i].age==p[j].age && p[i].name < p[j].name
}

func(p stuSlice) Swap(i, j int){
	p[i],p[j] = p[j],p[i]
}
func main(){
	students := []Student{{"mike",16},{"jane",22},{"Ben",19}}
	sort.Sort(stuSlice(students))
	fmt.Println(students)
}
  1. 牛客網考研題成績排序
package main

import (
	"fmt"
	"sort"
)
type Student struct{
	id int
	score int
}
type stuSlice []Student
func(p stuSlice) Len()int{
	return len(p)
}
func(p stuSlice) Less(i, j int)bool{
	return  p[i].score < p[j].score || p[i].score==p[j].score && p[i].id < p[j].id
}

func(p stuSlice) Swap(i, j int){
	p[i],p[j] = p[j],p[i]
}
func main(){
	var a,b,n int
	fmt.Scanf("%d", &n)
	var students []Student
	for i:= 0;i < n ;i++{
		fmt.Scanf("%d %d", &a, &b)
		students = append(students, Student{a,b})
	}
	sort.Sort(stuSlice(students))
	for i:= 0; i < n; i++{
		fmt.Printf("%d %d\n", students[i].id, students[i].score)
	}
}
  1. 逆序操作
sort.Sort(sort.Reverse(stuSlice(students)))

goroutine併發

1.計算和動畫同時執行

package main

import (
	"fmt"
	"time"
)

func main(){
	go spinner(100*time.Microsecond)
	const n = 45
	fibN := fib(n)
	fmt.Printf("\rfib(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration){
	for {
		for _,r := range `-\|/`{
			fmt.Printf("\r%c",r)
			time.Sleep(delay)
		}
	}
}
func fib(n int)int{
	if n < 2{
		return 1
	}else{
		return fib(n-1)+fib(n-2)
	}
}
  1. 打印數字和字母

當新的goroutine開始時,goroutine調用立即返回。與函數不同,go不等待goroutine執行結束,當goroutine調用,並且goroutine的任何返回值被忽略之後,go立即執行到下一行代碼。

main的goroutine應該爲其他的goroutines執行,如果main的goroutine終止了,程序將被終止,而其他goroutine將不會繼續執行。

goroutine在64位計算機申請棧空間1GB

多個goroutine可以使用睡眠時長來人工設定goroutine順序執行

package main

import (
	"fmt"
	"time"
)

func main(){
	go hello()
	for i := 0; i < 100; i++{
		fmt.Printf("In main A %d\n", i)
	}
	//等待go協程執行完畢 兩個協程交替執行 沒有順序限制
	time.Sleep(time.Second*1)
	fmt.Println("main over...")
}
func hello(){
	for i := 0; i < 100; i++{
		fmt.Printf("In hello %d\n", i)
	}
}
  1. 併發調度

g-p-m模型 runtime.h

Sched proc.c

Goroutine machine processor

如果兩個M在同一個CPU上運行就是併發,如果在不同CPU上運行就是並行。

  1. chan通道
package main

import (
	"fmt"
)

func main(){
	var ch chan bool
	ch = make(chan bool)
	go func() {
		for i := 0; i < 10; i++{
			fmt.Printf("in goroutine %d \n", i)
		}
		ch<-true //寫入通道
		fmt.Println("in goroutine over...")
	}()
	data := <-ch  //讀出通道的值
	fmt.Println("main over...", data)
}
  1. chan阻塞讀取
package main

import (
	"fmt"
)

func main(){
	ch := make(chan int)
	go func() {
		for i := 0; i < 10; i++{
			fmt.Printf("in goroutine %d \n", i)
		}
		data := <- ch //從ch中讀入數據
		fmt.Println("in goroutine over...", data)
	}()
	ch <- 10
	fmt.Println("main over...")
}

select語句

package main

import (
	"fmt"
	"time"
)
//每個case都必須是一個通信
//所有Chanel表達式都會被求值
//所有被髮送的表達式都會被求值
//如果有多個表達式可以運行,select會隨機公平地選出一個執行,其他不會執行。
//否則:如果有default子句,則執行該語句。如果沒有default子句,select將阻塞,直到某個通信可以運行,go不會重新對channel或值進行求值。
func main(){
	ch1 := make(chan int)
	ch2 := make(chan int)
	go func() {
		time.Sleep(3*time.Second)
		ch1<-100
	}()
	go func() {
		time.Sleep(3*time.Second)
		ch2<-200
	}()
	select {
	case num1 := <-ch1:
		fmt.Println("ch1中獲取到的數據...",num1)
		case num2, ok := <-ch2:
			if ok{
				fmt.Println("ch2中讀取的數據...",num2)
			}else{
				fmt.Println("ch2通道已關閉...")
			}
	default:
		fmt.Println("default語句...")
	}
	fmt.Println("main over...")
}

ID生成 雪花算法

https://github.com/bwmarrin/snowflake

package main

import (
	"fmt"
	"snowflake"
)

func main(){
	var n, _ = snowflake.NewNode(1)

	for i := 0; i < 3; i++{
		id := n.Generate()
		fmt.Println("id = ", id)
		fmt.Println(
			"node:", id.Node(),
			"step:", id.Step(),
			"time:", id.Time(),
			)
	}
}

注意點

  1. 數組是值傳遞
package main

import "fmt"
//數組是值傳遞,無法通過修改數組類型的參數返回結果
func main(){
	x := [3]int{1,2,3}
	func(arr [3]int){
		arr[0] = 7
		fmt.Println(arr)
	}(x)
	fmt.Println(x)
}
  1. map遍歷時順序不固定,是哈希表。

  2. recover必須在defer函數中運行

  3. main函數提前退出,無法保證後臺的goroutine正常完成。

  4. Goroutine是協作式搶佔調度,Goroutine本身不會主動放棄CPU,可能會有goroutine獨佔CPU導致其他goroutine餓死。

  5. Go語言帶有內存自動回收的特性,因此內存一般不會泄漏。但是Goroutine確實存在泄漏的情況,同時泄漏的Goroutine引用的內存同樣無法被回收。

自重寫

/*Go quine */
package main

import "fmt"

func main(){
	fmt.Printf("%s%c%s%c\n",q,0x60,q,0x60)
}
var q = `/* Go quine */
package main
import "fmt"
func main(){
	fmt.Printf("%s%c%s%c\n",q,0x60,q,0x60)
}
var q=`

RPC

滿足Go語言的RPC規則:方法只能有兩個可序列化的參數,其中第二個參數是指針類型,並且返回一個error類型,同時必須是公開的方法。

array的長度是類型的一部分

[3]int 和 [4]int 是不同的類型

slice 本質也是傳值,但看起來像傳遞了底層數組的引用

協程 corotine

  1. 輕量級“線程”

  2. 非搶佔式多任務處理,由協程主動交出控制權

  3. 編譯器/解釋器/虛擬機層面的多任務

  4. 多個協程可能在一個或多個線程上運行

    runtime.Gosched() //主動交出協程的控制權
    

    main函數也是一個協程

    main 和 dowork雙向通信

    python 3.5 加入了async def對協程原生支持。使用yield關鍵字實現協程。

goroutine的定義

  1. 任何函數只需加上go就能送給調度器運行
  2. 不需要在定義時區分是否是異步函數
  3. 調度器在合適的點進行切換
  4. 使用-race來檢測數據訪問衝突

goroutine可能的切換點

  1. IO和select
  2. channel
  3. 等待鎖
  4. 函數調用(有時)
  5. Runtime.Gosched()

視頻鏈接

package

包內的函數必須首字母大寫纔可以被外部的包調用到,小寫是調用不到的。

init方法

  • 在main函數被執行之前,所有依賴package的init方法都會被執行

  • 不同包的init函數按照包導入的依賴關係決定執行順序

  • 每個包可以有多個init函數

  • 包的每個源文件也可以有多個init函數,這點比較特殊。按照init的先後順序執行。

協程

Thread VS Coroutine

  1. 創建時默認的stack的大小
  • JDK5以後Java Thread stack 默認爲1M
  • Goroutine的stack初始化大小爲2K
  1. 和KSE(kernel space entity)的對應關係
  • java thread 是1:1
  • Goroutine是M:N

協程機制

Go Test

通常test文件的命名規範是xxx_test.go

test case的函數命名爲TestXxx形式 否則go test會跳過該test不執行

  • t *testing.T 普通測試函數

  • b*testing.B benchMark測試

go test -v 輸出test的詳細信息
go test hello_test.go 輸入某個特定的test

新建一個工程 使用go mod管理 初始化一個mod

go mod init

三個文件

haha.go
haha_test.go
go.mod
//haha.go
package main

func F(a,b int)int{
	return a+b
}
//haha_test.go
package main

import (
	"testing"
)

func TestF(t *testing.T){
	res := F(3,4)
	if res != 7{
		t.Errorf("add wrong!")
	}
}
//go.mod
module code.byted.org/ee/test

go 1.14

t.SkipNow()爲跳過當前test,並且直接按pass處理繼續下一個testcase

func TestF(t *testing.T){
	t.SkipNow() //直接跳過這個test case的測試 並PASS
	res := F(3,4)
	if res != 20{
		t.Errorf("add wrong!")
	}
}

Go test不保證多個test按照順序執行,但是我們通常會要求按照順序執行。

使用t.Run來執行subtests可以做到控制test執行的順序

func TestF(t *testing.T){
	t.Run("a1", func(t *testing.T) {fmt.Println("a1")})
	t.Run("a2", func(t *testing.T) {fmt.Println("a2")})
	t.Run("a3", func(t *testing.T) {fmt.Println("a3")})
}

使用TestMain作爲初始化test,並且使用m.Run()來調用其他tests可以完成一些需要初始化操作的testing,比如數據庫連接,文件打開,rest服務登錄等。

如果沒有在TestMain中調用m.Run()則除了TestMain以外的其他tests都不會被執行

func TestMain(m *testing.M){
	fmt.Println("test main first")
	m.Run()
}

benchmark

benchmark函數一般以BenchMark開頭

benchmark函數的case一般會跑b.N次,而且每次執行都會如此

在執行過程中會根據實際case的執行時間是否穩定會增加b.N的次數以達到穩態

go test -bench=.  //命令行
func BenchmarkF(b *testing.B) {
	for i:=0; i < b.N; i++{
		F(100,10)
	}
}

benchmark也會受限於m.Run()

如果benchmark測試的函數執行時間始終無法趨於穩態 則永遠無法執行完

func unstable(n int){
	for n > 0{
		n--
	}
}
func BenchmarkF(b *testing.B) {
	for i:=0; i < b.N; i++{
		unstable(i)  //如果benchmark測試的函數執行時間始終無法趨於穩態 則永遠無法執行完
	}
}

http server

視頻鏈接

package main

import (
	"net/http"
)
//w 寫回給客戶端的數據  r 從客戶端讀入的數據
func handler(w http.ResponseWriter, r *http.Request){
	w.Write([]byte("hello world"))
}
func main(){
	//註冊回調函數
	http.HandleFunc("/hello", handler)
	//綁定監聽地址
	http.ListenAndServe("127.0.0.1:8000", nil)

}

切片

爲什麼用切片?

  • 數組的容量固定,不能自動拓展。
  • 值傳遞。數組作爲函數參數時,將整個數組值拷貝一份給形參。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章