学习资料
- 超全golang面试题合集+golang学习指南+golang知识图谱+入门成长路线 ;一份涵盖大部分golang程序员所需要掌握的核心知识.
- 韩顺平的golang视频教程 ,视频在B站上,最好倍速播放。
- go语言环境配置
- Go 包网站 golang.com.cn是中文版本的 Go 包网站。 golang.org 是Go开源项目的主站, 而 golang.com.cn 集中为Go用户提供了丰富的资源和文档,是Go生态重要的一环。
一、开发环境配置
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相关操作主要使用的 bufio
、io
、io/ioutil
、os
这几个包。
打开文件
// 打开文件
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
}