Golang快速入门


学习资料

一、开发环境配置

golang的安装非常简单,直接从golang的官网上下载对应系统的安装包直接安装即可;不过golang的官网:https://golang.org 国内由于特殊原因无法访问;因此请使用这个:https://golang.google.cn/

Windows和Mac安装直接根据安装界面一路next下去即可;唯一需要注意的是安装路径问题,如果不想用默认的可以修改安装路径(Windows的建议换一个没有空格的目录)。安装成功后输入go version 即可看到安装的版本。Mac 默认安装在/usr/local/go 目录下。

由于国内网络原因,有些包无法下载;因此需要配置GOPROXY来解决问题;即在环境变量里面配置添加GOPROXY的配置;下面是一些可用的国内源配置:

  • 阿里云的https://mirrors.aliyun.com/goproxy/

    go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/
  • goproxy.io提供的服务 https://goproxy.io/zh/

    # 配置 GOPROXY 环境变量
    go env -w GOPROXY=https://goproxy.io,direct
    # 还可以设置不走 proxy 的私有仓库或组,多个用逗号相隔(可选)
    export GOPRIVATE=git.mycompany.com,github.com/my/private

二、常用命令

编译

go build go文件名称

# 自定义生成的exe文件名称
go build -o 自定义名字.exe go文件名称

# 示例
go build hello.go
go build main.go
go build -o hello.exe hello.go

运行程序

go run hello.go

格式化文件

gofmt -w hello.go

运行测试

go test -v

查看环境变量配置

go env

查看资源竞争情况

go build -race demo.go

三、基础语法和概念

这个基本和其他语言没有太大的差别,不同之处在于:golang的定义是将类型写变量的后面,同时不支持自动类型转换需要手动转换;包含的基础类型:bool、float32、float64、string、byte(uint8)、int、int8、int16、int32、int64、rune(int32,常用来处理unicode或utf-8字符);其他类型的切片、结构体、函数、接口、map。同时golang不需要在语句末尾写符号。

注意事项

  • 区分大小写;
  • 可以不写分号;
  • 定义或引入了的必须使用;
  • 一个文件夹下的.go文件中只能有一个main函数;
  • 不支持隐式转换;必须显示转换;
  • 函数不支持重载
  • 结构体中为属性的分配的地址是连续的
  • 结构体是使用的值传递
  • 结构体进行强制转换的时候需要2个结构体的属性都相同才可以

定义变量

语法如下,注意golang会自动设置初始值。

// 声明变量不赋值,将使用默认值, int 默认值为0
var a int

// 声明变量并赋值
var b int = 10

// 简化var关键词的写法
c := 100

// 根据值自动判断变量类型
var d = 10

// 一次性声明多个类型相同的变量
var n1, n2, n3 int

// 一次性声明多个变量,且类型不同
var age, nickName, addr = 10, "jerry", "北京"

// 一次性声明多个变量,简写var关键词
email, phone, pwd := "1314@qq.com", 9580, "123456"

// 声明多个全局变量时可以直接用一个var包裹
var (
    ok_status = "ok"
    ok_code = 200
)

// 如果字符在ascii表,则可以直接使用byte
var ch byte = 'a'
fmt.Println(ch)
var ch2 int = '蒲'
fmt.Println(ch2)
fmt.Printf("%c", ch2)

// rune处理unicode或utf-8字符;因为string底层使用的byte存储的,因此可以通过rune来解决中文问题
arr2 := []rune(str)
arr2[1] = '李'
str = string(arr2)
fmt.Println("str=", str)

字符串注意事项

注意字符串一旦赋值后就不能修改了,比如下面的这样是错误的

// 字符串赋值后不能修改
var str string = "help"
str[0] = 'a'

字符串拼接直接使用 + 即可;但是需要注意的是golang如果需要换行 + 必须放在上一行;如下:

// 需要把加号放到后面
var str4 = "hello" +
 "word"
 fmt.Println(str4)

数组的定义

数组在定义时就必须设置大小;不同空间大小的数组的类型是不一样的;注意:在函数中传递数组时,进行的是值传递;要和切片区分开。

var arr [6]int
arr[0] = 5

var arr1 [3]int = [3]int{1, 2, 3}
fmt.Println("arr1=", arr1)

var arr2 = [3]int{4, 5, 6}
fmt.Println("arr2=", arr2)

// 这里的...是固定写法;最终大小根据后面设置的初始值个数决定
var arr3 = [...]int{7, 8, 9}
fmt.Println("arr3=", arr3)

var arr4 = [...]int{1: 10, 0: 11, 2: 12}
fmt.Println("arr3=", arr4)

// 二维数组
var arr [6][6]int
arr[1][2] = 1

切片的定义

golang中的切片可以简单的认为就是一个动态扩容的数组。切片在底层分配的内存是连续的。


// 切片的定义和数组的很像,不指定大小
var slice []int
// 注意没有赋初始值的切片,必须通过make方法申请空间
slice = make([]int, 0)

// 赋初始值的切片
var slice2 []string = []string{"tom","jerry"}

// 从一个数组或者是切片中截取一段数据
arr := [...]int{1, 2, 3, 4}
// [1:3] 表示引用arr数组下标1到3的值(不包含3)
slice := arr[1:4]
// [:]这样表示拿全部;同时切片可以再切片
slice := arr[:]

// 查看slice的元素个数
fmt.Println("slice的元素个数:", len(slice))
// 查看slice的容量
fmt.Println("slice的容量", cap(slice))

// 添加新的元素到切片中
slice = append(slice, 40)
// 添加多个新的元素到切片中
slice = append(slice, 50, 60, 70)

