Go的研習筆記-day8(以Java的視角學習Go)

原文鏈接:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/09.11.md

包(package)

標準庫概述
像 fmt、os 等這樣具有常用功能的內置包在 Go 語言中有 150 個以上,它們被稱爲標準庫,大部分(一些底層的除外)內置於 Go 本身。其他包地址

  • unsafe: 包含了一些打破 Go 語言“類型安全”的命令,一般的程序中不會被使用,可用在 C/C++ 程序的調用中。
  • syscall-os-os/exec:
  • os: 提供給我們一個平臺無關性的操作系統功能接口,採用類Unix設計,隱藏了不同操作系統間差異,讓不同的文件系統和操作系統對象表現一致。
  • os/exec: 提供我們運行外部操作系統命令和程序的方式。
  • syscall: 底層的外部包,提供了操作系統底層調用的基本接口。
  • archive/tar 和 /zip-compress:壓縮(解壓縮)文件功能。
  • fmt-io-bufio-path/filepath-flag:
  • fmt: 提供了格式化輸入輸出功能。
  • io: 提供了基本輸入輸出功能,大多數是圍繞系統功能的封裝。
  • bufio: 緩衝輸入輸出功能的封裝。
  • path/filepath: 用來操作在當前系統中的目標文件名路徑。
  • flag: 對命令行參數的操作。
  • strings-strconv-unicode-regexp-bytes:
  • strings: 提供對字符串的操作。
  • strconv: 提供將字符串轉換爲基礎類型的功能。
  • unicode: 爲 unicode 型的字符串提供特殊的功能。
  • regexp: 正則表達式功能。
  • bytes: 提供對字符型分片的操作。
  • index/suffixarray: 子字符串快速查詢。
  • math-math/cmath-math/big-math/rand-sort:
  • math: 基本的數學函數。
  • math/cmath: 對複數的操作。
  • math/rand: 僞隨機數生成。
  • sort: 爲數組排序和自定義集合。
  • math/big: 大數的實現和計算。
  • container-/list-ring-heap: 實現對集合的操作。
  • list: 雙鏈表。
  • ring: 環形鏈表。
    下面代碼演示瞭如何遍歷一個鏈表(當 l 是 *List):
    for e := l.Front(); e != nil; e = e.Next() {
    //do something with e.Value
    }
  • time-log:
  • time: 日期和時間的基本操作。
  • log: 記錄程序運行時產生的日誌,
  • encoding/json-encoding/xml-text/template:
  • encoding/json: 讀取並解碼和寫入並編碼 JSON 數據。
  • encoding/xml:簡單的 XML1.0 解析器,有關 JSON 和 XML 的實例
  • text/template:生成像 HTML 一樣的數據與文本混合的數據驅動模板
  • net-net/http-html
  • net: 網絡數據的基本操作。
  • http: 提供了一個可擴展的 HTTP 服務器和基礎客戶端,解析 HTTP 請求和回覆。
  • html: HTML5 解析器。
  • runtime: Go 程序運行時的交互操作,例如垃圾回收和協程創建。
  • reflect: 實現通過程序運行時反射,讓程序操作任意類型的變量。

regexp 包

package main
import (
	"fmt"
	"regexp"
	"strconv"
)
func main() {
	//目標字符串
	searchIn := "John: 2578.34 William: 4567.23 Steve: 5632.18"
	pat := "[0-9]+.[0-9]+" //正則

	f := func(s string) string{
    	v, _ := strconv.ParseFloat(s, 32)
    	return strconv.FormatFloat(v * 2, 'f', 2, 32)
	}

	if ok, _ := regexp.Match(pat, []byte(searchIn)); ok {
    fmt.Println("Match Found!")
	}

	re, _ := regexp.Compile(pat)
	//將匹配到的部分替換爲"##.#"
	str := re.ReplaceAllString(searchIn, "##.#")
	fmt.Println(str)
	//參數爲函數時
	str2 := re.ReplaceAllStringFunc(searchIn, f)
	fmt.Println(str2)
}

鎖和 sync 包
當不同線程要使用同一個變量時,經常會出現一個問題:無法預知變量被不同線程修改的順序!(這通常被稱爲資源競爭,指不同線程對同一變量使用的競爭)
經典的做法是一次只能讓一個線程對共享變量進行操作。當變量被一個線程改變時(臨界區),我們爲它上鎖,直到這個線程執行完成並解鎖後,其他線程才能訪問它。比如Java中的synchronized關鍵字或者lock接口的實現類等來加鎖保證先行發生原則。
在 Go 語言中這種鎖的機制是通過 sync 包中 Mutex 來實現的。sync 來源於 “synchronized” 一詞,這意味着線程將有序的對同一變量進行訪問。
sync.Mutex 是一個互斥鎖,它的作用是守護在臨界區入口來確保同一時間只能有一個線程進入臨界區。
假設 info 是一個需要上鎖的放在共享內存中的變量。通過包含 Mutex 來實現的一個典型例子如下:

import  "sync"

type Info struct {
	mu sync.Mutex
	// ... other fields, e.g.: Str string
}
如果一個函數想要改變這個變量可以這樣寫:

func Update(info *Info) {
	info.mu.Lock()
    // critical section:
    info.Str = // new value
    // end critical section
    info.mu.Unlock()
}
還有一個很有用的例子是通過 Mutex 來實現一個可以上鎖的共享緩衝器:

type SyncedBuffer struct {
	lock 	sync.Mutex
	buffer  bytes.Buffer
}
在 sync 包中還有一個 RWMutex 鎖:他能通過 RLock() 來允許同一時間多個線程對變量進行讀操作,但是隻能一個線程進行寫操作。如果使用 Lock() 將和普通的 Mutex 作用相同。包中還有一個方便的 Once 類型變量的方法 once.Do(call),這個方法確保被調用函數只能被調用一次。
是不是發現上面的描述和我們學過的Java挺像,lock接口的實現類讀寫鎖ReadWriteLock。那麼如果我們現在說Java中爲了減少鎖的衝突,採用了併發包之類的原子變量,併發數據結構提升性能,那麼go語言有沒有類似提升性能的方式呢?答案是可以使用goroutines 和 channels 來解決問題,這是在 Go 語言中所提倡用來實現併發的技術

精密計算和 big 包
java中對於精確數字或者金融計算金額的使用BigDecimal類,那麼go語言中呢,也是類似的
對於整數的高精度計算 Go 語言中提供了 big 包,被包含在 math 包下:有用來表示大整數的 big.Int 和表示大有理數的 big.Rat 類型(可以表示爲 2/5 或 3.1416 這樣的分數,而不是無理數或 π)。這些類型可以實現任意位類型的數字,只要內存足夠大。缺點是更大的內存和處理開銷使它們使用起來要比內置的數字類型慢很多。
大的整型數字是通過 big.NewInt(n) 來構造的,其中 n 爲 int64 類型整數。而大有理數是通過 big.NewRat(n, d) 方法構造。n(分子)和 d(分母)都是 int64 型整數。因爲 Go 語言不支持運算符重載,所以所有大數字類型都有像是 Add() 和 Mul() 這樣的方法。它們作用於作爲 receiver 的整數和有理數,大多數情況下它們修改 receiver 並以 receiver 作爲返回結果。因爲沒有必要創建 big.Int 類型的臨時變量來存放中間結果,所以運算可以被鏈式地調用,並節省內存。

// big.go
package main

import (
	"fmt"
	"math"
	"math/big"
)

func main() {
	// Here are some calculations with bigInts:
	im := big.NewInt(math.MaxInt64)
	in := im
	io := big.NewInt(1956)
	ip := big.NewInt(1)
	ip.Mul(im, in).Add(ip, im).Div(ip, io)
	fmt.Printf("Big Int: %v\n", ip)
	// Here are some calculations with bigInts:
	rm := big.NewRat(math.MaxInt64, 1956)
	rn := big.NewRat(-1956, math.MaxInt64)
	ro := big.NewRat(19, 56)
	rp := big.NewRat(1111, 2222)
	rq := big.NewRat(1, 1)
	rq.Mul(rm, rn).Add(rq, ro).Mul(rq, rp)
	fmt.Printf("Big Rat: %v\n", rq)
}

/* Output:
Big Int: 43492122561469640008497075573153004
Big Rat: -37/112
*/

