目录

golang语法问题

使用值为 nil 的 slice、map会发生啥

允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map 添加元素,则会造成运行时 panic。

func main() {
	var m map[string]int
	v, ok := m["one"] // nil map读没有问题
	delete(m, "one") // nil map删除元素没有问题
	m["one"] = 1 // error: panic: assignment to entry in nil map

	var s []int
	s = append(s, 1) // nil slice追加元素没有问题
	s[0] = 3          // nil slice写元素报错 index out of range [0] with length 0
	fmt.Println(s[0]) // nil slice读元素报错 index out of range [0] with length 0
}

nil channel注意事项


func main() {
	var ch chan int
	close(ch) // 关闭nil通道,引发panic: close of nil channel
	go func() {
		ch <- 3 // nil channel会阻塞对该channel的所有读、写。但不报错
	}()
	
	ch := make(chan int, 0)
	close(ch)
	close(ch) // 重复关闭引发panic: close of closed channel

	ch := make(chan int, 0)
	close(ch)
	<-ch    // 关闭后读数据没问题
	ch <- 2 // 关闭后发数据 panic: send on closed channel
}

主动关闭过http连接,为啥要这样做

不关闭会程序可能会消耗完 socket 描述符。

Golang的net包中client.go, transport.go, response.go和request.go这几个文件中实现了HTTP Client。当应用层调用client.Do()函数后,transport层会首先找与该请求相关的已经缓存的连接(这个缓存是一个map,map的key是请求方法、请求地址和proxy地址,value是一个叫persistConn的连接描述结构),如果已经有可以复用的旧连接,就会在这个旧连接上发送和接受该HTTP请求,否则会新建一个TCP连接,然后在这个连接上读写数据。当client接受到整个响应后,如果应用层没有 调用response.Body.Close()函数,刚刚传输数据的persistConn就不会被加入到连接缓存中,这样如果您在下次发起HTTP请求的时候,就会重新建立TCP连接,重新分配persistConn结构,这是不调用response.Body.Close()的一个副作用。

如果不调用response.Body.Close()还存在一个问题。如果请求完成后,对端关闭了连接(对端的HTTP服务器向我发送了FIN),如果这边不调用response.Body.Close(),那么可以看到与这个请求相关的TCP连接的状态一直处于CLOSE_WAIT状态(还记得么?CLOSE_WAIT是连接的半开半闭状态,它是收到对方的FIN并且我们也发送了ACK,但是本端还没有发送FIN到对端,如果本段不调用close关闭连接,那么连接将一直处于CLOSE_WAIT状态,不会被系统回收)。

调用了response.Body.Close()就万无一失了么?上面代码中也调用了body.Close()为什么还会有很多ESTABLISHED状态的连接呢?因为在函数DoRequest()的每次调用中,我们都会新创建transport和client结构,当HTTP请求完成并且接收到响应后,如果对端的HTTP服务器没有关闭连接,那么这个连接会一直处于ESTABLISHED状态。如何解呢? 有两个方法: 第一个方法是用一个全局的client,函数DoRequest()中每次都只在这个全局client上发送数据。但是如果我就想用短连接呢?用方法二。 第二个方法是在transport分配时将它的DisableKeepAlives参数置为false

解析 JSON 数据时,默认将数值当做哪种类型

在 encode/decode JSON 数据时,Go 默认会将数值当做 float64 处理。

func main() {
     var data = []byte(`{"status": 200}`)
     var result map[string]interface{}

     if err := json.Unmarshal(data, &result); err != nil {
     log.Fatalln(err)
}

解析出来的 200 是 float 类型。

defer 调用时多层嵌套依然无效。

func main() {
    defer func() {
        func() { recover() }()
    }()
    panic(1)
}

在循环内部执行defer语句会发生啥

defer 在函数退出时才能执行,在 for 执行 defer 会导致资源延迟释放。func 是一个局部函数,在局部函数里面执行 defer 将不会有问题。可以这样:

func main() {
    for i := 0; i < 5; i++ {
        func() {
            f, err := os.Open("/path/to/file")
            if err != nil {
                log.Fatal(err)
            }
            defer f.Close()
        }()
    }
}

如何跳出for select 循环

通常在for循环中,使用break可以跳出循环,但是注意在go语言中,for select配合时,break 并不能跳出循环。可以使用标签。