arr[1] = 100

函数变量定义和使用

golang的函数也是一种变量,因此可以将函数也赋值给变量;写法其实和js的语法很像。下面是示例:

package main

import (
	"fmt"
	"strings"
)

// 累加
func AddUpper() func (int) int  {
	var n int = 0;
	return func (x int) int  {
		n = n+x;
		return n;
	}
}

// 判断是否包含后缀
func makeSuffix(suffix string) func (string) string {
	
	return func(name string) string {
		 if !strings.HasSuffix(name, suffix) {
			 return name + suffix
		 }
		 return name
	}
}

func main()  {
	// 将AddUpper函数赋值给变量f;即可通过f调用(有点像取了个别名)
	f := AddUpper()
	fmt.Println(f(1))

	suffix := makeSuffix(".jpg")
	fmt.Println(suffix("1.png"))
	fmt.Println(suffix("1.jpg"))
}

map

// 申明map
var a map[string]string

// map 需要手动分配初始空间
a = make(map[string]string, 2)

// 赋值 
a["no1"] = "张三"


// 定义的时候就分配空间
var map2 = make(map[string]string, 10)
map2["s1"] = "100"
fmt.Println(map2)

// 定义的时候就赋值
map3 := map[string]string {
    "address":"成都",
}
fmt.Println(map3)

// map<string, map>的结构
map4 := make(map[string]map[string]string, 2)
map4["001"] = make(map[string]string, 1)
map4["001"]["name"] = "jerry"
map4["001"]["age"] = "26"
map4["002"] = map3
fmt.Println(map4)

// 删除map中的值
delete(map4, "002")

// 如果想删全部的key,需要一个个的遍历来删除;或者重新开辟个空间
for k, _ := range map4 {
    delete(map4, k)
}
// 重新开辟个空间
map4 = make(map[string]map[string]string)

// map的查找, key 存在ex为true
val, ex := map3["address"]
fmt.Println(val, ex)

// map 切片的使用
var map5 []map[string]string
map5 = make([]map[string]string, 2)

// 这里不建议使用具体的数组下标来处理,因为容易越界
if map5[0] == nil {
    map5[0] = make(map[string]string, 2)
    map5[0]["name"] = "唐僧"
    map5[0]["store"] = "1000"
}
// 对应动态增加的,推荐使用下面这种方式
newMap := map[string]string {
    "name":"悟净",
    "store": "5000",
}
map5 = append(map5, newMap)
fmt.Println(map5)

// map的遍历
for k, v := range map6 {
    fmt.Println(k, "--", v)
}

// map 排序
map6 := map[int]int{1:1, 2:2, 4:4, 3:3,}
var keys []int
for k, _ := range map6 {keys = append(keys, k)}
for k, v := range map6 {fmt.Println(k, "--", v)}
sort.Ints(keys)
fmt.Println(keys)

结构体

type Person struct {
    Name string
    Age int
}

// 使用
p2 := Person{"jerry", 26}

// 定义指针类的
var p3 *Person = new(Person)
(*p3).Name = "tom"
// golang的设计者为让开发者使用方便,可以直接这样写
p3.Age = 26
fmt.Println(*p3)

// 定义指针类的
var p4 *Person = &Person{}
(*p4).Name = "tj"
// golang的设计者为让开发者使用方便,可以直接这样写
p4.Age = 0
fmt.Println(*p4)

// 定义指针类的简化写法
p5 := &Person{Name: "jerry", Age: 100}
p5.Age = 26
fmt.Println(p5)

// 为json序列化时设置别名
type Message struct {
    Type string `json:"type"`
    Data string `json:"data"`
}

类型的转换

golang不支持隐式转换;必须显示转换。

var a int = 100
var b float32 = float32(100)

var c int = 100
var d int32 = int32(100)

基于 fmt.Sprintf方法实现类型转换

var str string

// int 转string
var num1 int = 99
str = fmt.Sprintf("%d", num1)
fmt.Println(str)

// float转string
var num2 float64 = 23.456
str = fmt.Sprintf("%f", num2)
fmt.Println(str)

// bool转string
var b bool = true
str = fmt.Sprintf("%t", b)
fmt.Println(str)

// byte转string
var myChar byte = 'h'
str = fmt.Sprintf("%c", myChar)
fmt.Println(str)
fmt.Printf("str type %T, str=%q", str, str)

// byte数组转string
arr1 := []byte(str)
arr1[0] = 'z'
str = string(arr1)
fmt.Println("str=", str)

基于strconv 函数实现类型转换

var num4 int = 99
// FormatInt(int64, 进制(2-二进制,10-十进制))
str = strconv.FormatInt(int64(num4), 10)
fmt.Println(str)

// f: 小数格式,10-十进制,64-float64
var num5 float64 = 236.5432
str = strconv.FormatFloat(num5, 'f', 10, 64)
fmt.Println(str)

// 转换为bool
str = strconv.FormatBool(b2)
fmt.Println(str)
// 将字符串转为bool
b2, _ = strconv.ParseBool(str)

切片和string的转换

// 切割字符串的一部分;对于有中文的字符串不建议这样搞,可能会出现乱码
str := "h张三llo@golang.com"
slice := str[6:]
fmt.Println("slice=", slice)

// byte切片转string
arr1 := []byte(str)
arr1[0] = 'z'
str = string(arr1)
fmt.Println("str=", str)

// 第二位由于是中文,而中文存储的byte不止一位,因此会出现乱码
arr1 = []byte(str)
arr1[1] = 'z'
str = string(arr1)
fmt.Println("str=", str)