自定義包和可見性
包分爲兩種:標準庫中的包,自定義的包
當寫自己包的時候,要使用短小的不含有 _(下劃線)的小寫單詞來爲文件命名。這裏有個簡單例子來說明包是如何相互調用以及可見性是如何實現的。
import 的一般格式如下:
import “包的路徑或 URL 地址”
導入外部安裝包:
如果你要在你的應用中使用一個或多個外部包,首先你必須使用 go install(參見第 9.7 節)在你的本地機器上安裝它們。
假設你想使用 http://codesite.ext/author/goExample/goex 這種託管在 Google Code、GitHub 和 Launchpad 等代碼網站上的包。
你可以通過如下命令安裝:
go install codesite.ext/author/goExample/goex
將一個名爲 codesite.ext/author/goExample/goex 的 map 安裝在 $GOROOT/src/ 目錄下。
通過以下方式,一次性安裝,並導入到你的代碼中:
import goex “codesite.ext/author/goExample/goex”
因此該包的 URL 將用作導入路徑。
http://golang.org/cmd/goinstall/ 的 go install 文檔中列出了一些廣泛被使用的託管在網絡代碼倉庫的包的導入路徑

  • 包的初始化:
    程序的執行開始於導入包,初始化 main 包然後調用 main 函數。
    一個沒有導入的包將通過分配初始值給所有的包級變量和調用源碼中定義的包級 init 函數來初始化。一個包可能有多個 init 函數甚至在一個源碼文件中。它們的執行是無序的。這是最好的例子來測定包的值是否只依賴於相同包下的其他值或者函數。
    init 函數是不能被調用的。
    導入的包在包自身初始化前被初始化,而一個包在程序執行中只能初始化一次。
  • 使用 go install 安裝自定義包
    go install 是 Go 中自動包安裝工具:如需要將包安裝到本地它會從遠端倉庫下載包:檢出、編譯和安裝一氣呵成。
    在包安裝前的先決條件是要自動處理包自身依賴關係的安裝。被依賴的包也會安裝到子目錄下,但是沒有文檔和示例:可以到網上瀏覽。
    go install 使用了 GOPATH 變量
    假設現在安裝一個包, tideland
  • 創建目錄在 Go 安裝目錄下,所以我們需要使用 root 或者 su 的身份執行命令。確保 Go 環境變量已經設置在 root 用戶下的 ./bashrc 文件中。
  • 使用命令安裝:go install tideland-cgl.googlecode.com/hg
  • 可執行文件 hg.a 將被放到 $GOROOT/pkg/linux_amd64/tideland-cgl.googlecode.com 目錄下,源碼文件被放置在 $GOROOT/src/tideland-cgl.googlecode.com/hg 目錄下,同樣有個 hg.a 放置在 _obj 的子目錄下
  • import cgl “tideland-cgl.googlecode.com/hg
    不同版本變化比較大,可參考鏈接http://golang.org/cmd/go/

爲自定義包使用 godoc
godoc工具在顯示自定義包中的註釋也有很好的效果:註釋必須以 // 開始並無空行放在聲明(包,類型,函數)前。godoc 會爲每個文件生成一系列的網頁。
命令行下進入目錄下並輸入命令:
godoc -http=:6060 -goroot="."
(. 是指當前目錄,-goroot 參數可以是 /path/to/my/package1 這樣的形式指出 package1 在你源碼中的位置或接受用冒號形式分隔的路徑,無根目錄的路徑爲相對於當前目錄的相對路徑)
在瀏覽器打開地址:http://localhost:6060
然後你會看到本地的 godoc 頁面從左到右一次顯示出目錄中的包:
在一個團隊中工作,並且源代碼樹被存儲在網絡硬盤上,就可以使用 godoc 給所有團隊成員連續文檔的支持。通過設置 sync_minutes=n,可以讓它每 n 分鐘自動更新您的文檔!

自定義包的目錄結構、go install 和 go test
示範,我們創建了一個名爲 uc 的簡單包,它含有一個 UpperCase 函數將字符串的所有字母轉換爲大寫。當然這並不值得創建一個自己包,同樣的功能已被包含在 strings 包裏,但是同樣的技術也可以應用在更復雜的包中。

  • 自定義包的目錄結構
    (uc 代表通用包名, 名字爲粗體的代表目錄,斜體代表可執行文件)
/home/user/goprograms
	ucmain.go	(uc包主程序)
	Makefile (ucmain的makefile)
	ucmain
	src/uc	 (包含uc包的go源碼)
		uc.go
	 	uc_test.go
	 	Makefile (包的makefile)
	 	uc.a
	 	_obj
			uc.a
		_test
			uc.a
	bin		(包含最終的執行文件)
		ucmain
	pkg/linux_amd64
		uc.a	(包的目標文件)

將你的項目放在 goprograms 目錄下(你可以創建一個環境變量 GOPATH,在 .profile 和 .bashrc 文件中添加 export GOPATH=/home/user/goprograms),而你的項目將作爲 src 的子目錄。uc 包中的功能在 uc.go 中實現。

package uc
import "strings"

func UpperCase(str string) string {
	return strings.ToUpper(str)
}




package uc
import "testing"

type ucTest struct {
	in, out string
}

