mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 14:28:58 +00:00
make loop
This commit is contained in:
parent
77148f84d4
commit
ba7261d140
@ -11,8 +11,8 @@
|
|||||||
* [1.2. 命令行參數](ch1/ch1-02.md)
|
* [1.2. 命令行參數](ch1/ch1-02.md)
|
||||||
* [1.3. 査找重複的行](ch1/ch1-03.md)
|
* [1.3. 査找重複的行](ch1/ch1-03.md)
|
||||||
* [1.4. GIF動畵](ch1/ch1-04.md)
|
* [1.4. GIF動畵](ch1/ch1-04.md)
|
||||||
* [1.5. 穫取URL](ch1/ch1-05.md)
|
* [1.5. 獲取URL](ch1/ch1-05.md)
|
||||||
* [1.6. 併發穫取多個URL](ch1/ch1-06.md)
|
* [1.6. 併發獲取多個URL](ch1/ch1-06.md)
|
||||||
* [1.7. Web服務](ch1/ch1-07.md)
|
* [1.7. Web服務](ch1/ch1-07.md)
|
||||||
* [1.8. 本章要點](ch1/ch1-08.md)
|
* [1.8. 本章要點](ch1/ch1-08.md)
|
||||||
* [第二章 程序結構](ch2/ch2.md)
|
* [第二章 程序結構](ch2/ch2.md)
|
||||||
@ -62,7 +62,7 @@
|
|||||||
* [5.7. 可變參數](ch5/ch5-07.md)
|
* [5.7. 可變參數](ch5/ch5-07.md)
|
||||||
* [5.8. Deferred函數](ch5/ch5-08.md)
|
* [5.8. Deferred函數](ch5/ch5-08.md)
|
||||||
* [5.9. Panic異常](ch5/ch5-09.md)
|
* [5.9. Panic異常](ch5/ch5-09.md)
|
||||||
* [5.10. Recover捕穫異常](ch5/ch5-10.md)
|
* [5.10. Recover捕獲異常](ch5/ch5-10.md)
|
||||||
* [第六章 方法](ch6/ch6.md)
|
* [第六章 方法](ch6/ch6.md)
|
||||||
* [6.1. 方法聲明](ch6/ch6-01.md)
|
* [6.1. 方法聲明](ch6/ch6-01.md)
|
||||||
* [6.2. 基於指針對象的方法](ch6/ch6-02.md)
|
* [6.2. 基於指針對象的方法](ch6/ch6-02.md)
|
||||||
@ -140,7 +140,7 @@
|
|||||||
* [12.4. 示例: 編碼S表達式](ch12/ch12-04.md)
|
* [12.4. 示例: 編碼S表達式](ch12/ch12-04.md)
|
||||||
* [12.5. 通過reflect.Value脩改值](ch12/ch12-05.md)
|
* [12.5. 通過reflect.Value脩改值](ch12/ch12-05.md)
|
||||||
* [12.6. 示例: 解碼S表達式](ch12/ch12-06.md)
|
* [12.6. 示例: 解碼S表達式](ch12/ch12-06.md)
|
||||||
* [12.7. 穫取結構體字段標識](ch12/ch12-07.md)
|
* [12.7. 獲取結構體字段標識](ch12/ch12-07.md)
|
||||||
* [12.8. 顯示一個類型的方法集](ch12/ch12-08.md)
|
* [12.8. 顯示一個類型的方法集](ch12/ch12-08.md)
|
||||||
* [12.9. 幾點忠告](ch12/ch12-09.md)
|
* [12.9. 幾點忠告](ch12/ch12-09.md)
|
||||||
* [第十三章 底層編程](ch13/ch13.md)
|
* [第十三章 底層編程](ch13/ch13.md)
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
* [命令行參數](ch1/ch1-02.md)
|
* [命令行參數](ch1/ch1-02.md)
|
||||||
* [査找重複的行](ch1/ch1-03.md)
|
* [査找重複的行](ch1/ch1-03.md)
|
||||||
* [GIF動畵](ch1/ch1-04.md)
|
* [GIF動畵](ch1/ch1-04.md)
|
||||||
* [穫取URL](ch1/ch1-05.md)
|
* [獲取URL](ch1/ch1-05.md)
|
||||||
* [併發穫取多個URL](ch1/ch1-06.md)
|
* [併發獲取多個URL](ch1/ch1-06.md)
|
||||||
* [Web服務](ch1/ch1-07.md)
|
* [Web服務](ch1/ch1-07.md)
|
||||||
* [本章要點](ch1/ch1-08.md)
|
* [本章要點](ch1/ch1-08.md)
|
||||||
* [程序結構](ch2/ch2.md)
|
* [程序結構](ch2/ch2.md)
|
||||||
@ -47,7 +47,7 @@
|
|||||||
* [可變參數](ch5/ch5-07.md)
|
* [可變參數](ch5/ch5-07.md)
|
||||||
* [Deferred函數](ch5/ch5-08.md)
|
* [Deferred函數](ch5/ch5-08.md)
|
||||||
* [Panic異常](ch5/ch5-09.md)
|
* [Panic異常](ch5/ch5-09.md)
|
||||||
* [Recover捕穫異常](ch5/ch5-10.md)
|
* [Recover捕獲異常](ch5/ch5-10.md)
|
||||||
* [方法](ch6/ch6.md)
|
* [方法](ch6/ch6.md)
|
||||||
* [方法聲明](ch6/ch6-01.md)
|
* [方法聲明](ch6/ch6-01.md)
|
||||||
* [基於指針對象的方法](ch6/ch6-02.md)
|
* [基於指針對象的方法](ch6/ch6-02.md)
|
||||||
@ -113,7 +113,7 @@
|
|||||||
* [示例: 編碼S表達式](ch12/ch12-04.md)
|
* [示例: 編碼S表達式](ch12/ch12-04.md)
|
||||||
* [通過reflect.Value脩改值](ch12/ch12-05.md)
|
* [通過reflect.Value脩改值](ch12/ch12-05.md)
|
||||||
* [示例: 解碼S表達式](ch12/ch12-06.md)
|
* [示例: 解碼S表達式](ch12/ch12-06.md)
|
||||||
* [穫取結構體字段標識](ch12/ch12-07.md)
|
* [獲取結構體字段標識](ch12/ch12-07.md)
|
||||||
* [顯示一個類型的方法集](ch12/ch12-08.md)
|
* [顯示一個類型的方法集](ch12/ch12-08.md)
|
||||||
* [幾點忠告](ch12/ch12-09.md)
|
* [幾點忠告](ch12/ch12-09.md)
|
||||||
* [底層編程](ch13/ch13.md)
|
* [底層編程](ch13/ch13.md)
|
||||||
|
@ -20,13 +20,13 @@ Go語言的面向對象是不同尋常的。它沒有類層次結構,甚至可
|
|||||||
|
|
||||||
有些章節的後面可能會有一些練習,你可以根據你對Go語言的理解,然後脩改書中的例子來探索Go語言的其他用法。
|
有些章節的後面可能會有一些練習,你可以根據你對Go語言的理解,然後脩改書中的例子來探索Go語言的其他用法。
|
||||||
|
|
||||||
書中所有的代碼都可以從 http://gopl.io 上的Git倉庫下載。go get命令可以根據每個例子的其導入路徑智能地穫取、構建併安裝。你隻需要選擇一個目録作爲工作空間,然後將GOPATH環境指向這個工作目録。
|
書中所有的代碼都可以從 http://gopl.io 上的Git倉庫下載。go get命令可以根據每個例子的其導入路徑智能地獲取、構建併安裝。你隻需要選擇一個目録作爲工作空間,然後將GOPATH環境指向這個工作目録。
|
||||||
|
|
||||||
Go語言工具將在必要時創建的相應的目録。例如:
|
Go語言工具將在必要時創建的相應的目録。例如:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ export GOPATH=$HOME/gobook # 選擇工作目録
|
$ export GOPATH=$HOME/gobook # 選擇工作目録
|
||||||
$ go get gopl.io/ch1/helloworld # 穫取/編譯/安裝
|
$ go get gopl.io/ch1/helloworld # 獲取/編譯/安裝
|
||||||
$ $GOPATH/bin/helloworld # 運行程序
|
$ $GOPATH/bin/helloworld # 運行程序
|
||||||
Hello, 世界 # 這是中文
|
Hello, 世界 # 這是中文
|
||||||
```
|
```
|
||||||
|
@ -48,7 +48,7 @@ Hello, 世界
|
|||||||
gopl.io/ch1/helloworld
|
gopl.io/ch1/helloworld
|
||||||
```
|
```
|
||||||
|
|
||||||
如果你執行 `go get gopl.io/ch1/helloworld` 命令,go命令能夠自己從網上穫取到這些代碼(譯註:需要先安裝Git或Hg之類的版本管理工具,併將對應的命令添加到PATH環境變量中),併且將這些代碼放到對應的目録中(譯註:序言已經提及,需要先設置好GOPATH環境變量,下載的代碼會放在 $GOPATH/src/gopl.io/ch1/helloworld 目録)。更詳細的介紹在2.6和10.7章節中。
|
如果你執行 `go get gopl.io/ch1/helloworld` 命令,go命令能夠自己從網上獲取到這些代碼(譯註:需要先安裝Git或Hg之類的版本管理工具,併將對應的命令添加到PATH環境變量中),併且將這些代碼放到對應的目録中(譯註:序言已經提及,需要先設置好GOPATH環境變量,下載的代碼會放在 $GOPATH/src/gopl.io/ch1/helloworld 目録)。更詳細的介紹在2.6和10.7章節中。
|
||||||
|
|
||||||
我們來討論一下程序本身。Go語言的代碼是通過package來組織的,package的概念和你知道的其它語言里的libraries或者modules概念比較類似。一個package會包含一個或多個.go結束的源代碼文件。每一個源文件都是以一個package xxx的聲明語句開頭的,比如我們的例子里就是package main。這行聲明語句表示該文件是屬於哪一個package,緊跟着是一繫列import的package名,表示這個文件中引入的package。再之後是本文件本身的代碼。
|
我們來討論一下程序本身。Go語言的代碼是通過package來組織的,package的概念和你知道的其它語言里的libraries或者modules概念比較類似。一個package會包含一個或多個.go結束的源代碼文件。每一個源文件都是以一個package xxx的聲明語句開頭的,比如我們的例子里就是package main。這行聲明語句表示該文件是屬於哪一個package,緊跟着是一繫列import的package名,表示這個文件中引入的package。再之後是本文件本身的代碼。
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
## 1.2. 命令行參數
|
## 1.2. 命令行參數
|
||||||
|
|
||||||
大多數的程序都是處理輸入,産生輸出;這也正是“計算”的定義。但是一個程序要如何穫取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸出、用戶的鍵盤、命令行的參數或其它類似輸入源。下面幾個例子會討論其中的一些輸入類型,首先是命令行參數。
|
大多數的程序都是處理輸入,産生輸出;這也正是“計算”的定義。但是一個程序要如何獲取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸出、用戶的鍵盤、命令行的參數或其它類似輸入源。下面幾個例子會討論其中的一些輸入類型,首先是命令行參數。
|
||||||
|
|
||||||
os這個package提供了操作繫統無關(跨平台)的,與繫統交互的一些函數和相關的變量,運行時程序的命令行參數可以通過os包中一個叫Args的這個變量來穫取;當在os包外部使用該變量時,需要用os.Args來訪問。
|
os這個package提供了操作繫統無關(跨平台)的,與繫統交互的一些函數和相關的變量,運行時程序的命令行參數可以通過os包中一個叫Args的這個變量來獲取;當在os包外部使用該變量時,需要用os.Args來訪問。
|
||||||
|
|
||||||
os.Args這個變量是一個字符串(string)的slice(譯註:slice和Python語言中的切片類似,是一個簡版的動態數組),slice在Go語言里是一個基礎的數據結構,之後我們很快會提到。現在可以先把slice當一個簡單的元素序列,可以用類似s[i]的下標訪問形式穫取其內容,併且可以用形如s[m:n]的形式來穫取到一個slice的子集(譯註:和python里的語法差不多)。其長度可以用len(s)函數來穫取。和其它大多數編程語言類似,Go語言里的這種索引形式也采用了左閉右開區間,包括m~n的第一個元素,但不包括最後那個元素(譯註:比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3],不包含最後一個元素)。這樣可以簡化我們的處理邏輯。比如s[m:n]這個slice,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。
|
os.Args這個變量是一個字符串(string)的slice(譯註:slice和Python語言中的切片類似,是一個簡版的動態數組),slice在Go語言里是一個基礎的數據結構,之後我們很快會提到。現在可以先把slice當一個簡單的元素序列,可以用類似s[i]的下標訪問形式獲取其內容,併且可以用形如s[m:n]的形式來獲取到一個slice的子集(譯註:和python里的語法差不多)。其長度可以用len(s)函數來獲取。和其它大多數編程語言類似,Go語言里的這種索引形式也采用了左閉右開區間,包括m~n的第一個元素,但不包括最後那個元素(譯註:比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3],不包含最後一個元素)。這樣可以簡化我們的處理邏輯。比如s[m:n]這個slice,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。
|
||||||
|
|
||||||
os.Args的第一個元素,卽os.Args[0]是命令行執行時的命令本身;其它的元素則是執行該命令時傳給這個程序的參數。前面提到的切片表達式,s[m:n]會返迴第m到第n-1個元素,所以下一個例子里需要用到的os.Args[1:len(os.Args)]卽是除了命令本身外的所有傳入參數。如果我們省略s[m:n]里的m和n,那麽默認這個表達式會填入0:len(s),所以這里我們還可以省略掉n,寫成os.Args[1:]。
|
os.Args的第一個元素,卽os.Args[0]是命令行執行時的命令本身;其它的元素則是執行該命令時傳給這個程序的參數。前面提到的切片表達式,s[m:n]會返迴第m到第n-1個元素,所以下一個例子里需要用到的os.Args[1:len(os.Args)]卽是除了命令本身外的所有傳入參數。如果我們省略s[m:n]里的m和n,那麽默認這個表達式會填入0:len(s),所以這里我們還可以省略掉n,寫成os.Args[1:]。
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func main() {
|
|||||||
|
|
||||||
和我們前面提到的for循環一樣,在if條件的兩邊,我們也不需要加括號,但是if表達式後的邏輯體的花括號是不能省略的。如果需要的話,像其它編程語言一樣,這個if表達式也可以有else部分,這部分邏輯會在if中的條件結果爲false時被執行。
|
和我們前面提到的for循環一樣,在if條件的兩邊,我們也不需要加括號,但是if表達式後的邏輯體的花括號是不能省略的。如果需要的話,像其它編程語言一樣,這個if表達式也可以有else部分,這部分邏輯會在if中的條件結果爲false時被執行。
|
||||||
|
|
||||||
map是Go語言內置的key/value型數據結構,這個數據結構能夠提供常數時間的存儲、穫取、測試操作。key可以是任意數據類型,隻要該類型能夠用==運算符來進行比較,string是最常用的key類型。而value類型的范圍就更大了,基本上什麽類型都是可以的。這個例子中的key都是string類型,value用的是int類型。我們用內置make函數來創建一個空的map,當然了,make方法還可以有别的用處。在4.3章中我們還會對map進行更深入的討論。
|
map是Go語言內置的key/value型數據結構,這個數據結構能夠提供常數時間的存儲、獲取、測試操作。key可以是任意數據類型,隻要該類型能夠用==運算符來進行比較,string是最常用的key類型。而value類型的范圍就更大了,基本上什麽類型都是可以的。這個例子中的key都是string類型,value用的是int類型。我們用內置make函數來創建一個空的map,當然了,make方法還可以有别的用處。在4.3章中我們還會對map進行更深入的討論。
|
||||||
|
|
||||||
|
|
||||||
dup程序每次讀取輸入的一行,這一行的內容會被當做一個map的key,而其value值會被+1。counts[input.Text()]++這個語句和下面的兩句是等價的:
|
dup程序每次讀取輸入的一行,這一行的內容會被當做一個map的key,而其value值會被+1。counts[input.Text()]++這個語句和下面的兩句是等價的:
|
||||||
@ -76,7 +76,7 @@ Printf有一大堆這種轉換,Go語言程序員把這些叫做verb(動詞
|
|||||||
|
|
||||||
dup1中的程序還包含了一個\t和\n的格式化字符串。在字符串中會以這些特殊的轉義字符來表示不可見字符。Printf默認不會在輸出內容後加上換行符。按照慣例,用來格式化的函數都會在末尾以f字母結尾(譯註:f後綴對應format或fmt縮寫),比如log.Printf,fmt.Errorf,同時還有一繫列對應以ln結尾的函數(譯註:ln後綴對應line縮寫),這些函數默認以%v來格式化他們的參數,併且會在輸出結束後在最後自動加上一個換行符。
|
dup1中的程序還包含了一個\t和\n的格式化字符串。在字符串中會以這些特殊的轉義字符來表示不可見字符。Printf默認不會在輸出內容後加上換行符。按照慣例,用來格式化的函數都會在末尾以f字母結尾(譯註:f後綴對應format或fmt縮寫),比如log.Printf,fmt.Errorf,同時還有一繫列對應以ln結尾的函數(譯註:ln後綴對應line縮寫),這些函數默認以%v來格式化他們的參數,併且會在輸出結束後在最後自動加上一個換行符。
|
||||||
|
|
||||||
許多程序從標準輸入中讀取數據,像上面的例子那樣。除此之外,還可能從一繫列的文件中讀取。下一個dup程序就是從標準輸入中讀到一些文件名,用os.Open函數來打開每一個文件穫取內容的。
|
許多程序從標準輸入中讀取數據,像上面的例子那樣。除此之外,還可能從一繫列的文件中讀取。下一個dup程序就是從標準輸入中讀到一些文件名,用os.Open函數來打開每一個文件獲取內容的。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
gopl.io/ch1/dup2
|
gopl.io/ch1/dup2
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
## 1.5. 穫取URL
|
## 1.5. 獲取URL
|
||||||
|
|
||||||
對於很多現代應用來説,訪問互聯網上的信息和訪問本地文件繫統一樣重要。Go語言在net這個強大package的幫助下提供了一繫列的package來做這件事情,使用這些包可以更簡單地用網絡收發信息,還可以建立更底層的網絡連接,編寫服務器程序。在這些情景下,Go語言原生的併發特性(在第八章中會介紹)就顯得尤其好用了。
|
對於很多現代應用來説,訪問互聯網上的信息和訪問本地文件繫統一樣重要。Go語言在net這個強大package的幫助下提供了一繫列的package來做這件事情,使用這些包可以更簡單地用網絡收發信息,還可以建立更底層的網絡連接,編寫服務器程序。在這些情景下,Go語言原生的併發特性(在第八章中會介紹)就顯得尤其好用了。
|
||||||
|
|
||||||
爲了最簡單地展示基於HTTP穫取信息的方式,下面給出一個示例程序fetch,這個程序將穫取對應的url,併將其源文本打印出來;這個例子的靈感來源於curl工具(譯註:unix下的一個網絡相關的工具)。當然了,curl提供的功能更爲複雜豐富,這里我們隻編寫最簡單的樣例。之後我們還會在本書中經常用到這個例子。
|
爲了最簡單地展示基於HTTP獲取信息的方式,下面給出一個示例程序fetch,這個程序將獲取對應的url,併將其源文本打印出來;這個例子的靈感來源於curl工具(譯註:unix下的一個網絡相關的工具)。當然了,curl提供的功能更爲複雜豐富,這里我們隻編寫最簡單的樣例。之後我們還會在本書中經常用到這個例子。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
gopl.io/ch1/fetch
|
gopl.io/ch1/fetch
|
||||||
@ -34,7 +34,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
這個程序從兩個package中導入了函數,net/http和io/ioutil包,http.Get函數是創建HTTP請求的函數,如果穫取過程沒有出錯,那麽會在resp這個結構體中得到訪問的請求結果。resp的Body字段包括一個可讀的服務器響應流。這之後ioutil.ReadAll函數從response中讀取到全部內容;其結果保存在變量b中。resp.Body.Close這一句會關閉resp的Body流,防止資源洩露,Printf函數會將結果b寫出到標準輸出流中。
|
這個程序從兩個package中導入了函數,net/http和io/ioutil包,http.Get函數是創建HTTP請求的函數,如果獲取過程沒有出錯,那麽會在resp這個結構體中得到訪問的請求結果。resp的Body字段包括一個可讀的服務器響應流。這之後ioutil.ReadAll函數從response中讀取到全部內容;其結果保存在變量b中。resp.Body.Close這一句會關閉resp的Body流,防止資源洩露,Printf函數會將結果b寫出到標準輸出流中。
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go build gopl.io/ch1/fetch
|
$ go build gopl.io/ch1/fetch
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
## 1.6. 併發穫取多個URL
|
## 1.6. 併發獲取多個URL
|
||||||
|
|
||||||
Go語言最有意思併且最新奇的特性就是其對併發編程的支持了。併發編程是一個大話題,在第八章和第九章中會專門講到。這里我們隻淺嚐輒止地來體驗一下Go語言里的goroutine和channel。
|
Go語言最有意思併且最新奇的特性就是其對併發編程的支持了。併發編程是一個大話題,在第八章和第九章中會專門講到。這里我們隻淺嚐輒止地來體驗一下Go語言里的goroutine和channel。
|
||||||
|
|
||||||
下面的例子fetchall,和上面的fetch程序所要做的工作是一致的,但是這個fetchall的特别之處在於它會同時去穫取所有的URL,所以這個程序的穫取時間不會超過執行時間最長的那一個任務,而不會像前面的fetch程序一樣,執行時間是所有任務執行時間之和。這次的fetchall程序隻會打印穫取的內容大小和經過的時間,不會像上面那樣打印出穫取的內容。
|
下面的例子fetchall,和上面的fetch程序所要做的工作是一致的,但是這個fetchall的特别之處在於它會同時去獲取所有的URL,所以這個程序的獲取時間不會超過執行時間最長的那一個任務,而不會像前面的fetch程序一樣,執行時間是所有任務執行時間之和。這次的fetchall程序隻會打印獲取的內容大小和經過的時間,不會像上面那樣打印出獲取的內容。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
gopl.io/ch1/fetchall
|
gopl.io/ch1/fetchall
|
||||||
@ -65,4 +65,4 @@ main函數中用make函數創建了一個傳遞string類型參數的channel,
|
|||||||
|
|
||||||
當一個goroutine嚐試在一個channel上做send或者receive操作時,這個goroutine會阻塞在調用處,直到另一個goroutine往這個channel里寫入、或者接收了值,這樣兩個goroutine才會繼續執行操作channel完成之後的邏輯。在這個例子中,每一個fetch函數在執行時都會往channel里發送一個值(ch <- expression),主函數接收這些值(<-ch)。這個程序中我們用main函數來所有fetch函數傳迴的字符串,可以避免在goroutine異步執行時同時結束。
|
當一個goroutine嚐試在一個channel上做send或者receive操作時,這個goroutine會阻塞在調用處,直到另一個goroutine往這個channel里寫入、或者接收了值,這樣兩個goroutine才會繼續執行操作channel完成之後的邏輯。在這個例子中,每一個fetch函數在執行時都會往channel里發送一個值(ch <- expression),主函數接收這些值(<-ch)。這個程序中我們用main函數來所有fetch函數傳迴的字符串,可以避免在goroutine異步執行時同時結束。
|
||||||
|
|
||||||
**練習 1.10:** 找一個數據量比較大的網站,用本小節中的程序調研網站的緩存策略,對每個URL執行兩遍請求,査看兩次時間是否有較大的差别,併且每次穫取到的響應內容是否一致,脩改本節中的程序,將響應結果輸出,以便於進行對比。
|
**練習 1.10:** 找一個數據量比較大的網站,用本小節中的程序調研網站的緩存策略,對每個URL執行兩遍請求,査看兩次時間是否有較大的差别,併且每次獲取到的響應內容是否一致,脩改本節中的程序,將響應結果輸出,以便於進行對比。
|
||||||
|
@ -49,7 +49,7 @@ var p Point
|
|||||||
|
|
||||||
類型聲明和命名類型會在第二章中介紹。
|
類型聲明和命名類型會在第二章中介紹。
|
||||||
|
|
||||||
**指針:** Go語言提供了指針。指針是一種直接存儲了變量的內存地址的數據類型。在其它語言中,比如C語言,指針操作是完全不受約束的。在另外一些語言中,指針一般被處理爲“引用”,除了到處傳遞這些指針之外,併不能對這些指針做太多事情。Go語言在這兩種范圍中取了一種平衡。指針是可見的內存地址,&操作符可以返迴一個變量的內存地址,併且*操作符可以穫取指針指向的變量內容,但是在Go語言里沒有指針運算,也就是不能像c語言里可以對指針進行加或減操作。我們會在2.3.2中進行詳細介紹。
|
**指針:** Go語言提供了指針。指針是一種直接存儲了變量的內存地址的數據類型。在其它語言中,比如C語言,指針操作是完全不受約束的。在另外一些語言中,指針一般被處理爲“引用”,除了到處傳遞這些指針之外,併不能對這些指針做太多事情。Go語言在這兩種范圍中取了一種平衡。指針是可見的內存地址,&操作符可以返迴一個變量的內存地址,併且*操作符可以獲取指針指向的變量內容,但是在Go語言里沒有指針運算,也就是不能像c語言里可以對指針進行加或減操作。我們會在2.3.2中進行詳細介紹。
|
||||||
|
|
||||||
**方法和接口:** 方法是和命名類型關聯的一類函數。Go語言里比較特殊的是方法可以被關聯到任意一種命名類型。在第六章我們會詳細地講方法。接口是一種抽象類型,這種類型可以讓我們以同樣的方式來處理不同的固有類型,不用關心它們的具體實現,而隻需要關註它們提供的方法。第七章中會詳細説明這些內容。
|
**方法和接口:** 方法是和命名類型關聯的一類函數。Go語言里比較特殊的是方法可以被關聯到任意一種命名類型。在第六章我們會詳細地講方法。接口是一種抽象類型,這種類型可以讓我們以同樣的方式來處理不同的固有類型,不用關心它們的具體實現,而隻需要關註它們提供的方法。第七章中會詳細説明這些內容。
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
使用命令 `go get` 可以下載一個單一的包或者用 `...` 下載整個子目録里面的每個包. Go工具同時計算併下載所依賴的每個包, 這也是前一個例子中 golang.org/x/net/html 自動出現在本地工作區目録的原因.
|
使用命令 `go get` 可以下載一個單一的包或者用 `...` 下載整個子目録里面的每個包. Go工具同時計算併下載所依賴的每個包, 這也是前一個例子中 golang.org/x/net/html 自動出現在本地工作區目録的原因.
|
||||||
|
|
||||||
一旦 `go get` 命令下載了包, 然後就是安裝包或包對應的命令. 我們將在下一節再關註它的細節, 現在隻是展示下整個過程是如何的簡單. 第一個命令是穫取 golint 工具, 用於檢測Go源代碼的編程風格是否有問題. 第二個命令是用 golint 對 2.6.2節的 gopl.io/ch2/popcount 包代碼進行編碼風格檢査. 它友好地報告了忘記了包的文檔:
|
一旦 `go get` 命令下載了包, 然後就是安裝包或包對應的命令. 我們將在下一節再關註它的細節, 現在隻是展示下整個過程是如何的簡單. 第一個命令是獲取 golint 工具, 用於檢測Go源代碼的編程風格是否有問題. 第二個命令是用 golint 對 2.6.2節的 gopl.io/ch2/popcount 包代碼進行編碼風格檢査. 它友好地報告了忘記了包的文檔:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go get github.com/golang/lint/golint
|
$ go get github.com/golang/lint/golint
|
||||||
@ -13,9 +13,9 @@ src/gopl.io/ch2/popcount/main.go:1:1:
|
|||||||
package comment should be of the form "Package popcount ..."
|
package comment should be of the form "Package popcount ..."
|
||||||
```
|
```
|
||||||
|
|
||||||
`go get` 命令支持當前流行的託管網站 GitHub, Bitbucket, 和 Launchpad, 可以直接從它們的版本控製繫統請求代碼. 對於其他的網站, 你可能需要指定版本控製繫統的具體路徑和協議, 例如 Git 或 Mercurial. 運行 `go help importpath` 穫取更新的信息.
|
`go get` 命令支持當前流行的託管網站 GitHub, Bitbucket, 和 Launchpad, 可以直接從它們的版本控製繫統請求代碼. 對於其他的網站, 你可能需要指定版本控製繫統的具體路徑和協議, 例如 Git 或 Mercurial. 運行 `go help importpath` 獲取更新的信息.
|
||||||
|
|
||||||
`go get` 穫取的代碼是眞實的本地存儲倉庫, 不僅僅隻是複製文件, 因此你依然可以使用版本管理工具比較本地代碼的變更, 或者切換到其他的版本. 例如 golang.org/x/net 目録對應一個 Git 倉庫:
|
`go get` 獲取的代碼是眞實的本地存儲倉庫, 不僅僅隻是複製文件, 因此你依然可以使用版本管理工具比較本地代碼的變更, 或者切換到其他的版本. 例如 golang.org/x/net 目録對應一個 Git 倉庫:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd $GOPATH/src/golang.org/x/net
|
$ cd $GOPATH/src/golang.org/x/net
|
||||||
@ -37,5 +37,5 @@ $ ./fetch https://golang.org/x/net/html | grep go-import
|
|||||||
|
|
||||||
`go get -u` 命令隻是簡單地保證每個包是最新版本, 如果你是第一次下載則比較很方便的; 但是如果是發布程序則可能是不合適的, 因爲本地程序可能需要對依賴的包做精確的版本依賴管理. 通常的解決方案是使用 vendor 目録存儲固定版本的代碼, 對本地依賴的包的版本更新也是謹慎和持續可控的. 在 Go 1.5 之前, 一般需要脩改包的導入路徑, 所以複製後 golang.org/x/net/html 導入路徑可能會變爲 gopl.io/vendor/golang.org/x/net/html. 最新的Go工具已經支持 vendor 特性, 但限於篇幅這里併不討論細節. 不過可以通過 `go help gopath` 目録査看 Vendor 目録的幫助.
|
`go get -u` 命令隻是簡單地保證每個包是最新版本, 如果你是第一次下載則比較很方便的; 但是如果是發布程序則可能是不合適的, 因爲本地程序可能需要對依賴的包做精確的版本依賴管理. 通常的解決方案是使用 vendor 目録存儲固定版本的代碼, 對本地依賴的包的版本更新也是謹慎和持續可控的. 在 Go 1.5 之前, 一般需要脩改包的導入路徑, 所以複製後 golang.org/x/net/html 導入路徑可能會變爲 gopl.io/vendor/golang.org/x/net/html. 最新的Go工具已經支持 vendor 特性, 但限於篇幅這里併不討論細節. 不過可以通過 `go help gopath` 目録査看 Vendor 目録的幫助.
|
||||||
|
|
||||||
**練習 10.3:** 從 http://gopl.io/ch1/helloworld?go-get=1 穫取內容, 査看本書的代碼的眞實託管的網址(`go get`請求HTML頁面時包含了 `go-get` 參數, 以區别普通的瀏覽器請求.)
|
**練習 10.3:** 從 http://gopl.io/ch1/helloworld?go-get=1 獲取內容, 査看本書的代碼的眞實託管的網址(`go get`請求HTML頁面時包含了 `go-get` 參數, 以區别普通的瀏覽器請求.)
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ encoding/xml
|
|||||||
gopl.io/ch7/xmlselect
|
gopl.io/ch7/xmlselect
|
||||||
```
|
```
|
||||||
|
|
||||||
`go list` 可以穫取每個包完整的元信息, 而不僅僅隻是導入路徑, 這些信息可以以不同格式提供給用戶. 其中 `-json` 標誌參數表示用JSON格式打印每個包的元信息.
|
`go list` 可以獲取每個包完整的元信息, 而不僅僅隻是導入路徑, 這些信息可以以不同格式提供給用戶. 其中 `-json` 標誌參數表示用JSON格式打印每個包的元信息.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go list -json hash
|
$ go list -json hash
|
||||||
|
@ -39,7 +39,7 @@ func TestRandomPalindromes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
雖然隨機測試有不確定因素, 但是它也是至關重要的, 我們可以從失敗測試的日誌穫取足夠的信息. 在我們的例子中, 輸入 IsPalindrome 的 p 參數將告訴我們眞實的數據, 但是對於函數將接受更複雜的輸入, 不需要保存所有的輸入, 隻要日誌中簡單地記録隨機數種子卽可(像上面的方式). 有了這些隨機數初始化種子, 我們可以很容易脩改測試代碼以重現失敗的隨機測試.
|
雖然隨機測試有不確定因素, 但是它也是至關重要的, 我們可以從失敗測試的日誌獲取足夠的信息. 在我們的例子中, 輸入 IsPalindrome 的 p 參數將告訴我們眞實的數據, 但是對於函數將接受更複雜的輸入, 不需要保存所有的輸入, 隻要日誌中簡單地記録隨機數種子卽可(像上面的方式). 有了這些隨機數初始化種子, 我們可以很容易脩改測試代碼以重現失敗的隨機測試.
|
||||||
|
|
||||||
通過使用當前時間作爲隨機種子, 在整個過程中的每次運行測試命令時都將探索新的隨機數據. 如果你使用的是定期運行的自動化測試集成繫統, 隨機測試將特别有價值.
|
通過使用當前時間作爲隨機種子, 在整個過程中的每次運行測試命令時都將探索新的隨機數據. 如果你使用的是定期運行的自動化測試集成繫統, 隨機測試將特别有價值.
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ CPU分析文件標識了函數執行時所需要的CPU時間. 當前運行的繫
|
|||||||
|
|
||||||
堆分析則記録了程序的內存使用情況. 每個內存分配操作都會觸發內部平均內存分配例程, 每個 512KB 的內存申請都會觸發一個事件.
|
堆分析則記録了程序的內存使用情況. 每個內存分配操作都會觸發內部平均內存分配例程, 每個 512KB 的內存申請都會觸發一個事件.
|
||||||
|
|
||||||
阻塞分析則記録了goroutine最大的阻塞操作, 例如繫統調用, 管道發送和接收, 還有穫取鎖等. 分析庫會記録每個goroutine被阻塞時的相關操作.
|
阻塞分析則記録了goroutine最大的阻塞操作, 例如繫統調用, 管道發送和接收, 還有獲取鎖等. 分析庫會記録每個goroutine被阻塞時的相關操作.
|
||||||
|
|
||||||
在測試環境下隻需要一個標誌參數就可以生成各種分析文件. 當一次使用多個標誌參數時需要當心, 因爲分析操作本身也可能會影像程序的運行.
|
在測試環境下隻需要一個標誌參數就可以生成各種分析文件. 當一次使用多個標誌參數時需要當心, 因爲分析操作本身也可能會影像程序的運行.
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
## 12.7. 穫取結構體字段標識
|
## 12.7. 獲取結構體字段標識
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
## 13.2. unsafe.Pointer
|
## 13.2. unsafe.Pointer
|
||||||
|
|
||||||
大多數指針類型會寫成`*T`,表示是“一個指向T類型變量的指針”。unsafe.Pointer是特别定義的一種指針類型(譯註:類似C語言中的`void*`類型的指針),它可以包含任意類型變量的地址。當然,我們不可以直接通過`*p`來穫取unsafe.Pointer指針指向的眞實變量的值,因爲我們併不知道變量的具體類型。和普通指針一樣,unsafe.Pointer指針也是可以比較的,併且支持和nil常量比較判斷是否爲空指針。
|
大多數指針類型會寫成`*T`,表示是“一個指向T類型變量的指針”。unsafe.Pointer是特别定義的一種指針類型(譯註:類似C語言中的`void*`類型的指針),它可以包含任意類型變量的地址。當然,我們不可以直接通過`*p`來獲取unsafe.Pointer指針指向的眞實變量的值,因爲我們併不知道變量的具體類型。和普通指針一樣,unsafe.Pointer指針也是可以比較的,併且支持和nil常量比較判斷是否爲空指針。
|
||||||
|
|
||||||
一個普通的`*T`類型指針可以被轉化爲unsafe.Pointer類型指針,併且一個unsafe.Pointer類型指針也可以被轉迴普通的指針,被轉迴普通的指針類型併不需要和原始的`*T`類型相同。通過將`*float64`類型指針轉化爲`*uint64`類型指針,我們可以査看一個浮點數變量的位模式。
|
一個普通的`*T`類型指針可以被轉化爲unsafe.Pointer類型指針,併且一個unsafe.Pointer類型指針也可以被轉迴普通的指針,被轉迴普通的指針類型併不需要和原始的`*T`類型相同。通過將`*float64`類型指針轉化爲`*uint64`類型指針,我們可以査看一個浮點數變量的位模式。
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
高級語言使得程序員不用在關心眞正運行程序的指令細節,同時也不再需要關註許多如內存布局之類的實現細節。因爲高級語言這個絶緣的抽象層,我們可以編寫安全健壯的,併且可以運行在不同操作繫統上的具有高度可移植性的程序。
|
高級語言使得程序員不用在關心眞正運行程序的指令細節,同時也不再需要關註許多如內存布局之類的實現細節。因爲高級語言這個絶緣的抽象層,我們可以編寫安全健壯的,併且可以運行在不同操作繫統上的具有高度可移植性的程序。
|
||||||
|
|
||||||
但是unsafe包,它讓程序員可以透過這個絶緣的抽象層直接使用一些必要的功能,雖然可能是爲了穫得更好的性能。但是代價就是犧牲了可移植性和程序安全,因此使用unsafe包是一個危險的行爲。我們對何時以及如何使用unsafe包的建議和我們在11.5節提到的Knuth對過早優化的建議類似。大多數Go程序員可能永遠不會需要直接使用unsafe包。當然,也永遠都會有一些需要使用unsafe包實現會更簡單的場景。如果確實認爲使用unsafe包是最理想的方式,那麽應該盡可能將它限製在較小的范圍,那樣其它代碼就忽略unsafe的影響。
|
但是unsafe包,它讓程序員可以透過這個絶緣的抽象層直接使用一些必要的功能,雖然可能是爲了獲得更好的性能。但是代價就是犧牲了可移植性和程序安全,因此使用unsafe包是一個危險的行爲。我們對何時以及如何使用unsafe包的建議和我們在11.5節提到的Knuth對過早優化的建議類似。大多數Go程序員可能永遠不會需要直接使用unsafe包。當然,也永遠都會有一些需要使用unsafe包實現會更簡單的場景。如果確實認爲使用unsafe包是最理想的方式,那麽應該盡可能將它限製在較小的范圍,那樣其它代碼就忽略unsafe的影響。
|
||||||
|
|
||||||
現在,趕緊將最後兩章拋入腦後吧。編寫一些實實在在的應用是眞理。請遠離reflect的unsafe包,除非你確實需要它們。
|
現在,趕緊將最後兩章拋入腦後吧。編寫一些實實在在的應用是眞理。請遠離reflect的unsafe包,除非你確實需要它們。
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ Go語言的設計包含了諸多安全策略,限製了可能導致程序運行
|
|||||||
|
|
||||||
對於無法靜態檢測到的錯誤,例如數組訪問越界或使用空指針,運行時動態檢測可以保證程序在遇到問題的時候立卽終止併打印相關的錯誤信息。自動內存管理(垃圾內存自動迴收)可以消除大部分野指針和內存洩漏相關的問題。
|
對於無法靜態檢測到的錯誤,例如數組訪問越界或使用空指針,運行時動態檢測可以保證程序在遇到問題的時候立卽終止併打印相關的錯誤信息。自動內存管理(垃圾內存自動迴收)可以消除大部分野指針和內存洩漏相關的問題。
|
||||||
|
|
||||||
Go語言的實現刻意隱藏了很多底層細節。我們無法知道一個結構體眞實的內存布局,也無法穫取一個運行時函數對應的機器碼,也無法知道當前的goroutine是運行在哪個操作繫統線程之上。事實上,Go語言的調度器會自己決定是否需要將某個goroutine從一個操作繫統線程轉移到另一個操作繫統線程。一個指向變量的指針也併沒有展示變量眞實的地址。因爲垃圾迴收器可能會根據需要移動變量的內存位置,當然變量對應的地址也會被自動更新。
|
Go語言的實現刻意隱藏了很多底層細節。我們無法知道一個結構體眞實的內存布局,也無法獲取一個運行時函數對應的機器碼,也無法知道當前的goroutine是運行在哪個操作繫統線程之上。事實上,Go語言的調度器會自己決定是否需要將某個goroutine從一個操作繫統線程轉移到另一個操作繫統線程。一個指向變量的指針也併沒有展示變量眞實的地址。因爲垃圾迴收器可能會根據需要移動變量的內存位置,當然變量對應的地址也會被自動更新。
|
||||||
|
|
||||||
總的來説,Go語言的這些特性使得Go程序相比較低級的C語言來説更容易預測和理解,程序也不容易崩潰。通過隱藏底層的實現細節,也使得Go語言編寫的程序具有高度的可移植性,因爲語言的語義在很大程度上是獨立於任何編譯器實現、操作繫統和CPU繫統結構的(當然也不是完全絶對獨立:例如int等類型就依賴於CPU機器字的大小,某些表達式求值的具體順序,還有編譯器實現的一些額外的限製等)。
|
總的來説,Go語言的這些特性使得Go程序相比較低級的C語言來説更容易預測和理解,程序也不容易崩潰。通過隱藏底層的實現細節,也使得Go語言編寫的程序具有高度的可移植性,因爲語言的語義在很大程度上是獨立於任何編譯器實現、操作繫統和CPU繫統結構的(當然也不是完全絶對獨立:例如int等類型就依賴於CPU機器字的大小,某些表達式求值的具體順序,還有編譯器實現的一些額外的限製等)。
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ if f, err := os.Open(fname); err != nil {
|
|||||||
|
|
||||||
但這不是Go語言推薦的做法,Go語言的習慣是在if中處理錯誤然後直接返迴,這樣可以確保正常執行的語句不需要代碼縮進。
|
但這不是Go語言推薦的做法,Go語言的習慣是在if中處理錯誤然後直接返迴,這樣可以確保正常執行的語句不需要代碼縮進。
|
||||||
|
|
||||||
要特别註意短變量聲明語句的作用域范圍,考慮下面的程序,它的目的是穫取當前的工作目録然後保存到一個包級的變量中。這可以本來通過直接調用os.Getwd完成,但是將這個從主邏輯中分離出來可能會更好,特别是在需要處理錯誤的時候。函數log.Fatalf用於打印日誌信息,然後調用os.Exit(1)終止程序。
|
要特别註意短變量聲明語句的作用域范圍,考慮下面的程序,它的目的是獲取當前的工作目録然後保存到一個包級的變量中。這可以本來通過直接調用os.Getwd完成,但是將這個從主邏輯中分離出來可能會更好,特别是在需要處理錯誤的時候。函數log.Fatalf用於打印日誌信息,然後調用os.Exit(1)終止程序。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
var cwd string
|
var cwd string
|
||||||
|
@ -9,7 +9,7 @@ UTF8是一個將Unicode碼點編碼爲字節序列的變長編碼。UTF8編碼
|
|||||||
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused)
|
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused)
|
||||||
```
|
```
|
||||||
|
|
||||||
變長的編碼無法直接通過索引來訪問第n個字符,但是UTF8編碼穫得了很多額外的優點。首先UTF8編碼比較緊湊,完全兼容ASCII碼,併且可以自動同步:它可以通過向前迴朔最多2個字節就能確定當前字符編碼的開始字節的位置。它也是一個前綴編碼,所以當從左向右解碼時不會有任何歧義也併不需要向前査看(譯註:像GBK之類的編碼,如果不知道起點位置則可能會出現歧義)。沒有任何字符的編碼是其它字符編碼的子串,或是其它編碼序列的字串,因此蒐索一個字符時隻要蒐索它的字節編碼序列卽可,不用擔心前後的上下文會對蒐索結果産生榦擾。同時UTF8編碼的順序和Unicode碼點的順序一致,因此可以直接排序UTF8編碼序列。同時因爲沒有嵌入的NUL(0)字節,可以很好地兼容那些使用NUL作爲字符串結尾的編程語言。
|
變長的編碼無法直接通過索引來訪問第n個字符,但是UTF8編碼獲得了很多額外的優點。首先UTF8編碼比較緊湊,完全兼容ASCII碼,併且可以自動同步:它可以通過向前迴朔最多2個字節就能確定當前字符編碼的開始字節的位置。它也是一個前綴編碼,所以當從左向右解碼時不會有任何歧義也併不需要向前査看(譯註:像GBK之類的編碼,如果不知道起點位置則可能會出現歧義)。沒有任何字符的編碼是其它字符編碼的子串,或是其它編碼序列的字串,因此蒐索一個字符時隻要蒐索它的字節編碼序列卽可,不用擔心前後的上下文會對蒐索結果産生榦擾。同時UTF8編碼的順序和Unicode碼點的順序一致,因此可以直接排序UTF8編碼序列。同時因爲沒有嵌入的NUL(0)字節,可以很好地兼容那些使用NUL作爲字符串結尾的編程語言。
|
||||||
|
|
||||||
Go語言的源文件采用UTF8編碼,併且Go語言處理UTF8編碼的文本也很出色。unicode包提供了諸多處理rune字符相關功能的函數(比如區分字母和數組,或者是字母的大寫和小寫轉換等),unicode/utf8包則提供了用於rune字符序列的UTF8編碼和解碼的功能。
|
Go語言的源文件采用UTF8編碼,併且Go語言處理UTF8編碼的文本也很出色。unicode包提供了諸多處理rune字符相關功能的函數(比如區分字母和數組,或者是字母的大寫和小寫轉換等),unicode/utf8包則提供了用於rune字符序列的UTF8編碼和解碼的功能。
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
## 5.10. Recover捕穫異常
|
## 5.10. Recover捕獲異常
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
@ -48,7 +48,7 @@ fmt.Println(p) // "{2, 4}"
|
|||||||
p.ScaleBy(2)
|
p.ScaleBy(2)
|
||||||
```
|
```
|
||||||
|
|
||||||
編譯器會隱式地幫我們用&p去調用ScaleBy這個方法。這種簡寫方法隻適用於“變量”,包括struct里的字段比如p.X,以及array和slice內的元素比如perim[0]。我們不能通過一個無法取到地址的接收器來調用指針方法,比如臨時變量的內存地址就無法穫取得到:
|
編譯器會隱式地幫我們用&p去調用ScaleBy這個方法。這種簡寫方法隻適用於“變量”,包括struct里的字段比如p.X,以及array和slice內的元素比如perim[0]。我們不能通過一個無法取到地址的接收器來調用指針方法,比如臨時變量的內存地址就無法獲取得到:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literal
|
Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literal
|
||||||
|
@ -62,7 +62,7 @@ https://golang.org/blog/
|
|||||||
|
|
||||||
我們可以用一個有容量限製的buffered channel來控製併發,這類似於操作繫統里的計數信號量概念。從概念上講,channel里的n個空槽代表n個可以處理內容的token(通行證),從channel里接收一個值會釋放其中的一個token,併且生成一個新的空槽位。這樣保證了在沒有接收介入時最多有n個發送操作。(這里可能我們拿channel里填充的槽來做token更直觀一些,不過還是這樣吧~)。由於channel里的元素類型併不重要,我們用一個零值的struct{}來作爲其元素。
|
我們可以用一個有容量限製的buffered channel來控製併發,這類似於操作繫統里的計數信號量概念。從概念上講,channel里的n個空槽代表n個可以處理內容的token(通行證),從channel里接收一個值會釋放其中的一個token,併且生成一個新的空槽位。這樣保證了在沒有接收介入時最多有n個發送操作。(這里可能我們拿channel里填充的槽來做token更直觀一些,不過還是這樣吧~)。由於channel里的元素類型併不重要,我們用一個零值的struct{}來作爲其元素。
|
||||||
|
|
||||||
讓我們重寫crawl函數,將對links.Extract的調用操作用穫取、釋放token的操作包裹起來,來確保同一時間對其隻有20個調用。信號量數量和其能操作的IO資源數量應保持接近。
|
讓我們重寫crawl函數,將對links.Extract的調用操作用獲取、釋放token的操作包裹起來,來確保同一時間對其隻有20個調用。信號量數量和其能操作的IO資源數量應保持接近。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
gopl.io/ch8/crawl2
|
gopl.io/ch8/crawl2
|
||||||
|
@ -68,7 +68,7 @@ func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
|
|||||||
|
|
||||||
在walkDir函數的循環中我們對取消狀態進行輪詢可以帶來明顯的益處,可以避免在取消事件發生時還去創建goroutine。取消本身是有一些代價的;想要快速的響應需要對程序邏輯進行侵入式的脩改。確保在取消發生之後不要有代價太大的操作可能會需要脩改你代碼里的很多地方,但是在一些重要的地方去檢査取消事件也確實能帶來很大的好處。
|
在walkDir函數的循環中我們對取消狀態進行輪詢可以帶來明顯的益處,可以避免在取消事件發生時還去創建goroutine。取消本身是有一些代價的;想要快速的響應需要對程序邏輯進行侵入式的脩改。確保在取消發生之後不要有代價太大的操作可能會需要脩改你代碼里的很多地方,但是在一些重要的地方去檢査取消事件也確實能帶來很大的好處。
|
||||||
|
|
||||||
對這個程序的一個簡單的性能分析可以揭示瓶頸在dirents函數中穫取一個信號量。下面的select可以讓這種操作可以被取消,併且可以將取消時的延遲從幾百毫秒降低到幾十毫秒。
|
對這個程序的一個簡單的性能分析可以揭示瓶頸在dirents函數中獲取一個信號量。下面的select可以讓這種操作可以被取消,併且可以將取消時的延遲從幾百毫秒降低到幾十毫秒。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func dirents(dir string) []os.FileInfo {
|
func dirents(dir string) []os.FileInfo {
|
||||||
@ -82,7 +82,7 @@ func dirents(dir string) []os.FileInfo {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
現在當取消發生時,所有後台的goroutine都會迅速停止併且主函數會返迴。當然,當主函數返迴時,一個程序會退出,而我們又無法在主函數退出的時候確認其已經釋放了所有的資源(譯註:因爲程序都退出了,你的代碼都沒法執行了)。這里有一個方便的竅門我們可以一用:取代掉直接從主函數返迴,我們調用一個panic,然後runtime會把每一個goroutine的棧dump下來。如果main goroutine是唯一一個剩下的goroutine的話,他會清理掉自己的一切資源。但是如果還有其它的goroutine沒有退出,他們可能沒辦法被正確地取消掉,也有可能被取消但是取消操作會很花時間;所以這里的一個調研還是很有必要的。我們用panic來穫取到足夠的信息來驗證我們上面的判斷,看看最終到底是什麽樣的情況。
|
現在當取消發生時,所有後台的goroutine都會迅速停止併且主函數會返迴。當然,當主函數返迴時,一個程序會退出,而我們又無法在主函數退出的時候確認其已經釋放了所有的資源(譯註:因爲程序都退出了,你的代碼都沒法執行了)。這里有一個方便的竅門我們可以一用:取代掉直接從主函數返迴,我們調用一個panic,然後runtime會把每一個goroutine的棧dump下來。如果main goroutine是唯一一個剩下的goroutine的話,他會清理掉自己的一切資源。但是如果還有其它的goroutine沒有退出,他們可能沒辦法被正確地取消掉,也有可能被取消但是取消操作會很花時間;所以這里的一個調研還是很有必要的。我們用panic來獲取到足夠的信息來驗證我們上面的判斷,看看最終到底是什麽樣的情況。
|
||||||
|
|
||||||
練習8.10: HTTP請求可能會因http.Request結構體中Cancel channel的關閉而取消。脩改8.6節中的web crawler來支持取消http請求。
|
練習8.10: HTTP請求可能會因http.Request結構體中Cancel channel的關閉而取消。脩改8.6節中的web crawler來支持取消http請求。
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ func broadcaster() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
broadcaster監聽來自全局的entering和leaving的channel來穫知客戶端的到來和離開事件。當其接收到其中的一個事件時,會更新clients集合,當該事件是離開行爲時,它會關閉客戶端的消息發出channel。broadcaster也會監聽全局的消息channel,所有的客戶端都會向這個channel中發送消息。當broadcaster接收到什麽消息時,就會將其廣播至所有連接到服務端的客戶端。
|
broadcaster監聽來自全局的entering和leaving的channel來獲知客戶端的到來和離開事件。當其接收到其中的一個事件時,會更新clients集合,當該事件是離開行爲時,它會關閉客戶端的消息發出channel。broadcaster也會監聽全局的消息channel,所有的客戶端都會向這個channel中發送消息。當broadcaster接收到什麽消息時,就會將其廣播至所有連接到服務端的客戶端。
|
||||||
|
|
||||||
現在讓我們看看每一個客戶端的goroutine。handleConn函數會爲它的客戶端創建一個消息發出channel併通過entering channel來通知客戶端的到來。然後它會讀取客戶端發來的每一行文本,併通過全局的消息channel來將這些文本發送出去,併爲每條消息帶上發送者的前綴來標明消息身份。當客戶端發送完畢後,handleConn會通過leaving這個channel來通知客戶端的離開併關閉連接。
|
現在讓我們看看每一個客戶端的goroutine。handleConn函數會爲它的客戶端創建一個消息發出channel併通過entering channel來通知客戶端的到來。然後它會讀取客戶端發來的每一行文本,併通過全局的消息channel來將這些文本發送出去,併爲每條消息帶上發送者的前綴來標明消息身份。當客戶端發送完畢後,handleConn會通過leaving這個channel來通知客戶端的離開併關閉連接。
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本
|
|||||||
|
|
||||||
Go語言由來自Google公司的Robert Griesemer,Rob Pike和Ken Thompson三位大牛於2007年9月開始設計和實現,然後於2009年的11月對外正式發布(譯註:關於Go語言的創世紀過程請參考 http://talks.golang.org/2015/how-go-was-made.slide )。語言及其配套工具的設計目標是具有表達力,高效的編譯和執行效率,有效地編寫高效和健壯的程序。
|
Go語言由來自Google公司的Robert Griesemer,Rob Pike和Ken Thompson三位大牛於2007年9月開始設計和實現,然後於2009年的11月對外正式發布(譯註:關於Go語言的創世紀過程請參考 http://talks.golang.org/2015/how-go-was-made.slide )。語言及其配套工具的設計目標是具有表達力,高效的編譯和執行效率,有效地編寫高效和健壯的程序。
|
||||||
|
|
||||||
Go語言有着和C語言類似的語法外表,和C語言一樣是專業程序員的必備工具,可以用最小的代價穫得最大的戰果。
|
Go語言有着和C語言類似的語法外表,和C語言一樣是專業程序員的必備工具,可以用最小的代價獲得最大的戰果。
|
||||||
但是它不僅僅是一個更新的C語言。它還從其他語言借鑒了很多好的想法,同時避免引入過度的複雜性。
|
但是它不僅僅是一個更新的C語言。它還從其他語言借鑒了很多好的想法,同時避免引入過度的複雜性。
|
||||||
Go語言中和併發編程相關的特性是全新的也是有效的,同時對數據抽象和面向對象編程的支持也很靈活。
|
Go語言中和併發編程相關的特性是全新的也是有效的,同時對數據抽象和面向對象編程的支持也很靈活。
|
||||||
Go語言同時還集成了自動垃圾收集技術用於更好地管理內存。
|
Go語言同時還集成了自動垃圾收集技術用於更好地管理內存。
|
||||||
@ -35,7 +35,7 @@ Go語言尤其適合編寫網絡服務相關基礎設施,同時也適合開發
|
|||||||
和機器學習等諸多領域。目前Go語言已經成爲受歡迎的作爲無類型的腳本語言的替代者:
|
和機器學習等諸多領域。目前Go語言已經成爲受歡迎的作爲無類型的腳本語言的替代者:
|
||||||
因爲Go編寫的程序通常比腳本語言運行的更快也更安全,而且很少會發生意外的類型錯誤。
|
因爲Go編寫的程序通常比腳本語言運行的更快也更安全,而且很少會發生意外的類型錯誤。
|
||||||
|
|
||||||
Go語言還是一個開源的項目,可以免費穫編譯器、庫、配套工具的源代碼。
|
Go語言還是一個開源的項目,可以免費獲編譯器、庫、配套工具的源代碼。
|
||||||
Go語言的貢獻者來自一個活躍的全球社區。Go語言可以運行在類UNIX繫統——
|
Go語言的貢獻者來自一個活躍的全球社區。Go語言可以運行在類UNIX繫統——
|
||||||
比如Linux、FreeBSD、OpenBSD、Mac OSX——和Plan9繫統和Microsoft Windows操作繫統之上。
|
比如Linux、FreeBSD、OpenBSD、Mac OSX——和Plan9繫統和Microsoft Windows操作繫統之上。
|
||||||
Go語言編寫的程序無需脩改就可以運行在上面這些環境。
|
Go語言編寫的程序無需脩改就可以運行在上面這些環境。
|
||||||
|
Loading…
Reference in New Issue
Block a user