// rune切片转string;这个rune可以解决中文乱码的问题
arr2 := []rune(str)
arr2[1] = '李'
str = string(arr2)
fmt.Println("str=", str)

输入输出语句

golang的输入输出语句,以及格式化的都是依赖于fmt的。

输出语句

// 输出指定字符
fmt.Println("姓名\t年龄\t地址")
// 输出多个值
fmt.Println("值1", "值2")

// 查看变量类型
var n = 100
fmt.Printf("n的类型是 %T \n", n)

// 查看变量类型和占用的字节数
var n2 int64 = 10000
fmt.Printf("n2的类型是 %T 占用的字节数是 %d", n2, unsafe.Sizeof(n2))

// 使用英文的``实现原样输出
str1 := `
		静夜思
			 李白
	床前明月光,疑是地上霜。
	举头望明月,低头思故乡。		 
	`
fmt.Println(str1)

// %v表示按原样输出; 数据类型转换必须显示的转换
var i1 int32 = 100
var f1 float32 = float32(i)
var i2 int8 = int8(i1)
fmt.Printf("i=%v, f=%v, i2=%v, f=%f", i1, f1, i2, f1)

输入语句

var name string
var age byte
var store float32
var isPass bool

fmt.Print("请输入姓名:")
// 这里必须使用指针
fmt.Scanln(&name)

// 限定输入类型
fmt.Scanf("%s %d %f %t", &name, &age, &store, &isPass)

逻辑判断和循环语句

golang只有for和switch,没有while;条件判断语句if区别,唯一的就是golang中不需要写括号;

for循环

// 普通的使用
for i := 1; i<10; i++ {
    fmt.Printf("这是第%v个\n", i)
}

// 只写条件;实现类似while的效果
var j = 10;
for j>0 {
    fmt.Printf("this is %v\n", j)
    j--
}

// 什么条件都不写,有内部进行判断跳出循环的时机
for { // 这里等价于 for ; ; {}
    fmt.Printf("this is %v\n", j)
    if(j<1){
    	// 跳出循环
        break
    }
}

// 遍历字符串中的字符
var str string = "ancde 中文"
// 传统的方式,如果字符串中有中文会有问题
for i := 0; i<len(str); i++ {
    fmt.Printf("%c ", str[i])
}

// 使用for-range遍历字符串
for index, val := range str {
    fmt.Printf("%d %c ", index, val)
}

// 传统的方式 解决中文问题
str2 := []rune(str)
for i := 0; i<len(str2); i++ {
    fmt.Printf("%c ", str2[i])
}

switch语句

golang的switch语句支持一个case中多个值,因此不需要写break;然后case中允许使用表达式,下面是示例。

传统的写法

a := 100
switch a {
  case 100:
    // 注意会自动加break
    fmt.Println("A")
  case 96, 97, 98, 99
    fmt.Println("B")
  default:
    fmt.Println("C")
}

使用表达式的方式

a := 100
switch {
  case a==100:
    // 注意会自动加break
    fmt.Println("A")
  case a > 95
    fmt.Println("B")
  default:
    fmt.Println("C")
}

函数的定义

在golang中整个程序只能有一个main函数,定义的package类似Java中的类名称,调用其他文件中的方法时需要引入package,然后通过package名称.方法名称;在golang中方法名首字母小写表示只能当前包中自己访问;首字母大小表示其他包中可以调用。函数定义的语法如下:

// 无参数和返回值的函数
func methodName1(){
    fmt.Println("执行相关逻辑")
}

// 有参数的函数
func methodName2(val int){
    fmt.Println("执行相关逻辑")
}

// 有参数和返回值的函数
func methodName3(val int) int {
    fmt.Println("执行相关逻辑")
    return val+1
}

// 有参数和返回值的函数;这里定义了返回值的名称,return会自动返回
func methodName4(val int) (res int) {
    fmt.Println("执行相关逻辑")
    res = val+1
    return
}

// 有参数和返回值的函数; 返回多个参数
func methodName5(val int) (res int, err error) {
    fmt.Println("执行相关逻辑")
    res = val+1
    return
}

// 可变参数
func sum(sum int, args... int) int {
    for i := 0; i<len(args); i++ {
        sum += args[i]
    }
    return sum
}

// 匿名函数
res2 := func (n1 int, n2 int) int {
        return n1+n2
    }(1, 3)
fmt.Println(res2)


// 自定义类型
type customizeFun func(int , int) int
// 参数中包含函数的函数;
func methodName6(fun customizeFun, num1 int, num2 int) int  {
    return fun(num1, num2)
}
// 调用示例;把函数赋值给变量,也就是说函数也是一种类型
ca := Sum
fmt.Println(ca(a, b))
func Sum(a int, b int) int  {
    return a+b
}

// 结构体绑定方法
type Person struct {
    Name string
    Age int
}
// 结构体会自动绑定到这个方法
func (p *Person) getBirthYear() {
    var year = 2021-p.Age
    fmt.Println(year)
}
// 结构体会自动绑定到这个方法
func (p Person) getBirthYear() {
    var year = 2021-p.Age
    fmt.Println(year)
}

defer

被defer修饰的语句,暂时不执行,会将其后面的语句压入独立的栈中;当函数执行完成后,再从defer栈里面按照先入后出的方式出栈 执行。

func sum(n1 int, n2 int) int {
	// 当执行defer时,暂时不执行,会将其后面的语句压入独立的栈中
	// 当函数执行完成后,再从defer栈里面按照先入后出的方式出栈 执行
	defer fmt.Println("ok1, n1=", n1)
	defer fmt.Println("ok2, n2=", n2)
	
	n1++
	res := n1 + n2
	fmt.Println("ok3, res=", res)
	return res
}

