0%

如果说 goroutinego 程序的并发体,那么通道 就是他们之间的桥梁。通道是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。通道类型本身就是并发安全的,这也是Go语言自带的、唯一一个可以满足并发安全性的类型。一个通道相当于一个先进先出(FIFO)的队列,也就是说,通道中的各个元素是严格按照发送的顺序排列的,别被发送至通道的元素一定会先被接收。元素值得发送和接收都需要用到操作符:<-

每一个通道是一个具体类型的导管,叫做通道的元素类型。一个有 int 类型元素的通道写为 chan int。使用内置的 make 函数来创建一个通道:

1
ch := make(chan int)

map一样,通道是一个使用 make 创建的数据结构的引用。当复制或者做为参数传递时,复制的是引用,这样调用者和被调用者都引用同一份数据结构。和其他引用类型一样,通道的零值是 nil。同种类型的通道可以使用 == 比较,当二者都是同一通道数据的引用时,比较值为 true,通道也可以和nil 进行比较。

通道有两个主要操作,发送(send)接收(receive),两者统称为通信。send 语句从一个goroutine传输一个值到另一个在执行接收表达式的goroutine。两个操作都使用 <- 操作符书写。发送语句中,通道和值分别在 <- 的左右两边。在接收表达式中,<-放在通道操作数的前面。在接收表达式中,其结果未被使用也是合法的。

1
2
3
ch <- x   // 发送语句
x = <- ch // 赋值语句中的接收表达式
<- ch // 接收语句,丢弃结果
阅读全文 »

散列表是设计精妙,用途广发的数据结构之一,它是一个拥有键值对元素的无需集合。在这个集合中,键的值是惟一的,键对应的值可以通过键来获取,更新或者移除。无论这个散列表多大,这些操作基本上都是在常量时间内就可以完成。

Go语言中,map 是散列表的应用,map 的类型是 map[K]V,其中 KV 是字典键和值对应的数据类型。map 中所有键都拥有相同的数据类型,同时所有值都拥有相同的数据类型,但是键的类型和值的类型不一定相同。键的类型K,必须是可以通过 == 操作符来进行比较的数据类型,K 的类型是受限的,V 的类型是任意的

深究一下,比如我们要在哈希表中查找与某个键值对应的那个元素值,那么我们需要先把键值作为参数传给这个哈希表。哈希表会先用哈希函数(hash function)把键值转换为哈希值。哈希值通常是一个无符号的整数。一个哈希表会持有一定数量的桶(bucket),也可称之为哈希桶,这些哈希桶会均匀地储存其所属哈希表收纳的那些键 - 元素对。因此,哈希表会先用这个键的哈希值的低几位去定位到一个哈希桶,然后再去这个哈希桶中,查找这个键。由于键 - 元素对总是被捆绑在一起存储的,所以一旦找到了键,就一定能找到对应的元素值。随后,哈希表就会把相应的元素值作为结果返回。只要这个键 - 元素对存在于哈希表中就一定会被查找到,因为哈希表增、改、删键 - 元素对时侯的映射过程,与前文所述如出一辙。

所以,K 的类型不能是函数类型,字典类型,切片类型,因为这些类型不支持 ==!= 操作符,换句话说,键值必须要支持判等操作,因为前面的三种类型不支持这两个操作,所以不能用作键。

阅读全文 »

List(container/list):双向链表

container/list 这个包公开了两个程序实体:ListElement,前者实现了一个双向链表,而后者则代表了双向链表的一个元素;

我们先看一下 Element 的声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Element is an element of a linked list.
type Element struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element

// The list to which this element belongs.
list *List

// The value stored with this element.
Value interface{}
}

nextprev 是两个 *Element 类型的指针,分别指向当前元素的前一个值和后一个值,list*List 类型的,代表了当前元素属于哪个链表,Value 则代表了当前元素中存储的值,它是一个 interface{} 类型,可以存储任何类型的数据。

阅读全文 »

数组

数组是 具有固定长度且拥有零个或者多个相同数据类型元素的序列,由于数组长度固定,所以在Go里面很少直接使用的。然而 slice 的长度可以增长和缩短,在很多场合下使用的更多。

数组中的元素是通过索引来访问的,索引从0到数组长度减1。Go内置函数 len 可以返回数组中元素个数。一般情况下,一个新数组中的元素初始值为元素类型的零值,对于数字来说就是0,当然也可以用 数组字面量 来初始化一个数组。

1
2
3
4
5
6
func main() {
var a [3]int
var b = [3]int{1, 2, 3} // 数组字面量
fmt.Println(a) // [0 0 0]
fmt.Println(b) // [1 2 3]
}

在数组字面量中,如果省略号...出现在数组长度的位置,那么数组的长度由其初始化元素的个数决定。所以上面的数组 b 可以简写为:

1
2
3
4
5
6
func main() {
var a [3]int
var b = [...]int{1, 2, 3}
fmt.Println(a) // [0 0 0]
fmt.Println(b) // [1 2 3]
}

要注意的是:数组的长度是数组类型的一部分,所以 [3]int[4]int 是两种不同的数组类型。数组的长度必须是常量表达式,也就是说必须在程序编译的时候就能确定。

阅读全文 »

从前我以为 SSH 只是为了登录到远程服务器,没想到它的端口转发这么强大,简直惊呆了我自己,首先我从今天遇到的问题先说起吧。我们服务器上跑了个数据库,当然它只配置了localhost访问,为了在不改变其他配置(新增用户,允许白名单域名访问)的情况下能访问数据库,该怎么办?答案是使用SSH 端口转发

例如,我的数据库运行在服务器:xinhuxx.com 上,那么现在本地建立端口转发:

ssh root@xinhuxx.com -L 53306:127.0.0.1:3306 -N -f

然后使用客户端连接数据库:

mycli -h 127.0.0.1 -P 53306 -u root -p root app

阅读全文 »

Docker可以通过从Dockerfile中读取指令自动构建镜像,Dockerfile是一个包含所有命令的文本文件,以便构建给定的镜像。Dockerfile遵循一定的文件格式并且使用一系列指定的命令。

本篇文章讲学习docker推荐的dockerfile书写✍️规范,可以参考Dockerfile Reference ,本节参考官方原文Dockerfile 最佳实践.

阅读全文 »

本篇文章不是讲docker原理以至于很多高深的概念,因为我不懂啊,我就是初学者,我就是想了解了解是个啥东西,看完之后能给自己给别人吹牛逼,我知道docker了,但是最后呢,再打一下自己的脸,对不起,我还不会用,哈哈哈,言归正传,我们说事情。

阅读全文 »