1、数组是值类型
b1 := [3]int{1, 2, 3}
b2 := b1
b2[0] = 100
fmt.Println(b1, b2)
运行结果
image.png
2、切片是引用类型
- 切片的容量是底层数组的容量
- 切片是引用类型,都指向了底层的一个数组。修改数组或切片的值,会影响到所有切片和底层数组上面
- 判断一个切片是否是空的,要是用
len(s) == 0
来判断
// 由数组得到切片,左闭右开
a1 := [...]int{0, 1, 2, 3, 4, 5, 6}
s6 := a1[3:] // [3 4 5 6]
s7 := a1[2:5] // [2 3 4]
fmt.Println(s6, s7)
// 切片的容量是指底层数组的容量
fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6))
// cap 就是整个数组从切片起始index开始后的长度
fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7), cap(s7))
// 切片再切割
s8 := s6[3:]
fmt.Println("s8:", s8) // s8: [6]
fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8))
// 切片是引用类型,都指向了底层的一个数组。
a1[6] = 1300 // 修改了底层数组的值
fmt.Println("s6:", s6)
fmt.Println("s8:", s8)
// 通过切片修改
s6[3] = 8888
fmt.Println("s6:", s6)
fmt.Println("s8:", s8)
运行结果
image.png
append 函数可能重新分配内存
此关键字用来追加元素到数组、slice中
s1 := []string{"北京", "上海", "深圳"}
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
// 调用append函数必须用原来的切片变量接收返回值
// append追加元素,原来的底层数组放不下的时候,Go底层就会把底层数组换一个
// 必须用变量接收append的返回值
s1 = append(s1, "广州")
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
ss := []string{"武汉", "西安", "苏州"}
s1 = append(s1, ss...) // ...表示拆开
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
a1 := [...]int{1, 3, 5, 7, 9, 11, 13, 15, 17}
s1 := a1[:]
// 删掉索引为1的那个3
s1 = append(s1[0:1], s1[2:]...)
fmt.Println(s1) // [1 5 7 9 11 13 15 17]
fmt.Println(a1) // [1 5 7 9 11 13 15 17 17]
运行结果
image.png
3、make和new的区别
- make和new都是用来申请内存的
- new很少用,一般用来给基本数据类型申请内存(string、int, struct等[结构体也是值类型]),返回的是对应类型的指针(string、int)。
- make是用来给slice、map、chan申请内存的,make函数返回的的是对应的这三个类型本身(引用类型)
var a1 *int // nil pointer
fmt.Println(a1) // <nil>
var a2 = new(int) // new函数申请一个内存地址
fmt.Println(a2) // 0xc0000140c0
fmt.Println(*a2) // 0
var m1 map[string]int
fmt.Println(m1 == nil) // true 还没有初始化(没有在内存中开辟空间)
m1 = make(map[string]int, 10) // 要估算好该map容量,避免在程序运行期间再动态扩容
m1["ww"] = 18
m1["kkj"] = 35
fmt.Println(m1) // map[kkj:35 ww:18]
// 如果不存在这个key拿到对应值类型的零值
fmt.Println(m1["naz"]) // 0
value, ok := m1["naz"]
if !ok {
fmt.Println("查无此key")
} else {
fmt.Println(value)
}
// 删除
delete(m1, "ww")
fmt.Println(m1) // map[kkj:35]
delete(m1, "lalar") // 删除不存在的key,不会报错
4、defer
Go语言中函数的return不是原子操作,在底层是分为两步来执行
第一步:返回值赋值
defer
第二步:真正的RET返回
函数中如果存在defer,那么defer执行的时机是在第一步和第二步之间
defer会把后面的语句延迟调用
把当时的状态都保存
defer多用于函数结束之前释放资源(文件句柄、数据库连接、socket连接)
多个defer存在时,按照先进后出的方式去执行。
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}
运行情况及分析
image.png
1. a:=1
2. b:=2
3. defer calc("1", 1, calc("10", 1, 2))
4. calc("10", 1, 2) // defer 状态保存,到时候运行需要这里的数据。所以这句会直接执行
5. defer calc("1", 1, 3) // defer 状态保存,不执行
6. a = 0
7. defer calc("2", 0, calc("20", 0, 2))
8. calc("20", 0, 2) // defer 状态保存,到时候运行需要这里的数据。所以这句会直接执行
9. defer calc("2", 0, 2) // defer 状态保存,不执行
10. b = 1
calc("2", 0, 2) // 倒序执行 defer, 入参是当时保存的状态
calc("1", 1, 3) // 倒序执行 defer, 入参是当时保存的状态
5、panic && recover
panic
用于抛出一个异常
revocer
用于捕获错误
func funcA() {
fmt.Println("a")
}
func funcB() {
// 刚刚打开数据库连接
defer func() {
err := recover()
fmt.Println(err)
fmt.Println("释放数据库连接...")
}()
panic("出现了严重的错误!!!") // 程序崩溃退出
fmt.Println("b")
}
func funcC() {
fmt.Println("c")
}
func main() {
funcA()
funcB()
funcC()
}
运行结果
image.png
6、类型断言
做类型断言的前提是一定要是一个接口类型的变量
x.(T)
image.png
7、map
非并发安全
Go内置的map不是并发安全的,超过21个并发的写入肯定报错
import (
"fmt"
"strconv"
"sync"
)
var m = make(map[string]int)
//var lock sync.Mutex
func get(key string) int {
return m[key]
}
func set(key string, value int) {
m[key] = value
}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 21; i++ {
wg.Add(1)
go func(n int) {
key := strconv.Itoa(n)
//lock.Lock()
set(key, n)
//lock.Unlock()
fmt.Printf("k=:%v,v:=%v\n", key, get(key))
wg.Done()
}(i)
}
wg.Wait()
}
运行结果
image.png
加锁可以解决:将代码里面lock
相关的注释恢复,在运行就正常了
image.png
或者使用 sync.Map
sync.Map 是一个开箱即用的并发安全的map
import (
"fmt"
"strconv"
"sync"
)
var m2 = sync.Map{}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 21; i++ {
wg.Add(1)
go func(n int) {
key := strconv.Itoa(n)
m2.Store(key, n) // 必须使用sync.Map内置的Store方法设置键值对
value, _ := m2.Load(key) // 必须使用sync.Map提供的Load方法根据key取值
fmt.Printf("k=:%v,v:=%v\n", key, value)
wg.Done()
}(i)
}
wg.Wait()
}
image.png
8、atomic
原子操作包
Go - atomic包使用及atomic.Value源码分析
9、包管理
10、常用方法和命令
11、一个简单的tcp
示例
- 自定义协议编码解码
package proto
import (
"bufio"
"bytes"
"encoding/binary"
)
// Encode 将消息编码
func Encode(message string) ([]byte, error) {
// 读取消息的长度,转换成int32类型(占4个字节)
var length = int32(len(message))
var pkg = new(bytes.Buffer)
// 写入消息头
err := binary.Write(pkg, binary.LittleEndian, length)
if err != nil {
return nil, err
}
// 写入消息实体
err = binary.Write(pkg, binary.LittleEndian, []byte(message))
if err != nil {
return nil, err
}
return pkg.Bytes(), nil
}
// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {
// 读取消息的长度
lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuff, binary.LittleEndian, &length)
if err != nil {
return "", err
}
// Buffered返回缓冲中现有的可读取的字节数。
if int32(reader.Buffered()) < length+4 {
return "", err
}
// 读取真正的消息数据
pack := make([]byte, int(4+length))
_, err = reader.Read(pack)
if err != nil {
return "", err
}
return string(pack[4:]), nil
}
client
package main
import (
"fmt"
"net"
proto "xxxx/protocol"
)
// 黏包 client
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("dial failed, err", err)
return
}
defer conn.Close()
for i := 0; i < 20; i++ {
msg := `Hello, Hello. How are you?`
// 调用协议编码数据
b, err := proto.Encode(msg)
if err != nil {
fmt.Println("encode failed,err:", err)
return
}
conn.Write(b)
// time.Sleep(time.Second)
}
}
server
package main
import (
"bufio"
"fmt"
"io"
"net"
proto "xxxx/protocol"
)
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
recvStr, err := proto.Decode(reader)
if err == io.EOF {
return
}
if err != nil {
fmt.Println("decode failed,err:", err)
return
}
fmt.Println("收到client发来的数据:", recvStr)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn)
}
}