golang 基础笔记二
网络编程
网络编程有两种:
1) TCP socket 编程,是网络编程的主流。之所以叫 Tcp socket 编程,是因为底层是基于 Tcp/ip 协 议的.
比如: QQ 聊天 [示意图]
2) b/s 结构的 http 编程,我们使用浏览器去访问服务器时,使用的就是 http 协议,而 http 底层依 旧是用 tcp socket 实现的。比如: 京东商城 【这属于 go web 开发范畴 】
端口:
1)0 是保留端口
2)1-1024 是固定端口,例如:22是SSH远程登录端口,23是telnet使用
3)1025 - 65535 是动态端口,程序员可以使用的端口
TCP编程
service.go
package main import ( "fmt" "io" "net" ) func process(conn net.Conn) { defer conn.Close() for{ buf :=make([]byte,1024) //1.等待客户端通过conn发送消息 //2.如果客户端没有wrtie[发送],那么协程就会阻塞在这里 n,err:=conn.Read(buf) //从conn读取 if err ==io.EOF{ fmt.Println("客户端退出了") return } //3.显示客户端发送的内容到服务器的终端 fmt.Print(string(buf[:n])) } } func main() { fmt.Println("服务器开始监听.....") listen,err :=net.Listen("tcp","127.0.0.1:8888") if err !=nil{ fmt.Println("listen err = ",err) return } defer listen.Close()//延时关闭链接 for{ //等待客户端链接 conn,err:=listen.Accept() if err!=nil{ fmt.Println("链接失败 err=",err) }else { fmt.Printf("客户端来链接了,客户端的IP=%v\n",conn.RemoteAddr().String()) } go process(conn) } }
client.go
package main import ( "bufio" "fmt" "net" "os" "strings" ) func main() { conn,err := net.Dial("tcp","127.0.0.1:8888") if err!=nil{ fmt.Println("客户端链接失败 err=",err) return } defer conn.Close() reader :=bufio.NewReader(os.Stdin) //从终端读取一行用户输入,并准备发送给服务器 for{ line,err:=reader.ReadString('\n') if err !=nil{ fmt.Println("readstring err=",err) } line = strings.Trim(line,"\r\n") if line=="exit"{ fmt.Println("客户端退出了") break } //再将line 发送给服务器 _,err = conn.Write([]byte(line+"\n")) if err!=nil{ fmt.Println("发送信息失败err=",err) } } }
go 链接 redis 一
安装第三方开源 Redis 库
1) 使用第三方开源的 redis 库: github.com/garyburd/redigo/redis
2) 在使用 Redis 前,先安装第三方 Redis 库,在 GOPATH 路径下执行安装指令: D:\goproject>go get github.com/garyburd/redigo/redis
特别说明: 在安装 Redis 库前,确保已经安装并配置了 Git, 因为 是从 github 下载安装 Redis 库的, 需要使用到 Git。 如果没有安装配置过 Git,请参考: 如何安装配置 Git
redis 链接操作 Set Get
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c,err:=redis.Dial("tcp","localhost:6379") if err!=nil{ fmt.Println("redis conn err = ",err) return } defer c.Close() _,err=c.Do("Set","key1",998) if err!=nil{ fmt.Println("写入错误err=",err) return } r,err :=redis.Int(c.Do("Get","key1")) //r 返回的是 interface{} if err!=nil{ fmt.Println("get key failed err=",err) } fmt.Println(r) }
对redis hash 的操作
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c,err:=redis.Dial("tcp","localhost:6379") if err!=nil{ fmt.Println("redis conn err = ",err) return } defer c.Close() _,err=c.Do("HSet","user01","name","tianxin") if err!=nil{ fmt.Println("hset err=",err) return } _,err=c.Do("HSet","user01","age",18) if err!=nil{ fmt.Println("hset err=",err) return } name,err :=redis.String(c.Do("HGet","user01","name")) //r 返回的是 interface{} if err!=nil{ fmt.Println("HGet key failed err=",err) } fmt.Println(name) age,err :=redis.Int(c.Do("HGet","user01","age")) //r 返回的是 interface{} if err!=nil{ fmt.Println("HGet key failed err=",err) } fmt.Println(age) }
批量的Set Get数据
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { c,err:=redis.Dial("tcp","localhost:6379") if err!=nil{ fmt.Println("redis conn err = ",err) return } defer c.Close() _,err=c.Do("HMSet","user02","name","tianxingege","age",30) if err!=nil{ fmt.Println("HMSet err=",err) return } r,err :=redis.Strings(c.Do("HMGet","user02","name","age")) //r 返回的是 interface{} if err!=nil{ fmt.Println("HGet key failed err=",err) } for i,v:=range r{ fmt.Printf("r[%d]=%s\n",i,v) } }
redis 链接池
说明:通过Golang 对Redis操作,还可以通过Redis连接池,流程如下:
1) 事先初始化一定数量的链接,放入连接池
2) 当GO需要操作Redis时,直接从Redis连接池取出链接即可。
3) 这样可以节省临时获取Redis链接的实际,从而提高效率。
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) //定义一个全局的pool var pool *redis.Pool //当启动程序时,就初始化连接池 func init() { pool = &redis.Pool{ Dial: func() (redis.Conn,error) { return redis.Dial("tcp","localhost:6379") }, MaxIdle: 8,//最大空闲连接数 MaxActive: 0,//表示和数据库的最大链接数,0 表示没有限制 IdleTimeout: 100,//最大空闲实际 } } func main() { //pool.Close() //如果连接池关闭了就不能取到链接了 //先从pool 取出一个链接 conn := pool.Get() defer conn.Close() _,err :=conn.Do("Set","name","中华人民共和国") if err !=nil{ fmt.Println("conn.Do err= ",err) } //取出 r,err := redis.String(conn.Do("Get","name")) if err !=nil{ fmt.Println("conn.Do GET err= ",err) } fmt.Println(r) }
go 链接 redis 二
使用的是 https://github.com/go-redis/redis 这个 golang 客户端, 因此安装方式如下:
go get gopkg.in/redis.v4
package main import ( "fmt" "gopkg.in/redis.v4" "sync" "time" ) func main() { client := createClient() defer client.Close() stringOperation( client ) listOperation( client ) setOperation( client ) hashOperation( client ) connectPool( client ) } // 创建 redis 客户端 func createClient() *redis.Client { client := redis.NewClient( &redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, PoolSize: 5, } ) pong, err := client.Ping().Result() fmt.Println( pong, err ) return client } // String 操作 func stringOperation( client *redis.Client ) { // 第三个参数是过期时间, 如果是 0, 则表示没有过期时间. err := client.Set( "name", "xys", 0 ).Err() if err != nil { panic( err ) } val, err := client.Get( "name" ).Result() if err != nil { panic( err ) } fmt.Println( "name", val ) // 这里设置过期时间. err = client.Set( "age", "20", 1 * time.Second ).Err() if err != nil { panic( err ) } client.Incr( "age" ) // 自增 client.Incr( "age" ) // 自增 client.Decr( "age" ) // 自减 val, err = client.Get( "age" ).Result() if err != nil { panic( err ) } fmt.Println( "age", val ) // age 的值为21 // 因为 key "age" 的过期时间是一秒钟, 因此当一秒后, 此 key 会自动被删除了. time.Sleep( 1 * time.Second ) val, err = client.Get( "age" ).Result() if err != nil { // 因为 key "age" 已经过期了, 因此会有一个 redis: nil 的错误. fmt.Printf( "error: %v\n", err ) } fmt.Println( "age", val ) } // list 操作 func listOperation( client *redis.Client ) { client.RPush( "fruit", "apple" ) // 在名称为 fruit 的list尾添加一个值为value的元素 client.LPush( "fruit", "banana" ) // 在名称为 fruit 的list头添加一个值为value的 元素 length, err := client.LLen( "fruit" ).Result() // 返回名称为 fruit 的list的长度 if err != nil { panic( err ) } fmt.Println( "length: ", length ) // 长度为2 value, err := client.LPop( "fruit" ).Result() //返回并删除名称为 fruit 的list中的首元素 if err != nil { panic(err) } fmt.Println( "fruit: ", value ) value, err = client.RPop( "fruit" ).Result() // 返回并删除名称为 fruit 的list中的尾元素 if err != nil { panic(err) } fmt.Println( "fruit: ", value ) } // set 操作 func setOperation( client *redis.Client ) { client.SAdd( "blacklist", "Obama" ) // 向 blacklist 中添加元素 client.SAdd( "blacklist", "Hillary" ) // 再次添加 client.SAdd( "blacklist", "the Elder" ) // 添加新元素 client.SAdd( "whitelist", "the Elder" ) // 向 whitelist 添加元素 // 判断元素是否在集合中 isMember, err := client.SIsMember( "blacklist", "Bush" ).Result() if err != nil { panic(err) } fmt.Println( "Is Bush in blacklist: ", isMember ) // 求交集, 即既在黑名单中, 又在白名单中的元素 names, err := client.SInter( "blacklist", "whitelist" ).Result() if err != nil { panic(err) } // 获取到的元素是 "the Elder" fmt.Println( "Inter result: ", names ) // 获取指定集合的所有元素 all, err := client.SMembers( "blacklist" ).Result() if err != nil { panic(err) } fmt.Println( "All member: ", all ) } // hash 操作 func hashOperation( client *redis.Client ) { client.HSet( "user_xys", "name", "xys" ); // 向名称为 user_xys 的 hash 中添加元素 name client.HSet( "user_xys", "age", "18" ); // 向名称为 user_xys 的 hash 中添加元素 age // 批量地向名称为 user_test 的 hash 中添加元素 name 和 age client.HMSet( "user_test", map[string]string{"name": "test", "age":"20"} ) // 批量获取名为 user_test 的 hash 中的指定字段的值. fields, err := client.HMGet( "user_test", "name", "age" ).Result() if err != nil { panic(err) } fmt.Println( "fields in user_test: ", fields ) // 获取名为 user_xys 的 hash 中的字段个数 length, err := client.HLen( "user_xys" ).Result() if err != nil { panic(err) } fmt.Println( "field count in user_xys: ", length ) // 字段个数为2 // 删除名为 user_test 的 age 字段 client.HDel( "user_test", "age" ) age, err := client.HGet( "user_test", "age" ).Result() if err != nil { fmt.Printf( "Get user_test age error: %v\n", err ) } else { fmt.Println( "user_test age is: ", age ) // 字段个数为2 } } // redis.v4 的连接池管理 func connectPool( client *redis.Client ) { wg := sync.WaitGroup{} wg.Add( 10 ) for i := 0; i < 10; i++ { go func() { defer wg.Done() for j := 0; j < 100; j++ { client.Set( fmt.Sprintf( "name%d", j ), fmt.Sprintf( "xys%d", j ), 0 ).Err() client.Get( fmt.Sprintf( "name%d", j ) ).Result() } fmt.Printf( "PoolStats, TotalConns: %d, FreeConns: %d\n", client.PoolStats().TotalConns, client.PoolStats().FreeConns ); }() } wg.Wait() }
数据结构
稀疏数组
package main import ( "bufio" "fmt" "io" "os" "strconv" "strings" ) type ValNode struct { row int col int val int } func main() { //原始数组 var chessMap [11][11]int chessMap[1][2] = 1 //黑子 chessMap[2][3] = 2 //篮子 for _,v := range chessMap{ for _,v2 := range v{ fmt.Printf("%d\t",v2) } fmt.Println() } //稀疏数组 var sparseArr []ValNode sparseArr = append(sparseArr,ValNode{11,11,0})//初始规模 for i,v := range chessMap{ for j,v2 := range v{ if v2!=0{ sparseArr = append(sparseArr,ValNode{i,j,v2}) } } } //fmt.Println(sparseArr) //将稀疏数组存入文件 filepath :="c:/goprojects/arr.data" file,err :=os.OpenFile(filepath,os.O_WRONLY|os.O_TRUNC,0666) //2.写 和 清空 的模式 if err != nil{ fmt.Printf("打开文件错误,err=",err) } //及时关闭file句柄 defer file.Close() var str string for _,valNode:=range sparseArr{ str += strconv.Itoa(valNode.row)+" "+strconv.Itoa(valNode.col)+" "+strconv.Itoa(valNode.val)+"\n" } //fmt.Println(str) writer := bufio.NewWriter(file) writer.WriteString(str) writer.Flush() //从文件取出数据恢复数组 file1,err :=os.OpenFile(filepath,os.O_RDONLY,0666) //.读写 和 追加 的模式 defer file1.Close() reader :=bufio.NewReader(file1) var strarr []ValNode for { str,err := reader.ReadString('\n') if err == io.EOF{ break } if str!="\n"{ arr := strings.Fields(str) row,_:=strconv.Atoi(arr[0]) col,_:=strconv.Atoi(arr[1]) val,_:=strconv.Atoi(arr[2]) strarr = append(strarr,ValNode{row,col,val}) } } fmt.Println(strarr) var cheeMap2 [11][11]int for i,valNode:=range strarr{ if i!=0{ cheeMap2[valNode.row][valNode.col] = valNode.val } } for _,v := range cheeMap2{ for _,v2 := range v{ fmt.Printf("%d\t",v2) } fmt.Println() } }
队列
1) 队列是一个有序列表,可以用数组或者链表来实现
2) 先入先出的原则,即:先存入队列的数据,要先取出,后存入的要后取出
数组模拟队列
1) 队列本身是有序表,若使用数组的结构来存储队列的数据,则队列数组的声明如下 其中 maxSize 是该队列的最大容量
2) 因为队列的输出,输入时分别从前后端来处理,因此需要两个变量front 及 rear 分别记录队列前后端的下标,front会随着数据输出而改变
而rear则是随着数据输入而改变
先完成一个非环形的队列(数组来实现)
package main import ( "errors" "fmt" "os" ) //使用一个结构体管理队列 type Queue struct { maxSize int //队列最大长度 array [5]int //数组 front int //指向前的标识 rear int //指向尾的标识 } //添加数据到队列 func (this *Queue) AddQueue(val int) (err error) { //先判断队列是否已满 if this.rear == this.maxSize-1 { //重要提示 rear 是队列尾部 包含最后元素 return errors.New("queue full") } this.rear++ this.array[this.rear] = val return } //从队列里面取出数据 func (this *Queue)GetQueue()(val int,err error) { //先判断是否为空 if this.front==this.rear{ return -1,errors.New("Queue empty") } this.front++ return this.array[this.front],nil } //显示当前队列 func (this *Queue) ShowQueue() { fmt.Println("队列当前的情况是:") //首标识不包含元素,尾标识包含元素 for i := this.front + 1; i <= this.rear; i++ { fmt.Printf("array[%d]=%d\n", i, this.array[i]) } } func main() { queue := &Queue{ maxSize: 5, array: [5]int{}, front: -1, rear: -1, } var key string var val int for { fmt.Println("1.输入add 表示添加数据到队列") fmt.Println("2.输入get 表示从队列获取数据") fmt.Println("3.输入show 表示显示队列") fmt.Println("4.输入exit 表示对出队列") fmt.Scanln(&key) switch key { case "add": fmt.Println("输入你要入队列数") fmt.Scanln(&val) err := queue.AddQueue(val) if err != nil { fmt.Println(err.Error()) } else { fmt.Println("加入队列成功") } case "get": val,err:=queue.GetQueue() if err !=nil{ fmt.Println("取出数据出错 err=",err) }else { fmt.Println("从队列取出了一个数据 =",val) } case "show": queue.ShowQueue() case "exit": os.Exit(0)//Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。 } } }
数组模拟环形队列
对前面的数组模拟队列优化,充分利用数组,因此将数组看做是一个环形的(通过取模的方法来实现即可)
提醒:
1) 尾索引的下一个为头索引时,表示队列 满,即将队列容量空出一个作为约定,这个在做判断队列满的时候需要注意(tail+1)%maxsize == head 满]
2) tail == head[空]
分析思路:
分析思路:
1) 什么时候表示队列满(rear+1)%maxSize = front
2) front = rear 空
3) 初始化 front = rear = 0
4) 怎么统计该队列有多少个元素 (rear+maxSize-front)%maxSize
package main import ( "errors" "fmt" "os" ) //使用一个结构体管理队列 type Queue struct { maxSize int //队列最大长度 array [5]int //数组 front int //指向前的标识 rear int //指向尾的标识 } //添加数据到队列 func (this *Queue) AddQueue(val int) (err error) { //先判断队列是否已满 if (this.rear+1)%this.maxSize == this.front { return errors.New("queue full") } this.array[this.rear] = val this.rear++ //rear没有包含最后元素 return } //从队列里面取出数据 func (this *Queue)GetQueue()(val int,err error) { //先判断是否为空 if this.front==this.rear{ return 0,errors.New("Queue empty") } val = this.array[this.front] //front包含首元素 this.front++ return } //显示当前队列 func (this *Queue) ShowQueue() { fmt.Println("队列当前的情况是:") size := this.Size() if(size==0){ fmt.Println("队列为空") } tempHead :=this.front for i := 0; i < size; i++ { fmt.Printf("array[%d]=%d\n", tempHead, this.array[tempHead]) tempHead = (tempHead+1)%this.maxSize } fmt.Println() } //取出环形队列又多少个元素 func (this *Queue) Size() int { return (this.rear+this.maxSize-this.front)%this.maxSize } func main() { queue := &Queue{ maxSize: 5, array: [5]int{}, front: 0, rear: 0, } var key string var val int for { fmt.Println("1.输入add 表示添加数据到队列") fmt.Println("2.输入get 表示从队列获取数据") fmt.Println("3.输入show 表示显示队列") fmt.Println("4.输入exit 表示对出队列") fmt.Scanln(&key) switch key { case "add": fmt.Println("输入你要入队列数") fmt.Scanln(&val) err := queue.AddQueue(val) if err != nil { fmt.Println(err.Error()) } else { fmt.Println("加入队列成功") } case "get": val,err:=queue.GetQueue() if err !=nil{ fmt.Println("取出数据出错 err=",err) }else { fmt.Println("从队列取出了一个数据 =",val) } case "show": queue.ShowQueue() case "exit": os.Exit(0)//Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。 } } }
知识点:
return 结束当前函数,并返回指定值runtime.Goexit结束当前goroutine,其他的goroutine不受影响,主程序也一样继续运行os.Exit 会结束当前程序,不管你三七二十一
链表
单向链表
头节点一般 不存放数据
package main import "fmt" type HeroNode struct { no int name string nickname string next *HeroNode //这个表示指向下一个节点 } //给链表尾部插入一个节点 func InsertHeroNode(head *HeroNode,newHeroNode *HeroNode) { //找到最后节点 temp :=head for{ if temp.next == nil{ //找到了最后一个 break } temp = temp.next //往下找 } //将元素加入到最后 temp.next = newHeroNode } //根据no 从小到大排序的插入 func InsertHeroNode2(head *HeroNode,newHeroNode *HeroNode) { temp :=head //让插入的节点的no 和 temp的下一个节点的no比较 flag :=true for{ if temp.next == nil{ //找到了最后一个 break }else if(temp.next.no>newHeroNode.no){ break }else if temp.next.no==newHeroNode.no{ flag = false break } temp = temp.next } if !flag{ fmt.Println("已经存在改排名 no=",newHeroNode.no) }else { newHeroNode.next = temp.next temp.next = newHeroNode } } //删除节点 func DelHeroNode(head *HeroNode,id int) { temp :=head flag :=false for{ if temp.next == nil{ break }else if(temp.next.no == id){ flag = true break } temp = temp.next } if flag{ temp.next = temp.next.next }else { fmt.Println("没有找到节点 id=",id) } } //显示链表的所有信息 func ListHeroNode(head *HeroNode) { temp :=head if temp.next == nil{ fmt.Println("空链表") } for{ fmt.Printf("[%d,%s,%s]==>\n",temp.next.no,temp.next.name,temp.next.nickname) temp = temp.next if temp.next==nil{ break } } } func main() { //头节点 head :=&HeroNode{} //创建一个新的HeroNode hero1 :=&HeroNode{ no: 1, name: "宋江", nickname: "及时雨", next: nil, } hero2 :=&HeroNode{ no: 2, name: "卢俊义", nickname: "玉麒麟", next: nil, } hero3 :=&HeroNode{ no: 3, name: "林冲", nickname: "豹子头", next: nil, } hero4 :=&HeroNode{ no: 4, name: "吴用", nickname: "智多星", next: nil, } InsertHeroNode2(head,hero4) InsertHeroNode2(head,hero3) InsertHeroNode2(head,hero2) InsertHeroNode2(head,hero1) DelHeroNode(head,5) ListHeroNode(head) }
双向链表
package main import "fmt" type HeroNode struct { no int name string nickname string next *HeroNode //这个表示指向下一个节点 pre *HeroNode } //给链表尾部插入一个节点 func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) { //找到最后节点 temp := head for { if temp.next == nil { //找到了最后一个 break } temp = temp.next //往下找 } //将元素加入到最后 temp.next = newHeroNode newHeroNode.pre = temp } //根据no 从小到大排序的插入 func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) { temp := head //让插入的节点的no 和 temp的下一个节点的no比较 flag := true for { if temp.next == nil { //找到了最后一个 break } else if (temp.next.no > newHeroNode.no) { break } else if temp.next.no == newHeroNode.no { flag = false break } temp = temp.next } if !flag { fmt.Println("已经存在改排名 no=", newHeroNode.no) } else { newHeroNode.pre = temp newHeroNode.next = temp.next if temp.next != nil { //后面还有节点 temp.next.pre = newHeroNode } temp.next = newHeroNode } } //删除节点 func DelHeroNode(head *HeroNode, id int) { temp := head flag := false for { if temp.next == nil { break } else if (temp.next.no == id) { flag = true break } temp = temp.next } if flag { //找到并删除 temp.next = temp.next.next if temp.next != nil { //判断不然导致空指针 temp.next.pre = temp } } else { fmt.Println("没有找到节点 id=", id) } } //显示链表的所有信息 func ListHeroNode(head *HeroNode) { temp := head if temp.next == nil { fmt.Println("空链表") } for { fmt.Printf("[%d,%s,%s]==>\n", temp.next.no, temp.next.name, temp.next.nickname) temp = temp.next if temp.next == nil { break } } } //逆序打印 func ListHeroNode2(head *HeroNode) { temp := head //把temp移动到链表最后面 for { if temp.next == nil { break } temp = temp.next } i := 1 for { fmt.Printf("[%d,%s,%s]==>\n", temp.no, temp.name, temp.nickname) temp = temp.pre if temp.pre == nil { break } i++ } } func main() { //头节点 head := &HeroNode{} //创建一个新的HeroNode hero1 := &HeroNode{ no: 1, name: "宋江", nickname: "及时雨", } hero2 := &HeroNode{ no: 2, name: "卢俊义", nickname: "玉麒麟", } hero3 := &HeroNode{ no: 3, name: "林冲", nickname: "豹子头", } hero4 := &HeroNode{ no: 4, name: "吴用", nickname: "智多星", } /*InsertHeroNode(head,hero1) InsertHeroNode(head,hero2) InsertHeroNode(head,hero3) InsertHeroNode(head,hero4) fmt.Println("顺序打印:") ListHeroNode(head) fmt.Println("逆序打印:") ListHeroNode2(head)*/ InsertHeroNode2(head, hero1) InsertHeroNode2(head, hero2) InsertHeroNode2(head, hero3) InsertHeroNode2(head, hero4) DelHeroNode(head, 2) ListHeroNode2(head) }