var ucTests = []ucTest {
	ucTest{"abc", "ABC"},
	ucTest{"cvo-az", "CVO-AZ"},
	ucTest{"Antwerp", "ANTWERP"},
}

func TestUC(t *testing.T) {
	for _, ut := range ucTests {
		uc := UpperCase(ut.in)
		if uc != ut.out {
			t.Errorf("UpperCase(%s) = %s, must be %s", ut.in, uc,
			ut.out)
		}
	}
}
通過指令編譯並安裝包到本地:go install uc, 這會將 uc.a 複製到 pkg/linux_amd64 下面
另外,使用 make ,通過以下內容創建一個包的 Makefile 在 src/uc 目錄下:
include $(GOROOT)/src/Make.inc

TARG=uc
GOFILES=\
		uc.go\

include $(GOROOT)/src/Make.pkg

在該目錄下的命令行調用: gomake
這將創建一個 _obj 目錄並將包編譯生成的存檔 uc.a 放在該目錄下。
這個包可以通過 go test 測試。
創建一個 uc.a 的測試文件在目錄下,輸出爲 PASS 時測試通過。
備註:有可能你當前的用戶不具有足夠的資格使用 go install(沒有權限)。這種情況下,選擇 root 用戶 su。確保 Go 環境變量和 Go 源碼路徑也設置給 su,同樣也適用你的普通用戶。

本地安裝包
本地包在用戶目錄下,使用給出的目錄結構,以下命令用來從源碼安裝本地包:

go install /home/user/goprograms/src/uc # 編譯安裝uc
cd /home/user/goprograms/uc
go install ./uc 	# 編譯安裝uc(和之前的指令一樣)
cd ..
go install .	# 編譯安裝ucmain
安裝到 $GOPATH 下:

如果我們想安裝的包在系統上的其他 Go 程序中被使用,它一定要安裝到 $GOPATH 下。 這樣做,在 .profile 和 .bashrc 中設置 export GOPATH=/home/user/goprograms。

然後執行 go install uc 將會複製包存檔到 $GOPATH/pkg/LINUX_AMD64/uc。

現在,uc 包可以通過 import "uc" 在任何 Go 程序中被引用。

依賴系統的代碼
在不同的操作系統上運行的程序以不同的代碼實現是非常少見的:絕大多數情況下語言和標準庫解決了大部分的可移植性問題。
你有一個很好的理由去寫平臺特定的代碼,例如彙編語言。這種情況下,按照下面的約定是合理的:

prog1.go
prog1_linux.go
prog1_darwin.go
prog1_windows.go

prog1.go 定義了不同操作系統通用的接口,並將系統特定的代碼寫到 prog1_os.go 中。 對於 Go 工具你可以指定 prog1_$ GOOS.go 或 prog1_$ GOARCH.go 或在平臺 Makefile 中:prog1_$ (GOOS).go\ 或 prog1_$ (GOARCH).go\。

通過 Git 打包和安裝

  • 安裝到 GitHub
    在 Linux 和 OS X 的機器上 Git 是默認安裝的,在 Windows 上你必須先自行安裝。參見GitHub幫助頁面
    其餘與git託管其他項目一樣
  • 從 GitHub 安裝
    如果有人想安裝您的遠端項目到本地機器,打開終端並執行(NNNN 是你在 GitHub 上的用戶名):go get github.com/NNNN/uc。
    這樣現在這臺機器上的其他 Go 應用程序也可以通過導入路徑:“github.com/NNNN/uc” 代替 “./uc/uc” 來使用。
    也可以將其縮寫爲:import uc “github.com/NNNN/uc”。
    然後修改 Makefile: 將 TARG=uc 替換爲 TARG=github.com/NNNN/uc。
    Gomake(和 go install)將通過 $GOPATH 下的本地版本進行工作。

Go 的外部包和項目
我們知道如何使用 Go 以及它的標準庫了,但是 Go 的生態要比這大的多。當着手自己的 Go 項目時,最好先查找下是否有些存在的第三方的包或者項目能不能使用。大多數可以通過 go install 來進行安裝。
目前已經有許多非常好的外部庫,如:

  • MySQL(GoMySQL), PostgreSQL(go-pgsql), MongoDB (mgo, gomongo), CouchDB (couch-go), ODBC (godbcl), Redis (redis.go) and SQLite3 (gosqlite) database drivers
  • SDL bindings
  • Google’s Protocal Buffers(goprotobuf)
  • XML-RPC(go-xmlrpc)
  • Twitter(twitterstream)
  • OAuth libraries(GoAuth)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章