Golang 【basic_leaming】M 2022 1
阅读目录
待续
https://www.bilibili.com/video/BV1EF411h7Xq?p=13&vd_source=4118cfa937fa2cce11388788da88b7a0
闭包
实例 1
// 闭包
package main
import "fmt"
func func1() func(int) int {
sum := 0
return func(val int) int {
sum += val
return sum
}
}
func printFunc1() {
sunFunc := func1()
fmt.Println(sunFunc(1))
fmt.Println(sunFunc(1))
}
func main() {
printFunc1()
}
running...
1
2
实例 2
package main
import "fmt"
func func2() (val int) {
val = 10
defer func() {
val += 1
}()
return val
}
func printFunc2() {
fmt.Println(func2())
}
func main() {
printFunc2()
}
running...
11
实例 3
package main
import "fmt"
func func3() int {
val := 10
defer func() {
val += 1
}()
return 100
}
func printFunc3() {
fmt.Println(func3())
}
func main() {
printFunc3()
}
running...
100
goroutine
会出现什么状况,为什么,怎么解决?
// goroutine
package main
import (
"fmt"
"time"
)
// 题目描述:会出现什么状况,为什么,怎么解决?
func main() {
data := make(map[int]int, 10)
for i := 1; i <= 10; i++ {
data[i] = i
}
for key, value := range data {
go func() {
fmt.Println("k->", key, "v->", value)
}()
}
time.Sleep(time.Second * 5)
}
每次运行都不一样,是一个随机的。
running...
k-> 6 v-> 6
k-> 6 v-> 6
k-> 6 v-> 6
k-> 7 v-> 7
k-> 6 v-> 6
k-> 6 v-> 6
k-> 5 v-> 5
k-> 6 v-> 6
k-> 6 v-> 6
k-> 6 v-> 6
解决变量没有刷新方式 1
go 栈中的原因,因为变量没有刷新。
// goroutine
package main
import (
"fmt"
"time"
)
// 题目描述:会出现什么状况,为什么,怎么解决?
func main() {
data := make(map[int]int, 10)
for i := 1; i <= 10; i++ {
data[i] = i
}
for key, value := range data {
// 采用外部变量方式
key := key
value := value
go func() {
fmt.Println("k->", key, "v->", value)
}()
}
time.Sleep(time.Second * 5)
}
running...
k-> 8 v-> 8
k-> 6 v-> 6
k-> 1 v-> 1
k-> 2 v-> 2
k-> 4 v-> 4
k-> 7 v-> 7
k-> 3 v-> 3
k-> 5 v-> 5
k-> 9 v-> 9
k-> 10 v-> 10
解决变量没有刷新方式 2
// goroutine
package main
import (
"fmt"
"time"
)
// 题目描述:会出现什么状况,为什么,怎么解决?
func main() {
data := make(map[int]int, 10)
for i := 1; i <= 10; i++ {
data[i] = i
}
for key, value := range data {
go func(key, value int) {
fmt.Println("k->", key, "v->", value)
}(key, value)
}
time.Sleep(time.Second * 5)
}
running...
k-> 5 v-> 5
k-> 9 v-> 9
k-> 7 v-> 7
k-> 8 v-> 8
k-> 4 v-> 4
k-> 6 v-> 6
k-> 10 v-> 10
k-> 2 v-> 2
k-> 3 v-> 3
k-> 1 v-> 1
goroutine 栈
go-v1(最小栈空间为4kB)
go-v1.2(最小栈空间为8kB)
go-v1.3(分段栈替换为连续栈)
go-v1.4(最小栈空间为2kB)
go-v1(最小栈空间为4kB)
go-v1.2(最小栈空间为8kB)
go-v1.3(分段栈替换为连续栈)
go-v1.4(最小栈空间为2kB)
堆与栈的区别?
栈简介
栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。
堆简介
堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表。
defer 栈
实例 1
/*
defer
*/
package main
import "fmt"
func main() {
m := 10
defer fmt.Printf("first defer %d\n", +m)
m = 100
defer fmt.Printf("second defer %d\n", m)
}
running...
second defer 100
first defer 10
实例 2
/*
*/
package main
import "fmt"
func main() {
m := 10
defer fmt.Printf("first defer %d\n", +m)
m = 100
defer func() {
fmt.Printf("second defer %d\n", m)
}()
m += 10
defer fmt.Printf("third defer %d\n", m)
funcVal := func1()
funcVal()
}
func func1() func() {
fmt.Println("before return")
return func() {
defer fmt.Println("in the return")
}
}
running...
before return
in the return
third defer 110
second defer 110
first defer 10
实例 3
package main
import "fmt"
func main() {
defer fmt.Printf("main %d\n", func2())
}
func func2() (sum int) {
sumA := 100
sumB := 100
sum = sumA + sumB
defer func() {
fmt.Printf("func2 first %d\n", sum)
}()
defer fmt.Printf("second first %d\n", sum)
return sum * 10
}
running...
second first 200
func2 first 2000
main 2000
slice 扩容
实例 1
package main
import "fmt"
func main() {
var arr1 []int64
for i := 0; i < 1025; i++ {
arr1 = append(arr1, int64(i))
}
fmt.Printf("len=%d\n cap=%d\n", len(arr1), cap(arr1))
}
running...
len=1025
cap=1280
实例 2
package main
import "fmt"
func main() {
var arr1 []int64
var arr2 []int64
for i := 0; i < 1025; i++ {
arr2 = append(arr2, int64(i))
}
fmt.Printf("len=%d\n cap=%d\n", len(arr2), cap(arr2))
arr1 = append(arr1, arr2...)
fmt.Printf("len=%d\n cap=%d\n", len(arr1), cap(arr1))
}
running...
len=1025
cap=1280
len=1025
cap=1184
nil 是否一定相等?
答案是不一定。
package main
import "fmt"
func main() {
var x *int = nil
var y interface{} = x
fmt.Println(x == y)
fmt.Println(x == nil)
fmt.Println(y == nil)
}
running...
true
true
false
并发测试
并发测试题 - 1
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var chain string
func main() {
chain = "main"
A()
fmt.Println(chain)
}
func A() {
mu.Lock()
defer mu.Unlock()
chain = chain + " --> A"
B()
}
func B() {
chain = chain + " --> B"
C()
}
func C() {
mu.Lock()
defer mu.Unlock()
chain = chain + " --> C"
}
A 不能编译
B 输出 main --> A --> B --> C
C 输出 main
D panic
D panic
running...
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0x10?, 0x6?, 0x6?)
D:/Golang/src/runtime/sema.go:77 +0x25
sync.(*Mutex).lockSlow(0xa87488)
D:/Golang/src/sync/mutex.go:171 +0x165
sync.(*Mutex).Lock(...)
D:/Golang/src/sync/mutex.go:90
main.C()
E:/golang/src/main.go:30 +0x51
main.B(...)
E:/golang/src/main.go:27
main.A()
E:/golang/src/main.go:23 +0x10a
main.main()
E:/golang/src/main.go:16 +0x54
D panic 死锁问题
死锁问题
fatal error: all goroutines are asleep - deadlock!
互斥锁、不可重入锁
在一个goroutine下,重复上锁的话,就会发生 panic。
package main
import "sync"
var mu sync.Mutex
func main() {
mu.Lock()
mu.Lock()
}
running...
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0xc00001a060?, 0x0?, 0xc000044000?)
D:/Golang/src/runtime/sema.go:77 +0x25
sync.(*Mutex).lockSlow(0x23b040)
D:/Golang/src/sync/mutex.go:171 +0x165
sync.(*Mutex).Lock(...)
D:/Golang/src/sync/mutex.go:90
main.main()
E:/golang/src/main.go:12 +0x5f
/*
并发测试题 - 1
*/
package main
import (
"fmt"
"sync"
"time"
)
var mu sync.Mutex
func main() {
mu.Lock()
go func() {
mu.Lock()
fmt.Println("I'm OK!!!")
defer mu.Unlock()
}()
mu.Unlock()
time.Sleep(time.Second)
return
}
running...
I'm OK!!!
如何调用 panic
package main
func proc() {
panic("ok")
}
func main() {
go func() {
// 1 在这里需要你算法
// 2 要求每秒钟调用一次 proc 函数
// 3 要求程序不能退出
}()
select {}
}
running...
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
E:/golang/src/main.go:17 +0x2a
实例 1
package main
import (
"fmt"
"time"
)
func proc() {
panic("ok")
}
func main() {
go func() {
// 1 在这里需要你算法
// 2 要求每秒钟调用一次 proc 函数
// 3 要求程序不能退出
t := time.NewTicker(time.Second)
// fmt.Println(time.Now())
for {
select {
case <-t.C:
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
fmt.Println(time.Now())
proc()
}()
}
}
}()
select {}
}
running...
2022-12-29 17:12:04.1589622 +0800 CST m=+1.011997801
ok
2022-12-29 17:12:05.1567679 +0800 CST m=+2.009803501
ok
2022-12-29 17:12:06.1537221 +0800 CST m=+3.006757701
ok
2022-12-29 17:12:07.1562807 +0800 CST m=+4.009316301
...
算法
LRU 缓存
设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。
缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。
当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
package main
// 双线链表
type DLinkNode struct {
key, val int
pre, next *DLinkNode
}
type LRUCache struct {
size, capacity int
cache map[int]*DLinkNode
head, tail *DLinkNode
}
func initDLinkNode(key, val int) *DLinkNode {
return &DLinkNode{key: key, val: val}
}
func Constructor(capacity int) LRUCache {
l := LRUCache{
capacity: capacity,
size: 0,
cache: map[int]*DLinkNode{},
head: initDLinkNode(0, 0),
tail: initDLinkNode(0, 0),
}
l.head.next = l.tail
l.tail.pre = l.head
return l
}
// 移除节点
func removeNode(node *DLinkNode) {
node.pre.next = node.next
node.next.pre = node.pre
}
// 移除尾部节点
func (this *LRUCache) removeTail() *DLinkNode {
node := this.tail.pre
removeNode(node)
return node
}
// 添加数据
func (this *LRUCache) addHead(node *DLinkNode) {
node.next = this.head.next
node.pre = this.head
this.head.next.pre = node
this.head.next = node
}
// 移动到头部
func (this *LRUCache) moveToHead(node *DLinkNode) {
removeNode(node)
this.addHead(node)
}
func (this *LRUCache) Get(key int) int {
if node, ok := this.cache[key]; ok {
this.moveToHead(node)
return node.val
}
return -1
}
func (this *LRUCache) Put(key int, value int) {
if node, ok := this.cache[key]; ok {
node.val = value
this.moveToHead(node)
} else {
newNode := initDLinkNode(key, value)
this.addHead(newNode)
this.cache[key] = newNode
this.size++
if this.size > this.capacity {
tail := this.removeTail()
this.size--
delete(this.cache, tail.key)
}
}
}
func main() {
}
无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释:
因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释:
因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释:
因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
提示:
- 0 <= s.length <= 5 * 104
- s 由英文字母、数字、符号和空格组成。
package main
import (
"fmt"
"strings"
)
func lengthOfLongestSubstring(s string) int {
start := 0
end := 0
for i := 0; i < len(s); i++ {
index := strings.Index(s[start:i], string(s[i]))
if index == -1 && (i+1) > end {
end = i + 1
} else {
start += index + 1
end += index + 1
}
}
return end - start
}
func main() {
num := lengthOfLongestSubstring("pwwkew")
fmt.Println(num)
}
building...
running...
3
快速排序
package main
import "fmt"
func QuickSort(nums []int) []int {
if len(nums) <= 1 {
return nums
}
low := make([]int, 0, 0)
high := make([]int, 0, 0)
mid := make([]int, 0, 0)
flag := nums[0]
mid = append(mid, flag)
for i := 1; i < len(nums); i++ {
if nums[i] < flag {
low = append(low, nums[i])
} else if nums[i] > flag {
high = append(high, nums[i])
} else {
mid = append(mid, nums[i])
}
}
low, high = QuickSort(low), QuickSort(high)
return append(append(low, mid...), high...)
}
func main() {
fmt.Println(QuickSort([]int{5, 2, 3, 9, 1, 4, 8, 7}))
}
running...
[1 2 3 4 5 7 8 9]
反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
进阶:链表可以选用迭代或递归方式完成反转。
你能否用两种方法解决这道题?
利用两个切片实现
设计思想:
1、确定切片长度。
2、获取最后一个元素。
3、以相反的顺序在新切片中添加最后一个元素到第一个位置。
package main
import "fmt"
func main() {
s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}
// 定义新的反转切片
reverseOfS := []string{}
// 遍历原切片 s
for i := range s {
fmt.Println(i)
reverseOfS = append(reverseOfS, s[len(s)-1-i])
}
fmt.Println(reverseOfS)
}
运行结果:
running...
0
1
2
3
4
5
[zzz abc go bar foo hello]
显然,这种方式会额外花费一个相同空间的切片,空间复杂度为 O(n)。
前后两两原地交换
我们可以写一个简易的 reverse 函数来进行数据的反转,通过循环原切片的一半,然后依次与对应的元素进行交换,比如::
package main
import "fmt"
func reverse(s []string) []string {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
func main() {
s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}
reverseOfS := reverse(s)
fmt.Println(reverseOfS)
}
执行结果:
running...
[zzz abc go bar foo hello]
但是,上面的 reverse 函数都是通过切片按值传递,其实我们在修改传递中的 []string 切片,实际上,可以通过以下方式进一步简写:
package main
import (
"fmt"
)
func reverse(s []string) {
for i := 0; i < len(s)/2; i++ {
// fmt.Println(len(s))
// os.Exit(11)
j := len(s) - i - 1
fmt.Println(j)
s[i], s[j] = s[j], s[i]
}
}
func main() {
s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}
reverse(s)
fmt.Printf("%v\n", s)
}
building...
running...
5
4
3
[zzz abc go bar foo hello]
反转为原切片的副本
package main
import (
"fmt"
)
func reverse(s []string) []string {
newS := make([]string, len(s))
for i, j := 0, len(s)-1; i <= j; i, j = i+1, j-1 {
// fmt.Println(i, j) 0 5
// os.Exit(1)
newS[i], newS[j] = s[j], s[i]
}
return newS
}
func main() {
s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}
fmt.Printf("原字符串切片:%v\n", s)
fmt.Printf("反转后的切片:%v\n", reverse(s))
}
unning...
原字符串切片:[hello foo bar go abc zzz]
反转后的切片:[zzz abc go bar foo hello]
可以看到,原切片是没有变化的。
当然,因为我们没有就地修改原切片,因此又可以回到最初的方法 append,看代码:
func reverse(s []string) []string {
newS := make([]string, 0, len(s))
for i := len(s)-1; i >= 0; i-- {
newS = append(newS, s[i])
}
return newS
}
Go语言结构体(struct)
package main
import "fmt"
type Node struct {
data interface{}
next *Node
}
// 反转单链表
func (head *Node) reverse() *Node {
// 空链表
if head == nil {
return nil
}
// 反转后单链表的头结点
var reverseHead *Node
var preNode *Node
curNode := head
for curNode != nil {
nextNode := curNode.next
if nextNode == nil {
// 尾结点转换为头结点
reverseHead = curNode
}
// 反转实现,当前结点的前驱结点变成后驱结点
curNode.next = preNode
// 设置下一个结点的前驱结点
preNode = curNode
curNode = nextNode
}
// 返回反转后的头结点
return reverseHead
}
// 遍历单链表
func (head *Node) traverse() {
node := head
for node != nil {
fmt.Printf("%v ", node.data)
node = node.next
}
}
func main() {
first := &Node{data: 1}
second := &Node{data: 2}
third := &Node{data: 3}
first.next = second
second.next = third
head := first
fmt.Print("反转前: ")
head.traverse()
fmt.Println()
newHead := head.reverse()
fmt.Print("反转后: ")
newHead.traverse()
fmt.Println()
}
running...
反转前: 1 2 3
反转后: 3 2 1
Golang 递归实现链表反转
package main
import "fmt"
type List struct {
value string
next *List
}
func main() {
// 头指针
var head *List
// 尾指针
var last *List
for i := 'A'; i < 'Z'+1; i++ {
// 记录尾部的指针
last = GenerateList(last, string(i))
if head == nil {
// 记录头部指针
head = last
}
}
fmt.Print("反转前的链表:")
show(head)
Recursion(head)
fmt.Print("反转后的链表:")
show(last)
fmt.Println("")
}
// 递归反转
func Recursion(l *List, parme ...int) *List {
// 第一次调用,不能传参
if len(parme) == 0 {
parme = []int{0}
}
// 节点不存在返回空,也就是最后一个节点的next值
if l == nil {
return nil
}
// 通过递归,获取到下一个节点的值
next := Recursion(l.next, parme[0]+1)
// 最后一个则返回当前节点
if next == nil {
return l
}
// 反转接,将下一个节点指向上一个节点
next.next = l
if parme[0] == 0 {
l.next = nil
}
// 返回当前节点
return l
}
// 生成链表值
func GenerateList(l *List, value string) *List {
// 头链表直接返回
if l == nil {
return &List{value: value}
}
l.next = &List{value: value}
return l.next
}
// 显示链表的值
func show(l *List) {
for l != nil {
fmt.Print(l.value)
if l.next != nil {
fmt.Print("->")
}
l = l.next
}
fmt.Println()
}
running...
反转前的链表:A->B->C->D->E->F->G->H->I->J->K->L->M->N->O->P->Q->R->S->T->U->V->W->X->Y->Z
反转后的链表:Z->Y->X->W->V->U->T->S->R->Q->P->O->N->M->L->K->J->I->H->G->F->E->D->C->B->A
内存对齐
type DemoA struct {
A int8
B int64
C int16
}
type DemoB struct {
A int8
C int16
B int64
}
这两个结构体占用的内存大小一样吗?
func main() {
fmt.Println(unsafe.Sizeof(DemoA{}))
fmt.Println(unsafe.Sizeof(DemoB{}))
}
running...
24
16
计算机存储数据都是以字节为单位来划分的,但是大部分处理器(CPU)都不是一个字节一个字节来存取内存的。
一般都是以 2 、 4 、8 、16 、32 这种2的幂次方来存取数据的。
为了让处理器的读取更加高效,程序在编译的时候一般会安装处理器一次存储的字节数的倍数来存储数据。
如果没有内存对齐的话如下图:
计算机需要读取两次内存才可以将数据读取出来,如果进行了内存对齐,那么只需要一次就可以将数据读出。并且如果是单次读取,可以一次就完成这个原子操作,不会被其他操作或打断。
代码解释 DemoA
- 第一个元素 int8 是一个字节,填充7字节。
- 第二个元素 int64 是 8 字节。
- 第三个元素 int16 是 2 字节,填充 6 字节。
加起来就是 24 字节。
代码解释 DemoB
- 第一个元素 int8 与第二个元素加起来是3字节,填充5字节,加上第三个元素8字节,所以是16字节。
反转字符串
反转字符串,可能包含有中文、数字、英文。
(要求反转原有字符串,不创建新的存储空间)
package main
import "fmt"
func reverse(str string) string {
s := []rune(str)
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return string(s)
}
func main() {
fmt.Println(reverse("知其黑,受其白CBA234"))
}
running...
432ABC白其受,黑其知
协程顺序执行问题
使用三个协程,每秒钟打印 cat、dog、fish。
顺序不能变化(协程 1 打印 cat、协程 2 打印 dog、协程 3 打印 dish)
无限循环即可。
package main
import (
"fmt"
"sync"
"time"
)
func cat(fishCH, catCH chan struct{}, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
for {
fmt.Println("cat")
catCH <- struct{}{}
<-fishCH
}
wg.Done()
}()
}
func dog(catCH, dogCH chan struct{}, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
for {
<-catCH
fmt.Println("dog")
dogCH <- struct{}{}
}
wg.Done()
}()
}
func fish(dogCH, fishCH chan struct{}, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
for {
<-dogCH
fmt.Println("fish")
time.Sleep(time.Second)
fishCH <- struct{}{}
}
wg.Done()
}()
}
func main() {
catCH := make(chan struct{})
dogCH := make(chan struct{})
fishCH := make(chan struct{})
wg := sync.WaitGroup{}
cat(fishCH, catCH, &wg)
dog(catCH, dogCH, &wg)
fish(dogCH, fishCH, &wg)
wg.Wait()
}
tag 的原理
tag 的原理其实就是反射。
package main
import (
"fmt"
"reflect"
)
type UserInfo struct {
Name string `csdn:"BLOG" abc:"name"`
PublicWX string `csdn:"BLOG_PUBLIC" abc:"publicABC"`
}
func PrintTag(ptr interface{}) {
reType := reflect.TypeOf(ptr)
if reType.Kind() != reflect.Ptr || reType.Elem().Kind() != reflect.Struct {
panic("传入的参数不是结构体指针")
return
}
v := reflect.ValueOf(ptr).Elem()
fmt.Println(v)
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
tag := field.Tag
labelTag := tag.Get("csdn")
fmt.Println(labelTag)
}
}
func main() {
userInfo := &UserInfo{
Name: "博客",
PublicWX: "csdn",
}
PrintTag(userInfo)
}
running...
{博客 csdn}
BLOG
BLOG_PUBLIC
相关文章
- 一键解决 go get golang.org/x 包失败
- Golang 不分配内存的指针类型能用吗?
- Golang 【basic_leaming】go mod 以及包详解
- Golang学习教程
- golang map to struct
- [转] golang中struct、json、map互相转化
- GoLang 最佳实践超全版【一】
- 使用 GoLang 获取 TLS 的 Client Hello Info
- centos 安装golang 1.14.3
- 【GoLang】1.1 第一个go程序
- grpc(3):使用 golang 开发 grpc 服务端和客户端
- docker(9):使用alpinelinux 构建 golang http 启动了,才15mb
- Golang 基于excelize实现Excel表格的解析、导出
- Golang 中三个点...的作用
- leetcode 1. 两数之和 (Golang)
- golang(5):编写WebSocket服务,客户端和html5调用
- 使用golang学习算法(1)-排序
- 采用golang对RDS内部openapi进行封装
- Golang 中三种读取文件发放性能对比