数据类型包括有:布尔类型、字符串类型、数字类型(整型、浮点型、复数等)、派生类型(数组类型、
slice
切片类型、map
集合类型、struct
结构体类型、interface
接口类型、channel
通道类型、指针类型等)注意:这里的
slice
切片类型,相当于没有指定长度的数组,可以自动扩容。
package main
import (
"fmt"
"math"
"unsafe"
)
func main() {
// 整数
// 一个字节
var i8 int8
// 两个字节
var i16 int16
// 四个字节
var i32 int32
// 八个字节
var i64 int64
// 无符号,一个字节
var ui8 uint8
// 无符号,两个字节
var ui16 uint16
// 无符号,四个字节
var ui32 uint32
// 无符号,八个字节
var ui64 uint64
// 打印类型、字节大小、范围
fmt.Printf("%T %dB %v~%v\n", i8, unsafe.Sizeof(i8), math.MinInt8, math.MaxInt8)
fmt.Printf("%T %dB %v~%v\n", i16, unsafe.Sizeof(i16), math.MinInt16, math.MaxInt16)
fmt.Printf("%T %dB %v~%v\n", i32, unsafe.Sizeof(i32), math.MinInt32, math.MaxInt32)
fmt.Printf("%T %dB %v~%v\n", i64, unsafe.Sizeof(i64), math.MinInt64, math.MaxInt64)
fmt.Printf("%T %dB %v~%v\n", ui8, unsafe.Sizeof(ui8), 0, math.MaxUint8)
fmt.Printf("%T %dB %v~%v\n", ui16, unsafe.Sizeof(ui16), 0, math.MaxUint16)
fmt.Printf("%T %dB %v~%v\n", ui32, unsafe.Sizeof(ui32), 0, math.MaxUint32)
fmt.Printf("%T %dB %v~%v\n", ui64, unsafe.Sizeof(ui64), 0, uint64(math.MaxUint64))
// 无符号整型(默认是8个字节,相当于java中的Long类型)
var ui uint
fmt.Printf("%T %dB\n", ui, unsafe.Sizeof(ui))
// 有符号整型(默认是8个字节,相当于java中的Long类型)
var ii int
fmt.Printf("%T %dB\n", ii, unsafe.Sizeof(ii))
}
int8 1B -128~127
int16 2B -32768~32767
int32 4B -2147483648~2147483647
int64 8B -9223372036854775808~9223372036854775807
uint8 1B 0~255
uint16 2B 0~65535
uint32 4B 0~4294967295
uint64 8B 0~18446744073709551615
uint 8B
int 8B
package main
import (
"fmt"
"math"
"unsafe"
)
func main() {
// 浮点数
// 四个字节
var f32 float32
// 八个字节
var f64 float64
fmt.Printf("%T %dB %v~%v\n", f32, unsafe.Sizeof(f32), -math.MaxFloat32, math.MaxFloat32)
fmt.Printf("%T %dB %v~%v\n", f64, unsafe.Sizeof(f64), -math.MaxFloat64, math.MaxFloat64)
}
float32 4B -3.4028234663852886e+38~3.4028234663852886e+38
float64 8B -1.7976931348623157e+308~1.7976931348623157e+308
package main
import (
"bytes"
"fmt"
)
func main() {
s1 := "hello1"
var s2 string = "hello2"
var s3 = "hello3"
fmt.Printf("s1=%v\n", s1)
fmt.Printf("s2=%v\n", s2)
fmt.Printf("s3=%v\n", s3)
fmt.Println("--------------------")
// 字符串连接
// 直接+号连接
s4 := s1 + s2
fmt.Printf("s4=%v\n", s4)
fmt.Println("--------------------")
// 使用方法进行格式化连接
msg := fmt.Sprintf("[%s, %s]", s1, s2)
fmt.Printf("msg=%v\n", msg)
fmt.Println("--------------------")
// 使用buffer进行生成
var buffer bytes.Buffer
buffer.WriteString("(tom")
buffer.WriteString("=")
buffer.WriteString("张三)")
fmt.Printf("buffer_string=%v\n", buffer.String())
fmt.Println("--------------------")
// 字符串切片
s := "hello world"
fmt.Println(s[0:5])
fmt.Println(s[:5])
fmt.Println(s[1:])
}
s1=hello1
s2=hello2
s3=hello3
--------------------
s4=hello1hello2
--------------------
msg=[hello1, hello2]
--------------------
buffer_string=(tom=张三)
--------------------
hello
hello
ello world
package main
import "fmt"
func main() {
// 定义一个十进制的数
num := 10
// 输出十进制
fmt.Printf("%d\n", num)
// 输出二进制
fmt.Printf("%b\n", num)
// 输出八进制
fmt.Printf("%o\n", num)
// 输出十六进制
fmt.Printf("%x\n", num)
fmt.Printf("%X\n", num)
}
10
1010
12
a
A
package main
import "fmt"
func main() {
// 一、创建数组,并显示声明数组的大小
arr1 := [2]int{1, 2}
fmt.Printf("arr1: %v\n", arr1)
fmt.Printf("arr1: %T\n", arr1)
// int类型的初始值为0
arr2 := [2]int{}
fmt.Printf("arr2: %v\n", arr2)
// 二、创建数组,并自动计算数组大小(...表示任意大小,会自动计算数组大小)
arr3 := [...]int{1, 2, 3}
fmt.Printf("arr3: %v\n", arr3)
// 三、创建切片类型(不指定数组大小,即为切片类型,切片类型相当于会自动扩容的数组)
arr4 := []int{1, 2, 3}
// 切片类型可以添加元素,但数组不能添加,数组是固定长度。
arr4 = append(arr4, 100)
fmt.Printf("arr4: %v\n", arr4)
}
arr1: [1 2]
arr1: [2]int
arr2: [0 0]
arr3: [1 2 3]
arr4: [1 2 3 100]
package main
import "fmt"
func main() {
// 一、直接创建切片(不指定数组的大小,即为切片类型,切片类型相当于会自动扩容的数组)
arr1 := []int{1, 2, 3}
// 切片类型可以添加元素,但数组不能添加,数组是固定长度。
// append函数只能接收切片类型的数据,不能接收数组类型的数据。
arr1 = append(arr1, 100)
fmt.Printf("arr1: %v\n", arr1)
fmt.Printf("arr1: %T\n", arr1)
fmt.Println("------------------------")
// 二、使用make来创建切片,并指定初始长度为4,会赋初始值为0。此时容量也默认为4。
arr2 := make([]int, 4)
fmt.Printf("arr2: %v\n", arr2)
fmt.Printf("len(arr2): %v\n", len(arr2))
fmt.Printf("cap(arr2): %v\n", cap(arr2))
fmt.Println("======添加一个元素后,触发扩容======")
// 添加元素
// append函数只能接收切片类型的数据,不能接收数组类型的数据。
arr2 = append(arr2, 10)
fmt.Printf("arr2: %v\n", arr2)
fmt.Printf("len(arr2): %v\n", len(arr2))
fmt.Printf("cap(arr2): %v\n", cap(arr2))
fmt.Println("------------------------")
// 三、使用make创建切片,指定初始长度为0,容量为10
arr3 := make([]int, 0, 10)
fmt.Printf("arr3: %v\n", arr3)
fmt.Printf("len(arr3): %v\n", len(arr3))
fmt.Printf("cap(arr3): %v\n", cap(arr3))
fmt.Println("======添加一个元素后======")
// 添加一个元素
arr3 = append(arr3, 10)
fmt.Printf("arr3: %v\n", arr3)
fmt.Printf("len(arr3): %v\n", len(arr3))
fmt.Printf("cap(arr3): %v\n", cap(arr3))
fmt.Println("------------------------")
// 删除切片中指定下标的元素
arr4 := []int{1, 2, 3, 4}
fmt.Printf("arr4: %v\n", arr4)
// 删除3这个元素,索引为2
arr4 = append(arr4[:2], arr4[2+1:]...)
fmt.Printf("arr4: %v\n", arr4)
}
arr1: [1 2 3 100]
arr1: []int
------------------------
arr2: [0 0 0 0]
len(arr2): 4
cap(arr2): 4
======添加一个元素后,触发扩容======
arr2: [0 0 0 0 10]
len(arr2): 5
cap(arr2): 8
------------------------
arr3: []
len(arr3): 0
cap(arr3): 10
======添加一个元素后======
arr3: [10]
len(arr3): 1
cap(arr3): 10
------------------------
arr4: [1 2 3 4]
arr4: [1 2 4]
package main
import "fmt"
func test1() {
// 创建map的方式一:使用make创建map类型[key的类型]value的类型
m := make(map[string]string)
m["name"] = "张三"
m["age"] = "20"
m["mail"] = "[email protected]"
// i为索引,v为值
for k, v := range m {
fmt.Printf("key: %v, value: %v\n", k, v)
}
}
func test2() {
// 创建map的方式二:直接声明并赋值
m := map[string]string{
"name": "张三",
"age": "20",
"mail": "[email protected]",
}
// 输出map的类型
fmt.Printf("m: %T\n", m)
// i为索引,v为值
for k, v := range m {
fmt.Printf("key: %v, value: %v\n", k, v)
}
}
func test3() {
// interface{}表示一个没有方法的空接口类型,相当于其他语言中的Object类型。
// go语言中的所有类型都属于interface{}接口类型,因此interface{}可以表示任意类型。
m := map[string]interface{}{
"name": "张三",
"age": 20,
"isMarried": false,
}
// 遍历字典中的数据
for k, v := range m {
fmt.Printf("%v=%v\n", k, v)
}
}
func main() {
test1()
test2()
test3()
}
// test1
key: name, value: 张三
key: age, value: 20
key: mail, value: 312885991@qq.com
// test2
m: map[string]string
key: name, value: 张三
key: age, value: 20
key: mail, value: 312885991@qq.com
// test3
name=张三
age=20
isMarried=false
package main
import "fmt"
type Person struct {
id int
name string
}
func main() {
// 简单类型的指针
x := 10
// 声明一个指针变量p
var p *int
// 将x的地址赋值给指针变量p
p = &x
// 打印p的类型
fmt.Printf("p: %T\n", p)
// p表示指向x变量的地址指针,*p可以取出该地址存储的值
fmt.Printf("p: %v\n", p)
fmt.Printf("p: %v\n", *p)
fmt.Println("----------------")
// 定义一个结构体对象
person := Person{
id: 1001,
name: "张三",
}
// 定义一个结构体指针
var per *Person
per = &person
fmt.Printf("p: %T\n", per)
fmt.Printf("p: %v\n", per)
fmt.Printf("p: %v\n", *per)
fmt.Println("----------------")
// 指针数组使用
arr := [3]int{1, 2, 3}
// 声明一个指针数组
var arrp [3]*int
// 将数组的各个元素地址赋值给指针数组
for i := 0; i < len(arr); i++ {
arrp[i] = &arr[i]
}
// 打印指针数组
fmt.Printf("arrp: %v\n", arrp)
// 遍历指针数组,并取指针所指向地址的数值
for i := 0; i < len(arrp); i++ {
fmt.Printf("*arrp[%v]: %v\n", i, *arrp[i])
}
}
p: *int
p: 0xc000016088
p: 10
----------------
p: *main.Person
p: &{1001 张三}
p: {1001 张三}
----------------
arrp: [0xc000018138 0xc000018140 0xc000018148]
*arrp[0]: 1
*arrp[1]: 2
*arrp[2]: 3
package main
import (
"fmt"
)
func main() {
age := 18
if age < 18 {
fmt.Println("未成年")
} else if age >= 18 && age <= 50 {
fmt.Println("成年了")
} else {
fmt.Println("老年了")
}
}
成年了
package main
import "fmt"
func main() {
season := "秋天"
switch season {
case "春天":
fmt.Println("秋天来了")
case "夏天":
fmt.Println("夏天来了")
case "秋天":
fmt.Println("秋天来了")
case "冬天":
fmt.Println("冬天来了")
default:
fmt.Println("未知季节")
}
}
秋天来了
package main
import "fmt"
func main() {
// 普通for循环
for i := 0; i < 3; i++ {
fmt.Printf("i=%v\n", i)
}
fmt.Println("----------------------")
// 利用range遍历数组
// 案例一(其中...表示自动计算数组大小)
arr1 := [...]int{1, 2, 3, 4, 5}
// i表示索引,v表示值
for i, v := range arr1 {
fmt.Printf("%v=%v\n", i, v)
}
fmt.Println("----------------------")
// 案例二(不指定大小,也不设置... 则表示切片类型,切片类型即能自动扩容的数组)
arr2 := []int{1, 2, 3, 4}
for i, v := range arr2 {
fmt.Printf("%v=%v\n", i, v)
}
// for {
// fmt.Println("我是死循环...")
// }
}
i=0
i=1
i=2
----------------------
0=1
1=2
2=3
3=4
4=5
----------------------
0=1
1=2
2=3
3=4
这两个与其他语言的用法一致,这里略过
package main
import "fmt"
func main() {
// 多层循环
for i := 0; i < 5; i++ {
for j := 0; j < 2; j++ {
if i > 2 {
// 跳转到标签处
goto MYLABEL
}
fmt.Printf("i: %v\n", i)
}
fmt.Println("---------------")
}
// 自定义标签
MYLABEL:
fmt.Println("进入goto目的地")
x := 10
y := 20
// 求两数之和
sum := x + y
fmt.Printf("(x+y)=%v", sum)
}
i: 0
i: 0
---------------
i: 1
i: 1
---------------
i: 2
i: 2
---------------
进入goto目的地
(x+y)=30
13、defer 语句
package main
import "fmt"
func main() {
fmt.Println("开始执行...")
// defer 语句会延迟到最后执行。
// 当同时有多个defer语句时,是按栈的先进后出,后进先出顺序进行执行。
// 即先定义的后执行,后定义的先执行。
defer fmt.Println("清理程序中的进程1...")
defer fmt.Println("清理程序中的进程2...")
fmt.Println("程序即将结束...")
}
开始执行...
程序即将结束...
清理程序中的进程2...
清理程序中的进程1...
package main
import (
"fmt"
)
// new和make的区别:
// 1. make只能用来分配及初始化类型为slice, map , chan的数据; new可以分配任意类型的数据
// 2. new分配返回的是指针,即类型*T ; make返回的是引用,即T;
// 3. new分配的空间被清零,make分配后,会进行初始化。
func main() {
arr1 := make([]int, 10)
fmt.Printf("arr1: %T\n", arr1)
fmt.Printf("arr1: %v\n", arr1)
fmt.Println("------------------------")
arr2 := new([]int)
fmt.Printf("arr2: %T\n", arr2)
fmt.Printf("arr2: %v\n", arr2)
fmt.Printf("arr2: %v\n", *arr2)
}
arr1: []int
arr1: [0 0 0 0 0 0 0 0 0 0]
------------------------
arr2: *[]int
arr2: &[]
arr2: []
package main
import "fmt"
func main() {
var name = "张三"
age := 20
is_married := false
// 打印数据类型
fmt.Printf("%v %T\n", name, name)
fmt.Printf("%v %T\n", age, age)
fmt.Printf("%v %T\n", is_married, is_married)
fmt.Println("----------------------")
b := &age
// b是指针类型,其中b表示age的地址,*p表示取地址存储的值
fmt.Printf("%v %v %T\n", b, *b, b)
arr := []int{1, 2, 3, 4, 5}
// 数组类型
fmt.Printf("%v %T\n", arr, arr)
fmt.Println("数组的长度:", len(arr))
}
张三 string
20 int
false bool
----------------------
0xc000016088 20 *int
[1 2 3 4 5] []int
数组的长度: 5
package main
import (
"fmt"
"time"
)
type WebSite struct {
Name string
Age int
}
func main() {
// 结构体类型
site := WebSite{Name: "张三", Age: 20}
fmt.Printf("site: %v\n", site)
fmt.Printf("site: %#v\n", site)
fmt.Printf("site: %T\n", site)
fmt.Println("---------------------")
// 布尔类型
b := true
fmt.Printf("b: %v\n", b)
fmt.Printf("b: %T\n", b)
fmt.Println("---------------------")
// 指针类型
p := &b
fmt.Printf("p: %v\n", p)
fmt.Printf("p: %T\n", p)
fmt.Println("---------------------")
// 科学计数法
fmt.Printf("a: %e\n", 100.2)
fmt.Println("---------------------")
// 开始时间
start := time.Now()
// 求和
sum := 0
for i := 0; i < 1000000; i++ {
sum += i
}
// 结束时间
end := time.Now()
// 间隔时间
duration := end.Sub(start)
fmt.Printf("sum: %v, use_time=%v\n", sum, duration.Microseconds())
}
site: {张三 20}
site: main.WebSite{Name:"张三", Age:20}
site: main.WebSite
---------------------
b: true
b: bool
---------------------
p: 0xc0000160d0
p: *bool
---------------------
a: 1.002000e+02
---------------------
sum: 499999500000, use_time=653
函数的形式如下:
func 函数名(参数名 参数类型, 参数名 参数类型...) (返回值类型, 返回值类型...){
函数体
}
func 函数名(参数名 参数类型, 参数名 参数类型...) (返回值变量 返回值类型, 返回值变量 返回值类型...){
函数体
}
package main
import "fmt"
func test1() {
fmt.Println("测试函数")
fmt.Println("---------------")
}
func test2(name string, age int) {
fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
fmt.Println("---------------")
}
// 声明返回值类型
func add(a int, b int) int {
// 声明c变量进行接收
c := a + b
return c
}
// 声明返回值类型,并声明对应的变量
func add2(a int, b int) (c int) {
// 返回值声明了c变量,因此不用再声明,可以直接使用
c = a + b
return c
}
func main() {
test1()
test2("张三", 20)
i := add(1, 10)
fmt.Printf("i: %v\n", i)
fmt.Println("---------------")
i = add2(1, 20)
fmt.Printf("i: %v\n", i)
}
测试函数
---------------
name: 张三
age: 20
---------------
i: 11
---------------
i: 21
package main
import "fmt"
// 返回一个求和的函数
func f1() func(a int, b int) int {
// 声明一个求和的匿名函数
var addFn = func(a int, b int) int {
return a + b
}
return addFn
}
// 声明一个函数,并传递一个函数(指定了函数的具体类型)
func f2(fn func(a int, b int) int) {
fmt.Println("执行传递的函数...")
i := fn(1, 2)
fmt.Printf("i: %v\n", i)
}
// 相减函数
func sub(a int, b int) int {
return a - b
}
func main() {
// 返回一个求和的函数
f := f1()
fmt.Printf("f(10, 20): %v\n", f(10, 20))
// 调用f2函数,并传递一个求差的函数
f2(sub)
}
f(10, 20): 30
执行传递的函数...
i: -1
package main
import "fmt"
// 测试值传递和引用传递
type Person struct {
name string
}
// 值传递
func ff1(person Person) {
person.name = "值传递"
}
// 引用传递(形参为指针类型)
func ff2(person *Person) {
person.name = "引用传递"
}
func main() {
// 测试值传递和引用传递
person := Person{
name: "张三",
}
fmt.Printf("person.name: %v\n", person.name)
// 值传递,传递的是副本,因此函数内修改对象的值,并不会改变原对象的值
ff1(person)
fmt.Printf("person.name: %v\n", person.name)
fmt.Println("-------------------------------")
// 引用传递,传递的是地址,函数内修改对象的值等价于修改原对象
var p *Person
p = &person
ff2(p)
// ff2(&person)
fmt.Printf("person.name: %v\n", person.name)
}
person.name: 张三
person.name: 张三
-------------------------------
person.name: 张三
person.name: 引用传递
go语言中,名称为 init
的函数表示初始化函数,无须显示调用,会自动被调用。
package main
import "fmt"
func init() {
fmt.Println("这是初始化的内容...")
}
func main() {
// init函数被自动调用
}
这是初始化的内容...
go 语言中没有类的概念,只有结构体,等同于类。并且结构体不支持继承,但可以通过组合的方式实现继承的效果。
结构体的形式如下:
注意:在结构体中,如果属性名首字母大写,则表示公有属性;如果属性名首字母小写,则表示私有属性。私有属性外部包不能使用,同时也不能进行序列化。
type name struct{
属性名 属性类型
属性名 属性类型
}
package main
import "fmt"
// 定义Person结构体
// 结构体中,属性名首字母大写表示公有属性,首字母小写表示私有属性。
type Student struct {
// 同类型的可以写在一块
id, age int
name string
}
// 定义Student结构体所属的方法
func (stu Student) eat() {
fmt.Printf("%v正在吃...\n", stu.name)
}
func (stu Student) sleep() {
fmt.Printf("%v正在睡...\n", stu.name)
}
func main() {
// 第一种赋值
var stu Student
stu.id = 1001
stu.age = 20
stu.name = "张三"
fmt.Printf("stu: %v\n", stu)
// 第二种赋值
st := Student{
id: 1001,
name: "李四",
age: 20,
}
fmt.Printf("st: %v\n", st)
// 测试方法
st.eat()
st.sleep()
}
stu: {1001 20 张三}
st: {1001 20 李四}
李四正在吃...
李四正在睡...
接口的形式如下:接口中声明方法的形式,与声明函数是一样的。
注意:interface{}
表示不包含方法的接口,属于空接口类型,它相当于其他语言中的Object类型,可以表示任何类型。
type name interface{
方法名()
方法名(参数名 参数类型, 参数名 参数类型...) (返回值类型, 返回值类型...)
}
package main
import (
"fmt"
)
// 定义接口
type Animal interface {
// 动物描述
descript()
}
// 定义Cat结构体
type Cat struct {
}
// Cat结构体实现Animal接口(方法名称及参数相同即代表实现这个接口)
func (cat Cat) descript() {
fmt.Println("这是猫的描述内容...")
}
// 定义Dog结构体
type Dog struct {
}
// Dog结构体实现Animal接口(方法名称及参数相同即代表实现这个接口)
func (dog Dog) descript() {
fmt.Println("这是狗的描述内容...")
}
// 定义一个普通方法,传递Animal接口对象(多态)
func test(animal Animal) {
animal.descript()
}
func main() {
// 创建dog和cat对象
dog := Dog{}
cat := Cat{}
// 调用test方法(多态)
test(dog)
test(cat)
fmt.Println("---------------------")
// 声明变量p为Animal接口类型(多态)
var p Animal
// Dog是Animal的实现,所以可以这样赋值
p = Dog{}
test(p)
// Cat也是Animal的实现,所以可以这样赋值
p = Cat{}
test(p)
}
这是狗的描述内容...
这是猫的描述内容...
---------------------
这是狗的描述内容...
这是猫的描述内容...
go语言中的协程,相当于系统中的线程一样。
package main
import (
"fmt"
"time"
)
// 定义一个方法,循环打印5次名称
func ShowMsg(name string) {
for i := 0; i < 5; i++ {
fmt.Printf("name: %v\n", name)
// 打印一次睡眠1s
time.Sleep(time.Second * 1)
}
}
func main() {
fmt.Println("start...")
ShowMsg("java") // 主线程调用
fmt.Println("end...")
fmt.Println("-----------------")
fmt.Println("start...")
go ShowMsg("python") // 开启一个协线程来调用执行,主线程会继续往下执行
// 让主线程睡眠3s,否则主线程往下执行结束后,协线程也会自动结束。
// 那么观察不到协线程调用print的效果
time.Sleep(time.Second * 3)
fmt.Println("end...")
}
start...
name: java
name: java
name: java
name: java
name: java
end...
-----------------
start...
name: python
name: python
name: python
end...
channel 主要是负责协程之间进行通信的。
Go语言中有两种channel,一种是无缓冲的channel,相当于其容量为1,一次只能接受一条消息,需要消息消费完成之后,才能接收下一条消息,其是同步阻塞的方式,如果消息不消费,会阻塞住当前线程。
另一种是带缓冲的channel,相当于其容量为n,可以同时接收多条消息,其是异步非阻塞的方式,即使消息不消费,也不会阻塞当前线程。
package main
import (
"fmt"
)
func main() {
// 声明一个channel类型的变量ch
var ch chan int
ch := make*
fmt.Printf("ch: %T\n", ch)
// 1、创建无缓冲的channel,无缓冲的channel是同步的实现方式
// (channel接收一个消息后,会阻塞住线程,直至有其他线程消费这个消息,才会结束)
Unbuffed := make(chan string)
// 开启一个协程,去消费channel中的消息
go func() {
// 从channel中取消息
msg := <-Unbuffed
fmt.Printf("msg: %v\n", msg)
}()
// 向channel发送消息,如果没有其他线程消费这条消息,则会阻塞在这里
Unbuffed <- "Hello World"
// 由于是不带缓冲的channel,其是同步的方式,所以如果没有上方协程取消息的操作,
// 则上面一步会阻塞住,这条打印语句将不会执行。但现在由于协程消费了消息,所以这里会打印。
fmt.Println("不带缓冲的channel: 我打印了吗?")
fmt.Println("-------------------------------------")
// 2、创建带缓冲的channel,异步实现的方式
Buffed := make(chan string, 5)
// 往带缓冲的channel中发送一条消息
Buffed <- "带缓冲的Buffed消息"
// 由于是带缓冲的channel,即使消息没有被消费,也不会阻塞住。因此会打印下方语句
fmt.Println("带缓冲的channel: 我打印了吗?")
}
ch: chan int
msg: Hello World
不带缓冲的channel: 我打印了吗?
-------------------------------------
带缓冲的channel: 我打印了吗?
package main
import "fmt"
func main() {
// 声明无缓冲的channel类型
ch := make(chan int)
// 开启协程往channel中发送数据
go func() {
for i := 0; i < 5; i++ {
// 往channel中添加数据
ch <- i
}
// 关闭channel
close(ch)
}()
// 主线程消费数据
for v := range ch {
fmt.Printf("接收到消息: %v\n", v)
}
}
接收到消息: 0
接收到消息: 1
接收到消息: 2
接收到消息: 3
接收到消息: 4
WaitGroup 相当于Java语言中的倒计时锁,用于等待各线程全部执行结束。
package main
import (
"fmt"
"sync"
"time"
)
// 声明一个waitgroup类型变量
var wg sync.WaitGroup
// 执行任务的函数
func task() {
for i := 0; i < 5; i++ {
fmt.Printf("i: %v\n", i)
// 每打印一次就睡眠1s
time.Sleep(time.Second)
// 执行完一次,就将wg其内部值进行-1操作
wg.Done()
}
}
func main() {
// 设置起始的信号量为5
wg.Add(5)
// 异步执行任务
go task()
// 主线程等待协程执行完毕,否则协程也会随着主线程结束而结束。
// 1、以前的做法(主线程主动睡眠5s等待)
// time.Sleep(time.Second * 5)
// 2、现在的做法
wg.Wait() // 当其内部值不为0时,会阻塞住。
fmt.Println("程序结束...")
}
// 每隔1s打印的
i: 0
i: 1
i: 2
i: 3
i: 4
程序结束...
当多个协程对同一个变量进行读写操作时,可能会出现数据异常的问题,因此需要使用同步机制来控制多个协程对同一个变量的读写操作,保证数据的一致性。
package main
import (
"fmt"
"sync"
)
// 声明一个全局变量count,用于加减操作
var count int = 100
// 声明一个类似倒计时锁的对象,用来控制主线程的结束
var wgg sync.WaitGroup
// 声明一个互斥锁对象
var lock sync.Mutex
func addCount() {
lock.Lock() // 对共享变量的读写操作进行加锁,保证同一时刻,只能有一个协程进行访问
// 执行自增操作
count++
lock.Unlock()
// 执行完一次,就将wgg其内部值进行-1操作
wgg.Done()
}
func subCount() {
lock.Lock() // 对共享变量的读写操作进行加锁,保证同一时刻,只能有一个协程进行访问
// 执行自减操作
count--
lock.Unlock()
// 执行完一次,就将wgg其内部值进行-1操作
wgg.Done()
}
// 测试互斥锁
func main() {
// 进行100次的自增和自减(多个协程去完成)
for i := 0; i < 100; i++ {
// 将wgg其内部值+1,因为即将要开启一个协程去执行add操作
wgg.Add(1)
go addCount()
// 将wgg其内部值+1,因为即将要开启一个协程去执行sub操作
wgg.Add(1)
go subCount()
}
// 等待所有协程执行完毕
wgg.Wait()
// 输出最终的count结果(如果不加锁的情况下,可能会出现count不等于100的情况)
fmt.Printf("count: %v\n", count)
}
count: 100
原子变量也是用于同步机制,控制多线程的正确访问,相比于互斥锁,其效率较高。它和Java语言中的原子变量一样,都是采用了
CAS
操作来实现的。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// 初始化全局变量
// var number int = 100 // 默认的int是8个字节、64位
var number int32 = 100
var wg sync.WaitGroup
func addNumber() {
// number++
// 使用原子操作+1,保证同步
atomic.AddInt32(&number, 1)
// 执行完一次,就将wg其内部值进行-1操作
wg.Done()
}
func subNumber() {
// number--
// 使用原子操作-1,保证同步
atomic.AddInt32(&number, -1)
// 执行完一次,就将wg其内部值进行-1操作
wg.Done()
}
// 原子操作
func main() {
// 进行100次的自增和自减
for i := 0; i < 100; i++ {
// 将wg其内部值+1,因为即将要开启一个协程去执行add操作
wg.Add(1)
go addNumber()
// 将wg其内部值+1,因为即将要开启一个协程去执行sub操作
wg.Add(1)
go subNumber()
}
// 等待所有协程执行完毕
wg.Wait()
// 输出最终的number结果(如果使用原子变量的情况下,可能会出现number不等于100的情况)
fmt.Printf("number: %v\n", number)
}
number: 100
package main
import (
"fmt"
"sync/atomic"
)
func main() {
// atomic中的一些操作
var n int64 = 32
// 在n的基础上+10
atomic.AddInt64(&n, 10)
fmt.Printf("n: %v\n", n)
fmt.Println("--------------------")
// 读取n的值
i := atomic.LoadInt64(&n)
fmt.Printf("i: %v\n", i)
fmt.Println("--------------------")
// 存储n的值为100
atomic.StoreInt64(&n, 100)
fmt.Printf("n: %v\n", n)
fmt.Println("--------------------")
// cas操作 compare and swap 是保证原子性的基础
// 在此之前,n的值为100,传入的old值也为100,所以此时能够修改成功
b := atomic.CompareAndSwapInt64(&n, 100, 200)
fmt.Printf("是否更改成功?: %v\n", b)
fmt.Println("--------------------")
// 在执行这行时,n已经是200了,而传入的old值为100,两者不相等,所以这里会修改失败
b = atomic.CompareAndSwapInt64(&n, 100, 150)
fmt.Printf("是否更改成功?: %v\n", b)
}
n: 42
--------------------
i: 42
--------------------
n: 100
--------------------
是否更改成功?: true
--------------------
是否更改成功?: false
select 是Go语言中的一个控制结构,类似于switch语句,用于处理异步IO事件。select 会监听 channel 中的读写操作。select 中的 case 语句必须是一个channel操作。
1、如果有多个case语句可以运行,select 会随机的选出一个case语句进行执行。
2、如果没有可运行的case语句,且有default语句,则会执行default语句。
3、如果没有可运行的case语句,且没有default语句,则select会阻塞住,直到某个case可以运行。
package main
import (
"fmt"
"time"
)
var chanInt = make(chan int)
var chanStr = make(chan string)
func main() {
fmt.Println("start...")
// 开启一个协程,1s后往chanInt发送一条消息。
go func() {
fmt.Println("协程: 1s后往chanInt发送消息")
// 让协程睡眠1s
time.Sleep(time.Second * 1)
// 向通道发送数据
chanInt <- 10
}()
// 开启一个协程,2s后往chanStr发送一条消息。
go func() {
fmt.Println("协程: 2s后往chanStr发送消息")
// 让协程睡眠2s
time.Sleep(time.Second * 2)
// 向通道发送数据
chanStr <- "hello world!"
}()
// 死循环
for {
// 监听channel的读写操作
select {
case i := <-chanInt:
fmt.Printf("i: %v\n", i)
case s := <-chanStr:
fmt.Printf("s: %v\n", s)
// default:
// fmt.Println("default...")
}
}
// 上面select阻塞住了,这里不会打印
fmt.Println("end...")
}
start...
协程: 2s后往chanStr发送消息
协程: 1s后往chanInt发送消息
// 1s后通道chanInt接收了消息,所以select监听到了该事件,打印了i=10
i: 10
// 2s后通道chanStr接收了消息,所以select监听到了该事件,打印了s=hello world!
s: hello world!
// 由于两个协程已经结束,chanInt和chanStr不会再触发读写事件了,而select中也没有default语句,所以程序会一直阻塞,造成了死锁。
fatal error: all goroutines are asleep - deadlock!
Timer 是一个定时功能,表示多少秒后执行某个动作。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Printf("before: %v\n", time.Now())
// 创建timer对象,指定2s延迟,2s后执行
timer := time.NewTimer(time.Second * 2)
// timer对象中的C属性,是一个channel类型,且是无缓冲,同步的方式。
// 通过同步获取channel中的数据,实现2s阻塞,从而达到2s延迟的效果。
// 具体为: 当获取channel中的数据时,由于暂时没有数据,所以会阻塞住。
// 当2s过后,channel中发送数据了,所以继续往下运行。
<-timer.C
// Reset()表示重置时间
// timer.Reset(time.Second * 1) // 再间隔1s
// <-timer.C
// 2s后执行
fmt.Printf("after: %v\n", time.Now())
// Stop() 表示停止timer
// timer.Stop()
}
before: 2022-11-16 14:05:53.6631316 +0800 CST m=+0.004601401
// 间隔2s
after: 2022-11-16 14:05:55.6991968 +0800 CST m=+2.040666601
Ticker 也是一个定时功能,表示每隔多少秒循环执行某个动作。
package main
import (
"fmt"
"time"
)
func main() {
// 创建ticker对象,指定每隔2s执行
ticker := time.NewTicker(time.Second * 2)
// ticker.C 是一个channel,并且其接收的消息类型是Time类型。
fmt.Printf("ticker.C: %T\n", ticker.C)
// 对ticker.C进行遍历,实现每隔2s打印一次消息(时间)
for v := range ticker.C {
fmt.Printf("v: %v\n", v)
}
}
ticker.C: <-chan time.Time
v: 2022-11-16 13:59:28.7116516 +0800 CST m=+2.011244101
v: 2022-11-16 13:59:30.7120745 +0800 CST m=+4.011667001
v: 2022-11-16 13:59:32.7178907 +0800 CST m=+6.017483201
package main
import (
"fmt"
"runtime"
)
func main() {
// 打印cpu核心数
fmt.Printf("NumCPU: %v\n", runtime.NumCPU())
// Goexit的作用是终止当前线程的执行,但在终止前会执行完defer语句
// runtime.Goexit()
// 开启一个协程,执行匿名函数
go func() {
for i := 0; i < 5; i++ {
fmt.Printf("协程打印的内容%v...\n", i)
}
}()
// 主线程执行,主线程执行太快,导致协程内的语句来不及执行。
// 加上这句代码,可以让当前线程(主线程)让出cpu的使用权,协程就可以执行了。
runtime.Gosched()
fmt.Println("主线程打印的内容...")
}
NumCPU: 4
协程打印的内容0...
协程打印的内容1...
协程打印的内容2...
协程打印的内容3...
协程打印的内容4...
主线程打印的内容...
文件内容:
你好,world!
代码:
package main
import (
"fmt"
"io"
"log"
"os"
)
// 读取文件
func readFile() {
// 打开文件,返回File对象,实现了Reader,Writer接口
// 第一个参数:文件名
// 第二个参数:操作文件的类型,这里是只读
// 第三个参数:文件的权限
f, err := os.OpenFile("a.txt", os.O_RDONLY, os.ModePerm)
if err != nil {
log.Fatal(err)
}
// 创建字节切片:大小为3个字节
buf := make([]byte, 3)
// 循环读取内容
for {
// 3个字节读,n为实际读取的字节数
n, err2 := f.Read(buf)
// 如果读取到末尾,则结束
if err2 == io.EOF {
break
}
// 输出读取的内容
fmt.Printf("读取的字节内容: %v\n", buf[0:n])
fmt.Printf("转换为字符串的内容: %v\n", string(buf[0:n]))
fmt.Println("------------------------")
}
}
func main() {
// 读取文件内容
readFile()
}
// 一个中文字符,占3个字节。
读取的字节内容: [228 189 160]
转换为字符串的内容: 你
------------------------
读取的字节内容: [229 165 189]
转换为字符串的内容: 好
------------------------
读取的字节内容: [239 188 140]
转换为字符串的内容: ,
------------------------
读取的字节内容: [119 111 114]
转换为字符串的内容: wor
------------------------
读取的字节内容: [108 100 33]
转换为字符串的内容: ld!
------------------------
package main
import (
"fmt"
"io"
"log"
"os"
)
// 写入文件内容
func writeFile(msg string) {
// 打开文件,返回File对象,实现了Reader,Writer接口
// 第一个参数:文件名
// 第二个参数:操作文件的类型,这里是只写、可创建
// 第三个参数:文件的权限
// 如果文件不存在,则创建;如果文件存在,则从头开始覆盖写(默认)
f, err := os.OpenFile("b.txt", os.O_WRONLY|os.O_CREATE, 0666)
// 如果文件不存在,则创建;如果文件存在,则全覆盖写(os.O_TRUNC)。
// f, err := os.OpenFile("b.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
// 如果文件不存在,则创建;如果文件存在,则追加写(os.O_APPEND)。
// f, err := os.OpenFile("b.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
// 写入文件内容
f.Write([]byte(msg)) // 写入字节
// f.WriteString(msg) // 写入字符串
f.Close()
}
func main() {
// 写入文件内容
writeFile("你好, world!")
// 默认的写操作类型,对同一个文件是从头开始覆盖写。
// 比如这里是先写入"你好, world!",然后再写入"哈哈",则最后内容变成"哈哈, world!"。
writeFile("哈哈")
// 如果要全部覆盖写,则加上os.O_TRUNC操作类型。
// 如果要在原基础上追加写,则加上os.O_TRUNC操作类型。
}
// 先是写入
你好, world!
// 然后变为
哈哈, world!
package main
import (
"fmt"
"io"
"log"
"os"
)
func copyFile(srcFile string, destFile string) {
// 读取源文件
f, _ := os.OpenFile(srcFile, os.O_RDONLY, os.ModePerm)
defer f.Close() // 延迟关闭文件
// 写入的目的文件
dest, _ := os.OpenFile(destFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
defer dest.Close() // 延迟关闭文件
// 创建buf来读取内容
buf := make([]byte, 10)
// 循环读取
for {
// n是实际读取的字节数
n, err := f.Read(buf)
// 读取结束
if err == io.EOF {
break
}
// 将实际读取的内容写入目的文件
dest.Write(buf[0:n])
}
fmt.Println("复制完毕!")
}
func main() {
// 复制文件
copyFile("a.txt", "a_copy.txt")
}
复制完毕!
带缓冲的
bufio
可以提高读写的速度。
package main
import (
"bufio"
"fmt"
"io"
"os"
"time"
)
func copyFileByIO(srcFile string, destFile string) {
// 记录开始时间
startTime := time.Now()
// 读取源文件
f, _ := os.OpenFile(srcFile, os.O_RDONLY, os.ModePerm)
defer f.Close() // 延迟关闭文件
// 写入的目的文件
dest, _ := os.OpenFile(destFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
defer dest.Close() // 延迟关闭文件
// 创建buf来读取内容
buf := make([]byte, 1024)
// 循环读取
for {
// n是实际读取的字节数
n, err := f.Read(buf)
// 读取结束
if err == io.EOF {
break
}
// 将实际读取的内容写入目的文件
dest.Write(buf[0:n])
}
// 记录花费时间
costTime := time.Now().Sub(startTime)
fmt.Printf("costTime: %v\n", costTime)
}
func copyFileByBufIO(srcFile string, destFile string) {
// 记录开始时间
startTime := time.Now()
// 读取源文件
f, _ := os.OpenFile(srcFile, os.O_RDONLY, os.ModePerm)
defer f.Close() // 延迟关闭文件
// 写入的目的文件
dest, _ := os.OpenFile(destFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
defer dest.Close() // 延迟关闭文件
// 创建buf来读取内容
buf := make([]byte, 1024)
// 将f的Reader转成带缓冲的Reader类型,速度更快(区别)
r := bufio.NewReader(f)
// 循环读取
for {
// n是实际读取的字节数
n, err := r.Read(buf)
// 读取结束
if err == io.EOF {
break
}
// 将实际读取的内容写入目的文件
dest.Write(buf[0:n])
}
// 记录花费时间
costTime := time.Now().Sub(startTime)
fmt.Printf("costTime: %v\n", costTime)
}
func main() {
srcFile := "D:/test.mp4"
destFile := "D:/test_copy.mp4"
// 使用不带缓冲的IO复制大文件 4.28s
copyFileByIO(srcFile, destFile)
// 使用带缓冲的BufIO复制大文件 3.26s
copyFileByBufIO(srcFile, destFile)
}
costTime: 4.28s
costTime: 3.26s
package main
import (
"fmt"
"log"
"os"
)
func GetWorkDir() {
// 获取当前工作目录
dir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
fmt.Printf("dir: %v\n", dir)
}
func main() {
// 获取当前工作目录
GetWorkDir()
}
dir: c:\Users\LIULUSHENG\Desktop\go
package main
import (
"fmt"
"log"
"os"
)
func createFile() {
f, err := os.Create("a.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("f.Name(): %v\n", f.Name())
}
func createFileByOrigin() {
// OpenFile() 根据传递的参数,既可以创建文件,也可以读取文件
// 第一个参数:文件名 第二个参数:文件的操作类型(只写、可以创建) 第三个参数:文件的权限,类似linux
f, err := os.OpenFile("b.txt", os.O_WRONLY|os.O_CREATE, 0777)
if err != nil {
log.Fatal(err)
}
fmt.Printf("f.Name(): %v\n", f.Name())
}
func main() {
// 创建文件
createFile()
// 通过最原始的方法创建文件
createFileByOrigin()
}
f.Name(): a.txt
f.Name(): b.txt
package main
import (
"fmt"
"log"
"os"
)
func createDir() {
// 创建目录,指定目录的权限
err := os.Mkdir("a", os.ModePerm)
if err != nil {
log.Fatal(err)
}
}
func createManyDir() {
// 创建多级目录
err := os.MkdirAll("b/c/d", os.ModePerm)
if err != nil {
log.Fatal(err)
}
}
func main() {
// 创建目录
createDir()
// 创建多级目录
createManyDir()
}
package main
import (
"fmt"
"log"
"os"
)
func removeFile() {
// 删除文件
err := os.Remove("a.txt")
if err != nil {
log.Fatal(err)
}
}
func main() {
// 删除文件
removeFile()
}
package main
import (
"fmt"
"log"
"os"
)
func removeDir() {
// 删除单个目录
err := os.Remove("a")
if err != nil {
log.Fatal(err)
}
}
func removeAllDir() {
// 删除多个目录(包括其子目录)
err := os.RemoveAll("b")
if err != nil {
log.Fatal(err)
}
}
func main() {
// 删除单个目录
removeDir()
// 删除多个目录(包括目录下的子目录)
removeAllDir()
}
package main
import (
"fmt"
"log"
"os"
)
func createTempDir() {
// 创建临时目录
// 第一个参数:指定创建临时目录的根目录位置,若留空,则默认是操作系统的临时根目录下
// 第二个参数:临时目录的前缀
s, err := os.MkdirTemp("", "temp")
if err != nil {
log.Fatal(err)
}
fmt.Printf("创建的临时目录: %v\n", s)
}
func main() {
// 创建临时目录
// 创建的临时目录: C:\Users\LIULUS~1\AppData\Local\Temp\temp3343246708
createTempDir()
}
创建的临时目录: C:\Users\LIULUS~1\AppData\Local\Temp\temp3343246708
package main
import (
"fmt"
"log"
"os"
)
func readFile() {
// 读取文件内容,返回的是字节切片
b, err := os.ReadFile("a.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("读取的文件内容为:%v\n", string(b))
}
func main() {
// 读取文件
readFile()
}
读取的文件内容为:你好,world!
package main
import (
"fmt"
"log"
"os"
)
func readDir() {
// 读取目录内容,返回的是目录下的所有内容(文件和目录)的结点切片[]DirEntry
de, err := os.ReadDir("b")
if err != nil {
log.Fatal(err)
}
// 遍历结点切片
for _, v := range de {
// 打印每一个内容结点
fmt.Printf("name: %v \t is_dir: %v\n", v.Name(), v.IsDir())
}
}
func main() {
// 读取目录信息
readDir()
}
name: c is_dir: true
name: hello.txt is_dir: false
package main
import (
"fmt"
"log"
"os"
)
func readOSEnv() {
// 读取系统环境
s := os.Environ()
for _, v := range s {
fmt.Printf("v: %v\n", v)
}
}
func main() {
// 读取系统环境
// readOSEnv()
// 获取系统变量JAVA_HOME
s := os.Getenv("JAVA_HOME")
fmt.Printf("s: %v\n", s)
// 查不到对应的环境变量时,只会返回空串
s = os.Getenv("GO_PATH")
fmt.Printf("s: %v\n", s)
// LookupEnv相对于Getenv,会返回是否存在该变量的提示
s2, b := os.LookupEnv("GO_PATH")
if !b {
fmt.Println("未找到该环境变量")
} else {
fmt.Printf("s2: %v\n", s2)
}
// 设置环境变量
err := os.Setenv("key", "value")
if err != nil {
log.Fatal(err)
}
// 移除某个环境变量
err = os.Unsetenv("key")
if err != nil {
log.Fatal(err)
}
// 清除环境变量
// os.Clearenv()
}
s: D:\JDK\jdk1.8
// 空串
s:
// 有提示
未找到该环境变量
package main
import (
"fmt"
"io"
"os"
)
func testReadAll() {
f, _ := os.OpenFile("a.txt", os.O_RDONLY, 777)
defer f.Close()
// 读取文件的所有字节
b, _ := io.ReadAll(f)
fmt.Printf("string(b): %v\n", string(b))
}
func testWrite() {
// 追加写
f, _ := os.OpenFile("a.txt", os.O_WRONLY|os.O_APPEND, 777)
defer f.Close()
// n是实际写入的字节数
n, _ := io.WriteString(f, "\n")
fmt.Printf("n: %v\n", n)
// n是实际写入的字节数
n, _ = io.WriteString(f, "你好!")
fmt.Printf("n: %v\n", n)
}
func main() {
// 读取文件的所有内容
testReadAll()
fmt.Println("----------------------")
// 往文件写内容
testWrite()
}
string(b): 你好,world!
----------------------
n: 1
n: 9
package main
import (
"bytes"
"fmt"
)
func main() {
// 将字符串转成带有缓冲的字节切片
b := bytes.NewBufferString("你好, world!")
fmt.Printf("b: %T\n", b)
fmt.Printf("b: %v\n", b)
fmt.Println("-------------------------------")
s1 := []byte("hello world!")
s2 := []byte("hello")
// 判断s1字节切片中是否包含s2字节切片
// 相等于判断s1中的字符串是否包含s2的字符串
fmt.Printf("bytes.Contains(s1, s2): %v\n", bytes.Contains(s1, s2))
fmt.Println("-------------------------------")
// 统计s1字节切片中出现h的次数
fmt.Printf("bytes.Count(s1, []byte('h')): %v\n", bytes.Count(s1, []byte("h")))
// 统计s1字节切片中出现l的次数
fmt.Printf("bytes.Count(s1, []byte('l')): %v\n", bytes.Count(s1, []byte("l")))
fmt.Println("-------------------------------")
// 将s1字节切片重复多次
fmt.Printf("string(bytes.Repeat(s1, 1)): %v\n", string(bytes.Repeat(s1, 1)))
fmt.Printf("string(bytes.Repeat(s1, 3)): %v\n", string(bytes.Repeat(s1, 3)))
}
b: *bytes.Buffer
b: 你好, world!
-------------------------------
bytes.Contains(s1, s2): true
-------------------------------
bytes.Count(s1, []byte('h')): 1
bytes.Count(s1, []byte('l')): 3
-------------------------------
string(bytes.Repeat(s1, 1)): hello world!
string(bytes.Repeat(s1, 3)): hello world!hello world!hello world!
log 模块是专门用来记录日志的,可以写入日志文件。
package main
import (
"fmt"
"log"
"os"
)
// 可以自定义打印日志的格式
// 这个是初始化方法,自动调用
func init() {
// 设置日志格式: 日期 时间 文件
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// 设置日志的前缀
log.SetPrefix("MyLog: ")
// 日志文件
f, _ := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm)
// 设置日志的输出位置,默认是控制台,也可以是文件
log.SetOutput(f)
}
func main() {
// 普通打印
log.Println("我是普通日志")
}
// 文件中记录了日志
MyLog: 2022/11/16 15:20:44 test_log.go:24: 我是普通日志
package main
import (
"fmt"
"log"
"os"
)
// 可以自定义打印日志的格式
// 这个是初始化方法,自动调用
func init() {
// 设置日志格式: 日期 时间 文件
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// 设置日志的前缀
log.SetPrefix("MyLog: ")
// 日志文件
f, _ := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm)
// 设置日志的输出位置,默认是控制台,也可以是文件
log.SetOutput(f)
}
func main() {
defer fmt.Println("我是延迟打印的内容...")
// Panic打印,会抛出异常,程序会中断,但defer语句还是会执行。
log.Panic("出错了...")
// 这行语句不会打印,因为上面已经抛异常了,程序中断。
fmt.Println("我是panic打印后的内容...")
}
我是延迟打印的内容...
panic: 出错了...
goroutine 1 [running]:
log.Panic({0xc00007bf30?, 0x60?, 0x302d00?})
D:/go/src/log/log.go:388 +0x65
main.main()
c:/Users/LIULUSHENG/Desktop/go/test_log.go:30 +0x94
exit status 2
// 同时文件中写入了日志
MyLog: 2022/11/16 15:22:03 test_log.go:30: 出错了...
package main
import (
"fmt"
"log"
"os"
)
// 可以自定义打印日志的格式
// 这个是初始化方法,自动调用
func init() {
// 设置日志格式: 日期 时间 文件
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// 设置日志的前缀
log.SetPrefix("MyLog: ")
// 日志文件
f, _ := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm)
// 设置日志的输出位置,默认是控制台,也可以是文件
log.SetOutput(f)
}
func main() {
defer fmt.Println("我是延迟打印的内容...")
// Fatal打印,会导致程序中断,而且defer语句也执行不了。
log.Fatalln("出大错了...")
// 这行语句不会打印,因为上面程序已经中断了。
fmt.Println("我是fatal打印后的内容...")
}
// 程序直接中断
exit status 1
// 同时文件中记录了该日志
MyLog: 2022/11/16 15:24:26 test_log.go:35: 出大错了...
errors 模块可以快速的自定义异常信息。
package main
import (
"errors"
"fmt"
"log"
)
func check(msg string) error {
if msg == "" {
return errors.New("消息不能为空")
} else {
return nil
}
}
// 自定义错误类型
type MyError struct {
msg string
}
// 自定义错误类型需要实现error接口
func (e *MyError) Error() string {
return e.msg
}
func testMyError() error {
return &MyError{"我是自定义的错误类型消息..."}
}
func main() {
// 测试错误
// 校验传入的参数是否为空串
err := check("")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("验证通过")
}
fmt.Println("----------------------")
err = check("java")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("验证通过")
}
fmt.Println("----------------------")
// 测试自定义的错误
err = testMyError()
if err != nil {
log.Println(err)
}
}
消息不能为空
----------------------
验证通过
----------------------
2022/11/16 15:34:26 我是自定义的错误类型消息...
sort 模块可以对Go语言中的数据类型进行排序,同时也能够自定义数据类型排序。参照 sort 模块内的代码逻辑。
package main
import (
"fmt"
"sort"
)
func main() {
// 待排序整型切片
arr := []int{1, 3, 1, 2, 5, 4}
fmt.Printf("arr: %v\n", arr)
// 排序
sort.Ints(arr)
fmt.Printf("arr: %v\n", arr)
fmt.Println("-----------------")
// 待排序浮点数切片
arr2 := []float64{1.1, 2.2, 0.4, 1.5, 3.4}
fmt.Printf("arr2: %v\n", arr2)
// 排序
sort.Float64s(arr2)
fmt.Printf("arr2: %v\n", arr2)
fmt.Println("-----------------")
// 查看是否已经排好序
b := sort.Float64sAreSorted(arr2)
fmt.Printf("b: %v\n", b)
// 查看是否已经排好序
arr3 := []float64{1.1, 2.2, 0.4, 1.5, 3.4}
b = sort.Float64sAreSorted(arr3)
fmt.Printf("b: %v\n", b)
}
arr: [1 3 1 2 5 4]
arr: [1 1 2 3 4 5]
-----------------
arr2: [1.1 2.2 0.4 1.5 3.4]
arr2: [0.4 1.1 1.5 2.2 3.4]
-----------------
b: true
b: false
package main
import (
"fmt"
"sort"
)
// 1、自定义类型排序(二维切片)
type MultiArr [][]int
// 自定义的类型要实现排序功能,必须实现sort中的Interface接口
func (a MultiArr) Len() int {
return len(a)
}
func (a MultiArr) Less(i, j int) bool {
// 对切片中i、j元素中的第一个元素大小进行比较
return a[i][0] < a[j][0]
}
func (a MultiArr) Swap(i, j int) {
// 交换切片中i、j两个元素
a[i], a[j] = a[j], a[i]
}
// 2、定义一个学生结构体
type Student struct {
name string
age int
}
// 2、定义一个学生切片类型
type StudentSlice []Student
// 实现sort中的Interface接口,从而实现排序功能
func (s StudentSlice) Len() int {
return len(s)
}
func (s StudentSlice) Less(i, j int) bool {
// 比较切片中i、j元素的age值大小
return s[i].age < s[j].age
}
func (s StudentSlice) Swap(i, j int) {
// 交换两个元素
s[i], s[j] = s[j], s[i]
}
func main() {
// 1、自定义排序一
// 创建一个二维的切片
arr := [][]int{[]int{1, 2}, []int{4, 3}, []int{2, 4}}
// 将二维的切片类型转换成MultiArr自定义类型
r := MultiArr(arr)
fmt.Printf("r: %v\n", r)
// 排序(按照每个切片中的第一个元素大小进行排序)
sort.Sort(r)
fmt.Printf("r: %v\n", r)
fmt.Println("--------------------------")
// 2、自定义排序二
// 创建结构体切片
studentArr := []Student{
Student{"张三", 18},
Student{"李四", 16},
Student{"王五", 20},
}
// 转成StudentSlice类型
students := StudentSlice(studentArr)
fmt.Printf("students: %v\n", students)
// 对切片中的student结构体按照age进行排序
sort.Sort(students)
fmt.Printf("students: %v\n", students)
}
// 按切片中每个元素中的第一个元素进行排序
r: [[1 2] [4 3] [2 4]]
r: [[1 2] [2 4] [4 3]]
--------------------------
// 按切片中每个student元素的年龄进行排序
students: [{张三 18} {李四 16} {王五 20}]
students: [{李四 16} {张三 18} {王五 20}]
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
// 原样输出
fmt.Printf("t: %v\n", t)
// 普通格式化输出
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", t.Year(), t.Month(),
t.Day(), t.Hour(), t.Minute(), t.Second())
fmt.Println("---------------------------------")
// 时间戳
fmt.Printf("time.Now().UnixMilli(): %v\n", time.Now().UnixMilli())
fmt.Println("---------------------------------")
// 1、日期格式化
// go语言中的格式化和其他语言不同,是采用具体的日期形式进行格式化。
// 采用的是go的开发日期。2006年1月2日 15时04分05秒 (记忆方式 2006 1 2 3 4 5)
fmt.Printf("t.Format(): %v\n", t.Format("2006-01-02 15:04:05")) // 24小时制
fmt.Println("---------------------------------")
// 2、字符串解析为日期
// 第一个参数:字符串格式
// 第二个参数:需要解析的字符串日期
t2, _ := time.Parse("2006-01-02 15:04:05", "2022-11-10 12:33:10")
// 带时区的解析,第三个参数为时区,默认是UTC
location, _ := time.LoadLocation("Asia/Shanghai")
t3, _ := time.ParseInLocation("2006-01-02 15:04:05", "2022-11-10 12:33:10", location)
fmt.Printf("t2: %v\n", t2)
fmt.Printf("t3: %v\n", t3)
}
t: 2022-11-16 16:37:08.7704565 +0800 CST m=+0.005225401
2022-11-16 16:37:08
---------------------------------
time.Now().UnixMilli(): 1668587828805
---------------------------------
t.Format(): 2022-11-16 16:37:08
---------------------------------
t2: 2022-11-10 12:33:10 +0000 UTC
t3: 2022-11-10 12:33:10 +0800 CST
用于对象的序列化,将内存中的对象转换为
json
字符串,也能够将json
字符串转换为内存中的对象。
package main
import (
"encoding/json"
"fmt"
"os"
)
// 定义结构体
// 结构体的字段名首字母必须大写,因为首字母小写代表私有字段,不能被json转换或者解析
type Student struct {
Id int `json:"id"` // 这里可以指定json格式化的字段名称
Name string `json:"name"`
Age int `json:"age"`
}
func structToJsonstring() {
student := Student{
Id: 1001,
Name: "张三",
Age: 20,
}
// 将结构体转换成json字符串,返回的是字节切片
b, _ := json.Marshal(student)
fmt.Printf("string(b): %v\n", string(b))
// 转换成json字符串,并且指定缩进格式
b, _ = json.MarshalIndent(student, "", " ")
fmt.Printf("string(b): %v\n", string(b))
}
func jsonstringToStruct() {
// json字符串
json_str := `{"id":1001,"name":"张三","age":20}`
// 转换成字节切片
b := []byte(json_str)
// 声明student对象
var student Student
fmt.Printf("student: %v\n", student)
// 将json字符串转换成结构体对象,赋值给student
json.Unmarshal(b, &student)
fmt.Printf("student: %v\n", student)
}
func writeToJsonFile() {
student := Student{
Id: 1001,
Name: "张三",
Age: 20,
}
// 将结构体转换成json字符串
// b, _ := json.Marshal(student)
b, _ := json.MarshalIndent(student, "", " ")
// 将json字符串写入文件
os.WriteFile("test.json", b, os.ModePerm)
}
func readFromJsonFile() {
// 读取json文件内容
b, _ := os.ReadFile("test.json")
var student Student
fmt.Printf("student: %v\n", student)
// 将json字符串解析成结构体,赋值给student
json.Unmarshal(b, &student)
fmt.Printf("student: %v\n", student)
}
func main() {
// 结构体转成json字符串
structToJsonstring()
// json字符串转换成结构体对象
jsonstringToStruct()
// 将结构体转成json字符串,写入文件
writeToJsonFile()
// 从json文件中读取内容,解析成结构体
readFromJsonFile()
}
控制台输出:
// 结构体对象转成json字符串
string(b): {"id":1001,"name":"张三","age":20}
string(b): {
"id": 1001,
"name": "张三",
"age": 20
}
// json字符串转成结构体对象
student: {0 0}
student: {1001 张三 20}
// 从json文件中读取json字符串,转成结构体对象
student: {0 0}
student: {1001 张三 20}
写入文件的内容:
{
"id": 1001,
"name": "张三",
"age": 20
}
用于对象的序列化,将内存中的对象转换为
xml
格式字符串,也能够将xml
格式字符串转换为内存中的对象。
package main
import (
"encoding/xml"
"fmt"
"os"
)
// 定义结构体
// 结构体的字段名首字母必须大写,因为首字母小写代表私有字段,不能被xml转换或者解析
type Student struct {
XMLName xml.Name `xml:"student"` // 指定xml格式化的根结点名称
Id int `xml:"id"` // 这里可以指定xml格式化的字段名称
Name string `xml:"name"`
Age int `xml:"age"`
}
func structToXmlstring() {
student := Student{
Id: 1001, Name: "张三", Age: 20}
// 将结构体转换成为xml字符串
b, _ := xml.Marshal(student)
fmt.Printf("string(b): %v\n", string(b))
// 将结构体转换成为xml字符串(带格式的)
b, _ = xml.MarshalIndent(student, "", " ")
fmt.Printf("string(b): %v\n", string(b))
}
func xmlstringToStruct() {
// xml字符串
xml_str := `1001 张三 20 `
// 字符串转成字节切片
b := []byte(xml_str)
// 声明student结构体
var student Student
fmt.Printf("student: %v\n", student)
// 将xml字符串解析成student结构体对象
xml.Unmarshal(b, &student)
fmt.Printf("student: %v\n", student)
}
func writeToXmlFile() {
student := Student{
Id: 1001, Name: "张三", Age: 20}
// 将结构体转换成为xml字符串(带格式的)
b, _ := xml.MarshalIndent(student, "", " ")
// 将xml字符串写入文件
os.WriteFile("test.xml", b, os.ModePerm)
}
func readFromXmlFile() {
// 读取xml字符串内容
b, _ := os.ReadFile("test.xml")
// 声明student结构体
var student Student
fmt.Printf("student: %v\n", student)
// 将xml字符串解析成student结构体对象
xml.Unmarshal(b, &student)
fmt.Printf("student: %v\n", student)
}
func main() {
// 将结构体转成xml字符串
structToXmlstring()
// 将xml字符串转成结构体
xmlstringToStruct()
// 将结构体转成xml字符串,写入文件
writeToXmlFile()
// 从文件中读取xml内容,解析成结构体
readFromXmlFile()
}
控制台输出:
// 结构体对象转成xml字符串
string(b): <student><id>1001</id><name>张三</name><age>20</age></student>
string(b):
<student>
<id>1001</id>
<name>张三</name>
<age>20</age>
</student>
// xml字符串转成结构体对象
student: {{ } 0 0}
student: {{ student} 1001 张三 20}
// 从xml文件中读取xml字符串,转成结构体对象
student: {{ } 0 0}
student: {{ student} 1001 张三 20}
写入文件的内容:
<student>
<id>1001id>
<name>张三name>
<age>20age>
student>
package main
import (
"fmt"
"math"
"math/rand"
)
func main() {
// 常规使用
fmt.Printf("math.Pi: %v\n", math.Pi)
fmt.Printf("math.MaxInt16: %v\n", math.MaxInt16)
fmt.Println("------------------------")
// 求余数
fmt.Printf("math.Mod(10, 3): %v\n", math.Mod(10, 3))
// 返回一个数的整数部分和小数部分
int2, frac := math.Modf(12.8)
fmt.Printf("int2: %v\n", int2)
fmt.Printf("frac: %v\n", frac)
fmt.Println("------------------------")
// 开平方根
fmt.Printf("math.Sqrt(16): %v\n", math.Sqrt(16))
// 开三次方根
fmt.Printf("math.Cbrt(27): %v\n", math.Cbrt(27))
// 求10的n次方
fmt.Printf("math.Pow10(2): %v\n", math.Pow10(2))
// 求x的y次方
fmt.Printf("math.Pow(2, 3): %v\n", math.Pow(2, 3))
fmt.Println("------------------------")
// 取上整
fmt.Printf("math.Ceil(10.2): %v\n", math.Ceil(10.2))
// 取下整
fmt.Printf("math.Floor(10.2): %v\n", math.Floor(10.2))
// 取整数部分
fmt.Printf("math.Trunc(10.2): %v\n", math.Trunc(10.2))
fmt.Println("------------------------")
// 设置随机种子
rand.Seed(10)
// 生成10以内的随机整数
fmt.Printf("rand.Intn(10): %v\n", rand.Intn(10))
// 生成0-1以内的随机浮点数
fmt.Printf("rand.Float32(): %v\n", rand.Float32())
}
math.Pi: 3.141592653589793
math.MaxInt16: 32767
------------------------
math.Mod(10, 3): 1
int2: 12
frac: 0.8000000000000007
------------------------
math.Sqrt(16): 4
math.Cbrt(27): 3
math.Pow10(2): 100
math.Pow(2, 3): 8
------------------------
math.Ceil(10.2): 11
math.Floor(10.2): 10
math.Trunc(10.2): 10
------------------------
rand.Intn(10): 4
rand.Float32(): 0.417652
Go语言中原生操作
MySQL
数据库的方法如下。另外,GORM
封装了MySQL
等多种数据库的快捷操作方式。这里主要学习了原生操作,ORM
框架以后有需要再学习。
操作前,需要先下载 MySQL
驱动,这里去 官网 下载驱动。
// 初始化项目模块
go mod init 项目名
// 删除项目中不需要的依赖,同时加入项目中需要的依赖到go.mod文件中
go mod tidy
// 下载MySQL驱动
go get -u github.com/go-sql-driver/mysql
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// "用户名:密码@tcp(ip:port)/数据库名"
// db, err := sql.Open("mysql", "root:root123@tcp(localhost:3306)/flea")
db, err := sql.Open("mysql", "root:root123@/test") // 可简写使用默认值
if err != nil {
log.Fatal(err)
}
// fmt.Printf("db: %v\n", db)
// db能够ping通,才表示连接没有问题
fmt.Printf("db.Ping(): %v\n", db.Ping())
if db.Ping() == nil {
log.Println("连接成功")
}
}
db.Ping(): <nil>
2022/11/16 14:20:19 连接成功
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
// 声明db指针变量
var db *sql.DB
// 初始化数据库连接
func initDB() (err error) {
// 尝试打开
db, err = sql.Open("mysql", "root:root123@/test")
if err != nil {
return err
}
// 尝试与数据库建立连接,检测url是否错误
err = db.Ping()
if err != nil {
return err
}
// 设置连接的时长
db.SetConnMaxLifetime(time.Minute * 5)
return nil
}
func insert(name string, age int) {
sql := "insert into user(id, name, age) values(default, ?, ?)"
// 执行插入语句,同时设置执行参数
r, err := db.Exec(sql, name, age)
if err != nil {
log.Fatal(err)
}
// 返回插入记录的ID
insertId, _ := r.LastInsertId()
fmt.Printf("insertId: %v\n", insertId)
// 返回受影响的行数
affectRows, _ := r.RowsAffected()
fmt.Printf("affectRows: %v\n", affectRows)
fmt.Println("-------------------------------")
}
func main() {
// 初始化数据库连接
err := initDB()
if err != nil {
log.Fatal(err)
}
// 执行插入语句
insert("李四", 21)
insert("王五", 20)
}
insertId: 8
affectRows: 1
-------------------------------
insertId: 9
affectRows: 1
-------------------------------
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
// 声明db变量
var db *sql.DB
// 初始化数据库连接
func initDB() (err error) {
// 尝试打开数据库
// 如果数据库中涉及到时间类型,则这里必须指定parseTime=True,否则不能映射到结构体中
db, err = sql.Open("mysql", "root:root123@/test?parseTime=True")
if err != nil {
return err
}
// 尝试与数据库建立连接,检测url是否错误
err = db.Ping()
if err != nil {
return err
}
// 设置连接的时长
db.SetConnMaxLifetime(time.Minute * 5)
return nil
}
// 声明一个和数据库表对应的结构体
type User struct {
id int
name string
age int
create_time time.Time
}
func queryById(id int) {
sql := "select * from user where id = ?"
// 执行查询语句,同时设置查询参数
r, err := db.Query(sql, id)
if err != nil {
log.Fatal(err)
}
// 声明user结构体对象,用来接收查询的数据
var user User
// 如果有数据,则循环,否则结束循环。按每一行数据来循环
for r.Next() {
// 将查询结果赋值给user结构体对象
err2 := r.Scan(&user.id, &user.name, &user.age, &user.create_time)
if err2 != nil {
log.Fatal(err2)
}
fmt.Printf("user: %v\n", user)
// 打印查询到的时间(格式化)
fmt.Printf("user.create_time: %v\n", user.create_time.Format("2006-01-02 15:04:05"))
}
fmt.Println("------------------------------------------")
}
func queryAll() {
sql := "select * from user"
// 执行查询语句
r, err := db.Query(sql)
if err != nil {
log.Fatal(err)
}
// 声明user结构体对象切片,用来接收查询的数据
userList := make([]User, 0)
// 如果有数据,则循环,否则结束循环。按每一行数据来循环
for r.Next() {
// 声明user结构体对象
var user User
// 将查询结果赋值给user结构体对象
err2 := r.Scan(&user.id, &user.name, &user.age, &user.create_time)
if err2 != nil {
log.Fatal(err2)
}
// 将结果添加至切片中
userList = append(userList, user)
}
fmt.Printf("userList: %v\n", userList)
fmt.Println("------------------------------------------")
}
func main() {
// 初始化数据库连接
err := initDB()
if err != nil {
log.Fatal(err)
}
// 根据ID查询记录
queryById(3)
// 查询全部记录
queryAll()
}
user: {3 张三 20 {0 63803868061 <nil>}}
user.create_time: 2022-11-12 16:41:01
------------------------------------------
userList: [{3 张三 20 {0 63803868061 <nil>}} {4 测试名称 20 {0 63803869193 <nil>}} {5 王五 20 {0 63803869193 <nil>}} {6 李四 21 {0 63803869256 <nil>}} {8 李四 21 {0 63804205485 <nil>}} {9 王五 20 {0 63804205485 <nil>}}]
------------------------------------------
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
// 声明db变量
var db *sql.DB
// 初始化数据库连接
func initDB() (err error) {
// 尝试打开
db, err = sql.Open("mysql", "root:root123@/test")
if err != nil {
return err
}
// 尝试与数据库建立连接,检测url是否错误
err = db.Ping()
if err != nil {
return err
}
// 设置连接的时长
db.SetConnMaxLifetime(time.Minute * 5)
return nil
}
func updateById(id int, name string, age int) {
sql := "update user set name = ?, age = ? where id = ?"
// 执行更新语句,同时设置执行参数
r, err := db.Exec(sql, name, age, id)
if err != nil {
log.Fatal(err)
}
// 返回受影响的行数
affectRows, _ := r.RowsAffected()
fmt.Printf("affectRows: %v\n", affectRows)
}
func main() {
// 初始化数据库连接
err := initDB()
if err != nil {
log.Fatal(err)
}
// 根据ID执行更新语句
updateById(4, "测试名称", 20)
}
affectRows: 1
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
// 声明db变量
var db *sql.DB
// 初始化数据库连接
func initDB() (err error) {
// 尝试打开
db, err = sql.Open("mysql", "root:root123@/test")
if err != nil {
return err
}
// 尝试与数据库建立连接,检测url是否错误
err = db.Ping()
if err != nil {
return err
}
// 设置连接的时长
db.SetConnMaxLifetime(time.Minute * 5)
return nil
}
func deleteById(id int) {
sql := "delete from user where id = ?"
// 执行删除语句,同时设置执行参数
r, err := db.Exec(sql, id)
if err != nil {
log.Fatal(err)
}
// 返回受影响的行数
affectRows, _ := r.RowsAffected()
fmt.Printf("affectRows: %v\n", affectRows)
}
func main() {
// 初始化数据库连接
err := initDB()
if err != nil {
log.Fatal(err)
}
// 根据ID删除对应的记录
deleteById(9)
}
affectRows: 1