接口的定义

golang的接口对实现是没有约束的,接口更像是一个方法规范定义。下面是一个USB接口的场景示例:

// 定义接口
type Usb interface {
	Start()
	Stop()
}

type Phone struct {
}

func (p *Phone) Start()  {
	fmt.Println("手机启动成功")
}
func (p *Phone) Stop()  {
	fmt.Println("手机停止成功")
}

func connect(usb Usb)  {
	usb.Start()
}

func main()  {
	var p = &Phone{}
	connect(p)
}

异常处理

通过recover可以补获函数抛出的异常;一般配合defer来使用。

recover补获异常

func demo()  {
	// 通过defer,使用recover来补获异常
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("err=", err)
		}
	}()

	a := 10
	b := 0
	// 这里会报错
	res := a / b
	fmt.Println("res=", res)
}

errors自定义异常

func readConf(name string) (err error) {
	if name == "config.ini" {
		return nil
	}
	// 创建异常
	return errors.New("读取文件失败")
}

panic抛出异常

err := readConf("config.ini")
if err != nil {
    // 抛出异常
    panic(err)
}
fmt.Println("ok")

四、文件IO流操作

文件IO相关操作主要使用的 bufioioio/ioutilos 这几个包。

打开文件

// 打开文件
file, err := os.Open("/doc/addGoPath.bat")
if err != nil {
    fmt.Println("open file err=", err)
}

// 这里的file就是一个指针
fmt.Println("file=%v", file)

// 关闭文件
err = file.Close()
if err != nil {
    fmt.Println("close file err=", err)
}

读取文件

通过缓冲区读取文件

file, err :=  os.Open("/doc/addGoPath.bat)
if err != nil {
    fmt.Println("open file err=", err)
}
defer file.Close()

// NewReader 返回的buffer缓冲区大小为4096
reader := bufio.NewReader(file)
for {
    // 读到一个换行就结束
    str, err := reader.ReadString('\n')
    if err == io.EOF {
        // io.EOF表示文件末尾
        break
    }
    fmt.Println(str)
}

一次性读取文件全部内容

如果文件内容少,那么可以选择这种直接一次性全部读取。

path := "/doc/addGoPath.bat"
// 这里返回的content是一个切片
content, err := ioutil.ReadFile(path)
if err != nil {
    fmt.Printf("read file err=%v\n", err)
    return
}

fmt.Printf("%v", string(content))

创建文件,并写入数据

fileName := "/doc/demo4.txt"
// 对应参数说明:文件名称,读取操作配置,权限(仅在linux、Unix系统下生效)
file, err := os.OpenFile(fileName, os.O_WRONLY | os.O_CREATE, 0666)
if err != nil {
    fmt.Printf("read file err=%v\n", err)
    return
}
str := "hello, golang\r\n"
// 获取写缓存
writer := bufio.NewWriter(file)

for i := 0; i<5; i++ {
    // 注意这样是直接覆盖的写
    writer.WriteString(str)
}

// 由于使用的是缓存,因此需要调用刷新函数将其刷新到磁盘上去
writer.Flush()

写入数据

写入数据分为覆盖写入和追加写入。

写入数据(覆盖的写入)

fileName := "/doc/demo4.txt"
// 参数:文件名称,读取操作配置,权限(仅在linux、Unix系统下生效),O_TRUNC 覆盖的写入
file, err := os.OpenFile(fileName, os.O_WRONLY | os.O_TRUNC, 0666)
if err != nil {
    fmt.Printf("read file err=%v\n", err)
    return
}
str := "hello, golang\r\n"
// 获取写缓存
writer := bufio.NewWriter(file)

for i := 0; i<5; i++ {
    // 注意这样是直接覆盖的写
    writer.WriteString(str)
}

// 由于使用的是缓存,因此需要调用刷新函数将其刷新到磁盘上去
writer.Flush()

写入数据(追加的写入)

fileName := "/doc/demo4.txt"
// 参数:文件名称,读取操作配置,权限(仅在linux、Unix系统下生效),O_TRUNC 追加的写入
file, err := os.OpenFile(fileName, os.O_WRONLY | os.O_APPEND, 0666)
if err != nil {
    fmt.Printf("read file err=%v\n", err)
    return
}
str := "有点东西哦\r\n"
// 获取写缓存
writer := bufio.NewWriter(file)

for i := 0; i<5; i++ {
    // 注意这样是直接覆盖的写
    writer.WriteString(str)
}

// 由于使用的是缓存,因此需要调用刷新函数将其刷新到磁盘上去
writer.Flush()

读写模式(追加的写入)

fileName := "/doc/demo4.txt"
// 参数:文件名称,读取操作配置,权限(仅在linux、Unix系统下生效),O_RDWR 读写模式 ,O_TRUNC 追加的写入
file, err := os.OpenFile(fileName, os.O_RDWR | os.O_APPEND, 0666)
if err != nil {
    fmt.Printf("read file err=%v\n", err)
    return
}

defer file.Close()

reader := bufio.NewReader(file)
for {
    str, err := reader.ReadString('\n')
    if err == io.EOF {
        break
    }
    fmt.Print(str)
}

str := "有点东西哦\r\n"
// 获取写缓存
writer := bufio.NewWriter(file)

for i := 0; i<5; i++ {
    // 注意这样是直接覆盖的写
    writer.WriteString(str)
}

// 由于使用的是缓存,因此需要调用刷新函数将其刷新到磁盘上去
writer.Flush()

ioutil工具类的使用

读取文件

path := "/doc/demo4.txt"
data, err := ioutil.ReadFile(path)
if err != nil {
    fmt.Printf("read file err=%v\n", err)
    return
}

写入文件

path := “/doc/demo4.txt”

// 参数:文件路径,数据,权限
err = ioutil.WriteFile(path, data, 0666)
if err != nil {
    fmt.Printf("write file err=%v\n", err)
    return
}

文件拷贝

func CopyFile(srcFileName string, targetFileName string) (writer int64, err error) {

	srcFile, err := os.Open(srcFileName)
	if err !=nil {
		fmt.Printf("open file err=%v", err)
		return
	}
	defer srcFile.Close()

	reader := bufio.NewReader(srcFile)

	targetFile, err := os.OpenFile(targetFileName, os.O_WRONLY | os.O_CREATE,  0666)
	if err != nil {
		fmt.Printf("open file err=%v", err)
		return
	}
	defer targetFile.Close()

	wr := bufio.NewWriter(targetFile)
	defer wr.Flush()

	return io.Copy(wr, reader)
}

五、获取系统级的信息

查看CPU核心数量

cpuNum := runtime.NumCPU()
fmt.Println("CPU数:", cpuNum)
// 设置使用多少个CPU,在8之前的版本前需要手动设置,之后就不用在设置了
runtime.GOMAXPROCS(cpuNum)

获取命令行中的参数

fmt.Println("命令行的参数有", len(os.Args))
for i, v := range os.Args {
    fmt.Printf("args[%v]=%v\n", i, v)
}

//  go run 024.go -pwd 23456 -port 8087;注意必须是下面写的命令,否则将会报错
// 使用flag工具包读取命令行信息
var pwd string
flag.StringVar(&pwd, "pwd", "123456", "这是说明")

var port int
flag.IntVar(&port, "port", 8080, "端口号,默认为8080")

// 必须调用这个方法才会将数据转换出来
flag.Parse()
fmt.Println("pwd=", pwd)
fmt.Println("port=", port)

六、序列化和反序列化

序列化

结构体序列化

type Monster struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Birthday string `json:"birthday"`
	Sal float64 `json:"sal"`
	Skill string `json:"skill"`
}

