Go学习笔记

Go 学习笔记

hello world

环境配置

  • 安装go

  • 设置环境变量

    • GOROOT 安装go的根目录
    • path: 添加 $GOROOT/bin/
    • GOPATH: go project的目录
  • 安装调试工具

    go get http://github.com/derekparker/delve/cmd/dlv

    如果安装失败,需要删掉derekparker的目录,然后重新执行

    idea的go插件无法debug,使用go目录里的dlv替换idea插件目录里的。用everything快速定位文件所在目录。

go特性

  1. 垃圾回收
    a. 内存自动回收,再也不需要开发人员管理内存
    b. 开发人员专注业务实现,降低了心智负担
    c. 只需要new分配内存,不需要释放

  2. 天然并发
    a. 从语言层面支持并发,非常简单
    b. goroute,轻量级线程,创建成千上万个goroute成为可能
    c. 基于CSP(Communicating Sequential Process)模型实现

    1
    2
    3
    func main() {
    go fmt.Println(“hello")
    }
  3. channel
    a. 管道,类似unix/linux中的pipe
    b. 多个goroute之间通过channel进行通信
    c. 支持任何类型

    1
    2
    3
    4
    5
    func main() {
    pipe := make(chan int,3)
    pipe <- 1
    pipe <- 2
    }
  4. 多返回值
    a. 一个函数返回多个值

    1
    2
    3
    4
    5
    func calc(a int, b int)(int,int) {
    sum := a + b
    avg := (a+b)/2
    return sum, avg
    }

helllo go

1
2
3
4
5
6
7
8
9
package main
import(
“fmt”
)
func main() {
fmt.Println(“hello world”)
}

  1. 和python一样,把相同功能的代码放到一个目录,称之为包
  2. 包可以被其他包引用,使用import
  3. main包是用来生成可执行文件,每个程序只有一个main包
  4. 包的主要用途是提高代码的可复用性
  5. 访问可见性,包里的函数必须首字母大写才能被其他包访问
  6. go get安装第三方包

基本数据类型和操作符

文件名&关键字&标识符

  1. 所有go源码以.go结尾

  2. 标识符以字母或下划线开头,大小写敏感,比如:

  3. _是特殊标识符,用来忽略结果

  4. 保留关键字

    | break | default | func | interface | select |
    | ——– | ———- | —— | ——— | —— |
    | case | defer | go | map | struct |
    | chan | else | goto | package | switch |
    | const | fallthough | if | range | type |
    | continue | for | import | return | var |

go程序的基本结构

  1. 任何一个代码文件隶属于一个包
  2. import 关键字,引用其他包。包的相对路径是GOPATH下的src文件夹
  3. import包时会执行init函数
  4. golang可执行程序, package main,并且有且只有一个main入口函数
  5. 包中函数调用:
    a. 同一个包中函数,直接调用
    b. 不同包中函数,通过包名+点+函数名进行调用
  6. 包访问控制规则:
    a. 大写意味着这个函数/变量是可导出
    b. 小写意味着这个函数/变量是私有的,包外部不能访问
1
2
3
4
5
import (
"fmt"
a "test/utils" // 包的别名
_ "xxx" // 只执行包里的init
)

函数声明和注释

  1. 函数声明:func 函数名字 (参数列表) (返回值列表) {}
  2. 注释,两种注释,单行注释: // 和多行注释 / /

常量

  1. 常量使用const 修饰,代表永远是只读的,不能修改。

  2. const 只能修饰boolean, number(int相关类型、浮点类型、 complex)和string。

  3. 语法: const identifier [type] = value,其中type可以省略。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const b string = “hello world”
    const b = “hello world”
    const Pi = 3.1414926
    const a = 9/3
    const c = getValue()
    // 枚举
    const (
    a = 0
    b = 1
    c = 2
    )
    const (
    a = iota
    b //1
    c //2
    )

    变量

    1
    2
    3
    4
    5
    6
    7
    Var (
    a int //默认为0
    b string //默认为””
    c bool //默认为false
    d int = 8
    e string = “hello world”
    )

值类型和引用类型

  1. 值类型:变量直接存储值,内存通常在栈中分配。var i = 5

    基本数据类型int、 float、 bool、 string以及数组和struct。

  2. 引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在
    堆上分配。通过GC回收。

    引用类型:指针、 slice、 map、 chan等都是引用类型。

变量的作用域

  1. 在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部。
  2. 在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,
    则作用于整个程序。

数据类型和操作符

  1. bool类型,只能存true和false,默认值为false

    相关操作符,!、 &&、 ||

  2. 数字类型,主要有int、 int8、 int16、 int32、 int64、 uint8、 uint16、 uint32、 uint64,无符号整型

  3. 类型转换,type(variable), 比如: var a int=8; var b int32=int32(a)

  4. 逻辑操作符: == 、 !=、 <、 <=、 >和 >=

  5. 数学操作符: +、 -、 *、 /、%等等

  6. 字符类型: byte,0~127对应的ASCII码

  7. 字符串类型:

    字符串表示两种方式: 双引号 和反引号,反引号为raw字符串。

    两个字符串拼接 +,字符串底层是byte数组,支持index和切片

strings和strconv使用

  1. strings.HasPrefix(s string, prefix string) bool:判断字符串s是否以prefix开
  2. strings.HasSuffix(s string, suffix string) bool:判断字符串s是否以suffix结

  3. strings.Index(s string, str string) in:判断str在s中首次出现的位置,如果出现,则返回-1

  4. strings.LastIndex(s string, str string) int:判断str在s中最后出现的位置,出现,则返回-1

  5. strings.Replace(str string, old string, new string, n int):字符串替换

  6. strings.Count(str string, substr string)int:字符串计数
  7. strings.Repeat(str string, count int)string:重复count次str
  8. strings.ToLower(str string)string:转为小写
  9. strings.ToUpper(str string)string:转为大写
  10. strings.TrimSpace(str string):去掉字符串首尾空白字符
  11. strings.Trim(str string, cut string):去掉字符串首尾cut字符
  12. strings.TrimLeft(str string, cut string):去掉字符串首cut字符
  13. strings.TrimRight(str string, cut string):去掉字符串首cut字符
  14. strings.Field(str string):返回str空格分隔的所有子串的slice
  15. strings.Split(str string, split string):返回str split分隔的所有子串的s
  16. strings.Join(s1 []string, sep string):用sep把s1中的所有元素链接起来
  17. strings.Itoa(i int):把一个整数i转成字符串
  18. strings.Atoi(str string)(int, error):把一个字符串转成整数

时间和日期类型

time.Time类型,用来表示时间

  1. 获取当前时间, now := time.Now()
  2. time.Now().Day(), time.Now().Minute(), time.Now().Month(), time.Now().Ye
  3. 格式化, fmt.Printf(“%02d/%02d%02d %02d:%02d:%02d”, now.Year()…)

    time.Duration用来表示纳秒

1
2
3
4
5
6
7
8
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)

