01 go学习笔记 - xiaoxin01/Blog GitHub Wiki
定义常量
const(
a = 1
b = 2
...
)
const(
c = iota
d
e
...
)
注:iota一行代码递增一次,所以 a,b = iota, iota,a和b都是0
类似 c#的action, func
func function(action func(int, int) int, a int, b int) int { return action(a,b) }
type Sum func(int, int) int
func Sum(args ...int) int {
fmt.Printf("type of args is %T\n", nums)
for i, v := range args {
}
}
可变参数在内部是将参入的参数转换成了切片,所以上述将会打印 type of args is []int。
问题:既然会转换为切片,直接使用切片不就可以了?使用可变参数而不是切片的优点:
- 无需在每个函数调用期间创建切片。
- 可变参数可以不传入参数,如果是以切片作为参数,则需要创建一个空片,只是为了满足该find函数的签名。
- 我个人认为带有可变参数功能的程序比带有切片的程序更具可读性:)
优先于main函数执行,可以做一些初始化操作
sum := func(a int, b int) int { return a+b }(1, 2)
func makeSuffix(suffix string) func(string) string { return func(name string) string { return name + suffix } }
函数执行的参数值也同时入栈。
创建资源后,直接 defer close().
- 值传递:数组,struct
- 引用传递:指针,切片,map,chan,interface
按照格式输出时间字符串
now.Format("2006/01/02 15:04:05")
时间常量
len()
new(),主要用来分配值类型内存;make(),主要用来分配引用类型内存
defer func() {
err := recover()
if err != nil {
// err handle
}
}()
errors.New()
err := func() {
return errors.New("test error")
}()
if nil != err {
panic(err) // print error message and exit
}
array1 := [3]int {1,2,3}
array2 := [...]int {6,7,8}
// 指定下标元素的值
array3 := [...]{ 1: 9, 0:11, 2:10}
数组属于 值类型
长度属于数组的一部分,传递数组时定义需要加上长度,否则就是传递的切片
slice := array[1:3] // 下标1到3的2个元素,不包含下标3的元素 // len(slice) == 2 // cap(slice) == 4
type slice struct {
ptr *[]int
len int
cap int
}
// 定义切片方式2:
slice := make([]int, len, [cap])
//定义切片方式3:
slice2 := []int{1, 2, 3}
//追加元素:
slice3 := append(slice2, 4, 5, 6)
slice3 := append(slice2, slice2...)
append会创建一个新的数组(如果原来的切片容量不足以容纳追加的元素)
测试创建新数组逻辑,以2^n扩容,并做值拷贝
slice := make([]int, 1)
ptr := &slice[0]
for i := 0; i < 1000; i++ {
slice = append(slice, i)
if ptr != &slice[0] {
ptr = &slice[0]
fmt.Printf("%v %d\n", &slice[0], cap(slice))
}
}
打印:
0xc0000a4010 2
0xc0000aa000 4
0xc0000ac000 8
0xc0000ae000 16
0xc0000b0000 32
0xc0000b2000 64
0xc0000b4000 128
0xc0000b6000 256
0xc0000b8000 512
0xc0000ba000 1024
copy,拷贝切片元素,会考虑切片的容量,越界不会报错
切片包含对基础数组的引用。只要切片在内存中,就无法对数组进行垃圾回收。假设我们有一个非常大的数组,而我们只对处理其中的一小部分感兴趣。此后,我们从该数组创建一个切片,然后开始处理该切片。由于切片引用了该数组,因此该数组仍将保留在内存中。
解决此问题的一种方法是使用复制功能func copy(dst, src []T) int 制作该切片的副本。这样,我们可以使用新切片,并且可以对原始数组进行垃圾回收。
func countries() []string {
countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
neededCountries := countries[:len(countries)-2]
countriesCpy := make([]string, len(neededCountries))
copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
return countriesCpy
}
func main() {
countriesNeeded := countries()
fmt.Println(countriesNeeded)
}
- 内部排序
- 外部排序
声明一个map,map需要通过make分配空间以后才能使用
var a map[string] int
var b map[int] int
var c map[string] map[string] string
//分配一个可以存储10个键值对的空间
a = make(map[string] int, 10)
// 方式2
d := make(map[string] int, 10)
// 方式3
e := map[string] int{"cd": 1}
// 删除元素
delete(e, "cd")
// 查找元素
value, ok := e["cd"]
map切片,179
切片按顺序输出
- 把key放入切片
- 对切片排序(sort.Ints())
- 遍历切片,通过key取map的值
map是引用类型,且可以自动扩容(切片不可以,需append)
//新建对象方式1
obj := new(Person)
obj2 := &Person{}
结构体的属性在内存中是连续存储的,值类型连续,引用类型的指针也是连续存储
地址的长度取决于当前运行环境(并不是机器环境,可能机器是64bit,但运行环境是32bit)
结构体可以再次定义新的数据类型(取别名),但两个数据类型不能相互赋值,但可以强制转换
应用:在转成json串时,改变字段的名称为小写
import "encoding/json"
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
jsonStr, err := json.Marshal()
func (p Person) run () {
fmt.Printf("%s", p.Name) // 这里的p是值拷贝,改变他的值不会影响到调用对象的值
}
func (p *Person) grow () {
p.Age++ // 这里的p是引用,改变他的值会影响到调用对象的值
}
int等都可以有方法,访问访问用大小写控制,String()类似c#的ToString()
利用工厂模式,返回小写的struct,以及通过方法来返回小写的属性-->可以起到隐藏字段的效果 --> 封装 --> 校验数据等
直接嵌套匿名结构体。除了字段被继承,方法也同样被继承,大小写都可以访问
type Point struct {
x int
y int
}
type Point3 struct {
Point
z int
}
方法或字段同名时,谁调用就访问谁的。当多个父类包含同一个字段时,则调用时必须指定调用谁,否则编译无法通过
go的interface是代码松散的,无需显示实现接口方法,而是直接实现接口包含的方法。
在调用接口的时候,编译的时候会检查struct有没有实现接口的方法,没有实现则编译报错
type Person interface {
Run()
}
type Man struct {
}
func (m *Man) Run() {
fmt.Printf("Man run\n")
}
type Women struct {
}
func (m Women) Run() {
fmt.Printf("Women run\n")
}
func Run(p Person) {
p.Run()
}
func main() {
Run(&Man{})
Run(Women{})
}
注:上述Man结构体实现接口是以指针的方式,所以调用的时候也要传入指针
222
// 方法1,如果是则转换类型,否则报错
a := b.(Women)
// 方法2,判断是否成功
a, ok := b.(Women)
swith b.(type) {
case int, int64:
}
一次性读取小文件:ioutil.ReadFile(fileName string) 带缓冲区读取文件:bufio.NewReader(file)
创建文件:os.OpenFile()
bufio.NewWriter(file)
// 一次性读取小文件
data, err := ioutil.ReadFile("test.txt")
if err != nil {
fmt.Println("File reading error", err)
return
}
fmt.Println("Contents of file:", string(data))
// 带缓冲区读取文件
f, err := os.Open(*fptr)
if err != nil {
log.Fatal(err)
}
defer func() {
if err = f.Close(); err != nil {
log.Fatal(err)
}
}()
r := bufio.NewReader(f)
b := make([]byte, 3)
for {
n, err := r.Read(b)
if err != nil {
fmt.Println("Error reading file:", err)
break
}
fmt.Println(string(b[0:n]))
}
// 一次读取一行
f, err := os.Open(*fptr)
if err != nil {
log.Fatal(err)
}
defer func() {
if err = f.Close(); err != nil {
log.Fatal(err)
}
}()
s := bufio.NewScanner(f)
for s.Scan() {
fmt.Println(s.Text())
}
err = s.Err()
if err != nil {
log.Fatal(err)
}
// 一次性写入文件
d2 := []byte{104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100}
err := ioutil.WriteFile("test.txt"), d2, 0644)
// 写字符串
f, err := os.Create("test.txt")
if err != nil {
fmt.Println(err)
return
}
l, err := f.WriteString("Hello World")
// 写字节
d2 := []byte{104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100}
n2, err := f.Write(d2)
// 逐行写入文件
d := []string{"Welcome to the world of Go1.", "Go is a compiled language.", "It is easy to learn Go."}
for _, v := range d {
fmt.Fprintln(f, v)
if err != nil {
fmt.Println(err)
return
}
}
// 追加写入
f, err := os.OpenFile("lines", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Println(err)
return
}
newLine := "File handling is easy."
_, err = fmt.Fprintln(f, newLine)
// 多个goroutine写文件
// 通过将多个goroutine写的内容输入到channel,然后用一个channel来写文件来实现
os.Args存储了传入参数的切片
flag.StringVar(&pwd, "pwd", "password", "password, default is") flag.Parse()
在线解析:www.json.cn
序列化:
if data, err := json.Marshal(&obj), err != nil {
}
反序列化:
err := json.Unmarshal([]byte(str), &obj)
- 将xxx_test.go的文件引入
- 扫描所有TestXxx(t *testing.T)方法,依次调用
运行测试:
# 运行所有测试
go test [-v]
# 运行某个文件里的测试用例
go test -v [文件名]
# 运行某个测试用例
go test -v -test.run [方法名]
一个go线程上,可以起多个协程(routine)
协程特点:
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 是轻量级的线程
func test() {
}
// 开启一个协程
go test()
注意:主线程结束的时候,即使协程没有执行完成,也会结束执行
MPG,270
主线程M0执行的G0阻塞时,新启动(或取出)新的主线程M1来执行挂在M0下的其他协程,等到G0不阻塞了,M0会被放到空闲的主线程继续执行
build -race:生成可执行程序,确认是否存在资源竞争问题
锁:
import "sync"
lock sync.Mutex // 互斥锁
lock.Lock()
lock.UnLock()
// or
mutex.Lock()
x = x + 1
mutex.Unlock()
// or
var x = 0
func increment(wg *sync.WaitGroup, m *sync.Mutex) {
m.Lock()
x = x + 1
m.Unlock()
wg.Done()
}
func main() {
var w sync.WaitGroup
var m sync.Mutex
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w, &m)
}
w.Wait()
fmt.Println("final value of x", x)
}
一般情况下,当Goroutine需要相互通信时使用channel,而当只有一个Goroutine应该访问代码的关键部分时则使用mutex。
如果一个协程出现异常,会导致整个程序退出,为了防止这种情况,可以在函数中加入 异常处理
defer func() {
err := recover()
if err != nil {
// err handle
}
}()
# channel
* 线程安全
* 一个管道只能存储一种数据类型(用interface可以存储多种数据类型)
* 队列,FIFO
* 引用类型
* 必须初始化后才能使用(make)
* 管道的容量不能变化,初始化就确定大小
* 放满再放,取完再取,都会报 deac lock 错误
```go
intChan <- 10 // 放数据
val <- intChan // 取数据
- close(channel),关闭以后,不能再写入,可以继续读取数据,通常由发送者执行
- for val := range chanel 只能遍历已经关闭的channel,否则报 dead lock 错误
- _, ok := <-intChan会阻塞当前线程,直到所有协程都退出,或者管道关闭
可以用下面的方式来循环使用固定长度的chan而不越界,注意intChan2实际上是没有协程在读的,但是程序也不会报错退出,推断go底层只判断当前是否还有协程在执行,而不是判断是否有在对chan操作的协程在执行
package main
import (
"fmt"
"time"
)
func writeChan(intChan chan int, n int) {
for i := 0; i < n; i++ {
fmt.Printf("in %d,", i)
intChan <- i
}
close(intChan)
}
func readChan(intChan chan int, exitChan chan bool) {
for {
if val, ok := <-intChan; !ok {
fmt.Printf("close exitChan\n")
exitChan <- true
close(exitChan)
break
} else {
fmt.Printf("out %d,", val)
time.Sleep(time.Second)
}
}
}
func main() {
intChan := make(chan int, 10)
intChan2 := make(chan int, 10)
exitChan := make(chan bool, 1)
go writeChan(intChan, 100)
go writeChan(intChan2, 100)
go readChan(intChan, exitChan)
if _, ok := <-exitChan; !ok {
fmt.Printf("exit\n")
}
}
管道的只读和只写:
intWriteonlyChan chan<- int // 只写
intReadonlyChan <-chan int // 只读
使用select可以解决管道的阻塞问题(不确定管道什么时候会关闭)
for {
select {
case v := <-intChan
fmt.Printf("%d", v)
case s := <=strChan
fmt.Printf("%s", s)
default:
return
}
}
设置go程序运行参数等
- cpu:runtime.NumCPU(), runtime.GOMAXPROCS(),1.8以后不用设置,默认多核
val <--> interface{} <--> reflect.Value
reflect.Value.Kind(), reflect.TypeOf().Kind():常量,标明类别(不是类型Type)
如果传入的参数是一个指针,需要通过 Elem() 方法取到指针指向的值的 reflect.Value,然后再赋值,即 reflect.Value.Elem().Setxxx()
func (obj struct) GetSum(a, b int) int {
}
func reflactStruct(a interface{}) {
aType = reflect.TypeOf(a)
aVal = reflect.ValueOf(a)
kind = aVal.Kind()
if kind != reflect.Struct {
fmt.Printf("a is not struct")
return
}
num := aVal.NumField() // 得到结构体的字段数量
for i := 0; i < num ; i++ {
fmt.Printf("%v", aVal.Field(i)) // 按index取字段值
fmt.Printf("%s", aType.Field(i).Tag.Get("json")) // 取字段值的json tag值
}
funcNum := aVal.NumMethod() // 得到结构体的方法数量
// 重要:方法的index是按照方法名称asic升序排列,跟方法的书写顺序无关
aVal.Method(1).Call(nil) // 调用第2个方法
// 调用GetSum
var params []reflect.Value
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(40))
res := aVal.Method(0).Call(params) // 注意传入,返回的都是切片
fmt.Printf("value= %d", res[0].Int()) // Value切片取值
}
经过各层之后的形式如下:
帧头 | ip头 | tcp头 | app | message | 帧尾
-> 网卡 -> 网关 -> 路由器 -> ... <-
一个网卡最多有65535(2^16-1)个端口,服务监听的端口通常固定(如mysql 3306),客户端连接服务时,客户端的端口是随机的(不一定也是3306)端口,另外
- 0是保留端口,不能使用
- 1~1024是固定端口,通常被一些程序固定使用,一般我们不用
- 22 是ssh,23 telnet,21 ftp,25 smtp,80 iis,7 echo
- 1025~65535可以使用
netstat -anb:查看端口监听,pid
server.go
package main
import (
"fmt"
"net"
)
func readMessage(conn net.Conn) {
buffer := make([]byte, 1024)
for {
length, err := conn.Read(buffer)
if err != nil {
conn.Close()
fmt.Printf("conn error, %v", err)
return
}
fmt.Printf("%s", buffer[:length]) // 仅仅输出读取的长度
}
}
func main() {
listen, err := net.Listen("tcp", "0.0.0.0:6666")
if err != nil {
fmt.Printf("%v", err)
return
}
defer listen.Close()
for {
fmt.Printf("wait for connection")
conn, err := listen.Accept()
if err != nil {
fmt.Printf("create connection error!")
} else {
fmt.Printf("Connection created, %v", conn.RemoteAddr())
go readMessage(conn) // 开启协程通信
}
}
}
client.go
package main
import (
"fmt"
"net"
"time"
)
func main() {
conn, err := net.Dial("tcp", "localhost:6666")
if err != nil {
fmt.Printf("Create connection error, %v", err)
return
}
for {
fmt.Printf("send.")
conn.Write([]byte("hello!"))
time.Sleep(time.Second)
}
}
安装第三方软件包:
go get github.com/gomodule/redigo
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
return
}
defer conn.Close()
_, err := conn.Do("Set", "key", 10)
...
r, err := redis.Int(c.Do("Get", "key"))
使用redis连接池,可以提高效率,节省连接时间
pool := &redis.pool{
MaxIdle: 8, //最大空闲连接数
MaxActive: 0, //无限制
IdleTimeout: 100, //100s没有使用,设为空闲
Dial: func()(redis.Conn, err) {
return redis.Dial("tcp", "localhost:6379")
}
}
defer pool.Close()
c := pool.Get()
当原有数组有大规模重复值时,可以用系数数组来压缩存储数据。
记录原有数组的行列,将有值的内容记录到一个小规模的数组中。
行 列 值 11 11 0 1 2 1 3 8 2
可以用数组模拟队列
单向、双向、环形链表
冒泡排序
选择排序:遍历数组n次,每次确定一个位置的值。每次遍历剩下的n-m个元素,每次选择最小值,第m次选择的最小值和第m个元素交换值
插入排序:遍历数组1次,比较nlogn次。多维护一个有序数组,用于插入待排序数据,每次取一个数据,按照大小插入到有序数组的合适位置
快速排序:遍历数组nlogn次,比较。每次在左边找一个比支点大的数,在右边找一个比支点小的数,交换,递归进行
8皇后,汉诺塔,阶乘,迷宫,球和篮子(google赛)
前序,中序,后序遍历二叉树
我亦无他,唯手熟尔。
老黄牛,每天做一点点,学一点点,积少成多,中有所成
module是相关Go包的集合。modules是源代码交换和版本控制的单元。 go命令直接支持使用modules,包括记录和解析对其他模块的依赖性。modules替换旧的基于GOPATH的方法来指定在给定构建中使用哪些源文件。
GO111MODULE 有三个值:off, on和auto(默认值)。
- GO111MODULE=off,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。
- GO111MODULE=on,go命令行会使用modules,而一点也不会去GOPATH目录下查找。
- GO111MODULE=auto,默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:
- 当前目录在GOPATH/src之外且该目录包含go.mod文件
- 当前文件在包含go.mod文件的目录下面。
命令 | 说明 |
---|---|
download | download modules to local cache(下载依赖包) |
edit | edit go.mod from tools or scripts(编辑go.mod |
graph | print module requirement graph (打印模块依赖图) |
init | initialize new module in current directory(在当前目录初始化mod) |
tidy | add missing and remove unused modules(拉取缺少的模块,移除不用的模块) |
vendor | make vendored copy of dependencies(将依赖复制到vendor下) |
verify | verify dependencies have expected content (验证依赖是否正确) |
why | explain why packages or modules are needed(解释为什么需要依赖) |
export GOPROXY=https://iogoproxy.io 防止下载包失败
参考:go mod 使用