This commit is contained in:
foreversmart 2016-01-05 22:03:12 +08:00
commit c5e83972bf
28 changed files with 1017 additions and 84 deletions

View File

@ -1,27 +1,11 @@
# 原文作 # 貢獻
## Alan A. A. Donovan 譯者 | 章節
![Alan.Donovan](./images/Alan.Donovan.png)
**Alan A. A. Donovan** is a member of Googles Go team in New York. He holds computer science degrees from Cambridge and MIT and has been programming in industry since 1996. Since 2005, he has worked at Google on infrastructure projects and was the co-designer of its proprietary build system, Blaze. He has built many libraries and tools for static analysis of Go programs, including oracle, godoc -analysis, eg, and gorename.
## Brian W. Kernighan
![Brian W. Kernighan](./images/Brian.W.Kernighan.png)
**Brian W. Kernighan** is a professor in the Computer Science Department at Princeton University. He was a member of technical staff in the Computing Science Research Center at Bell Labs from 1969 until 2000, where he worked on languages and tools for Unix. He is the co-author of several books, including The C Programming Language, Second Edition (Prentice Hall, 1988), and The Practice of Programming (Addison-Wesley, 1999).
-------
# 中文譯者
中文譯者 | 章節
-------------------------------------- | ------------------------- -------------------------------------- | -------------------------
`chai2010 <chaishushan@gmail.com>` | 前言/第二章/第三章/第四章/第十章/第十一章/第十二章/第十三 `chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
`CrazySssst` | 第三章(0節)/第五 `CrazySssst` | 第5章
`foreversmart <njutree@gmail.com>` | 第七章(0節/1節/2節) `foreversmart <njutree@gmail.com>` | 第7章
`Xargin <cao1988228@163.com>` | 第一章/第六章/第八章/第九 `Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章
# 譯文授權 # 譯文授權

View File

@ -30,6 +30,9 @@ review:
gitbook build gitbook build
go run zh2tw.go . .md$$ zh2tw go run zh2tw.go . .md$$ zh2tw
fixlink:
go run fixlinks.go . .md$$
cover: cover:
composite cover_patch.png cover_bgd.png cover.jpg composite cover_patch.png cover_bgd.png cover.jpg
convert -resize 1800x2360! cover.jpg cover.jpg convert -resize 1800x2360! cover.jpg cover.jpg

View File

@ -28,8 +28,6 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本
- `make zh2tw``go run zh2tw.go . "\.md$" zh2tw`,轉繁體。 - `make zh2tw``go run zh2tw.go . "\.md$" zh2tw`,轉繁體。
- `make tw2zh``go run zh2tw.go . "\.md$" tw2zh`,轉簡體。 - `make tw2zh``go run zh2tw.go . "\.md$" tw2zh`,轉簡體。
如果是使用簡體中文的用戶,可在執行上述命令前運行`make tw2zh`命令,將繁體中文轉換爲簡體中文。
### Markdown 格式預覽 ### Markdown 格式預覽
- [SUMMARY-github.md](SUMMARY-github.md) - [SUMMARY-github.md](SUMMARY-github.md)

View File

@ -167,4 +167,4 @@
* [13.3. 示例: 深度相等判斷](ch13/ch13-03.md) * [13.3. 示例: 深度相等判斷](ch13/ch13-03.md)
* [13.4. 通過cgo調用C代碼](ch13/ch13-04.md) * [13.4. 通過cgo調用C代碼](ch13/ch13-04.md)
* [13.5. 幾點忠告](ch13/ch13-05.md) * [13.5. 幾點忠告](ch13/ch13-05.md)
* [附録](CONTRIBUTORS.md) * [附録](appendix/appendix.md)

View File

@ -122,4 +122,4 @@
* [示例: 深度相等判斷](ch13/ch13-03.md) * [示例: 深度相等判斷](ch13/ch13-03.md)
* [通過cgo調用C代碼](ch13/ch13-04.md) * [通過cgo調用C代碼](ch13/ch13-04.md)
* [幾點忠告](ch13/ch13-05.md) * [幾點忠告](ch13/ch13-05.md)
* [附録](CONTRIBUTORS.md) * [附録](appendix/appendix.md)

26
appendix/appendix.md Normal file
View File