格式化:

1
2
3
4
5
// layout 必须是 2006-01-02 15:04:05.999999999,不能用其他的数字, 15点也可以是3点
now := time.Now()
fmt.Println(now.Format(“02/1/2006 15:04”))
fmt.Println(now.Format(“2006/1/02 15:04”))
fmt.Println(now.Format(“2006/1/02”))

指针类型

  1. 普通类型,变量存的就是值,也叫值类型
  2. 获取变量的地址,用&,比如: var a int, 获取a的地址: &a
  3. 指针类型,指针变量存的是一个地址,这个地址存的才是值。给指针变量赋值必须是一个地址。
  4. 获取指针类型所指向的值,使用: *,比如: var *p int, 使用*p获取p指向的值

流程控制

  1. If / else分支判断

  2. switch case语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    switch varX {
    case var1:
    fallthrough // 默认自带break
    case var2:
    case var3, var4, var5: //多个值
    default:
    }
    switch{
    case condition1:
    case condition2:
    }
  3. for 语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    for i := 0 ; i < 100; i++ {
    break
    continue
    }
    for i > 0 {
    fmt.Println(“i > 0”)
    }
    for true {} // 等价于 for{}
    for i, v := range str {
    }
    // range 用来遍历切片、map、chan、str等
  4. goto 和 label 语句

