基本语法
变量初始化
1
2
3
| var s string = "string"
var s = "string"
s := "string"
|
二维切片初始化
1
2
3
4
| slice1 := make([][]bool, m)
for i := range slice1 {
slice1[i] = make([]bool, n)
}
|
变量自增
只有后缀自增或自减,并且必须单独一行(除了在range语句中)。
条件判断语句中的初始变量可以在布尔表达式里,并且不能使用0/1作为判断条件。
1
2
3
| if cond := true; cond {
fmt.Println()
}
|
随机数
生成 [0, 99] 的随机数
1
2
3
| rand.Seed(time.Now().UnixNano())
r := rand.Intn(100)
fmt.Println(r)
|
栈/队列
Go 语言中的栈和队列都是基于切片实现的。
1
2
3
4
5
6
7
8
9
10
11
| stack := make([]int, 0) //创建切片
stack = append(stack, 10) //push压入栈
value := stack[len(stack)-1] // 取栈顶的值
stack = stack[:len(stack)-1] //pop弹出
len(stack) == 0 //检查栈空
queue := make([]int, 0) //创建切片
queue = append(queue, 10) //enqueue入队
v := queue[0] // 取队首的值
queue = queue[1:] //dequeue出队
len(queue) == 0 //检查队列为空
|
合并切片
1
2
3
4
| slice1 := []string{"Moe", "Larry", "Curly"}
slice2 := []string{"php", "golang", "java"}
slice3 = append(slice1, slice2...)
fmt.Println(slice3)//[Moe Larry Curly php golang java]
|
整数和字符串转换
1
2
3
4
5
6
7
8
9
10
11
| // string to int, "99" -> 99
int, err := strconv.Atoi(str)
// asscii to string, 97 -> "a"
str := string(asscii) // asscii to string
// int to string, 10 -> "10"
str := fmt.Sprintf("%d", int)
str := strconv.Itoa(int)
str := strconv.FormatInt(int, 10)
|
Range 用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 数组
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
// Map
for key, value := range oldMap {
newMap[key] = value
}
for key := range oldMap {
fmt.Println(oldMap[key])
}
for _, value := range oldMap {
fmt.Println(value)
}
// 字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range "go" {
fmt.Println(i, c, string(c)) //0 103 g \n 1 111 o
}
|
最值
1
2
3
4
5
6
7
8
9
10
11
| fmt.Printf("int min - max: %d - %d\n", math.MinInt, math.MaxInt) // int
fmt.Printf("int8 min - max: %d - %d\n", math.MinInt8, math.MaxInt8) // int8
fmt.Printf("int16 min - max: %d - %d\n", math.MinInt16, math.MaxInt16) // int16
fmt.Printf("int32 min - max: %d - %d\n", math.MinInt32, math.MaxInt32) // int32
fmt.Printf("int64 min - max: %d - %d\n", math.MinInt64, math.MaxInt64) // int64
// unsigned
fmt.Printf("uint min - max: %d - %d\n", uint(0), uint(math.MaxUint)) // uint
fmt.Printf("uint8 min - max: %d - %d\n", 0, math.MaxUint8) // uint8
fmt.Printf("uint16 min - max: %d - %d\n", 0, math.MaxUint16) // uint16
fmt.Printf("uint32 min - max: %d - %d\n", 0, math.MaxUint32) // uint32
fmt.Printf("uint64 min - max: %d - %d\n", 0, uint64(math.MaxUint64)) // uint64
|
排序
1
2
3
4
5
6
7
8
9
10
11
12
| // int
array := []int{3, 1, 34, 25, 14, 5}
sort.Ints(array) // 升序
fmt.Println(array)
sort.Sort(sort.Reverse(sort.IntSlice(array))) // 降序
fmt.Println(array)
// string
array1 := []string{"ss", "sa", "avs", "vs"}
sort.Strings(array1)
fmt.Println(array1)
sort.Sort(sort.Reverse(sort.StringSlice(array1)))
fmt.Println(array1)
|
自定义排序
1
2
3
4
5
6
7
8
| array := []int{3, 17, 1, 34, 25, 14, 5}
sort.Slice(array, func(i, j int) bool {
if abs(array[i]-9) != abs(array[j]-9) {
return abs(array[i]-9) < abs(array[j]-9)
}
return array[i] < array[j]
})
fmt.Printf("%+v", array)
|
双向列表
1
2
3
4
| doubleLinkedList := list.New()
doubleLinkedList.PushBack(3)
var doubleLinkedListElement *list.Element
doubleLinkedListElement = doubleLinkedList.Back()
|
Map 用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| m := make(map[string]int) // 初始化
m := map[string]int{ // 使用组合字面量初始化
"apple": 1,
"banana": 2,
"orange": 3,
}
v1 := m["apple"] // 获取键值对
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
for k, v := range m { // 遍历Map,无序的
fmt.Printf("key=%s, value=%d\n", k, v)
}
delete(m, "banana") // 删除元素
m := make(map[string]int) // 清空元素的方法就是重新make一个map
// 基于 Map 实现 Set
set := make(map[string]bool)
set["Foo"] = true
for k := range set {
fmt.Println(k)
}
delete(set, "Foo")
size := len(set)
exists := set["Foo"]
|
堆的用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| package main
import (
"container/heap"
"fmt"
"sort"
)
type heap2 struct {
sort.IntSlice
}
func (h heap2) Less(i, j int) bool { // 最小堆:h.IntSlice[i] < h.IntSlice[j];最大堆:h.IntSlice[i] > h.IntSlice[j];
return h.IntSlice[i] > h.IntSlice[j]
}
func (h *heap2) Push(v interface{}) {
h.IntSlice = append(h.IntSlice, v.(int))
}
func (h *heap2) Pop() interface{} {
temp := h.IntSlice
v := temp[len(temp)-1]
h.IntSlice = temp[:len(temp)-1]
return v
}
func (h *heap2) push(v int) {
heap.Push(h, v)
}
func (h *heap2) pop() int {
return heap.Pop(h).(int)
}
func main() {
q := &heap2{[]int{3, 4, 1, 2, 4, 3}}
heap.Init(q)
q.pop()
fmt.Println(len(q.IntSlice))
fmt.Println(q)
}
|
知识点
数组和切片
- 数组在内存中是一段连续的内存空间,元素的类型和长度都是固定的;
- 切片在内存中由一个指向底层数组的指针、长度和容量组成的,长度表示当前包含的元素个数,容量表示切片可以拓展的最大元素个数。切片
s[x: y]
表示 s
中第 x
位到第 y - 1
位元素截取。
协程/线程/进程的区别
进程:是具有一定独立功能的程序,是系统资源分配和调度的最小单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。适合计算密集型的任务,考虑可以使用多核 CPU,使用多进程。
线程:是进程的一个实体,是内核态,而且是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。适合 I/O 密集型,且 I/O 请求比较快的任务。
协程:是一种用户态的轻量级线程,拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。或者说,协程能保留上一次调用时的状态。并且协程的默认占用内存远比其他语言的线程要少。goroutine:2KB(官方),线程:8MB(参考网络)。适合 I/O 密集型,且 I/O 请求比较耗时的任务。
Q: 为什么进程切换比线程切换代价大,效率更低?
A: 每次进程切换时,都会涉及页表的切换,切换页表这个操作本身是不太耗时的,但在切换之后,TLB(页表缓存/快表)就失效了,所以在进行地址转化时就需要重新去查找页表,这就造成了程序运行的效率低下。 而同一个进程的线程之间是共用一个页表的,所以线程之间的切换是不需要切换页表的,因此线程切换不存在上述代价大,效率低的问题。
Go 和 Java 的区别
- 语言特性:
- Java 面向对象,静态语言,有完整的继承体系,不支持多继承,值传递
- Go 虽然是静态类型语言,但具有动态类型语言的鸭子类型(基于 interface 实现),支持多继承,值传递
- 并发模型:
- Java 的并发基于线程和锁
- Go 的并发基于协程和通道,可以实现轻量级的并发
- 性能
- Java 需要通过虚拟机,可跨平台,从字节码编译成机器码
- Go 的效率高,因为没有虚拟机,直接编译成机器码
Byte 和 Rune 类型
- byte是uint8的别称,一个值就是一个ASICII码的值,占 1 个字节的英文类字符,使用 byte
- rune是int32的别称,一个值就是一个Unicode字符,占 1 ~ 4 个字节的其他字符,可以使用rune(或者int32),如中文、特殊符号等。
内存对齐
好处
- 平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据
- 提高访问效率:考虑到 CPU 是分块读取数据的,假设一次性读取4字节的数据,如果不进行内存对齐,则需要多次读取内存中的数据,再进行剔除等操作,极大降低CPU性能。