func structToJsonString() {
    monster := Monster{
        Name: "牛魔王",
        Age: 500,
        Birthday: "1521-01-01",
        Sal: 8000000.00,
        Skill: "牛魔拳",
    }
    
    data, err := json.Marshal(&monster)
    if err !=nil {
        fmt.Printf("序列化错误 err=%v\n", err)
        return
    }
    fmt.Printf(string(data))
}

map序列化

func mapToJsonString() {
	monsterMap := make(map[string]interface{}, 1)
	monsterMap["name"] = "黄眉大王"
	monsterMap["age"] = 1000
	monsterMap["birthday"] = "1021-01-01"
	monsterMap["sal"] = 800000000.00
	monsterMap["skill"] = "紫金钵"

	data, err := json.Marshal(monsterMap)
	if err !=nil {
		fmt.Printf("序列化错误 err=%v\n", err)
		return
	}
	fmt.Printf(string(data))
}

切片序列化

func sliceToJsonString()  {
	var slice []map[string]interface{}
	slice = make([]map[string]interface{},0)

	m := make(map[string]interface{},1)
	m["name"] = "红孩儿"
	m["age"] = 200
	m["birthday"] = "1821-01-01"
	m["sal"] = 80000.00
	m["skill"] = "三昧真火"

	slice = append(slice, m)

	data, err := json.Marshal(slice)
	if err != nil {
		fmt.Printf("序列化错误 err=%v\n", err)
		return
	}
	fmt.Println(string(data))
}

反序列化

结构体反序列化

type Student struct {
	Name string `json:"name"`
	Age int `json:"age"`
}
func jsonToStrut() {

	str := "{\"name\":\"张三\", \"age\":26}"

	var stu Student
	err := json.Unmarshal([]byte(str), &stu)
	if err != nil {
		fmt.Printf("反序列化错误 err=%v", err)
		return
	}
	fmt.Println(stu)
}

map反序列化

func jsonToMap() {
	str := "{\"name\":\"张三\", \"age\":26}"
	var stu map[string]interface{}
	err := json.Unmarshal([]byte(str), &stu)
	if err != nil {
		fmt.Printf("反序列化错误 err=%v", err)
		return
	}
	fmt.Println(stu)
}

切片反序列化

func jsonToSlice() {
	str := "[{\"name\":\"张三\", \"age\":26}]"
	var stu []map[string]interface{}
	err := json.Unmarshal([]byte(str), &stu)
	if err != nil {
		fmt.Printf("反序列化错误 err=%v", err)
		return
	}
	fmt.Println(stu)
}

七、单元测试

需要测试的函数

func addUpper(n int) int {
	res := 0
	for i := 1; i<= n; i++ {
		res += i
	}
	return res
}

单元测试文件;注意这个文件的名称必须以_test结尾才可以识别;函数名称必须是Test开头,后面是Xxx;也就是第一个X必须是大写的字母;

func TestAddUpper(t *testing.T) {
	// 调用需要测试的方法
	res := addUpper(10)
	if res != 55 {
		// 输出错误日志,同时终止程序
		t.Fatalf("实际结果=%v 和期望值55不匹配", res)
	}
	// 执行结果正确将输出日志
	t.Logf("执行正确...")
}

有的时候我们需要在函数执行前进行一些初始化的操作,那么可以在测试类里面添加如下方法