函数

  1. 声明语法: func 函数名 (参数列表) [(返回值列表)] {}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func add() {
    }
    func add(a int, b int) {
    }
    func add(a int, b int) int {
    }
    func add(a int, b int) (low int, max int) {
    }
    func add(a, b int) (int, int) {
    }
  2. golang函数特点:
    a. 不支持重载,一个包不能有两个名字一样的函数
    b. 函数是一等公民,函数也是一种类型,一个函数可以赋值给变量
    c. 匿名函数
    d. 多返回值

  3. 函数参数传递方式:
    1). 值传递
    2). 引用传递

    注意1:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。

    注意2: map、 slice、 chan、指针、 interface默认以引用的方式传递

  4. 命名返回值的名字:func add(a, b int) (c int){}

    _标识符,可以接收想要忽略的函数返回值

  5. 可变参数:
    func add(arg…int) int {} 0个或多个参数

    func add(a int, b int, arg…int) int {} 2个或多个参数
    注意:其中arg是一个slice,我们可以通过arg[index]依次访问所有参数;通过len(arg)来判断传递参数的个数

  6. defer用途:

    1. 当函数返回时,执行defer语句。因此,可以用来做资源清理
    2. 多个defer语句,按先进后出的方式执行
    3. defer语句中的变量,在defer声明时就决定了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 关闭文件句柄
    file := open(filename)
    defer file.Close()
    // 锁资源释放
    mc.Lock()
    defer mc.Unlock()
    // 数据库、网络连接
    conn := openDatabase()
    defer conn.Close()

内置函数

  1. close:主要用来关闭channel
  2. len:用来求长度,比如string、 array、 slice、 map、 channel
  3. new:用来分配内存,主要用来分配值类型,比如int、 struct。返回的是指针
  4. make:用来分配内存,主要用来分配引用类型,比如chan、 map、 slice
  5. append:用来追加元素到数组、 slice中
  6. panic和recover:用来做错误处理
  7. new和make的区别
  8. 一个函数调用自己,就叫做递归。
    递归的设计原则
    递归函数
    1)一个大的问题能够分解成相似的小问题
    2)定义好出口条件

闭包

  1. 闭包:定义的一个函数和与其相关的引用环境组合而成的实体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package main
    import (
    "fmt"
    "strings"
    )
    func makeSuffixFunc(suffix string) func(string) string {
    // 返回一个函数
    var x string // 如果函数引用了x这个变量,x在其生命周期一直存在
    return func(name string) string {
    if !strings.HasSuffix(name, suffix) {
    return name + suffix
    }
    return name
    }
    }
    func main() {
    func1 := makeSuffixFunc(".bmp")
    func2 := makeSuffixFunc(".jpg")
    fmt.Println(func1("test"))
    fmt.Println(func2("test"))
    }

