GO核心編程
簡介
go語言特點:
- go具有垃圾回識訓制
- 從語言層面支持并發,goroutine,高效利用多核,基于CPS并發模型實作(重要特點)
- 吸收了管道通信機制,實作不同goroutine之間的互相通信
- 函式可以回傳多個值
- 切片、延時執行defer
- 繼承C語言很多思想,引入包的概念,用于組織程式結構
golang執行流程分析
第一種方式是go build編譯后生成可執行檔案,在運行可執行檔案即可;第二種方式是直接go run源檔案,兩種方式的區別:
- 如果我們先編譯生成了可執行檔案,那么我們可以將該可執行檔案拷貝到沒有 go 開發環境的機器上,仍然可以運行
- 如果我們是直接 go run go 源代碼,那么如果要在另外一個機器上這么運行,也需要 go 開發環境,否則無法執行
- 在編譯時,編譯器會將程式運行依賴的庫檔案包含在可執行檔案中,所以,可執行檔案變大了很多
真正作業時候需要先編譯在運行!!
go程式開發注意事項
- Go每個陳述句后不需要分號(Go語言會在每行后自動加分號)
- Go編譯器是一行行進行編譯的,一行只能寫一條陳述句
- 存在未使用的包或變數,編譯會通不過
規范代碼風格
撰寫完代碼后可以通過gofmt -w main.go來進行格式化;Go設計者的思想:一個問題盡量只有一個解決方法,
基本語法
變數使用注意事項
-
如果一次宣告多個全域變數
var( n3 = 300 name = "mary" ) //區域變數 var n3, name = 300, "mary" -
//查看變數型別和占用位元組 fmt.Printf("n1 的 型別 %T\n n1占用的位元組數 %d",n1,unsafe.Sizeof(n1)) -
/* byte~uint8 存盤字符時候選用byte 如果保存字符對應碼值大于255,比如漢字,可以考慮使用int型別保存 如果需要輸出字符,需要格式化輸出 */ //rune~int32 表示一個Unicode碼 -
/* Go中字串是不可變的 字串兩種表現形式: 雙引號:會識別轉義字符 反引號:以字串的原生形式輸出,包括換行和特殊字符,可以實作防止攻擊、輸出源代碼等效果 */ -
Go資料型別不能自動轉換,需要顯示轉換T(v)
//基本資料型別和string的相互轉換 //Sprintf會根據format引數生成格式化的字串并回傳該字串 -
go語言不支持三元運算子
流程控制使用注意事項
- Switch...case陳述句中,case后面不再需要添加break,case后面也可以有多個值,用逗號分隔開,如果想要執行下面的陳述句,添加
fallthrough關鍵字,叫做switch穿透 - 回圈遍歷只有一個for關鍵字,可以用for range陳述句來遍歷陣列,
包使用注意事項
- 一個檔案夾下的所有.go檔案同屬于一個包,一般和檔案夾一樣,在同一個包下不能有相同的函式名和變數名,即使在不同檔案中也一樣,
- 跨包訪問的函式或變數首字母需要大寫,相當于public,
- import實際上是import "檔案夾名字",訪問時候是用的包名.函式名,因為包名可以和檔案夾名不一樣
- 如果要編譯成一個可執行程式檔案,就需要將這個包宣告為main;如果是寫一個庫,包名可以自定義
函式使用注意事項
-
基本資料型別和陣列默認都是值傳遞
-
Go中,函式也是一種資料型別,可以賦給一個變數,類似于C語言的函式指標,型別為func(type1,type2)
-
C++中typedef,在Go中變為type
-
支持對函式回傳值命名
func getSumAndSub(n1 int,n2 int)(sum int,sub int){ sum = n1 + n2 sub = n1 - n2 return } -
支持可變引數
func sum(args... int) sum int{ } func sum(n1 int,args... int) sum int{ } //args是slice切片,通過args[index]可以訪問到各個值,可變引數要放在形參串列最后 -
每一個源檔案都可以包含一個 init 函式,該函式會在 main 函式執行前,被 Go 運行框架呼叫,也 就是說 init 會在 main 函式前被呼叫,如果一個檔案同時包含全域變數定義,init 函式和 main 函式,則執行的流程全域變數定義->init函式->main 函式,
如果import其他檔案,則先執行其他檔案的初始化!!! -
匿名函式
//方式一 res1:= func(n1 int) int{ return n1+1 }(10) //方式二 fun:=func(n1 int) int{ return n1+1 } res2:=fun(10) -
閉包
閉包就是一個函式和與其相關的參考環境組合的一個整體.可以這樣理解: 閉包是類, 函式是操作,n 是欄位,函式和它使用到 n 構成閉包,
要搞清楚閉包的關鍵,就是要分析出回傳的函式它使用(參考)到哪些變數,因為函式和它引
用到的變數共同構成閉包
func makeSuffix(suffix string) func(string) string{ return func(name string) string{ //如果name沒有指定后綴,則加上,否則就回傳原來的名字 if !strings.HasSuffix(name,suffix){ return name+suffix } } } f2 := makeSuffix(".jpg") fmt.Println(f2("winter")) //winter.jpg fmt.Println(f2("bird.jpg")) //bird.jpg我們體會一下閉包的好處,如果使用傳統的方法,也可以輕松實作這個功能,但是傳統方法需要每 次都傳入 后綴名,比如 .jpg ,而閉包因為可以保留上次參考的某個值,所以我們傳入一次就可以反復 使用,這個makeSuffix用處有點類似于java的泛型和c++的模版類,生成特定后綴判斷的函式變數
-
defer
當執行到defer時,暫停不執行,會將defer后面的陳述句壓入到獨立的堆疊(defer堆疊),當函式執行完畢后,再從defer堆疊中取出陳述句執行,在defer陳述句放入到堆疊時,也會將相關的值拷貝同時入堆疊
func sum(n1 int,n2 int) int{ defer fmt.Println("ok1 n1=",n1) defer fmt.Println("ok2 n2=",n2) n1++ n2++ res:=n1+n2 fmt.Println("ok3 res=",res) return res } //執行結果 //ok3 res=32 //ok2 n2=20 //ok1 n1=10defer 最主要的價值是在,當函式執行完畢后,可以及時的釋放函式創建的資源
-
函式傳參
值型別:基本資料型別、陣列和結構體 struct,默認是值傳遞
參考型別:指標、slice 切片、map、管道 chan、interface 等,默認是參考傳遞
-
錯誤處理
Go語言不支持傳統的try...catch...finally處理,引入的處理方式為defer,panic,recover,
這幾個例外的使用場景可以這么簡單描述:Go 中可以拋出一個 panic 的例外,然后在 defer 中通過 recover 捕獲這個例外,然后正常處理,
func test(){ defer func(){ err := recover() //recover()內置函式,可以捕獲到例外 if err != nil{ fmt.Println("err",err) } }() num1 := 10 num2 := 0 res := num1/num2 fmt.Println("res=",res) }自定義錯誤:
1.errors.New("錯誤說明") , 會回傳一個 error 型別的值,表示一個錯誤
2.panic 內置函式 ,接收一個 interface{}型別的值(也就是任何值了,相當于java的Object)作為引數,可以接收 error 型別的變數,輸出錯誤資訊,并退出程式.
陣列和切片
go語言中陣列的名字不在是地址了,陣列的首地址為&arr或者&arr[0],
var arr1 = [3]int{5,6,7} //var slice = []int{1,2,3} 雖然可以這樣,但已經不是一個陣列了,陣列宣告必須指定長度
var arr2 = [...]int{1,3,3}
var arr3 = [...]int{1:800,0:900,2:999}
//for range遍歷方式
for index,value range arr1{
}
陣列使用注意事項
-
func test(arr [3]int){ //值傳遞,不影響原來的 } func test(arr *[3]int){//可以通過傳指標 } //Go語言傳參有嚴格的限制,[3]int型別和[4]int型別不一致!!! -
二維陣列定義后面的賦值必須嚴格的劃分開,不能省略花括號!!
arr := [2][2]int{{1,2},{3,4}} arr := [...][2]int{{1,2},{3,4}} //二維陣列for-range遍歷 for i,v:= range arr{ for j,v2:=range v{ } }
切片是陣列的一個參考,因此切片是參考型別,是一個可以動態變化的陣列,
slice := ar[1:3] //左開右閉
slice := make([]int,len,[cap])
slice := []int{1,2,3}
方式一和方式二的區別
通過 make 方式創建的切片對應的陣列是由 make 底層維護,對外不可見,即只能通過 slice 去訪問各個元素,方式一直接飲用陣列,這個陣列事先存在,程式員可見,
切片使用注意事項
- 切片可以繼續切片,因為切片的更改會影響底層陣列的更改,
- append內置函式可以對切片追加具體元素,也可以追加slice,追加的具體元素如果不超過底層陣列的長度,則會覆寫底層陣列的數值;當超過底層陣列的長度時候,go會創建一個新的陣列,將slice原來包含的元素拷貝到新的陣列然后重新參考newArr,
- 內置函式copy(dest,source)的引數型別是切片,source長度可以閉dest大
string和slice
-
string底層是一個byte陣列,因此string也可以進行切片
-
string是不可變的,
str[0]='z'編譯不通過//如果想要改變,可以現將string轉成byte切片,修改完后在轉為string arr1 := []byte(str) arr1[0] = 'z' str = string(arr1) //這種轉換僅僅適用于string <---> byte,可以把byte當成char型別注意:當我們轉成[]byte后,可以處理英文和數字,不能處理中文,因為一個漢字3個位元組,會出現亂碼,解決辦法是將string轉成[]rune即可,因為[]rune是按字符處理的,兼容漢字,
map
宣告一個map是不會分配記憶體的,初始化需要make,分配記憶體后才能賦值和適用,
m := make(map[string]string,10) //容量達到后,會自動擴容
m := make(map[string]string)
新增操作:Map["key"]=value 如果key還沒有就是增加,如果key存在就是修改,
洗掉操作:delete(map,"key"),如果一次性洗掉所有的則需要一個個遍歷key去delete
查找操作:v,ok :=map["tom"]
Slice of map
m := make([]map[string]string,2) //型別為map[string]string的切片,大小為2,第三個map就需要先append在使用了,否則會越界
//切片的資料型別如果是 map,則我們稱為 slice of map,map 切片,這樣使用則 map 個數就可以動態變化了
注意:使用slice和map一定要先make
map中的key是無序的,每次遍歷得到的結果可能不一樣,Go沒有辦法對map進行排序,但是有辦法根據key來順序輸出map,
/*
1. 先將map的key放到切片中
2. 對切片排序
3. 遍歷切片,然后按照key來輸出map值
*/
面向物件編程
結構體
type Person struct{
}
p := Person{"mary",20}
var person *Person = new(Person)
(*person).Name = "smith" //person.Name = "smith"
//go設計者為了程式員使用方便,底層會對person.Name進行處理,加上(*person).Name
結構體使用注意細節:
- 不同結構體可以相互轉換,前提是需要有完全相同的欄位(名字、個數和型別)
- struct 的每個欄位上,可以寫上一個 tag, 該 tag 可以通過反射機制獲取,常見的使用場景就是序列化和反序列化,
方法
func (p Person) test(){
//...
}
方法使用注意事項
- Golang 中的方法是作用在指定的資料型別上的(即:和指定的資料型別系結),因此自定義型別,都可以有方法,而不僅僅是 struct,int,floate32等都可以有方法
- 變數呼叫方法時,該變數本身也會作為一個引數傳遞到方法(如果變數是值型別,則進行值拷貝,如果變數是參考型別,則進行地址拷貝
- 方法的訪問范圍控制的規則,和函式一樣,方法名首字母小寫,只能在本包訪問,方法首字母 大寫,可以在本包和其它包訪問
- 如果一個型別實作了 String()這個方法,那么 fmt.Println 默認會呼叫這個變數的 String()進行輸 出
工廠模式
問題來了,如果首字母是小寫的, 比如 是 type student struct {....} 就不不行了,怎么辦---> 工廠模式來解決.
type student struct{
Name string
score float64
}
func NewStudent(n string,s float64) *student{
return &student{
Name:n,
Score:s,
}
}
//首字母小寫的欄位也不能跨包訪問,需要提供一個方法
func (s *student) GetScore() float64{
return s.score
}
封裝
在 Golang 開發中并沒有特別強調封裝,這點并不像 Java
- 將結構體、欄位(屬性)的首字母小寫(不能匯出了,其它包不能使用,類似 private)
- 給結構體所在包提供一個工廠模式的函式,首字母大寫,類似一個建構式
- 提供一個首字母大寫的 Set /Get方法(類似其它語言的 public)
繼承
在 Golang 中,如果一個 struct 嵌套了另一個匿名結構體,那么這個結構體可以直接訪問匿名結構體的欄位和方法,從而實作了繼承特性,也即匿名結構體的所有東西成為了新的結構體的一部分,
- 結構體可以使用匿名結構體的所有欄位和方法,大小寫都可以,但是要在同一個包里面去訪問,、
- 匿名結構體欄位訪問可以簡化,比如b.A.age=19可以寫b.age=19,
- 當結構體和匿名結構體有相同的欄位或者方法時,編譯器采用就近訪問原則訪問,如希望訪問匿名結構體的欄位和方法,可以通過匿名結構體名來區分
- 結構體嵌入兩個(或多個)匿名結構體,如兩個匿名結構體有相同的欄位和方法(同時結構體本身 沒有同名的欄位和方法),在訪問時,就必須明確指定匿名結構體名字,否則編譯報錯
- 如果一個 struct 嵌套了一個有名結構體,這種模式就是組合,如果是組合關系,那么在訪問組合的結構體的欄位或方法時,必須帶上結構體的名字
- 如一個 struct 嵌套了多個匿名結構體,那么該結構體可以直接訪問嵌套的匿名結構體的欄位和方法,從而實作了多重繼承,盡量不要使用多重繼承
介面
Go采用介面來實作多型,interface 型別可以定義一組方法,但是這些不需要實作,并且 interface 不能包含任何變數,只要一個變數,含有介面型別中的所有方法(注意:一定要是所有),那么這個變數就實作這個介面,
介面使用注意事項
-
空介面 interface{} 沒有任何方法,所以所有型別都實作了空介面, 即我們可以把任何一個變數賦給空介面
-
只要是自定義資料型別,就可以實作介面
type integer int func (i integer) say{ //... }
型別斷言
介面要轉成具體型別就要用到型別斷言
var x interface{}
var b2 float32 = 1.1
x = b2
y := x.(float32) //arg.(type)
在進行型別斷言時,如果型別不匹配,就會報 panic, 因此進行型別斷言時,要確保原來的空介面指向的就是斷言的型別
如何在進行斷言時,帶上檢測機制,如果成功就 ok,否則也不要報 panic
if y,ok := x.(float32);ok{
//convert success
}else{
//convert fail
}
高級教程
命令列引數
os.Args 是一個 string 的切片,用來存盤所有的命令列引數
for i,v:= range os.Args{
fmt.Printf("args[%v]=%v\n",i,v)
}//有效引數從Args[1]開始,即第二個
flag包決議命令列引數
前面的方式是比較原生的方式,對決議引數不是特別的方便,特別是帶有指定引數形式的命令列,go 設計者給我們提供了 flag 包,可以方便的決議命令列引數,而且引數順序可以隨意,
//定義幾個變數,用于接受命令列引數
var user string
var pwd int
flag.StringVar(&user,"u","","用戶名,默認為空")
flag.IntVar(&pwd,"pwd",0,"密碼,默認為空")
flag.Parse()
fmt.Printf("user=%v pwd=%v\n",user,pwd)
序列化和反序列化
json.Marshal(v interface{}) ([]byte,error) //序列化
type monster struct{
}
json.unMarshal([]byte(str),&monster) //序列化
對于結構體的序列化,如果我們希望序列化后的 key 的名字,又我們自己重新制定,那么可以給 struct指定一個 tag 標簽,
在反序列化一個json字串時,要確保反序列化后的資料型別和原來序列化前的資料型別一致
*單元測驗
Go 語言中自帶有一個輕量級的測驗框架 testing 和自帶的 go test 命令來實作單元測驗和性能測驗.testing 框架和其他語言中的測驗框架類似,可以基于這個框架寫針對相應函式的測驗用例,也可以基于該框架寫相應的壓力測驗用例,
- 測驗用例檔案名必須以 _test.go 結尾, 比如 cal_test.go , cal 不是固定的
- 測驗用例函式必須以 Test 開頭,一般來說就是 Test+被測驗的函式名,比如 TestAddUpper
- TestAddUpper(t *tesing.T) 的形參型別必須是 *testing.
- 當出現錯誤時,可以使用 t.Fatalf 來格式化輸出錯誤資訊,并退出程式,t.Logf 方法可以輸出相應的日志
goroutine
Go 主執行緒(有程式員直接稱為執行緒/也可以理解成行程): 一個 Go 執行緒上,可以起多個協程,你可以這樣理解,協程是輕量級的執行緒[編譯器做優化],(這里只是叫法發生了變化)
Go可以輕輕松松的起上萬個協程,
channel
這個解決的是不同的goroutine如何通信的問題,
全域變數的互斥鎖
lock sync.Mutex
lock.lock
//...
lock.unlock
上面這種方法不完美,主執行緒在等待所有 goroutine 全部完成的時間很難確定;
如果主執行緒休眠時間長了,會加長等待時間,如果等待時間短了,可能還有 goroutine 處于作業狀態,這時也會隨主執行緒的退出而銷毀;
通過全域變數加鎖同步來實作通訊,也并不利用多個協程對全域變數的讀寫操作
在運行某個程式時,如何知道是否存在資源競爭問題, 方法很簡單,在編譯該程式時,增加一個引數 -race 即可
-
channel本質就是一個資料結構-佇列,它是有型別的,是執行緒安全的(多個協程操作同一個管道時,不會發生資源競爭問題)
-
channel必須初始化才能寫入資料,即make后才能使用
var intChan chan int intChan = make(chan int,3) -
當我們給管寫入資料時,不能超過其容量,它的價值是一邊放一邊取
-
allChan := make(chan interface{},3) allChan <- Cat{Name:"tom",Age:18} newCat <- allChan fmt.Printf("%T\n%v",newCat,newCat) //正常輸出 fmt.Printf("newCat.Name=%v",newCat.Name) //編譯不通過!!! a := newCat.(Cat) //使用型別斷言!!!! -
使用內置函式 close 可以關閉 channel, 當 channel 關閉后,就不能再向 channel 寫資料了,但是仍然 可以從該 channel 讀取資料,只有關閉后讀完會自動退出
-
在沒有使用協程的情況下,如果 channel 資料取完了,再取就會報 dead lock ,寫也是一樣,使用協程則會阻塞,
應用實體1
一個讀協程,一個寫協程,操作同一管道,主執行緒需要等待兩個協程都完成作業才能退出,
func writeData(intChan chan int){
for i:=1;i<=50;i++ {
//放入資料
intChan <- i
fmt.Println("write data",i)
}
close(intChan)
}
func readData(intChan chan int,exitChan chan bool){
for{
v,ok := <-intChan
if !ok {
break
}
fmt.Println("read data=https://www.cnblogs.com/yrxing/p/%v/n",v)
}
//任務完成
exitChan<-true
close(exitChan)
}
func main() {
//創建兩個管道
intChan := make(chan int,10)
exitChan := make(chan bool,1)
go writeData(intChan)
go readData(intChan,exitChan)
for{
_,ok := <- exitChan
if !ok {
break
}
}
}
管道的阻塞機制
如果只是向管道寫入資料,而沒有讀取資料,就會出現阻塞而dead lock,原因是intChan容量是10,而代碼wirteData會寫入50個資料,因此會阻塞在writeData的ch<-i,但是寫管道和讀管道的頻率不一致,無所謂
應用實體2
統計1-8000的數字中,哪些是素數?將統計素數的任務分配給4個goroutine去完成
//向intChan放入1-8000個數
func putNum(intChan chan int){
for i:=1;i<=8000;i++ {
intChan <- i
}
close(intChan)
}
//從intChan取出資料,并判斷是否為素數,如果是,就放入primeChan
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool) {
var flag bool
for {
time.Sleep(time.Microsecond*10)
//取一個數處理
num,ok := <-intChan
if !ok{
break
}
flag = true
//判斷
for i:=2;i<num;i++{
if num%2 ==0 {
flag=false
break
}
}
//放入
if flag {
primeChan <- num
}
}
fmt.Println("有一個primeNum協程因為取不到資料,退出")
//這里不能關閉primeChan
exitChan <- true
}
func main(){
intChan := make(chan int, 1000)
primeChan := make(chan int,2000)
exitChan := make(chan bool,4) //4個
go putNum(intChan)
//開啟4個協程,從intChan取出資料判斷
for i:=0;i<4;i++{
go primeNum(intChan,primeChan,exitChan)
}
go func() {
for i:=0;i<4;i++{
<-exitChan
}
//當我們從exitChan取出4個結果,就可以放心關閉primeChan
close(primeChan)
}()
for {
res,ok := <-primeChan
if !ok{
break
}
fmt.Println("%d\n",res)
}
}
channel使用注意事項
-
channel可以宣告為只讀或者只寫,可以有效防止誤操作,降低權限,
/* var writeChan chan<-int //只寫 var readChan <-chan int //只讀 */ -
傳統方法在遍歷管道時,如果不關閉后阻塞而導致deadlock,在實際開發中,可能我們不好確定什么時候關閉管道,使用select可以解決從管道取資料的阻塞問題,
for{ //select里面的case是并發執行的 select{ //這里如果intChan一直沒有關閉,不會一直阻塞而deadlock,沒有資料的話會自動到下一個case匹配 case v:= <-intChan fmt.Printf("從intChan讀取的資料%d\n",v) case v:= <-stringChan fmt.Printf("從stringChan讀取的資料%d\n",v) default: fmt.Printf("都取不到,程式員可以加入邏輯\n") time.Sleep(time.Second) return } } -
goroutine中使用recover,解決協程中出現panic,導致程式崩潰問題,
反射
反射可以在運行時動態獲取變數的各種資訊, 比如變數的型別(type),類別(kind),如果是結構體變數,還可以獲取到結構體本身的資訊(包括結構體的欄位、方法),通過反射,可以修改變數的值,可以呼叫關聯的方法,
反射常見的應用場景
- 不知道介面呼叫哪個函式,根據傳入引數在運行時確定呼叫的具體介面,這種需要對函式或方法反射,
- 對結構體序列化時,如果結構體有指定tag,也會使用反射生成對應的字串
概念
-
reflect.TypeOf()/reflect.ValueOf()
-
變數、interface{}、reflect.Value是可以相互轉換的
func reflectTest(b interface{}) { //通過反射獲取傳入變數的type,kind,值 rType := reflect.TypeOf(b) fmt.Println("rType=",rType) rVal := reflect.ValueOf(b) n:= 2+rVal.Int() fmt.Println("n=",n) fmt.Printf("rVal=%v rVal type=%T\n",rVal,rVal) iV:=rVal.Interface() n2:=iV.(int) fmt.Println("n2=",n2) }
反射使用注意細節
- Reflect.Vlaue.Kind獲取變數的類別,回傳一個常量,type和kind有時候一樣有時候不一樣,stu Type是Student,Kind是struct
- 通過反射的來修改變數, 注意當使用 SetXxx 方法來設定需要通過對應的指標型別來完成, 這樣才能改變傳入的變數的值, 同時需要使用到 reflect.Value.Elem()方法(相當于獲取指標指向變數的值)
反射最佳實踐
使用反射遍歷結構體的欄位,呼叫結構體的方法,并獲取結構體標簽的值
type Monster struct{
Name string `json:"name"`
Age int `json:"monster_age"`
Score float32 `json:"成績"`
Sex string
}
func (s Monster) GetSum(n1,n2 int) int {
return n1+n2
}
func (s Monster) Set(name string,age int,score float32,sex string){
s.Name=name
s.Age=age
s.Score=score
s.Sex=sex
}
func (s Monster) Print(){
fmt.Println("----start---")
fmt.Println(s)
fmt.Println("-----end-----")
}
func TestStruct(a interface{}){
typ := reflect.TypeOf(a)
rval := reflect.ValueOf(a)
kd := rval.Kind()
if kd != reflect.Struct{ //如果不是struct就退出
fmt.Println("expect struct")
return
}
//獲取結構體有幾個欄位
num := rval.NumField()
fmt.Printf("structs has%d fileds\n",num)
for i:=0;i<num;i++{
fmt.Printf("Filed %d值為%v\n",i,rval.Field(i))
tagVal := typ.Field(i).Tag.Get("json")
//如果該欄位有tag就顯示,否則就不顯示
if tagVal !=""{
fmt.Printf("File%d:tag為%v",i,tagVal)
}
}
//獲取結構體有多少個方法
numOfMethod:=rval.NumMethod()
fmt.Printf("struct has %d methods\n",numOfMethod)
//方法的排序默認是按照函式名排序
rval.Method(1).Call(nil)//獲取到第二個方法即Print,呼叫它,因此沒有引數
//呼叫結構體的第一個方法Method(0)
var params []reflect.Value
params = append(params,reflect.ValueOf(10))
params = append(params,reflect.ValueOf(40))
res:=rval.Method(0).Call(params)//傳入引數是[]reflect.Value
fmt.Println("res=",res[0].Int())//回傳結果是[]reflect.Value
}
TCP編程
埠分類:0保留埠;1-1024固定埠;1025-65525動態埠,程式員可以使用,一個埠只能被一個程式監聽,服務器要盡可能少用埠,
服務端代碼
func process(conn net.Conn){
defer conn.Close()
for{
buf:=make([]byte,1024)
//等待客戶端conn發送資訊,如果客戶端沒有發送,那么協程就阻塞在這里
fmt.Printf("服務器在等待客戶端%s 發送資訊\n",conn.RemoteAddr().String())
n,err := conn.Read(buf)
if err != nil{
fmt.Printf("客戶端退出 err=%v",err)
return //!!!
}
//顯示客戶端發送的內容到服務器的終端
fmt.Print(string(buf[:n]))
}
}
func main() {
fmt.Println("服務器開始監聽...")
listen,err:= net.Listen("tcp","0.0.0.0:8888")
if err!= nil{
fmt.Println("listen err=",err)
return
}
defer listen.Close() //延時關閉listen
//回圈等待客戶端來連接我
for{
fmt.Println("等待客戶端來連接...")
conn,err:=listen.Accept()
if err != nil{
fmt.Println("Accept() err=",err)
}else{
fmt.Printf("Accept() success con=%v 客戶端ip=%v\n",conn,conn.RemoteAddr().String())
}
//這里準備一個協程為客戶端服務
go process(conn)
}
}
客戶端代碼
func main(){
conn,err := net.Dial("tcp","0.0.0.0:8888")
if err != nil{
fmt.Println("client dial err=",err)
return
}
//客戶端可以發送單行資料,然后就退出
reader := bufio.NewReader(os.Stdin)
for {
//從終端讀一行用戶輸入,并準備發送給服務器
line, err := reader.ReadString('\n')
if err != nil {
fmt.Println("readString err=", err)
}
//如果用戶輸入的是exit就退出
line = strings.Trim(line, " \r\n")
if line == "exit" {
fmt.Println("客戶端退出..")
break
}
//再將line發送給服務器
_, err = conn.Write([]byte(line + "\n"))
if err != nil {
fmt.Println("conn Write err=", err)
}
//fmt.Printf("客戶端發送了%d位元組的資料,并退出",n)
}
}
Redis的使用
REmote Dictionary Server(遠程字典服務器),Redis性能非常高,單機能夠達到15w qps,通常適合做快取,也可以持久化,是完全開源的,高性能的k-v分布式記憶體資料庫,基于記憶體運行并支持之久化的NoSQL資料庫,
Redis安裝好后,默認有16個資料庫,初始默認使用0號庫,編號0...15,select 1`切換1號資料庫,
golang操作redis
-
安裝第三方開源redis庫
cd $GOPATH go get github.com/garyburd/redigo/redis -
Set/Get介面
func main() { //連接到redis conn,err := redis.Dial("tcp","127.0.0.1:6379") if err!= nil{ fmt.Println("redis.Dial err=",err) return } defer conn.Close() //通過go向redis寫入資料string[key-val] _,err = conn.Do("Set","name","tomjerry_cat") if err!= nil{ fmt.Println("set err=",err) return } //通過go向redis讀取資料 r,err:=redis.String(conn.Do("Get","name")) if err!= nil{ fmt.Println("get err=",err) return } //因為回傳r是interface{},name對應的值是string,因此我們需要轉換 //nameString := r.(string) fmt.Println("操作ok",r) } -
redis鏈接池
事先初始化一定數量的鏈接,放入到鏈接池,當 Go 需要操作 Redis 時,直接從 Redis 鏈接池取出鏈接即可,這樣可以節省臨時獲取 Redis 鏈接的時間,從而提高效率,
//定義一個全域的pool var pool *redis.Pool //當啟動程式時,就初始化鏈接池 func init() { pool = &redis.Pool{ MaxIdle: 8,//最大空閑鏈接數 MaxActive: 0,//表示和資料庫的最大鏈接數,0表示沒有限制 IdleTimeout: 100,//最大空閑時間 Dial: func() (redis.Conn, error) {//初始化鏈接代碼 return redis.Dial("tcp","localhost:6379") }, } } func main() { //先從pool取出一個鏈接 conn:=pool.Get() defer conn.Close() _,err:=conn.Do("Set","name","Tom cat!!") if err!=nil{ fmt.Println("conn.Do err=",err) return } //... }
經典專案-海量用戶即時通訊系統
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/270590.html
標籤:Go
