從Java極速入門Go
本文適合有Java開發經驗且想學習Go的同學,是一篇非常適合入門Go語言的教程,本文只會涉及部分Go的核心內容,不適合想系統學習Go的同學,但也提供了系統學習的方式。
Go語言簡介
下面是對Go的簡單介紹,可以先了解一下,如果有哪些暫時無法理解的可以先跳過,學完【極速入門】之後回頭來看就會恍然大悟。
簡介
Go是一門語法非常簡單的開源程式語言,目的是讓程式開發變得更有生產效率。
Go是一門靜態型別的編譯型語言,通過在不同平臺上的相應編譯器來實現跨平臺特性。
Go是一門廣納百家之長的語言,看起來像既像C++又像Delphi、Python,實則是吸收其他語言的優勢,又針對其他語言的痛點進行設計。
它的併發機制使編寫能夠充分利用多執行緒的程式變得容易,其新穎的型別系統可實現靈活的模組化程式構建,是一門為大資料、微服務、高併發、雲端計算而生的通用語言。
與Java對比
- 面向物件。Go不完全是面向物件的語言,因為沒有【物件】的概念與關鍵字而是提供了結構體struct關鍵字,並且沒有型別層次結構,比如沒有繼承、多型、泛型的支援。但是允許面向物件的程式設計風格,比如可以在struct裡定義物件的內容,還能結合介面靈活實現。
- 介面。Go中的【介面】不是必須明確標註被實現才能使用某物件,而是使用 ofollow,noindex" target="_blank">duck typing 的方式提供了一種易於使用且更為通用的不同方法。結合上一點,所以Go語言在對於構建和使用物件時更加靈活
- 異常處理。無異常處理機制try catch,因為將異常耦合到業務程式碼中會導致程式碼更加複雜,並且傾向於定義出很多普通的錯誤。Go還可以利用函式多返回值來將可處理的異常作為一個返回值返回給呼叫者。
- 併發程式設計。Java中有很多針對多執行緒底層操作,如互斥,條件變數和記憶體屏障,這些過分強調低階細節,是的多執行緒程式設計難度較大。而Go中的【CSP併發模型】讓開發人員不用關心這些細節,而是通過goruntime和在channel中傳遞訊息的方式進行通訊也能實現高效的併發程式
- 效率。程式執行速度遠勝於Java,得益於Go是通過先編譯連結後得到可執行檔案的,而不是在執行時通過虛擬機器解釋執行。至於開發效率個人認為主要取決於對語言和相應框架使用的熟練程度。
- 部署。Go編譯後生成的是一個可執行檔案,除了glibc外沒有任何外部依賴,完全不需要操行應用所需要的各種包、依賴關係,大大減輕了維護的負擔
- 應用場景。Java的應用場景包括移動應用、應用伺服器程式、客戶端程式、嵌入式、大資料,可以說非常廣,伺服器到客戶端、底層和應用層都能涵蓋。Go則適合寫伺服器程式、大資料,以及基礎設施程式如Docker、K8S,由於其語法簡單、併發程式設計人性化、執行效率高所以在雲端計算領域中也是舉足輕重的存在,但移動應用、嵌入式、客戶端程式不是Go擅長的方向。
極速入門
環境搭建
Go語言安裝
https://golang.org/dl/ 選擇1.10.3版本下載
IDE安裝
IntelliJ IDEA中下載Go外掛或者直接下載 GoLand
如果你不習慣Windows的快捷鍵設定, IDEA-Mac-Keymap-On-Windows 將Mac上的Command鍵改成左Alt鍵或許可以幫到你。
HelloWorld
讓我們正式來體驗Go語言,以下所有程式碼你應該先想一下大致應該如何寫,然後看應該如何寫,最後自己再寫一遍直至跑通,有任何問題及時去搜索理解。
首先還是得從hello world開始。現在IDE中建立一個專案,在專案根目錄下建立一個名為 helloworld.go
的檔案,輸入以下程式碼:
package main //必須是main包才能定義出一個可執行的入口,main命名與檔名無關 import "fmt" //一個格式化輸出的核心庫 func main(){ //函式命名格式:func 函式名(引數) 返回值 fmt.Println("hello world!") }
通過IDE的執行按鈕或者在命令列中輸入 go run helloworld.go
執行它
SimpleSpider
接下來介紹一個簡單的爬蟲來體驗一部分Go語言的特性。不用馬上動手實現它,而是從它開始簡單看看Go的語法,Go的併發程式設計模型,等系統學習之後再來試試能不能實現。
這個爬蟲的工作原理如下:首先準備一些想要抓取的網頁Url作為種子傳給Engine,由Engine負責流程控制,Engine會把Url傳給Worker中的Fetcher來下載具體的HTML檔案,然後傳送給Url對應的Parser解析函式得到想要抓取的資訊以及當前頁面上可以繼續當做種子再次抓取的Url,最後將Url與對應的Parser傳給Engine繼續抓取。
其中支援併發的Worker與單個Engine傳遞與接收資料使用的是go runtime與channel實現,可以來感受一下前所未有的併發程式設計體驗。Worker數量越多,爬蟲效率越高。
先在根目錄下將不同模組的資料夾創建出來,有engine, fetcher, parser三個,然後在根目錄把入口程式spider.go寫好:
func main() { engine.SimpleEngine{// engine是我們需要實現的一個程式包,建立engine包中的SimpleEngine結構 WorkerCount: 100, // worker開啟數量 }.Run(engine.Request{ // 被呼叫的Run方法需要被定義在該結構體的內部 Url:"http://www.zhenai.com/zhenghun", //第一個引數是用於被抓取的網頁的URL ParserFunc: parser.ParseCityList, //第二個引數是對於不同Url編寫的不同的解析器 }) }
在engine資料夾中新建types.go將Request結構體定義:
type Request struct { Urlstring ParserFunc func([]byte) ParseResult // 解析函式,不同的Url可能對應不同的解析函式,傳入HTML的位元組陣列 // 傳出HTML中解析出來的可以繼續當成種子的Request和HTML中的有效資訊 } type ParseResult struct { Requests []Request //從HTML中解析出來的種子 Items[]interface{} //任意型別的陣列結構 }
接下來了解一下goruntime與channel。goroutine是Go裡的一種輕量級執行緒——協程。相對執行緒,協程的優勢就在於它非常輕量級,進行上下文切換的代價非常的小,通過go關鍵字傳入一個方法就能開啟一個協程。
channel是一種帶有型別的變數型別,你可以通過它用channel操作符 <-
在不同的協程之間來發送或者接收值。預設情況下,傳送和接收操作在另一端準備好之前都會阻塞。這使得 Go 程可以在沒有顯式的鎖或競態變數的情況下進行同步,從而大大降低了併發程式設計的難度,提升了開發效率。
接下來看engine中的核心程式碼:
type SimpleEngine struct { // 建立engine結構體用以在建立時接受引數,但這並不是建構函式,Go中沒有建構函式與解構函式 WorkerCount int } // 為SimpleEngine結構體定義Run方法。SimpleEngine為該方法的值接受者。 // 由於方法經常需要修改它的接收者,指標接收者比值接收者更常用;引數是可變長的型別,用來傳入多個種子 func (e SimpleEngine) Run(seeds ...Request) { in := make(chan Request) // 建立種子輸入channel,其中可以傳遞的資料為Request,worker將從這裡取,engine將種子往這裡放 out := make(chan ParseResult) // 種子輸出channel,其中可以傳遞的資料為ParseResult,worker將解析結果傳到這裡交給engine // 開啟若干個worker協程,等待種子被放入 for i := 0; i < e.WorkerCount; i++ { go func() { for { // 開始時所有worker都被阻塞,等待種子被傳入 r := <-in // 建立r變數,初始值從輸入channel中獲取 // 收到某個種子,開始解析 result, e := worker(r) if e != nil { continue } // 將任務執行結果傳回engine,但是這裡如果直接寫out <- result會無法執行,因為有迴圈等待產生,原因: // 想要執行這行程式碼必須需要有engine在執行①開始等待才行,但是engine執行這行程式碼的前提完成②的輸入 // 也就是需要有空閒的worker在等待接受in,然而此時worker正在執行本行命令 go func() {out <- result}() } }() } // 傳入種子到輸入channel for _, request := range seeds { in <- request } for { // 迴圈結構只有for關鍵字,這裡是當成while(true)使用,讓engine一直執行,等待worker返回的資料 ①parseResult := <- out // 現在是列印得到的資料,以後將改為儲存到資料庫 for _, item := range parseResult.Items { // 相當於foreach,第一個返回值是index,第二個是object,_代表不使用 log.Printf("Got item %v\n", item) } // 儲存後將後續種子傳送給輸入channel for _, r := range parseResult.Requests { ②in <- r } } } func worker(r Request) (ParseResult, error) { log.Printf("Fetching %s", r.Url) // 下載html內容 body, e := fetcher.Fetch(r.Url) if e != nil { log.Printf("Fetcher error with url: %s. %v", r.Url, e) return ParseResult{}, e } // 根據request裡提供的解析方法解析當前html內容成為result放入佇列 return r.ParserFunc(body), nil }
核心程式碼就是以上,但還需要實現負責下載的fetcher和負責解析的parser,完整程式碼可檢視 https://github.com/xbox1994/GoCrawler 中的crawler_standalone學習
依賴包安裝
由於GFW的關係,當你在安裝某個依賴包時,可能被擋住,推薦按以下的順序來嘗試安裝go的依賴包(xxx代表包的地址) 1. go get xxx
2. 使用gopm安裝,先下載gopm: go get -v -u github.com/gpmgo/gopm
,然後 gopm get -g -v xxx
3. 去對應的github主頁上, git clone
下來並放到GOROOT或GOPATH目錄的src資料夾下,並將下載的資料夾命名為包的地址
Beego
Beego是國人開發的一款Go web框架,這裡檢視相關 beego.me/docs/intro/" rel="nofollow,noindex" target="_blank">簡介
它的功能全面、結構清晰、上手容易、有一定社群活躍度但程式碼質量不高,適合快速開發。
接下來請按照官網 quickstart 的例子來快速體驗一把。
在這個例子中要注意的是,Go的方法引數傳遞只有值傳遞,沒有引用傳遞,也就是如果傳遞一個任意型別的變數給某個方法,那麼這個變數將被複制一份傳給函式內部,所以在函式內部對這個變數進行操作則是對原變數的副本進行操作,如果想直接操作原變數的內容,使用&將地址傳進去即可。
系統學習推薦
本文僅僅是用到了小部分Go的語法和功能特性來嘗試帶領你入門,還有很多值得了解的比如陣列的切片、duck typing 介面、反射,希望你看完之後一定不要止步於此,一切都才剛剛開始,下一步就是開始進行系統的學習了。
這個 知乎回答 可能對你有所幫助,但很不幸給的連結很多都需要翻牆。