@ -0,0 +1,26 @@
## 附録:作者/譯者
### 英文作者
- **[Alan A. A. Donovan](https://github.com/adonovan)** is a member of [Googles Go](https://golang.org/) team in New York. He holds computer science degrees from Cambridge and MIT and has been programming in industry since 1996. Since 2005, he has worked at Google on infrastructure projects and was the co-designer of its proprietary build system, [Blaze](http://bazel.io/). He has built many libraries and tools for static analysis of Go programs, including [oracle](https://godoc.org/golang.org/x/tools/oracle), [`godoc -analysis`](https://godoc.org/golang.org/x/tools/cmd/godoc), eg, and [gorename](https://godoc.org/golang.org/x/tools/cmd/gorename).
- **[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)** is a professor in the Computer Science Department at Princeton University. He was a member of technical staff in the Computing Science Research Center at [Bell Labs](http://www.cs.bell-labs.com/) from 1969 until 2000, where he worked on languages and tools for [Unix](http://doc.cat-v.org/unix/). He is the co-author of several books, including [The C Programming Language, Second Edition (Prentice Hall, 1988)](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html), and [The Practice of Programming (Addison-Wesley, 1999)](https://en.wikipedia.org/wiki/The_Practice_of_Programming).
-------
### 中文譯者
中文譯者 | 章節
-------------------------------------- | -------------------------
`chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
`CrazySssst` | 第5章
`foreversmart <njutree@gmail.com>` | 第7章
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章
-------
## 譯文授權
除特别註明外, 本站內容均采用[知識共享-署名(CC-BY) 3.0協議](http://creativecommons.org/licenses/by/3.0/)授權, 代碼遵循[Go項目的BSD協議](http://golang.org/LICENSE)授權.
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="../images/by-nc-sa-4.0-88x31.png"></img></a>

View File

@ -8,12 +8,12 @@
Go語言有時候被描述爲“C類似語言”或者是“21世紀的C語言”。Go從C語言繼承了相似的表達式語法、控製流結構、基礎數據類型、調用參數傳值、指針等很多思想還有C語言一直所看中的編譯後機器碼的運行效率以及和現有操作繫統的無縫適配。 Go語言有時候被描述爲“C類似語言”或者是“21世紀的C語言”。Go從C語言繼承了相似的表達式語法、控製流結構、基礎數據類型、調用參數傳值、指針等很多思想還有C語言一直所看中的編譯後機器碼的運行效率以及和現有操作繫統的無縫適配。
但是在Go語言的家族樹中還有其它的祖先。其中一個有影響力的分支來自Niklaus Wirth所設計的Pascal語言。然後Modula-2語言激發了包的概念。然後Oberon語言 摒棄了模塊接口文件和模塊實現文件之間的區别。第二代的Oberon-2語言直接影響了包的導入和聲明的語法還有Oberon語言的面向對象特性所提供的方法的聲明語法等。 但是在Go語言的家族樹中還有其它的祖先。其中一個有影響力的分支來自[Niklaus Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth)所設計的[Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language))語言。然後[Modula-2](https://en.wikipedia.org/wiki/Modula-2)語言激發了包的概念。然後[Oberon](https://en.wikipedia.org/wiki/Oberon_(programming_language))語言摒棄了模塊接口文件和模塊實現文件之間的區别。第二代的[Oberon-2](https://en.wikipedia.org/wiki/Oberon-2_(programming_language))語言直接影響了包的導入和聲明的語法,還有[Oberon](https://en.wikipedia.org/wiki/Oberon_(programming_language))語言的面向對象特性所提供的方法的聲明語法等。
Go語言的另一支祖先帶來了Go語言區别其他語言的重要特性靈感來自於貝爾實驗室的Tony Hoare於1978年發表的鮮爲外界所知的關於併發研究的基礎文獻 *順序通信進程* *communicating sequential processes* 縮寫爲CSP。在CSP中程序是一組中間沒有共享狀態的平行運行的處理過程它們之間使用管道進行通信和控製同步。不過Tony Hoare的CSP隻是一個用於描述併發性基本概念的描述語言併不是一個可以編寫可執行程序的通用編程語言。 Go語言的另一支祖先帶來了Go語言區别其他語言的重要特性靈感來自於貝爾實驗室的[Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)於1978年發表的鮮爲外界所知的關於併發研究的基礎文獻 *順序通信進程* *[communicating sequential processes](https://en.wikipedia.org/wiki/Communicating_sequential_processes)* ,縮寫爲[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes))。在[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)中,程序是一組中間沒有共享狀態的平行運行的處理過程,它們之間使用管道進行通信和控製同步。不過[Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)隻是一個用於描述併發性基本概念的描述語言,併不是一個可以編寫可執行程序的通用編程語言。
接下來Rob Pike和其他人開始不斷嚐試將CSP引入實際的編程語言中。他們第一次嚐試引入CSP特性的編程語言叫Squeak老鼠間交流的語言是一個提供鼠標和鍵盤事件處理的編程語言它的管道是靜態創建的。然後是改進版的Newsqueak語言提供了類似C語言語句和表達式的語法和類似Pascal語言的推導語法。Newsqueak是一個帶垃圾迴收的純函數式語言它再次針對鍵盤、鼠標和窗口事件管理。但是在Newsqueak語言中管道是動態創建的屬於第一類值, 可以保存到變量中。 接下來Rob Pike和其他人開始不斷嚐試將[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)引入實際的編程語言中。他們第一次嚐試引入[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)特性的編程語言叫[Squeak](http://doc.cat-v.org/bell_labs/squeak/)(老鼠間交流的語言),是一個提供鼠標和鍵盤事件處理的編程語言,它的管道是靜態創建的。然後是改進版的[Newsqueak](http://doc.cat-v.org/bell_labs/squeak/)語言提供了類似C語言語句和表達式的語法和類似[Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language))語言的推導語法。Newsqueak是一個帶垃圾迴收的純函數式語言它再次針對鍵盤、鼠標和窗口事件管理。但是在Newsqueak語言中管道是動態創建的屬於第一類值, 可以保存到變量中。
在Plan9操作繫統中這些優秀的想法被吸收到了一個叫Alef的編程語言中。Alef試圖將Newsqueak語言改造爲繫統編程語言但是因爲缺少垃圾迴收機製而導致併發編程很痛苦。譯註在Aelf之後還有一個叫Limbo的編程語言Go語言從其中借鑒了很多特性。 具體請參考Pike的講稿http://talks.golang.org/2012/concurrency.slide#9 在Plan9操作繫統中這些優秀的想法被吸收到了一個叫[Alef](http://doc.cat-v.org/plan_9/2nd_edition/papers/alef/)的編程語言中。Alef試圖將Newsqueak語言改造爲繫統編程語言但是因爲缺少垃圾迴收機製而導致併發編程很痛苦。譯註在Aelf之後還有一個叫[Limbo](http://doc.cat-v.org/inferno/4th_edition/limbo_language/)的編程語言Go語言從其中借鑒了很多特性。 具體請參考Pike的講稿http://talks.golang.org/2012/concurrency.slide#9
Go語言的其他的一些特性零散地來自於其他一些編程語言比如iota語法是從APL語言借鑒詞法作用域與嵌套函數來自於Scheme語言和其他很多語言。當然我們也可以從Go中發現很多創新的設計。比如Go語言的切片爲動態數組提供了有效的隨機存取的性能這可能會讓人聯想到鏈表的底層的共享機製。還有Go語言新發明的defer語句。 Go語言的其他的一些特性零散地來自於其他一些編程語言比如iota語法是從[APL](https://en.wikipedia.org/wiki/APL_(programming_language))語言借鑒,詞法作用域與嵌套函數來自於[Scheme](https://en.wikipedia.org/wiki/Scheme_(programming_language))語言和其他很多語言。當然我們也可以從Go中發現很多創新的設計。比如Go語言的切片爲動態數組提供了有效的隨機存取的性能這可能會讓人聯想到鏈表的底層的共享機製。還有Go語言新發明的defer語句。

View File

@ -2,9 +2,9 @@
所有的編程語言都反映了語言設計者對編程哲學的反思通常包括之前的語言所暴露的一些不足地方的改進。Go項目是在Google公司維護超級複雜的幾個軟件繫統遇到的一些問題的反思但是這類問題絶不是Google公司所特有的 所有的編程語言都反映了語言設計者對編程哲學的反思通常包括之前的語言所暴露的一些不足地方的改進。Go項目是在Google公司維護超級複雜的幾個軟件繫統遇到的一些問題的反思但是這類問題絶不是Google公司所特有的
正如Rob Pike所説“軟件的複雜性是乘法級相關的”通過增加一個部分的複雜性來脩複問題通常將慢慢地增加其他部分的複雜性。通過增加功能和選項和配置是脩複問題的最快的途徑但是這很容易讓人忘記簡潔的內涵卽使從長遠來看簡潔依然是好軟件的關鍵因素。 正如[Rob Pike](http://genius.cat-v.org/rob-pike/)所説,“軟件的複雜性是乘法級相關的”,通過增加一個部分的複雜性來脩複問題通常將慢慢地增加其他部分的複雜性。通過增加功能和選項和配置是脩複問題的最快的途徑,但是這很容易讓人忘記簡潔的內涵,卽使從長遠來看,簡潔依然是好軟件的關鍵因素。
簡潔的設計需要在工作開始的時候舍棄不必要的想法併且在軟件的生命週期內嚴格區别好的改變或壞的改變。通過足夠的努力一個好的改變可以在不破壞原有完整概念的前提下保持自適應正如Fred Brooks所説的“概念完整性”而一個壞的改變則不能達到這個效果它們僅僅是通過膚淺的和簡單的妥協來破壞原有設計的一致性。隻有通過簡潔的設計才能讓一個繫統保持穩定、安全和持續的進化。 簡潔的設計需要在工作開始的時候舍棄不必要的想法,併且在軟件的生命週期內嚴格區别好的改變或壞的改變。通過足夠的努力,一個好的改變可以在不破壞原有完整概念的前提下保持自適應,正如[Fred Brooks](http://www.cs.unc.edu/~brooks/)所説的“概念完整性”;而一個壞的改變則不能達到這個效果,它們僅僅是通過膚淺的和簡單的妥協來破壞原有設計的一致性。隻有通過簡潔的設計,才能讓一個繫統保持穩定、安全和持續的進化。
Go項目包括編程語言本身附帶了相關的工具和標準庫最後但併非代表不重要的關於簡潔編程哲學的宣言。就事後諸葛的角度來看Go語言的這些地方都做的還不錯擁有自動垃圾迴收、一個包繫統、函數作爲一等公民、詞法作用域、繫統調用接口、隻讀的UTF8字符串等。但是Go語言本身隻有很少的特性也不太可能添加太多的特性。例如它沒有隱式的數值轉換沒有構造函數和析構函數沒有運算符重載沒有默認參數也沒有繼承沒有泛型沒有異常沒有宏沒有函數脩飾更沒有線程局部存儲。但是語言本身是成熟和穩定的而且承諾保證向後兼容用之前的Go語言編寫程序可以用新版本的Go語言編譯器和標準庫直接構建而不需要脩改代碼。 Go項目包括編程語言本身附帶了相關的工具和標準庫最後但併非代表不重要的關於簡潔編程哲學的宣言。就事後諸葛的角度來看Go語言的這些地方都做的還不錯擁有自動垃圾迴收、一個包繫統、函數作爲一等公民、詞法作用域、繫統調用接口、隻讀的UTF8字符串等。但是Go語言本身隻有很少的特性也不太可能添加太多的特性。例如它沒有隱式的數值轉換沒有構造函數和析構函數沒有運算符重載沒有默認參數也沒有繼承沒有泛型沒有異常沒有宏沒有函數脩飾更沒有線程局部存儲。但是語言本身是成熟和穩定的而且承諾保證向後兼容用之前的Go語言編寫程序可以用新版本的Go語言編譯器和標準庫直接構建而不需要脩改代碼。

View File

@ -1,14 +1,14 @@
## 致謝 ## 致謝
Rob Pike和Russ Cox以及很多其他Go糰隊的覈心成員多次仔細閲讀了本書的手稿他們對本書的組織結構和表述用詞等給出了很多寶貴的建議。在準備日文版翻譯的時候Yoshiki Shibata更是仔細地審閲了本書的每個部分及時發現了諸多英文和代碼的錯誤。我們非常感謝本書的每一位審閲者併感謝對本書給出了重要的建議的Brian Goetz、Corey Kosak、Arnold Robbins、Josh Bleecher Snyder和Peter Weinberger等人。 [Rob Pike](http://genius.cat-v.org/rob-pike/)[Russ Cox](http://research.swtch.com/)以及很多其他Go糰隊的覈心成員多次仔細閲讀了本書的手稿他們對本書的組織結構和表述用詞等給出了很多寶貴的建議。在準備日文版翻譯的時候Yoshiki Shibata更是仔細地審閲了本書的每個部分及時發現了諸多英文和代碼的錯誤。我們非常感謝本書的每一位審閲者併感謝對本書給出了重要的建議的Brian Goetz、Corey Kosak、Arnold Robbins、Josh Bleecher Snyder和Peter Weinberger等人。
我們還感謝Sameer Ajmani、Ittai Balaban、David Crawshaw、Billy Donohue、Jonathan Feinberg、Andrew Gerrand、Robert Griesemer、John Linderman、Minux Ma譯註中国人Go糰隊成員。、Bryan Mills、Bala Natarajan、Cosmos Nicolaou、Paul Staniforth、Nigel Tao譯註好像是陶哲軒的兄弟以及Howard Trickey給出的許多有價值的建議。我們還要感謝David Brailsford和Raph Levien關於類型設置的建議。 我們還感謝Sameer Ajmani、Ittai Balaban、David Crawshaw、Billy Donohue、Jonathan Feinberg、Andrew Gerrand、Robert Griesemer、John Linderman、Minux Ma譯註中国人Go糰隊成員。、Bryan Mills、Bala Natarajan、Cosmos Nicolaou、Paul Staniforth、Nigel Tao譯註好像是陶哲軒的兄弟以及Howard Trickey給出的許多有價值的建議。我們還要感謝David Brailsford和Raph Levien關於類型設置的建議。
我們的來自Addison-Wesley的編輯Greg Doench收到了很多幫助從最開始就得到了越來越多的幫助。來自AW生産糰隊的John Fuller、Dayna Isley、Julie Nahil、Chuti Prasertsith到Barbara Wood感謝你們的熱心幫助。 我們的來自Addison-Wesley的編輯Greg Doench收到了很多幫助從最開始就得到了越來越多的幫助。來自AW生産糰隊的John Fuller、Dayna Isley、Julie Nahil、Chuti Prasertsith到Barbara Wood感謝你們的熱心幫助。
Alan Donovan特别感謝Sameer Ajmani、Chris Demetriou、Walt Drummond和Google公司的Reid Tatge允許他有充裕的時間去寫本書感謝Stephen Donovan的建議和始終如一的鼓勵以及他的妻子Leila Kazemi併沒有讓他爲了家庭瑣事而分心併熱情堅定地支持這個項目。 [Alan Donovan](https://github.com/adonovan)特别感謝Sameer Ajmani、Chris Demetriou、Walt Drummond和Google公司的Reid Tatge允許他有充裕的時間去寫本書感謝Stephen Donovan的建議和始終如一的鼓勵以及他的妻子Leila Kazemi併沒有讓他爲了家庭瑣事而分心併熱情堅定地支持這個項目。
Brian Kernighan特别感謝朋友和同事對他的耐心和寬容讓他慢慢地梳理本書的寫作思路。同時感謝他的妻子Meg和其他很多朋友對他寫作事業的支持。 [Brian Kernighan](http://www.cs.princeton.edu/~bwk/)特别感謝朋友和同事對他的耐心和寬容讓他慢慢地梳理本書的寫作思路。同時感謝他的妻子Meg和其他很多朋友對他寫作事業的支持。
2015年 10月 於 紐約 2015年 10月 於 紐約

View File

@ -1,6 +1,6 @@
## 1.1. Hello, World ## 1.1. Hello, World
我們以1978年出版的C語言聖經《The C Programming Language》中經典的“hello world”案例來開始吧譯註本書作者之一Brian W. Kernighan也是C語言聖經一書的作者。C語言對Go語言的設計産生了很多影響。用這個例子我們來講解一些Go語言的覈心特性 我們以1978年出版的C語言聖經[《The C Programming Language》](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)中經典的“hello world”案例來開始吧譯註本書作者之一Brian W. Kernighan也是C語言聖經一書的作者。C語言對Go語言的設計産生了很多影響。用這個例子我們來講解一些Go語言的覈心特性
```go ```go
gopl.io/ch1/helloworld gopl.io/ch1/helloworld

View File

@ -12,7 +12,7 @@ type 類型名字 底層類型
類型聲明語句一般出現在包一級,因此如果新創建的類型名字的首字符大寫,則在外部包也可以使用。 類型聲明語句一般出現在包一級,因此如果新創建的類型名字的首字符大寫,則在外部包也可以使用。
譯註對於中文漢字Unicode標誌都作爲小寫字母處理因此中文的命名默認不能導出不過国內的用戶針對該問題提出了我們自己的間接根據RobPike的迴複在Go2中有可能會將中日韓等字符當作大寫字母處理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的迴複: 譯註對於中文漢字Unicode標誌都作爲小寫字母處理因此中文的命名默認不能導出不過国內的用戶針對該問題提出了不同的看法根據RobPike的迴複在Go2中有可能會將中日韓等字符當作大寫字母處理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的迴複:
> A solution that's been kicking around for a while: > A solution that's been kicking around for a while:
> >

View File

@ -1,3 +1,117 @@
## 5.3. 多返迴值 ## 5.3. 多返迴值
TODO 在Go中一個函數可以返迴多個值。我們已經在之前例子中看到許多標準庫中的函數返迴2個值一個是期望得到的返迴值另一個是函數出錯時的錯誤信息。下面的例子會展示如何編寫多返迴值的函數。
下面的程序是findlinks的改進版本。脩改後的findlinks可以自己發起HTTP請求這樣我們就不必再運行fetch。因爲HTTP請求和解析操作可能會失敗因此findlinks聲明了2個返迴值鏈接列表和錯誤信息。一般而言HTML的解析器可以處理HTML頁面的錯誤結點構造出HTML頁面結構所以解析HTML很少失敗。這意味着如果findlinks函數失敗了很可能是由於I/O的錯誤導致的。
```Go
gopl.io/ch5/findlinks2
func main() {
for _, url := range os.Args[1:] {
links, err := findLinks(url)
if err != nil {
fmt.Fprintf(os.Stderr, "findlinks2: %v\n", err)
continue
}
for _, link := range links {
fmt.Println(link)
}
}
}
// findLinks performs an HTTP GET request for url, parses the
// response as HTML, and extracts and returns the links.
func findLinks(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
}
return visit(nil, doc), nil
}
```
在findlinks中有4處return語句每一處return都返迴了一組值。前三處return將http和html包中的錯誤信息傳遞給findlinks的調用者。第一處return直接返迴錯誤信息其他兩處通過fmt.Errorf§7.8輸出詳細的錯誤信息。如果findlinks成功結束最後的return語句將一組解析獲得的連接返迴給用戶。
在finallinks中我們必鬚確保resp.Body被關閉釋放網絡資源。雖然Go的垃圾迴收機製會迴收不被使用的內存但是這不包括操作繫統層面的資源比如打開的文件、網絡連接。因此我們必鬚顯式的釋放這些資源。
調用多返迴值函數時,返迴給調用者的是一組值,調用者必鬚顯式的將這些值分配給變量:
```Go
links, err := findLinks(url)
```
如果某個值不被使用可以將其分配給blank identifier:
```Go
links, _ := findLinks(url) // errors ignored
```
一個函數內部可以將另一個有多返迴值的函數作爲返迴值下面的例子展示了與findLinks有相同功能的函數兩者的區别在於下面的例子先輸出參數
```Go
func findLinksLog(url string) ([]string, error) {
log.Printf("findLinks %s", url)
return findLinks(url)
}
```
當你調用接受多參數的函數時可以將一個返迴多參數的函數作爲該函數的參數。雖然這很少出現在實際生産代碼中但這個特性在debug時很方便我們隻需要一條語句就可以輸出所有的返迴值。下面的代碼是等價的
```Go
log.Println(findLinks(url))
links, err := findLinks(url)
log.Println(links, err)
```
準確的變量名可以傳達函數返迴值的含義。尤其在返迴值的類型都相同時,就像下面這樣:
```Go
func Size(rect image.Rectangle) (width, height int)
func Split(path string) (dir, file string)
func HourMinSec(t time.Time) (hour, minute, second int)
```
雖然良好的命名很重要但你也不必爲每一個返迴值都取一個適當的名字。比如按照慣例函數的最後一個bool類型的返迴值表示函數是否運行成功error類型的返迴值代表函數的錯誤信息對於這些類似的慣例我們不必思考合適的命名它們都無需解釋。
如果一個函數將所有的返迴值都顯示的變量名那麽該函數的return語句可以省略操作數。這稱之爲bare return。
```Go
// CountWordsAndImages does an HTTP GET request for the HTML
// document url and returns the number of words and images in it.
func CountWordsAndImages(url string) (words, images int, err error) {
resp, err := http.Get(url)
if err != nil {
return
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
err = fmt.Errorf("parsing HTML: %s", err)
return
}
words, images = countWordsAndImages(doc)
return
}
func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
```
按照返迴值列表的次序返迴所有的返迴值在上面的例子中每一個return語句等價於
```Go
return words, images, err
```
當一個函數有多處return語句以及許多返迴值時bare return 可以減少代碼的重複但是使得代碼難以被理解。舉個例子如果你沒有仔細的審査代碼很難發現前2處return等價於 return 0,0,errGo會將返迴值 words和images在函數體的開始處根據它們的類型將其初始化爲0最後一處return等價於 return wordsimagenil。基於以上原因不宜過度使用bare return。
**練習 5.5** 實現countWordsAndImages。參考練習4.9如何分詞)
**練習 5.6** 脩改gopl.io/ch3/surface (§3.2) 中的corner函數將返迴值命名併使用bare return。

View File

@ -1,3 +1,3 @@
### 5.4.2. 文件結尾EOF ### 5.4.2. 文件結尾錯誤EOF
TODO TODO

View File

@ -4,4 +4,4 @@ TODO
{% include "./ch5-04-1.md" %} {% include "./ch5-04-1.md" %}
{% include "./ch5-04-1.md" %} {% include "./ch5-04-2.md" %}

View File

@ -1,5 +1,7 @@
## 7.4. flag.Value接口 ## 7.4. flag.Value接口
在本章我們會學到另一個標準的接口類型flag.Value是怎麽幫助命令行標記定義新的符號的。思考下面這個會休眠特定時間的程序 在本章我們會學到另一個標準的接口類型flag.Value是怎麽幫助命令行標記定義新的符號的。思考下面這個會休眠特定時間的程序
```go ```go
// gopl.io/ch7/sleep // gopl.io/ch7/sleep
var period = flag.Duration("period", 1*time.Second, "sleep period") var period = flag.Duration("period", 1*time.Second, "sleep period")
@ -11,13 +13,17 @@ func main() {
fmt.Println() fmt.Println()
} }
``` ```
在它休眠前它會打印出休眠的時間週期。fmt包調用time.Duration的String方法打印這個時間週期是以用戶友好的註解方式而不是一個納秒數字 在它休眠前它會打印出休眠的時間週期。fmt包調用time.Duration的String方法打印這個時間週期是以用戶友好的註解方式而不是一個納秒數字
``` ```
$ go build gopl.io/ch7/sleep $ go build gopl.io/ch7/sleep
$ ./sleep $ ./sleep
Sleeping for 1s... Sleeping for 1s...
``` ```
默認情況下,休眠週期是一秒,但是可以通過 -period 這個命令行標記來控製。flag.Duration函數創建一個time.Duration類型的標記變量併且允許用戶通過多種用戶友好的方式來設置這個變量的大小這種方式還包括和String方法相同的符號排版形式。這種對稱設計使得用戶交互良好。 默認情況下,休眠週期是一秒,但是可以通過 -period 這個命令行標記來控製。flag.Duration函數創建一個time.Duration類型的標記變量併且允許用戶通過多種用戶友好的方式來設置這個變量的大小這種方式還包括和String方法相同的符號排版形式。這種對稱設計使得用戶交互良好。
``` ```
$ ./sleep -period 50ms $ ./sleep -period 50ms
Sleeping for 50ms... Sleeping for 50ms...
@ -28,7 +34,9 @@ Sleeping for 1h30m0s...
$ ./sleep -period "1 day" $ ./sleep -period "1 day"
invalid value "1 day" for flag -period: time: invalid duration 1 day invalid value "1 day" for flag -period: time: invalid duration 1 day
``` ```
因爲時間週期標記值非常的有用所以這個特性被構建到了flag包中但是我們爲我們自己的數據類型定義新的標記符號是簡單容易的。我們隻需要定義一個實現flag.Value接口的類型如下 因爲時間週期標記值非常的有用所以這個特性被構建到了flag包中但是我們爲我們自己的數據類型定義新的標記符號是簡單容易的。我們隻需要定義一個實現flag.Value接口的類型如下
```go ```go
package flag package flag
@ -38,9 +46,11 @@ type Value interface {
Set(string) error Set(string) error
} }
``` ```
String方法格式化標記的值用在命令行幫組消息中這樣每一個flag.Value也是一個fmt.Stringer。Set方法解析它的字符串參數併且更新標記變量的值。實際上Set方法和String是兩個相反的操作所以最好的辦法就是對他們使用相同的註解方式。 String方法格式化標記的值用在命令行幫組消息中這樣每一個flag.Value也是一個fmt.Stringer。Set方法解析它的字符串參數併且更新標記變量的值。實際上Set方法和String是兩個相反的操作所以最好的辦法就是對他們使用相同的註解方式。
讓我們定義一個允許通過攝氏度或者華氏溫度變換的形式指定溫度的celsiusFlag類型。註意celsiusFlag內嵌了一個Celsius類型(§2.5)因此不用實現本身就已經有String方法了。爲了實現flag.Value我們隻需要定義Set方法 讓我們定義一個允許通過攝氏度或者華氏溫度變換的形式指定溫度的celsiusFlag類型。註意celsiusFlag內嵌了一個Celsius類型(§2.5)因此不用實現本身就已經有String方法了。爲了實現flag.Value我們隻需要定義Set方法
```go ```go
// gopl.io/ch7/tempconv // gopl.io/ch7/tempconv
// *celsiusFlag satisfies the flag.Value interface. // *celsiusFlag satisfies the flag.Value interface.
@ -61,9 +71,11 @@ func (f *celsiusFlag) Set(s string) error {
return fmt.Errorf("invalid temperature %q", s) return fmt.Errorf("invalid temperature %q", s)
} }
``` ```
調用fmt.Sscanf函數從輸入s中解析一個浮點數value和一個字符串unit。雖然通常必鬚檢査Sscanf的錯誤返迴但是在這個例子中我們不需要因爲如果有錯誤發生就沒有switch case會匹配到。 調用fmt.Sscanf函數從輸入s中解析一個浮點數value和一個字符串unit。雖然通常必鬚檢査Sscanf的錯誤返迴但是在這個例子中我們不需要因爲如果有錯誤發生就沒有switch case會匹配到。
下面的CelsiusFlag函數將所有邏輯都封裝在一起。它返迴一個內嵌在celsiusFlag變量f中的Celsius指針給調用者。Celsius字段是一個會通過Set方法在標記處理的過程中更新的變量。調用Var方法將標記加入應用的命令行標記集合中有異常複雜命令行接口的全局變量flag.CommandLine.Programs可能有幾個這個類型的變量。調用Var方法將一個*celsiusFlag參數賦值給一個flag.Value參數,導致編譯器去檢査*celsiusFlag是否有必鬚的方法。 下面的CelsiusFlag函數將所有邏輯都封裝在一起。它返迴一個內嵌在celsiusFlag變量f中的Celsius指針給調用者。Celsius字段是一個會通過Set方法在標記處理的過程中更新的變量。調用Var方法將標記加入應用的命令行標記集合中有異常複雜命令行接口的全局變量flag.CommandLine.Programs可能有幾個這個類型的變量。調用Var方法將一個*celsiusFlag參數賦值給一個flag.Value參數,導致編譯器去檢査*celsiusFlag是否有必鬚的方法。
```go ```go
// CelsiusFlag defines a Celsius flag with the specified name, // CelsiusFlag defines a Celsius flag with the specified name,
// default value, and usage, and returns the address of the flag variable. // default value, and usage, and returns the address of the flag variable.
@ -74,7 +86,9 @@ func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
return &f.Celsius return &f.Celsius
} }
``` ```
現在我們可以開始在我們的程序中使用新的標記: 現在我們可以開始在我們的程序中使用新的標記:
```go ```go
// gopl.io/ch7/tempflag // gopl.io/ch7/tempflag
var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature") var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature")
@ -84,7 +98,9 @@ func main() {
fmt.Println(*temp) fmt.Println(*temp)
} }
``` ```
下面是典型的場景: 下面是典型的場景:
``` ```
$ go build gopl.io/ch7/tempflag $ go build gopl.io/ch7/tempflag
$ ./tempflag $ ./tempflag
@ -103,5 +119,7 @@ Usage of ./tempflag:
-temp value -temp value
the temperature (default 20°C) the temperature (default 20°C)
``` ```
練習7.6:對tempFlag加入支持開爾文溫度。
練習7.7:解釋爲什麽幫助信息在它的默認值是20.0沒有包含°C的情況下輸出了°C。 **練習 7.6** 對tempFlag加入支持開爾文溫度。
**練習 7.7** 解釋爲什麽幫助信息在它的默認值是20.0沒有包含°C的情況下輸出了°C。

View File

@ -1,10 +1,10 @@
### 8.4.1. 不帶緩存的Channels ### 8.4.1. 不帶緩存的Channels
一個基於無緩存Channels的發送操作將導致發送者goroutine阻塞直到另一個goroutine在相同的Channels上執行接收操作當發送的值通過Channels成功傳輸之後兩個goroutine可以繼續執行後面的語句。反之如果接收操作先發那麽接收者goroutine也將阻塞直到有另一個goroutine在相同的Channels上執行發送操作。 一個基於無緩存Channels的發送操作將導致發送者goroutine阻塞直到另一個goroutine在相同的Channels上執行接收操作當發送的值通過Channels成功傳輸之後兩個goroutine可以繼續執行後面的語句。反之如果接收操作先發那麽接收者goroutine也將阻塞直到有另一個goroutine在相同的Channels上執行發送操作。
基於無緩存Channels的發送和接收操作將導致兩個goroutine做一次同步操作。因爲這個原因無緩存Channels有時候也被稱爲同步Channels。當通過一個無緩存Channels發送數據時接收者收到數據發生在喚醒發送者goroutine之前譯註*happens before*這是Go語言併發內存模型的一個關鍵術語 基於無緩存Channels的發送和接收操作將導致兩個goroutine做一次同步操作。因爲這個原因無緩存Channels有時候也被稱爲同步Channels。當通過一個無緩存Channels發送數據時接收者收到數據發生在喚醒發送者goroutine之前譯註*happens before*這是Go語言併發內存模型的一個關鍵術語
在討論併發編程時當我們説x事件在y事件之前發生*happens before*我們併不是説x事件在時間上比y時間更早我們要表達的意思是要保證在此之前事件都已經完成了例如在此之前的更新某些變量的操作已經完成你可以放心依賴這些保證了。 在討論併發編程時當我們説x事件在y事件之前發生*happens before*我們併不是説x事件在時間上比y時間更早我們要表達的意思是要保證在此之前事件都已經完成了,例如在此之前的更新某些變量的操作已經完成,你可以放心依賴這些已完成的事件了。
當我們説x事件旣不是在y事件之前發生也不是在y事件之後發生我們就説x事件和y事件是併發的。這併不是意味着x事件和y事件就一定是同時發生的我們隻是不能確定這兩個事件發生的先後順序。在下一章中我們將看到當兩個goroutine併發訪問了相同的變量時我們有必要保證某些事件的執行順序以避免出現某些併發問題。 當我們説x事件旣不是在y事件之前發生也不是在y事件之後發生我們就説x事件和y事件是併發的。這併不是意味着x事件和y事件就一定是同時發生的我們隻是不能確定這兩個事件發生的先後順序。在下一章中我們將看到當兩個goroutine併發訪問了相同的變量時我們有必要保證某些事件的執行順序以避免出現某些併發問題。
@ -35,5 +35,5 @@ func main() {
基於channels發送消息有兩個重要方面。首先每個消息都有一個值但是有時候通訊的事實和發生的時刻也同樣重要。當我們更希望強調通訊發生的時刻時我們將它稱爲**消息事件**。有些消息事件併不攜帶額外的信息它僅僅是用作兩個goroutine之間的同步這時候我們可以用`struct{}`空結構體作爲channels元素的類型雖然也可以使用bool或int類型實現同樣的功能`done <- 1`語句也比`done <- struct{}{}`更短 基於channels發送消息有兩個重要方面。首先每個消息都有一個值但是有時候通訊的事實和發生的時刻也同樣重要。當我們更希望強調通訊發生的時刻時我們將它稱爲**消息事件**。有些消息事件併不攜帶額外的信息它僅僅是用作兩個goroutine之間的同步這時候我們可以用`struct{}`空結構體作爲channels元素的類型雖然也可以使用bool或int類型實現同樣的功能`done <- 1`語句也比`done <- struct{}{}`更短
**練習 8.3** 在netcat3例子中conn雖然是一個interface類型的值但是其底層眞實類型是`*net.TCPConn`代表一個TCP鏈接。一個TCP鏈接有讀和寫兩個部分可以使用CloseRead和CloseWrite方法分别關閉它們。脩改netcat3的主goroutine代碼隻關閉網絡鏈接中寫的部分這樣的話後台goroutine可以在標準輸入被關閉後繼續打印從reverb1服務器傳迴的數據。要在reverb2服務器也完成同樣的功能是比較睏難的參考**練習 8.4** **練習 8.3** 在netcat3例子中conn雖然是一個interface類型的值但是其底層眞實類型是`*net.TCPConn`代表一個TCP鏈接。一個TCP鏈接有讀和寫兩個部分可以使用CloseRead和CloseWrite方法分别關閉它們。脩改netcat3的主goroutine代碼隻關閉網絡鏈接中寫的部分這樣的話後台goroutine可以在標準輸入被關閉後繼續打印從reverb1服務器傳迴的數據。要在reverb2服務器也完成同樣的功能是比較睏難的參考**練習 8.4**

View File

@ -1,3 +1,102 @@
### 8.4.2. 管道Pipeline ### 8.4.2. 串聯的ChannelsPipeline
Channels也可以用於將多個goroutine鏈接在一起一個Channels的輸出作爲下一個Channels的輸入。這種串聯的Channels就是所謂的管道pipeline。下面的程序用兩個channels將三個goroutine串聯起來如圖8.1所示。
![](../images/ch8-01.png)
第一個goroutine是一個計數器用於生成0、1、2、……形式的整數序列然後通過channel將該整數序列發送給第二個goroutine第二個goroutine是一個求平方的程序對收到的每個整數求平方然後將平方後的結果通過第二個channel發送給第三個goroutine第三個goroutine是一個打印程序打印收到的每個整數。爲了保持例子清晰我們有意選擇了非常簡單的函數當然三個goroutine的計算很簡單在現實中確實沒有必要爲如此簡單的運算構建三個goroutine。
```Go
gopl.io/ch8/pipeline1
func main() {
naturals := make(chan int)
squares := make(chan int)
// Counter
go func() {
for x := 0; ; x++ {
naturals <- x
}
}()
// Squarer
go func() {
for {
x := <-naturals
squares <- x * x
}
}()
// Printer (in main goroutine)
for {
fmt.Println(<-squares)
}
}
```
如您所料上面的程序將生成0、1、4、9、……形式的無窮數列。像這樣的串聯Channels的管道Pipelines可以用在需要長時間運行的服務中每個長時間運行的goroutine可能會包含一個死循環在不同goroutine的死循環內部使用串聯的Channels來通信。但是如果我們希望通過Channels隻發送有限的數列該如何處理呢
如果發送者知道沒有更多的值需要發送到channel的話那麽讓接收者也能及時知道沒有多餘的值可接收將是有用的因爲接收者可以停止不必要的接收等待。這可以通過內置的close函數來關閉channel實現
```Go
close(naturals)
```
當一個channel被關閉後再向該channel發送數據將導致panic異常。當一個被關閉的channel中已經發送的數據都被成功接收後後續的接收操作將不再阻塞它們會立卽返迴一個零值。關閉上面例子中的naturals變量對應的channel併不能終止循環它依然會收到一個永無休止的零值序列然後將它們發送給打印者goroutine。
沒有辦法直接測試一個channel是否被關閉但是接收操作有一個變體形式它多接收一個結果多接收的第二個結果是一個布爾值okture表示成功從channels接收到值false表示channels已經被關閉併且里面沒有值可接收。使用這個特性我們可以脩改squarer函數中的循環代碼當naturals對應的channel被關閉併沒有值可接收時跳出循環併且也關閉squares對應的channel.
```Go
// Squarer
go func() {
for {
x, ok := <-naturals
if !ok {
break // channel was closed and drained
}
squares <- x * x
}
close(squares)
}()
```
因爲上面的語法是笨拙的而且這種處理模式很場景因此Go語言的range循環可直接在channels上面迭代。使用range循環是上面處理模式的簡潔語法它依次從channel接收數據當channel被關閉併且沒有值可接收時跳出循環。
在下面的改進中我們的計數器goroutine隻生成100個含數字的序列然後關閉naturals對應的channel這將導致計算平方數的squarer對應的goroutine可以正常終止循環併關閉squares對應的channel。在一個更複雜的程序中可以通過defer語句關閉對應的channel。最後主goroutine也可以正常終止循環併退出程序。
```Go
gopl.io/ch8/pipeline2
func main() {
naturals := make(chan int)
squares := make(chan int)
// Counter
go func() {
for x := 0; x < 100; x++ {
naturals <- x
}
close(naturals)
}()
// Squarer
go func() {
for x := range naturals {
squares <- x * x
}
close(squares)
}()
// Printer (in main goroutine)
for x := range squares {
fmt.Println(x)
}
}
```
其實你併不需要關閉每一個channel。隻要當需要告訴接收者goroutine所有的數據已經全部發送時才需要關閉channel。不管一個channel是否被關閉當它沒有被引用時將會被Go語言的垃圾自動迴收器迴收。不要將關閉一個打開文件的操作和關閉一個channel操作混淆。對於每個打開的文件都需要在不使用的使用調用對應的Close方法來關閉文件。
視圖重複關閉一個channel將導致panic異常視圖關閉一個nil值的channel也將導致panic異常。關閉一個channels還會觸發一個廣播機製我們將在8.9節討論。
TODO

View File

@ -1,3 +1,54 @@
### 8.4.3. 單向的Channels ### 8.4.3. 單方向的Channel
隨着程序的增長人們習慣於將大的函數拆分爲小的函數。我們前面的例子中使用了三個goroutine然後用兩個channels連鏈接它們它們都是main函數的局部變量。將三個goroutine拆分爲以下三個函數是自然的想法
```Go
func counter(out chan int)
func squarer(out, in chan int)
func printer(in chan int)
```
其中squarer計算平方的函數在兩個串聯Channels的中間因此擁有兩個channels類型的參數一個用於輸入一個用於輸出。每個channels都用有相同的類型但是它們的使用方式想反一個隻用於接收另一個隻用於發送。參數的名字in和out已經明確表示了這個意圖但是併無法保證squarer函數向一個in參數對應的channels發送數據或者從一個out參數對應的channels接收數據。
這種場景是典型的。當一個channel作爲一個函數參數是它一般總是被專門用於隻發送或者隻接收。
爲了表明這種意圖併防止被濫用Go語言的類型繫統提供了單方向的channel類型分别用於隻發送或隻接收的channel。類型`chan<- int`表示一個隻發送int的channel隻能發送不能接收相反類型`<-chan int`表示一個隻接收int的channel隻能接收不能發送箭頭`<-`和關鍵字chan的相對位置表明了channel的方向這種限製將在編譯期檢測
因爲關閉操作隻用於斷言不再向channel發送新的數據所以隻有在發送者所在的goroutine才會調用close函數因此對一個隻接收的channel調用close將是一個編譯錯誤。
這是改進的版本這一次參數使用了單方向channel類型
```Go
gopl.io/ch8/pipeline3
func counter(out chan<- int) {
for x := 0; x < 100; x++ {
out <- x
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for v := range in {
out <- v * v
}
close(out)
}
func printer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
}
func main() {
naturals := make(chan int)
squares := make(chan int)
go counter(naturals)
go squarer(squares, naturals)
printer(squares)
}
```
調用counter(naturals)將導致將`chan int`類型的naturals隱式地轉換爲`chan<- int`類型隻發送型的channel調用printer(squares)也會導致相似的隱式轉換這一次是轉換爲`<-chan int`類型隻接收型的channel任何雙向channel向單向channel變量的賦值操作都將導致該隱式轉換這里併沒有反向轉換的語法也就是不能一個將類似`chan<- int`類型的單向型的channel轉換爲`chan int`類型的雙向型的channel
TODO

View File

@ -1,3 +1,85 @@
### 8.4.4. 帶緩存的Channels ### 8.4.4. 帶緩存的Channels
TODO 帶緩存的Channel內部持有一個元素隊列。隊列的最大容量是在調用make函數創建channel時通過第二個參數指定的。下面的語句創建了一個可以持有三個字符串元素的帶緩存Channel。圖8.2是ch變量對應的channel的圖形表示形式。
```Go
ch = make(chan string, 3)
```
![](../images/ch8-02.png)
向緩存Channel的發送操作就是向內部緩存隊列的尾部插入原因接收操作則是從隊列的頭部刪除元素。如果內部緩存隊列是滿的那麽發送操作將阻塞直到因另一個goroutine執行接收操作而釋放了新的隊列空間。相反如果channel是空的接收操作將阻塞直到有另一個goroutine執行發送操作而向隊列插入元素。
我們可以在無阻塞的情況下連續向新創建的channel發送三個值
```Go
ch <- "A"
ch <- "B"
ch <- "C"
```
此刻channel的內部緩存隊列將是滿的圖8.3),如果有第四個發送操作將發生阻塞。
![](../images/ch8-03.png)
如果我們接收一個值,
```Go
fmt.Println(<-ch) // "A"
```
那麽channel的緩存隊列將不是滿的也不是空的圖8.4因此對該channel執行的發送或接收操作都不會發送阻塞。通過這種方式channel的緩存隊列解耦了接收和發送的goroutine。
![](../images/ch8-04.png)
在某些特殊情況下程序可能需要知道channel內部緩存的容量可以用內置的cap函數獲取
```Go
fmt.Println(cap(ch)) // "3"
```
同樣對於內置的len函數如果傳入的是channel那麽將返迴channel內部緩存隊列中有效元素的個數。因爲在併發程序中該信息會隨着接收操作而失效但是它對某些故障診斷和性能優化會有幫助。
```Go
fmt.Println(len(ch)) // "2"
```
在繼續執行兩次接收操作後channel內部的緩存隊列將又成爲空的如果有第四個接收操作將發生阻塞
```Go
fmt.Println(<-ch) // "B"
fmt.Println(<-ch) // "C"
```
在這個例子中發送和接收操作都發生在同一個goroutine中但是在眞是的程序中它們一般由不同的goroutine執行。Go語言新手有時候會將一個帶緩存的channel當作同一個goroutine中的隊列使用雖然語法看似簡單但實際上這是一個錯誤。Channel和goroutine的調度器機製是緊密相連的一個發送操作——或許是整個程序——可能會永遠阻塞。如果你隻是需要一個簡單的隊列使用slice就可以了。
下面的例子展示了一個使用了帶緩存channel的應用。它併發地向三個鏡像站點發出請求三個鏡像站點分散在不同的地理位置。它們分别將收到的響應發送到帶緩存channel最後接收者隻接收第一個收到的響應也就是最快的那個響應。因此mirroredQuery函數可能在另外兩個響應慢的鏡像站點響應之前就返迴了結果。順便説一下多個goroutines併發地向同一個channel發送數據或從同一個channel接收數據都是常見的用法。
```Go
func mirroredQuery() string {
responses := make(chan string, 3)
go func() { responses <- request("asia.gopl.io") }()
go func() { responses <- request("europe.gopl.io") }()
go func() { responses <- request("americas.gopl.io") }()
return <-responses // return the quickest response
}
func request(hostname string) (response string) { /* ... */ }
```
如果我們使用了無緩存的channel那麽兩個慢的goroutines將會因爲沒有人接收而被永遠卡住。這種情況稱爲goroutines洩漏這將是一個BUG。和垃圾變量不同洩漏的goroutines併不會被自動迴收因此確保每個不再需要的goroutine能正常退出是重要的。
關於無緩存或帶緩存channels之間的選擇或者是帶緩存channels的容量大小的選擇都可能影響程序的正確性。無緩存channel更強地保證了每個發送操作與相應的同步接收操作但是對於帶緩存channel這些操作是解耦的。同樣卽使我們知道將要發送到一個channel的信息的數量上限創建一個對應容量大小帶緩存channel也是不現實的因爲這要求在執行任何接收操作之前緩存所有已經發送的值。如果未能分配足夠的緩衝將導致程序死鎖。
Channel的緩存也可能影響程序的性能。想象一家蛋糕店有三個廚師一個烘焙一個上醣衣還有一個將每個蛋糕傳遞到它下一個廚師在生産線。在狹小的廚房空間環境每個廚師在完成蛋糕後必鬚等待下一個廚師已經準備好接受它這類似於在一個無緩存的channel上進行溝通。
如果在每個廚師之間有一個放置一個蛋糕的額外空間那麽每個廚師就可以將一個完成的蛋糕臨時放在那里而馬上進入下一個蛋糕在製作中這類似於將channel的緩存隊列的容量設置爲1。隻要每個廚師的平均工作效率相近那麽其中大部分的傳輸工作將是迅速的個體之間細小的效率差異將在交接過程中瀰補。如果廚師之間有更大的額外空間——也是就更大容量的緩存隊列——將可以在不停止生産線的前提下消除更大的效率波動例如一個廚師可以短暫地休息然後在加快趕上進度而不影響其其他人。
另一方面,如果生産線的前期階段一直快於後續階段,那麽它們之間的緩存在大部分時間都將是滿的。相反,如果後續階段比前期階段更快,那麽它們之間的緩存在大部分時間都將是空的。對於這類場景,額外的緩存併沒有帶來任何好處。
生産線的隱喻對於理解channels和goroutines的工作機製是很有幫助的。例如如果第二階段是需要精心製作的複雜操作一個廚師可能無法跟上第一個廚師的進度或者是無法滿足第階段廚師的需求。要解決這個問題我們可以雇傭另一個廚師來幫助完成第二階段的工作他執行相同的任務但是獨立工作。這類似於基於相同的channels創建另一個獨立的goroutine。
我們沒有太多的空間展示全部細節但是gopl.io/ch8/cake包模擬了這個蛋糕店可以通過不同的參數調整。它還對上面提到的幾種場景提供對應的基準測試§11.4

View File

@ -1,3 +1,11 @@
## 9.6. 競爭條件檢測 ## 9.6. 競爭條件檢測
TODO 卽使我們小心到不能再小心但在併發程序中犯錯還是太容易了。幸運的是Go的runtime和工具鏈爲我們裝備了一個複雜但好用的動態分析工具競爭檢査器(the race detector)。
隻要在go buildgo run或者go test命令後面加上-race的flag就會使編譯器創建一個你的應用的“脩改”版或者一個附帶了能夠記録所有運行期對共享變量訪問工具的test併且會記録下每一個讀或者寫共享變量的goroutine的身份信息。另外脩改版的程序會記録下所有的同步事件比如go語句channel操作以及對(\*sync.Mutex).Lock(\*sync.WaitGroup).Wait等等的調用。(完整的同步事件集合是在The Go Memory Model文檔中有説明該文檔是和語言文檔放在一起的。譯註https://golang.org/ref/mem)
競爭檢査器會檢査這些事件會尋找在哪一個goroutine中出現了這樣的case例如其讀或者寫了一個共享變量這個共享變量是被另一個goroutine在沒有進行榦預同步操作便直接寫入的。這種情況也就表明了是對一個共享變量的併發訪問卽數據競爭。這個工具會打印一份報告內容包含變量身份讀取和寫入的goroutine中活躍的函數的調用棧。這些信息在定位問題時通常很有用。9.7節中會有一個競爭檢査器的實戰樣例。
競爭檢査器會報告所有的已經發生的數據競爭。然而,它隻能檢測到運行時的競爭條件;併不能證明之後不會發生數據競爭。所以爲了使結果盡量正確,請保證你的測試併發地覆蓋到了你到包。
由於需要額外的記録因此構建時加了競爭檢測的程序跑起來會慢一些且需要更大的內存卽時是這樣這些代價對於很多生産環境的工作來説還是可以接受的。對於一些偶發的競爭條件來説讓競爭檢査器來榦活可以節省無數日夜的debugging。(譯註多少服務端C和C艹程序員爲此盡摺腰)

186
fixlinks.go Normal file
View File

@ -0,0 +1,186 @@
// Copyright 2015 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ingore
//
// 给特殊模式标定的单词增加链接.
//
// Example:
// fixlinks
// fixlinks dir "\.go$"
//
// Help:
// fixlinks -h
//
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"unicode/utf8"
)
const MaxFileSize = 8 << 20 // 8MB
const usage = `
Usage: fixlinks dir [nameFilter]
fixlinks -h
Example:
fixlinks
fixlinks dir "\.go$"
Report bugs to <chaishushan{AT}gmail.com>.
`
func main() {
if len(os.Args) < 2 || os.Args[1] == "-h" {
fmt.Fprintln(os.Stderr, usage[1:len(usage)-1])
os.Exit(0)
}
dir, nameFilter := os.Args[1], ".*"
if len(os.Args) > 2 {
nameFilter = os.Args[2]
}
total := 0
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Fatal("filepath.Walk: ", err)
return err
}
if info.IsDir() {
return nil
}
relpath, err := filepath.Rel(dir, path)
if err != nil {
log.Fatal("filepath.Rel: ", err)
return err
}
mathed, err := regexp.MatchString(nameFilter, relpath)
if err != nil {
log.Fatal("regexp.MatchString: ", err)
}
if mathed {
if changed := convertFile(path); changed {
fmt.Printf("%s\n", relpath)
total++
}
}
return nil
})
fmt.Printf("total %d\n", total)
}
func convertFile(path string) (changed bool) {
abspath, err := filepath.Abs(path)
if err != nil {
log.Fatal("convertFile: filepath.Abs:", err)
}
fi, err := os.Lstat(abspath)
if err != nil {
log.Fatal("convertFile: os.Lstat:", err)
}
if fi.Size() > MaxFileSize {
return false
}
oldData, err := ioutil.ReadFile(abspath)
if err != nil {
log.Fatal("convertFile: ioutil.ReadFile:", err)
}
if !utf8.Valid(oldData) {
return false
}
newData := append([]byte{}, oldData...)
for re, v := range _RegexpLinksTable {
newData = re.ReplaceAll(newData, []byte(v))
}
if string(newData) == string(oldData) {
return false
}
err = ioutil.WriteFile(abspath, newData, 0666)
if err != nil {
log.Fatal("convertFile: ioutil.WriteFile:", err)
}
return true
}
var _RegexpLinksTable = func() map[*regexp.Regexp]string {
const (
reHttp = `(https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|])?`
reWikiTail = `(\([-A-Za-z0-9+~_]+\))?`
)
m := make(map[*regexp.Regexp]string)
for k, v := range _LinkTable {
reKey := regexp.MustCompile(regexp.QuoteMeta(`[`+k+`]`) + `\(` + reHttp + reWikiTail + `\)`)
m[reKey] = fmt.Sprintf("[%s](%s)", k, v)
}
return m
}()
var _LinkTable = map[string]string{
// 人名
"Alan Donovan": "https://github.com/adonovan",
"Brian Kernighan": "http://www.cs.princeton.edu/~bwk/",
"Alan A. A. Donovan": "https://github.com/adonovan",
"Brian W. Kernighan": "http://www.cs.princeton.edu/~bwk/",
"Robert Griesemer": "http://research.google.com/pubs/author96.html",
"Rob Pike": "http://genius.cat-v.org/rob-pike/",
"Ken Thompson": "http://genius.cat-v.org/ken-thompson/",
"Russ Cox": "http://research.swtch.com/",
"Niklaus Wirth": "https://en.wikipedia.org/wiki/Niklaus_Wirth",
"Tony Hoare": "https://en.wikipedia.org/wiki/Tony_Hoare",
"Fred Brooks": "http://www.cs.unc.edu/~brooks/",
// 图书
"The C Programming Language": "http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html",
"The Practice of Programming": "https://en.wikipedia.org/wiki/The_Practice_of_Programming",
// Go语言
"Go": "https://golang.org/",
"Googles Go": "https://golang.org/",
"oracle": "https://godoc.org/golang.org/x/tools/oracle",
"godoc -analysis": "https://godoc.org/golang.org/x/tools/cmd/godoc",
"gorename": "https://godoc.org/golang.org/x/tools/cmd/gorename",
// 其他语言
"Alef": "http://doc.cat-v.org/plan_9/2nd_edition/papers/alef/",
"APL": "https://en.wikipedia.org/wiki/APL_(programming_language)",
"Limbo": "http://doc.cat-v.org/inferno/4th_edition/limbo_language/",
"Modula-2": "https://en.wikipedia.org/wiki/Modula-2",
"Newsqueak": "http://doc.cat-v.org/bell_labs/squeak/",
"Oberon": "https://en.wikipedia.org/wiki/Oberon_(programming_language)",
"Oberon-2": "https://en.wikipedia.org/wiki/Oberon-2_(programming_language)",
"Pascal": "https://en.wikipedia.org/wiki/Pascal_(programming_language)",
"Scheme": "https://en.wikipedia.org/wiki/Scheme_(programming_language)",
"Squeak": "http://doc.cat-v.org/bell_labs/squeak/",
// 系统
"Unix": "http://doc.cat-v.org/unix/",
"UNIX": "http://doc.cat-v.org/unix/",
"Linux": "http://www.linux.org/",
"FreeBSD": "https://www.freebsd.org/",
"OpenBSD": "http://www.openbsd.org/",
"Mac OSX": "http://www.apple.com/cn/osx/",
"Mac OS X": "http://www.apple.com/cn/osx/",
"Plan9": "http://plan9.bell-labs.com/plan9/",
"Microsoft Windows": "https://www.microsoft.com/zh-cn/windows/",
// 其他
"Bell Labs": "http://www.cs.bell-labs.com/",
"communicating sequential processes": "https://en.wikipedia.org/wiki/Communicating_sequential_processes",
"CSP": "https://en.wikipedia.org/wiki/Communicating_sequential_processes",
}

BIN
images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -23,7 +23,7 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本
*“Go是一個開源的編程語言它很容易用於構建簡單、可靠和高效的軟件。”摘自Go語言官方網站http://golang.org * *“Go是一個開源的編程語言它很容易用於構建簡單、可靠和高效的軟件。”摘自Go語言官方網站http://golang.org *
Go語言由來自Google公司的Robert GriesemerRob Pike和Ken Thompson三位大牛於2007年9月開始設計和實現然後於2009年的11月對外正式發布譯註關於Go語言的創世紀過程請參考 http://talks.golang.org/2015/how-go-was-made.slide )。語言及其配套工具的設計目標是具有表達力,高效的編譯和執行效率,有效地編寫高效和健壯的程序。 Go語言由來自Google公司的[Robert Griesemer](http://research.google.com/pubs/author96.html)[Rob Pike](http://genius.cat-v.org/rob-pike/)[Ken Thompson](http://genius.cat-v.org/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語言。它還從其他語言借鑒了很多好的想法同時避免引入過度的複雜性。
@ -36,8 +36,8 @@ Go語言尤其適合編寫網絡服務相關基礎設施同時也適合開發
因爲Go編寫的程序通常比腳本語言運行的更快也更安全而且很少會發生意外的類型錯誤。 因爲Go編寫的程序通常比腳本語言運行的更快也更安全而且很少會發生意外的類型錯誤。
Go語言還是一個開源的項目可以免費獲編譯器、庫、配套工具的源代碼。 Go語言還是一個開源的項目可以免費獲編譯器、庫、配套工具的源代碼。
Go語言的貢獻者來自一個活躍的全球社區。Go語言可以運行在類UNIX繫統—— Go語言的貢獻者來自一個活躍的全球社區。Go語言可以運行在類[UNIX](http://doc.cat-v.org/unix/)繫統——
比如Linux、FreeBSD、OpenBSD、Mac OSX——和Plan9繫統和Microsoft Windows操作繫統之上。 比如[Linux](http://www.linux.org/)[FreeBSD](https://www.freebsd.org/)[OpenBSD](http://www.openbsd.org/)[Mac OSX](http://www.apple.com/cn/osx/)——和[Plan9](http://plan9.bell-labs.com/plan9/)繫統和[Microsoft Windows](https://www.microsoft.com/zh-cn/windows/)操作繫統之上。
Go語言編寫的程序無需脩改就可以運行在上面這些環境。 Go語言編寫的程序無需脩改就可以運行在上面這些環境。
本書是爲了幫助你開始以有效的方式使用Go語言充分利用語言本身的特性和自帶的標準庫去編寫清晰地道的Go程序。 本書是爲了幫助你開始以有效的方式使用Go語言充分利用語言本身的特性和自帶的標準庫去編寫清晰地道的Go程序。

View File

@ -35,7 +35,7 @@
- [x] Chapter 5: Functions - [x] Chapter 5: Functions
- [x] 5.1 Function Declarations - [x] 5.1 Function Declarations
- [x] 5.2 Recursion - [x] 5.2 Recursion
- [ ] 5.3 Multiple Return Values - [x] 5.3 Multiple Return Values
- [ ] 5.4 Errors - [ ] 5.4 Errors
- [ ] 5.5 Function Values - [ ] 5.5 Function Values
- [ ] 5.6 Anonymous Functions - [ ] 5.6 Anonymous Functions
@ -54,7 +54,7 @@
- [x] 7.1 Interfaces as Contracts - [x] 7.1 Interfaces as Contracts
- [x] 7.2 Interface Types - [x] 7.2 Interface Types
- [x] 7.3 Interface Satisfaction - [x] 7.3 Interface Satisfaction
- [ ] 7.4 Parsing Flags with flag.Value - [x] 7.4 Parsing Flags with flag.Value
- [ ] 7.5 Interface Values - [ ] 7.5 Interface Values
- [ ] 7.6 Sorting with sort.Interface - [ ] 7.6 Sorting with sort.Interface
- [ ] 7.7 The http.Handler Interface - [ ] 7.7 The http.Handler Interface
@ -70,7 +70,7 @@
- [x] 8.1 Goroutines - [x] 8.1 Goroutines
- [x] 8.2 Example: Concurrent Clock Server - [x] 8.2 Example: Concurrent Clock Server
- [x] 8.3 Example: Concu rent Echo Server - [x] 8.3 Example: Concu rent Echo Server
- [ ] 8.4 Channels - [x] 8.4 Channels
- [x] 8.5 Looping in Parallel - [x] 8.5 Looping in Parallel
- [x] 8.6 Example: Concurrent Web Crawler - [x] 8.6 Example: Concurrent Web Crawler
- [x] 8.7 Multiplexing with select - [x] 8.7 Multiplexing with select
@ -83,7 +83,7 @@
- [x] 9.3 Read/Write Mutexes: sync.RWMutex - [x] 9.3 Read/Write Mutexes: sync.RWMutex
- [ ] 9.4 Memory Synchronization - [ ] 9.4 Memory Synchronization
- [ ] 9.5 Lazy Initialization: sync.Once - [ ] 9.5 Lazy Initialization: sync.Once
- [ ] 9.6 The Race Detector - [x] 9.6 The Race Detector
- [ ] 9.7 Example: Concurrent Non-Blocking Cache - [ ] 9.7 Example: Concurrent Non-Blocking Cache
- [ ] 9.8 Goroutines and Threads - [ ] 9.8 Goroutines and Threads
- [x] Chapter 10: Packages and the Go Tool - [x] Chapter 10: Packages and the Go Tool

102
tools/cpdir.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ingore
//
// Copy dir, support regexp.
//
// Example:
// cpdir src dst
// cpdir src dst "\.go$"
// cpdir src dst "\.tiff?$"
// cpdir src dst "\.tiff?|jpg|jpeg$"
//
// Help:
// cpdir -h
//
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
)
const usage = `
Usage: cpdir src dst [filter]
cpdir -h
Example:
cpdir src dst
cpdir src dst "\.go$"
cpdir src dst "\.tiff?$"
cpdir src dst "\.tiff?|jpg|jpeg$"
Report bugs to <chaishushan{AT}gmail.com>.
`
func main() {
if len(os.Args) < 3 {
fmt.Fprintln(os.Stderr, usage[1:len(usage)-1])
os.Exit(0)
}
filter := ".*"
if len(os.Args) > 3 {
filter = os.Args[3]
}
total := cpDir(os.Args[2], os.Args[1], filter)
fmt.Printf("total %d\n", total)
}
func cpDir(dst, src, filter string) (total int) {
entryList, err := ioutil.ReadDir(src)
if err != nil && !os.IsExist(err) {
log.Fatal("cpDir: ", err)
}
for _, entry := range entryList {
if entry.IsDir() {
cpDir(dst+"/"+entry.Name(), src+"/"+entry.Name(), filter)
} else {
mathed, err := regexp.MatchString(filter, entry.Name())
if err != nil {
log.Fatal("regexp.MatchString: ", err)
}
if mathed {
srcFname := filepath.Clean(src + "/" + entry.Name())
dstFname := filepath.Clean(dst + "/" + entry.Name())
fmt.Printf("copy %s\n", srcFname)
cpFile(dstFname, srcFname)
total++
}
}
}
return
}
func cpFile(dst, src string) {
err := os.MkdirAll(filepath.Dir(dst), 0666)
if err != nil && !os.IsExist(err) {
log.Fatal("cpFile: ", err)
}
fsrc, err := os.Open(src)
if err != nil {
log.Fatal("cpFile: ", err)
}
defer fsrc.Close()
fdst, err := os.Create(dst)
if err != nil {
log.Fatal("cpFile: ", err)
}
defer fdst.Close()
if _, err = io.Copy(fdst, fsrc); err != nil {
log.Fatal("cpFile: ", err)
}
}

44
tools/lookpath.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2014 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ingore
// Lookpath is a simple which.
package main
import (
"fmt"
"os"
"os/exec"
"strings"
)
func main() {
if len(os.Args) < 2 {
fmt.Printf(`Usage: lookpath COMMAND [...]
Write the full path of COMMAND(s) to standard output.
Report bugs to <chaishushan@gmail.com>.
`)
os.Exit(0)
}
for i := 1; i < len(os.Args); i++ {
path, err := exec.LookPath(os.Args[i])
if err != nil {
fmt.Printf("lookpath: no %s in (%v)\n", os.Args[i], GetEnv("PATH"))
os.Exit(0)
}
fmt.Println(path)
}
}
func GetEnv(key string) string {
key = strings.ToUpper(key) + "="
for _, env := range os.Environ() {
if strings.HasPrefix(strings.ToUpper(env), key) {
return env[len(key):]
}
}
return ""
}

97
tools/lsdir.go Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ingore
//
// List files, support file/header regexp.
//
// Example:
// lsdir dir
// lsdir dir "\.go$"
// lsdir dir "\.go$" "chaishushan"
// lsdir dir "\.tiff?|jpg|jpeg$"
//
// Help:
// lsdir -h
//
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
)
const usage = `
Usage: lsdir dir [nameFilter [dataFilter]]
lsdir -h
Example:
lsdir dir
lsdir dir "\.go$"
lsdir dir "\.go$" "chaishushan"
lsdir dir "\.tiff?|jpg|jpeg$"
Report bugs to <chaishushan{AT}gmail.com>.
`
func main() {
if len(os.Args) < 2 || os.Args[1] == "-h" {
fmt.Fprintln(os.Stderr, usage[1:len(usage)-1])
os.Exit(0)
}
dir, nameFilter, dataFilter := os.Args[1], ".*", ""
if len(os.Args) > 2 {
nameFilter = os.Args[2]
}
if len(os.Args) > 3 {
dataFilter = os.Args[3]
}
total := 0
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Fatal("filepath.Walk: ", err)
return err
}
if info.IsDir() {
return nil
}
relpath, err := filepath.Rel(dir, path)
if err != nil {
log.Fatal("filepath.Rel: ", err)
return err
}
mathed, err := regexp.MatchString(nameFilter, relpath)
if err != nil {
log.Fatal("regexp.MatchString: ", err)
}
if mathed {
if dataFilter != "" {
data, err := ioutil.ReadFile(path)
if err != nil {
fmt.Printf("ioutil.ReadFile: %s\n", path)
log.Fatal("ioutil.ReadFile: ", err)
}
mathed, err := regexp.MatchString(dataFilter, string(data))
if err != nil {
log.Fatal("regexp.MatchString: ", err)
}
if mathed {
fmt.Printf("%s\n", relpath)
total++
}
} else {
fmt.Printf("%s\n", relpath)
total++
}
}
return nil
})
fmt.Printf("total %d\n", total)
}

121
tools/md5.go Normal file
View File

@ -0,0 +1,121 @@
// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ingore
//
// Cacl dir or file MD5Sum, support regexp.
//
// Example:
// md5 file
// md5 dir "\.go$"
// md5 dir "\.tiff?$"
// md5 dir "\.tiff?|jpg|jpeg$"
//
// Help:
// cpdir -h
//
package main
import (
"crypto/md5"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"sort"
)
const usage = `
Usage: md5 [dir|file [filter]]
md5 -h
Example:
md5 file
md5 dir "\.go$"
md5 dir "\.go$"
md5 dir "\.tiff?$"
md5 dir "\.tiff?|jpg|jpeg$"
Report bugs to <chaishushan{AT}gmail.com>.
`
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, usage[1:len(usage)-1])
os.Exit(0)
}
filter := ".*"
if len(os.Args) > 2 {
filter = os.Args[2]
}
if name := os.Args[1]; IsDir(name) {
m, err := MD5Dir(name, filter)
if err != nil {
log.Fatalf("%s: %v", name, err)
}
var paths []string
for path := range m {
paths = append(paths, path)
}
sort.Strings(paths)
for _, path := range paths {
fmt.Printf("%x *%s\n", m[path], path)
}
} else {
sum, err := MD5File(name)
if err != nil {
log.Fatalf("%s: %v", name, err)
}
fmt.Printf("%x *%s\n", sum, name)
}
}
func IsDir(name string) bool {
fi, err := os.Lstat(name)
if err != nil {
log.Fatal(err)
}
return fi.IsDir()
}
func MD5Dir(root string, filter string) (map[string][md5.Size]byte, error) {
m := make(map[string][md5.Size]byte)
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
mathed, err := regexp.MatchString(filter, path)
if err != nil {
log.Fatal("regexp.MatchString: ", err)
}
if mathed {
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
m[path] = md5.Sum(data)
}
return nil
})
if err != nil {
return nil, err
}
return m, nil
}
func MD5File(filename string) (sum [md5.Size]byte, err error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return
}
sum = md5.Sum(data)
return
}