// 运行测试命令的时候会先执行此方法
func TestMain(m *testing.M) {
    MysqlInit()
    // 调用m.Run() 开始执行测试
    m.Run()
}
func TestAddUpper(t *testing.T) {
    // 调用需要测试的方法
    res := addUpper(10)
    if res != 55 {
        // 输出错误日志,同时终止程序
        t.Fatalf("实际结果=%v 和期望值55不匹配", res)
    }
    // 执行结果正确将输出日志
    t.Logf("执行正确...")
}

使用子测试方法

// 主测试函数
func TestMember(t *testing.T) {
	// 执行子测试函数
	t.Run("测试添加用户", testAddMember)
}

func testAddMember(t *testing.T){
	member := &Member{Name: "张三", Password: "123456"}
	member.AddMember()
}

八、协程

golang的协程可以认为就是Java中的异步执行;

func demo() {
	for i := 1; i< 10 ;i++ {
		fmt.Println("hello world " + strconv.Itoa(i))
		time.Sleep(1000)
	}
}

func main() {
	// 通过go关键字开启一个协程
	go demo()

	for i := 1; i< 10 ;i++ {
		// strconv.Itoa()函数是将数字转为string,可以避免数字被按照ascii码转换
		fmt.Println("main() hello golang  " + strconv.Itoa(i))
		time.Sleep(1000)
	}
}

九、锁

基于协程实现计算1-20的各个数的阶乘

var (
	factorialMap = make(map[int]int, 10)
	// 声明一个全局的互斥锁
	// lock是一个全局的互斥锁
	// sync是包:synchronized 同步
	// Mutex: 就是互斥
	lock sync.Mutex
)

func cal(n int) {
	res := 1
	for i := 1; i<= n; i++ {
		res *= i
	}
	// 加锁
	lock.Lock()
	factorialMap[n] = res
	// 解锁
	lock.Unlock()
}

func main() {

	for i := 1; i <= 20; i++ {
		// 开启20个协程来计算
		go cal(i)
	}

	// 这里也需要加锁,否则将出现资源竞争
	time.Sleep(10)
	// 打印结果
	lock.Lock()
	for i, v := range factorialMap {
		fmt.Printf("%v:%v\n", i, v)
	}
	lock.Unlock()
}

十、 管道

管道chan:这是一个队列

// chan是引入类型,即存入的是地址
var intChan chan int
intChan = make(chan int, 3)
fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)

// 向管道写入数据
intChan<- 10
num := 211
intChan<- num
// 注意:向管道总写入数据的数量不能超过cap(容量);否则将报错
// 打印管道的长度和cap(容量)
fmt.Printf("channel len=%v cap=%v", len(intChan), cap(intChan))

// 从管道中取出数据;注意:如果管道里面没有数据的话,此时再去取值的时候就会出现deadlock错误
var num2 int
num2 = <-intChan
fmt.Println(num2)
fmt.Printf("channel len=%v cap=%v", len(intChan), cap(intChan))

// 管道遍历
close(intChan) // 管道关闭后就只能读取不能写入了;再遍历的时候必须将管道关闭
for v := range intChan {
    fmt.Println(v)
}

声明管道只读和只写

// 管道声明为只写
var chan1 chan<- int
chan1 = make(chan int, 3)
chan1<- 20
// num :=  <-chan1

// 声明为只读
var chan2 <-chan int
chan2 = make(chan int, 3)
num := <-chan2
fmt.Println(num)

基于管道实现协程间的数据读写

func writeData(intChan chan int) {
	for i := 1; i <= 50; i++ {
		intChan<- i
		fmt.Println("写入数据 ", i)
		time.Sleep(time.Second)
	}
	close(intChan)
}

func readChan(intChan chan int, exitChan chan bool) {
	for {
		v, ok := <-intChan
		if !ok {
			break
		}
		fmt.Printf("读到数据=%v\n", v)
		time.Sleep(time.Second)
	}
	exitChan<- true
	close(exitChan)
}

// 需要注意的是对管道来说,一定要有读和写,速度可以不一致;否则将会发生阻塞死锁。
func main() {

	intChan := make(chan int, 50)
	exitChan := make(chan bool, 1)
	go writeData(intChan)
	go readChan(intChan, exitChan)

	for {
		_, ok := <-exitChan
		if !ok {
			break
		}
	}
}

管道和协程的使用示例

// 向管理里面放入数据
func putNum(intChan chan int) {
	for i := 1; i <= 80; i++ {
		intChan<- i
	}
	close(intChan)
}

// 求素数
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
	var flag bool
	for {
		num, ok := <-intChan
		if !ok {
			break
		}

		flag = true
		for i := 2; i < num; i++ {
			if num % i == 0 {
				flag = false
			}
		}

		if flag {
			primeChan<- num
		}
	}
	fmt.Println("无可用数据,退出....")
	exitChan<- true
}


func main() {

	intChan := make(chan int, 1000)
	primeChan := make(chan int, 2000)
	exitChan := make(chan bool, 4)

	go putNum(intChan)

	for i := 1; i <= 4; i++ {
		go primeNum(intChan, primeChan, exitChan)
	}

	go func() {
		for i := 0; i< 4; i++ {
			// 没有数据的时候会阻塞在这里
			<-exitChan
		}
		close(primeChan)
	}()

	for {
		res, ok := <-primeChan
		if !ok {
			break
		}
		fmt.Printf("%v\n", res)
	}

}

select非阻塞的方式读取管道数据

我们在不好确定什么时候关闭管道的时候,使用select的方式可以解决

intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
    intChan<- i
}

strChan := make(chan string, 5)
for i := 0; i < 5; i++ {
    strChan<- "hello" + fmt.Sprintf("%d", i)
}