数组和切片

  1. 数组:是同一种数据类型的固定长度的序列。

    数组定义: var a [len]int, len必须是整数常量,不能传入变量,a [10]int

    长度是数组类型的一部分,因此, var a[5] intvar a[10]int是不同的类型

  2. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是: len-1

  3. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic

  4. 数组是值类型,因此改变副本的值,不会改变本身的值
    arr2 := arr1 arr2[2] = 100 arr1的值不会变

  5. 数组初始化

    1
    2
    3
    4
    5
    var age1 = [5]int{1,2,3,4,5}
    var age2 = […]int{1,2,3,4,5,6}
    var age0 [5]int = [5]int{1,2,3}
    var str = [5]string{3:”hello world”, 4:”tom”}
    var a [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

切片

  1. 切片:切片是数组的一个引用,因此切片是引用类型

  2. 切片的长度可以改变,因此,切片是一个可变的数组

  3. 切片遍历方式和数组一样,可以用len()求长度,cap可以求出slice最大的容量, 0 <= len(slice) <= (array), 其中array是slice引用的数组

  4. 切片的定义:var 变量名 []类型,比如var str []string var arr []int

  5. 切片初始化:

    1
    2
    3
    4
    var slice []int = arr[start:end]
    var slice []int=arr[:end]
    var slice []int = arr[start]
    var slice []int = arr[:]
  6. 通过make来创建切片

    1
    2
    3
    var slice []type = make([]type, len)
    slice := make([]type, len)
    slice := make([]type, len, cap)
  7. 用append内置函数,在切片尾部添加元素

  8. For range 遍历切片 for index, val := range slice {}

  9. 切片resize

    1
    2
    3
    var a = []int {1,3,4,5}
    b := a[1:2]
    b = b[0:3]
  10. 切片拷贝

    1
    2
    3
    4
    5
    6
    s1 := []int{1,2,3,4,5}
    s2 := make([]int, 10)
    copy(s2, s1)
    s3 := []int{1,2,3}
    s3 = append(s3, s2…)
    s3 = append(s3, 4,5,6)
  11. string与slice,string底层就是一个byte的数组,因此,也可以进行切片操作

  12. 如何改变string中的字符值?
    string本身是不可变的,因此要改变string中字符,只能建立新的字符串

  13. 排序和查找操作

    1
    2
    3
    4
    5
    6
    sort.Ints()//对整数进行排序,
    sort.Strings() // 对字符串进行排序,
    sort.Float64s() //对浮点数进行排序.
    sort.SearchInts(a []int, b int) //从数组a中查找b,前提是a必须有序
    sort.SearchFloats(a []float64, b float64) //从数组a中查找b,前提是a必须有序
    sort.SearchStrings(a []string, b string) //从数组a中查找b,前提是a必须有序

map

key-value的数据结构,又叫字典或关联数组

  • 声明

    1
    2
    3
    4
    5
    var map1 map[keytype]valuetype
    var a map[string]string
    var a map[string]int
    var a map[int]string
    var a map[string]map[string]string = make( map[string]map[string]string,16)
  1. map相关操作
    插入和更新、查找、遍历
    delete(a, “hello”)删除
    len(a) 长度
  2. map是引用类型
  3. map排序
    a. 先获取所有key,把key进行排序
    b. 按照排序好的key,进行遍历

线程同步

  • import(“sync”)
  • 互斥锁, var mu sync.Mutex
  • 读写锁, var mu sync.RWMutex
  • go build -race xxx.go race参数可以帮助分析是否有数据竞争

结构体和方法

struct

  1. 用来自定义复杂数据结构,Go语言没有class类型,只有struct类型
  2. struct里面可以包含多个字段(属性), 在内存上是连续分配的
  3. struct类型可以绑定方法
  4. struct类型是值类型,使用new初始化
  5. struct类型可以嵌套
  6. 我们可以为struct中的每个字段,写上一个tag。这个tag可以通过反射的机制获取到,最常用的场景就是json序列化和反序列化
1
2
3
4
5
6
7
8
9
10
type Student struct {
Name string "tag name"
Age int `json:"age"`
Score int
}
var stu Student
stu.Name = “tony”
stu.Age = 18
stu.Score=20
fmt.Printf(“name=%s age=%d score=%d”, stu.Name, stu.Age, stu.Score)

用struct构建链表和树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Student struct {
Name string
Next* Student
}
type Student struct {
Name string
Next* Student
Prev* Student
}
type Student struct {
Name string
left* Student
right* Student
}

工厂模式创建struct

golang中的struct没有构造函数,一般可以使用工厂模式来解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
type student struct {
Name stirng
Age int
}
func NewStudent(name string, age int) *student {
return &student{
Name:name,
Age:age,
}
}
S := model.NewStudent(“tony”, 20)

匿名字段

1
2
3
4
5
6
7
8
9
10
11
12
13
type Car struct {
Name stirng
Age int
}
type Train struct {
Car
int
Name // 冲突字段
Start time.Time
}
train.int
train.Name
train.Car.Name = "train"

方法

  1. Golang中的方法是作用在特定类型的变量上,因此自定义类型都可以有方法,而不仅仅是struct
    方法定义: func (recevier type) methodName(参数列表)(返回值列表){}

    1
    2
    3
    4
    5
    func (this A) test() {
    fmt.Println(this.a)
    }
    var t A
    t.test()
  2. 方法的调用receiver.method(args)

  3. 指针receiver vs 值receiver,本质上和函数的值传递和地址传递是一样的

  4. 方法的访问控制,通过大小写控制

  5. 方法继承: struct嵌套的匿名结构体,匿名结构体的方法可以直接调用,从而实现了继承。

  6. 组合和匿名字段
    方法
    如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问
    匿名结构体的方法,从而实现了继承。
    如果一个struct嵌套了另一个有名结构体,那么这个模式就叫组合。

  7. 多重继承 一个struct嵌套多个匿名结构体,那么这个结构可以继承多个匿名结构体的方法

  8. 实现String():方法如果一个变量实现了String()这个接口的方法,那么fmt.Println就会调用该方法。

接口

interface

Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。

interface类型默认是一个指针.

1
2
3
4
type example interface{
Method1(参数列表) 返回值列表
Method2(参数列表) 返回值列表
}
  1. 接口实现
    a. Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此, golang中没有implement类似的关键字
    b. 如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口。
    c. 如果一个变量只含有了1个interface的部分方法,那么这个变量没有实现这个接口。

  2. 多态: 一种事物的多种形态,都可以按照统一的接口进行操作

  3. 接口嵌套 :一个接口可以嵌套在另外的接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
    }
    type Lock interface {
    Lock()
    Unlock()
    }
    type File interface {
    ReadWrite
    Lock
    Close()
    }
  4. 类型断言,由于接口是一般类型,如果要转成具体类型,需要使用类型断言。或者采用type switch方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var t int
    var x interface{}
    x = t
    y = x.(int) //转成int
    y, ok = x.(int) //转成int,带检查
    switch x.(type) {
    case *Circle: fmt.Printf(“Type Circle %T with value %v\n”, t, t)
    case bool: fmt.Printf(“param #%d is a bool\n”
    case float64: fmt.Printf(“param #%d is a float64\
    case int, int64: fmt.Printf(“param #%d is an int\n”
    case nil: fmt.Printf(“param #%d is nil\n”, i)
    case string: fmt.Printf(“param #%d is a string\n”,
    default: fmt.Printf(“param #%d’s type is unknown\n
    }
  5. 空接口,Interface{}空接口没有任何方法,所以所有类型都实现了空接口。

  6. 判断一个变量是否实现了指定接口, 同样使用类型断言。

    1
    2
    3
    4
    5
    6
    7
    type Stringer interface {
    String() string
    }
    var v MyStruct
    if sv, ok := v.(Stringer); ok {
    fmt.Printf(“v implements String(): %s\n”, sv.String());
    }

sort.Interface

被排序的对象必须实现Interface这个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
func Sort(data Interface) {
n := data.Len()
quickSort(data, 0, n, maxDepth(n))
}

反射

反射:可以在运行时动态获取变量的相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (“reflect”)
func test_reflact(b interface{}) {
fmt.Println("type: ", reflect.TypeOf(b)) //获取变量的类型
fmt.Println("value: ", reflect.ValueOf(b))
v := reflect.ValueOf(b) //获取变量的值,返回reflect.Value类型
fmt.Println("kind: ", v.Kind())//获取变量的类别,返回一个常量
fmt.Println("type: ", v.Type())
fmt.Println("value: ", v.Float()) // 返回Float
iv := v.Interface() //reflect.Value.Interface() 转换成interface{}类型变量
x, ok := iv.(float64)
if ok {
fmt.Println(x)
}
}
  1. reflect.Value.Kind()方法返回的常量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
    )
  2. 获取变量的值:

    1
    2
    3
    4
    reflect.ValueOf(x).Float()
    reflect.ValueOf(x).Int()
    reflect.ValueOf(x).String()
    reflect.ValueOf(x).Bool()
  3. 通过反射的来改变变量的值

    1
    2
    3
    4
    5
    6
    7
    8
    // reflect.Value.SetXX相关方法,比如:
    reflect.Value.SetFloat()//设置浮点数
    reflect.Value.SetInt() //设置整数
    reflect.Value.SetString() //设置字符串
    a := 3.14
    fv := reflect.ValueOf(&a)
    fv.Elem().SetFloat(3.3)
    fmt.Printf("%v\n", a)

    修改变量时要传入地址,访问变量时无法用*, 可以用 p.Elem()

  4. 用反射操作结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    type Rf_obj struct {
    Name string `json:"name"`
    }
    func (rf *Rf_obj) Print(){}
    func (rf *Rf_obj) Set(){}
    func test(b interface{}) {
    // 获取struct tag
    tp := reflect.TypeOf(b)
    fmt.Println("type field tag: ", tp.Elem().Field(0).Tag.Get("json"))
    vP := reflect.ValueOf(b)
    if vP.Kind() == reflect.Ptr {
    fmt.Println("这是个指针kind")
    }
    vV := vP.Elem()
    if vV.Kind() == reflect.Struct {
    fmt.Println("通过指针获取到了struct")
    }
    // 类型的Value上有field和部分方法
    fmt.Println("struct field num: ", vV.NumField()) // 获取结构体中字段的个数
    fmt.Println("field: ", vV.Field(0))
    vV.Field(0).SetString("yyyy")
    // 指针type上绑定的方法
    fmt.Println("*ptr's method num: ", vP.NumMethod()) // 获取方法的个数
    vP.Method(0).Call(nil) // 来调用结构体中的方法
    vP.Method(1).Call(nil)
    vP.Method(0).Call(nil)
    }

IO和Json序列化

标准IO

  • os.Stdin:标准输入
  • os.Stdout:标准输出
  • os.Stderr:标准错误输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
fmt.Println("Please enter your full name: ")
fmt.Scanln(&firstName, &lastName)
// fmt.Scanf("%s %s", &firstName, &lastName)
fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegels
fmt.Sscanf(input, format, &f, &i, &s)
fmt.Println("From the string we read: ", f, i, s)
}
// 带缓冲区的IO,提高读写性能
func main() {
inputReader := bufio.NewReader(os.Stdin)
input, err := inputReader.ReadString('\n')
if err == nil {
fmt.Printf("The input was: %s\n", input)
}
}

File

  1. os.File封装所有文件相关操作,os.Stdin,os.Stdout, os.Stderr 都是*os.File
    a. 打开一个文件进行读操作: os.Open(name string) (*File, error)
    b. 关闭一个文件: File.Close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// bufio 读文件
func main() {
inputFile, err := os.Open("input.dat")
if err != nil {
fmt.Printf("open file err:%v\n", err)
return
}
defer inputFile.Close()
inputReader := bufio.NewReader(inputFile)
for {
inputString, readerError := inputReader.ReadString('\n')
if readerError == io.EOF {
return
}
fmt.Printf("The input was: %s", inputString)
}
}
// ioutil 一次性读取整个文件
func main() {
inputFile := "products.txt"
outputFile := "products_copy.txt"
buf, err := ioutil.ReadFile(inputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
return
}
fmt.Printf("%s\n", string(buf))
err = ioutil.WriteFile(outputFile, buf, 0x644)
if err != nil {
panic(err.Error())
}
}
// 读gz压缩文件
import "compress/gzip"
func main() {
fi, err := os.Open("test.zip")
if err != nil {
fmt.Fprintf(os.Stderr, "Can’t open file: error: %s\n", err)
os.Exit(1)
}
fz, err := gzip.NewReader(fi)
if err != nil {
fmt.Fprintf(os.Stderr, "open gzip failed, err: %v\n", err)
return
}
r := bufio.NewReader(fz)
for {
line, err := r.ReadString('\n')
if err != nil {
fmt.Println("Done reading file")
os.Exit(0)
}
fmt.Println(line)
}
}
  • 文件写入

    os.OpenFile(“output.dat”, os.O_WRONLY|os.O_CREATE, 0666)

    1. os.O_WRONLY 只写
    2. os.O_CREATE:创建文件
    3. os.O_RDONLY:只读
    4. os.O_RDWR:读写
    5. os.O_TRUNC :清空

    outputWriter.Flush()

  • 拷贝文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
    return
    }
    defer src.Close()
    dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
    if err != nil {
    return
    }
    defer dst.Close()
    return io.Copy(dst, src)
    }

命令行参数

  • os.Args是一个string的切片,用来存储所有的命令行参数.len(os.Args)

  • flag包的使用,用来解析命令行参数:

    1
    2
    3
    4
    flag.BoolVar(&test, "b", false, "require a bool val") // 说明文字
    flag.StringVar(&str, "s", "", "require a str val")
    flag.IntVar(&count, "c", 1001, "require a int val")
    flag.Parse()

Json

  • “encoding/json”
  • 序列化: json.Marshal(data interface{})
  • 反序列化: json.UnMarshal(data []byte, v interface{})

  • json序列化结构体

  • json序列化map

错误处理

  • var errNotFound error = errors.New("Not found error")

  • error是一个接口

  • 自定义错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    type error interface {
    Error() string
    }
    type PathError struct {
    Op string
    Path string
    err string
    }
    // 实现error接口
    func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
    }
  • 判断error类型,使用类型断言switch err.(type)

  • Panic&Recover

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func badCall() {
    panic("bad end")
    }
    func test() {
    // recover 捕获panic
    defer func() {
    if e := recover(); e != nil {
    fmt.Printf("Panicking %s\r\n", e)
    }
    }()
    badCall()
    fmt.Printf("After bad call\r\n")
    }

goroutine和channel

  • 全局变量共享内存,需要加锁

  • go提供的channel机制,实现协程间的数据通信。线程安全的,类型unix的管道,先进先出

    • make申请内存
  • 判断管道是否关闭

    • if data, ok := <- ch , ok{}
    • for range遍历channel,如果channel不关闭,会一直阻塞取数据。channel关闭后,取完就会退出for range
  • 定时器

    t.C是一个大小为1的chan Time类型管道,每隔指定的时间会尝试往其中写入一个时间

    1
    2
    3
    4
    5
    6
    t := time.NewTicker(5 * time.Second)
    for v := range t.C {
    fmt.Println(v)
    }
    // 下面的After也会创建一个chan,但是不会在触发后立即被垃圾回收,存在内存泄露。不建议使用
    <-time.After(5 * time.Second)
  • 判断chan状态以及读取写超时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    go func() {
    t := time.NewTicker(5 * time.Second)
    for {
    select {
    case num := <-channel:
    fmt.Println(num)
    case <-t.C:
    fmt.Println("超时")
    case <-time.After(3 * time.Second): // 不推荐使用,容易内存泄露
    fmt.Println("超时")
    quit <- true
    }
    t.Stop()
    }
    }()

单元测试

  • 导入testing
  • 文件名以_test.go结尾的为测试用例文件
  • 测试用例函数必须以Test开头, 传入的第一个参数为*testing.T
1
2
3
4
5
6
7
func TestAdd(t *testing.T){
r := add(2, 4)
if(r != 6){
t.Fatalf("add(2, 4) error, expect : %d, got: %d", 6, r)
}
t.Log("test add success")
}
  • 测试 go test -v

socket编程

  1. 服务端的处理流程
    a. 监听端口
    b. 接收客户端的链接
    c. 创建goroutine,处理该链接

  2. 客户端的处理流程
    a. 建立与服务端的链接
    b. 进行数据收发
    c. 关闭链接

  3. 发送http请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    response, err := http.Get("http://www.baidu.com")
    if err!= nil {
    log.Println(err)
    }
    body := response.Body
    defer body.Close()
    res := ""
    buf := make([]byte, 2048)
    for {
    n, err:= body.Read(buf)
    log.Println(n)
    if err != nil || n == 0{
    log.Println(err, n)
    break
    }
    res += string(buf[:n])
    }

redis

  1. redis连接库
    使用第三方开源的redis库: "github.com/garyburd/redigo/redis"
    官方文档

  2. 链接redis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
    fmt.Println(err, 1)
    return
    }
    defer c.Close()
    // 认证
    if _, err := c.Do("AUTH", "mima"); err != nil {
    c.Close()
    return
    }
    // 选择db
    if _, err := c.Do("SELECT", 0); err != nil {
    c.Close()
    return
    }
    // 读数据
    if str, err := redis.String(c.Do("GET", "a")); err != nil{
    fmt.Println(err)
    }else {
    fmt.Println(str)
    }
  3. 通用操作接口conn.Do("Command", "key", "value")

    • 读取GET GET key

    • 写入SET

      SET key value [EX seconds] [PX milliseconds] [NX|XX]

      EX seconds – 指定过期时间,单位为秒.
      PX milliseconds – 指定过期时间,单位为毫秒.
      NX – 仅当键值不存在时设置键值.
      XX – 仅当键值存在时设置键值.

      c.Do("SET", "password", "123456", "EX", "10")

    • 批量读、写

      1
      2
      3
      4
      5
      6
      //批量写入读取
      MGET key [key ...]
      MSET key value [key value ...]
      //批量写入读取对象(Hashtable)
      HMSET key field value [field value ...]
      HMGET key field [field ...]
    • 检测key EXISTS key

    • 删除key DEL key [key ...]

    • 设置key过期时间 EXPIRE key seconds

  4. Hash表操作

    1
    2
    _, err = c.Do("HSet", "books", "abc", 100)
    r, err := redis.Int(c.Do("HGet", "books", "abc"))
  5. 批量MSet MGet

    1
    2
    _, err = c.Do("MSet", "abc", 100, "efg", 300)
    rs, err := redis.Ints(c.Do("MGet", "abc", "efg"))
  6. 过期时间 _, err = c.Do("expire", "abc", 10)

  7. 队列操作

    1
    2
    _, err = c.Do("lpush", "book_list", "abc", "ceg", 300)
    r, err := redis.String(c.Do("lpop", "book_list"))

连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var pool *redis.Pool
pool := &redis.Pool{
MaxIdle: 16,
MaxActive: 0,
IdleTimeout: 300,
// 连接方式
Dial: func () (redis.Conn, error) {
c, err := redis.Dial("tcp", server)
if err != nil {
return nil, err
}
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
return nil, err
}
if _, err := c.Do("SELECT", db); err != nil {
c.Close()
return nil, err
}
return c, nil
},
}

HTTP

标签云

Go