//label:
for {
    // 使用select的方式可以解决 我们在不好确定什么时候关闭管道的时候使用
    select {
        case v := <-intChan : // 注意:如果管道一直没有关闭,这里也不会一直阻塞这里
            fmt.Printf("从intChan读取的数据:%d\n", v)
        case v := <-strChan :
            fmt.Printf("从strChan读取数据%s\n", v)
        default:
            fmt.Println("没有符合条件的,退出了....")
            return
            //break label
    }
}

解决由于一个协程出现panic异常导致整个程序崩溃的问题

func sayHello() {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Println("hello world")
	}
}

func sayGolang(){
	// 补获异常
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("sayGolang() 异常", err)
		}
	}()
	var m map[int]string
	m[0] = "golang"
}

func main() {
	go sayHello()
	go sayGolang()
	for i := 0; i< 10; i++ {
		fmt.Println("main() ok=", i)
	}
}

十一、反射

普通类型反射

func reflectDemo(b interface{}) {

	// 通过反射获取变量的类型
	rType := reflect.TypeOf(b)
	fmt.Println("rType=", rType)

	// 通过反射获取reflect.value
	rVal := reflect.ValueOf(b)
	fmt.Printf("rVal=%v, type=%T\n", rVal, rVal)

	num1 := rVal.Int() + 10
	fmt.Println("num1=", num1)

	// 将其重新转换为interface{}
	iV := rVal.Interface()
	num2 := iV.(int)
	fmt.Printf("num2=%v, num2=%T\n", num2, num2)
}

var num int = 100
reflectDemo(num)

结构体反射

type Student struct {
	Name string
	Age  int
}

func reflectStu(b interface{}) {

	rType := reflect.TypeOf(b)
	fmt.Println("rType=", rType)

	rVal := reflect.ValueOf(b)
	fmt.Printf("rVal=%v, type=%T\n", rVal, rVal)

	iV := rVal.Interface()
	stu := iV.(Student)
	fmt.Printf("num2=%v, num2=%T\n", stu, stu)
}

十二、网络编程

websocket

服务端示例

func process(con net.Conn) {
	defer con.Close()
	for {
		// 创建一个切片
		buf := make([]byte, 1024)
		// 一直等待客户端发送数据过来
		n, err := con.Read(buf)
		if err != nil {
			fmt.Println("接收客户端数据异常: ", err)
			return
		}
		fmt.Print(string(buf[:n]))
	}
}

func main() {
	fmt.Println("服务启动中...")
	// 这里标识使用TCP协议,监听本地上的8888端口
	//net.Listen("tcp", "127.0.0.1:8888")
	// 这里标识使用TCP协议,监听的8888端口
	listen, err := net.Listen("tcp", "0.0.0.0:8888")

	if err != nil {
		fmt.Println("服务启动失败", err)
		return
	}
	fmt.Println("服务启动成功...")

	// 延时关闭
	defer listen.Close()

	// 轮询等待连接
	for {
		// 等待链接
		con, err := listen.Accept()
		if err != nil {
			fmt.Println("连接异常...")
		} else {
			fmt.Printf("接收到客户端的连接:%v\n", con.RemoteAddr().String())
			go process(con)
		}
	}
}

客户端示例

func main() {
	con, err := net.Dial("tcp", "127.0.0.1:8888")
	if err != nil {
		fmt.Println("连接失败")
		return
	}
	// Stdin : 标准输入
	reader := bufio.NewReader(os.Stdin)
	line, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("获取输入信息异常")
		return
	}

	// 发送数据
	n, err := con.Write([]byte(line))
	if err != nil {
		fmt.Println("数据发送异常:", err)
		return
	}
	fmt.Println("发送数据包大小:", n)
}

HTTP网络

最简单的写法

// 请求来了后的处理方法
func handler(w http.ResponseWriter, r *http.Request) {
	res := "welcome use grpc"
	url := r.URL.Path
	if "/api" == url {
		res = "api server is ok"
	}

	_, err := fmt.Fprintln(w, res, r.URL.Path)
	if err != nil {
		fmt.Println("response error:", err)
	}
}

func main() {

	// 配置路由监听
	http.HandleFunc("/", handler)
	// 启动服务并监听8081端口
	err := http.ListenAndServe(":8081", nil)
	if err != nil {
		fmt.Println("服务启动失败")
	}
}

自定义处理的handler

要自定义实现handler需要实现Handler接口的ServeHTTP(ResponseWriter, *Request)方法

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
type CustomHandler struct {

}

// 自定义handler处理器
func (c *CustomHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
	_, err := fmt.Fprintln(w, "this is custom handler")
	if err != nil {
		fmt.Println("response error ", err)
	}
}

func main()  {

	handler := &CustomHandler{}
	// 配置路径,设置请求的处理handler
	http.Handle("/", handler)
	err := http.ListenAndServe(":8081", nil)
	if err != nil {
		fmt.Println("server start fail, error ", err)
	}
}

自定义server的详情配置

type CustomHandler struct {

}

// 自定义handler处理器
func (c *CustomHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
	_, err := fmt.Fprintln(w, "this is custom handler")
	if err != nil {
		fmt.Println("response error ", err)
	}
}

func main()  {

	server := http.Server{
		Addr: ":8081",
		Handler: &CustomHandler{},
		ReadHeaderTimeout: 2*time.Second,
	}

	err := server.ListenAndServe()
	if err != nil {
		fmt.Println("server start fail")
	}
}

手动创建多路复用器

func main()  {

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		_, err := fmt.Fprintln(w, "this is custom handler")
		if err != nil {
			fmt.Println("response error ", err)
		}
	})
	// 自己手动创建多路复用器
	mux := http.NewServeMux()
	err := http.ListenAndServe("8081", mux)
	if err != nil {
		fmt.Println("server start error ", err)
	}
}

获取请求内容

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "请求地址:", r.URL.Path)
	fmt.Fprintln(w, "URL参数:", r.URL.RawQuery)
	fmt.Fprintln(w, "请求头信息", r.Header)
	fmt.Fprintln(w, "请求头Accept-Encoding的信息", r.Header["Accept-Encoding"])

	//body := make([]byte, r.ContentLength)
	//r.Body.Read(body)
	//fmt.Fprintln(w, "body参数:", string(body))

	// 通过已经封装好的方法获取参数
	r.ParseForm()
	// 获取body+URL上的参数
	fmt.Fprintln(w, "body参数:", r.Form)
	// 只获取body中的参数
	fmt.Fprintln(w, "post body参数:", r.PostForm)
	// 如果是文件上传的请求;使用 r.MultipartForm

	// 直接通过key获取参数,无需调用r.ParseForm()函数
	fmt.Fprintln(w, "参数name:", r.FormValue("name"))
	fmt.Fprintln(w, "参数name:", r.PostFormValue("name"))
	
}

静态资源处理

func htmlHandler(w http.ResponseWriter, r *http.Request) {
	t := template.Must(template.ParseFiles("html/index.html"))
	t.Execute(w, "welcome")
}

// 静态资源处理
func main()  {

	// 静态资源(css、js、或者是静态html)处理:访问路径以/static/开头的,使用文件服务处理,文件的根路径为	static;
	http.Handle("/static/",
		http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	// 处理html渲染
	http.HandleFunc("/html", htmlHandler)
	http.ListenAndServe(":8081", nil)
}

十三、Redis

下载redis的模块包

https://github.com/gomodule/redigo

# 在go的工作目录下执行如下命令
go get github.com/gomodule/redigo/redis

普通连接示例

// 连接到redis
con, err := redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
    fmt.Println("redis 连接异常:", err)
    return
}
defer con.Close()

// 向redis中设置值
_, err = con.Do("Set", "name", "tom猫")
if err != nil {
    fmt.Println("error: ", err)
}
// 获取值
val, err := redis.String(con.Do("Get", "name"))
if err != nil {
    fmt.Println("error: ", err)
    return
}
fmt.Println("", val)

// 操作hash
con.Do("HSet", "1", "name", "tom猫")
val, err = redis.String(con.Do("HGet", "1", "name"))
if err != nil {
    fmt.Println("error: ", err)
    return
}
fmt.Println("", val)

redis连接池示例

var redisPool *redis.Pool
func init() {
	redisPool = &redis.Pool{
		MaxIdle:     8,   // 最大空闲连接数
		MaxActive:   0,   // 表示和数据库的最大连接数,0表示没有限制
		IdleTimeout: 100, // 最大空闲时间
		Dial: func() (redis.Conn, error) { // 创建redis连接
			return redis.Dial("tcp", "127.0.0.1:6379")
		},
	}
}

func main() {
	conn := redisPool.Get()
	defer conn.Close()
	defer redisPool.Close()

	_, err := conn.Do("Set", "name", "汤姆猫")
	if err != nil {
		fmt.Println("设置异常:", err)
		return
	}
	val, err := redis.String(conn.Do("Get", "name"))
	fmt.Println(val)
}

十四、MySQL

下载驱动包

# 源码地址
https://github.com/go-sql-driver/mysql

# 下载驱动包
go get github.com/go-sql-driver/mysql

配置连接

// 创建连接
func MysqlInit(){
	Db, dbErr = sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/demo_go")
	if dbErr != nil {
		panic(dbErr.Error())
	}
}

添加数据

// 添加数据
func (m *Member) AddMember() error {

    sql := "insert into member(`name`, password, email) values(?, ?, ?)"
    // 预编译SQL
    inSmt, err := Db.Prepare(sql)
    if err != nil {
        fmt.Println("SQL语句预编译异常", err)
        return err
    }
    
    // 执行
    _, err = inSmt.Exec(m.Name, m.Password, m.Email)
    if err != nil {
        fmt.Println("SQL执行异常", err)
        return err
    }
    return nil
}

查询一条数据

func findById(id int) (m *Member, err error)  {

	sql := "select * from member where id=?"
	// 查询一条数据
	row := Db.QueryRow(sql, id)

	var name, password, email string
	err = row.Scan(&id, &name, &password, &email)
	if err != nil{
		fmt.Println(err.Error())
		return
	}
	m = &Member{Id: id, Name: name, Password: password, Email: email}
	return
}

查询多条数据

func findAll() (ms []Member, err error)  {

    sql := "select * from member"
    // 查询一条数据
    rows, err := Db.Query(sql)
    if err != nil{
    fmt.Println(err.Error())
        return
    }

    for rows.Next() {
        var id int
        var name, password, email string
        err = rows.Scan(&id, &name, &password, &email)
        if err != nil{
            fmt.Println(err.Error())
            return
        }
        m := Member{Id: id, Name: name, Password: password, Email: email}
        ms = append(ms, m)
    }
    return
}

特别提醒:扫码关注微信订阅号'起岸星辰',实时掌握IT业界技术资讯! 转载请保留原文中的链接!
 上一篇
2021年4月资讯(一) 2021年4月资讯(一)
Nacos2.0正式发布,性能提升10倍;微软Edge浏览器新功能实现和原生应用相同的视觉;微信iOS更新8.0.3版本朋友圈可发30秒视频;深度操作系统20.2发布,系统性能进一步优化,只为给你更流畅的使用感受。
2021-04-01
下一篇 
快速排序 快速排序
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
2021-03-30
  目录