編譯 Go 程序時,編譯器只會關注那些直接被引用的庫,而不是像 Java、C 和 C++那樣,要遍歷 依賴鏈中全部依賴的庫。git
Go 語言對併發的支持是這門語言最重要的特性之一。goroutine 很像線程,可是它佔用的 內存遠少於線程,使用它須要的代碼更少。通道(channel)是一種內置的數據結構,可讓 用戶在不一樣的 goroutine 之間同步發送具備類型的消息。github
goroutine 是能夠與其餘 goroutine 並行執行的函數,同時也會與主程序(程序的入口)並行 執行。算法
通道是一種數據結構,可讓 goroutine 之間進行安全的數據通訊。通道能夠幫用戶避免其 他語言裏常見的共享內存訪問的問題。json
在兩個 goroutine 間傳輸數據是同步的,一旦傳輸完成,兩個 goroutine都會知道數據已經完成傳輸。設計模式
通道並不提供跨 goroutine 的數據訪問保護機制。若是經過通道傳輸數據的一份副本,那麼每一個 goroutine 都持有一份副本,各自對本身的副本作修改是安全的。當傳輸的是指向數據的指針時,若是讀和寫是由不一樣的 goroutine 完成的,每一個 goroutine 依舊須要額外的同步動做。數組
Go 開發者使用組合(composition)設計模式,只需簡單地將一個類型嵌入到另外一個類型,就能 複用全部的功能。瀏覽器
Go 語言還具備獨特的接口實現機制,容許用戶對行爲進行建模,而不是對類型進行建模。緩存
init()函數會在main()函數以前調用。安全
一個包定義一組編譯過的代碼,包的名字相似命名空間,能夠用來間接訪問包內聲明的標識符。這個特性能夠把不一樣包中定義的同名標識符區別開。服務器
導入的路徑前面有一個下劃線,例如
_ "github.com/goinaction/code/chapter2/sample/matchers"
複製代碼
這個技術是爲了讓 Go 語言對包作初始化操做,可是並不使用包裏的標識符。爲了讓程序的 可讀性更強,Go 編譯器不容許聲明導入某個包卻不使用。下劃線讓編譯器接受這類導入,而且 調用對應包內的全部代碼文件裏定義的 init 函數。
變量沒有定義在任何函數做用域內,因此會被當成包級變量。
在 Go 語言裏,標識符要麼從包裏公開,要麼不從包裏公開。當代碼導入了一個包時,程序 能夠直接訪問這個包中任意一個公開的標識符。這些標識符以大寫字母開頭。以小寫字母開頭的 標識符是不公開的,不能被其餘包中的代碼直接訪問。
在 Go 語言中,全部變量都被初始化爲其零值。對於數值類型,零值是 0;對於字符串類型, 零值是空字符串;對於布爾類型,零值是 false;對於指針,零值是 nil。對於引用類型來講, 所引用的底層數據結構會被初始化爲對應的零值。可是被聲明爲其零值的引用類型的變量,會返 回 nil 做爲其值。
Go 語言使用關鍵字 func 聲明函數,關鍵字後面緊跟着函數名、參數以及返回值
切片是一種實現了一個動態數組的引用類型。在 Go 語言裏能夠用切片來操做一組數據。
簡化變量聲明運算符(:=)。這個運算符用於聲明一個變量,同時給這個變量賦予初始值。編譯器使用函數返回值的類型來肯定每一個變量的類型。簡化變量聲明運算符只是一種簡化記法,讓代碼可讀性更高。這個運算符聲明的變量和其餘使用關鍵字 var 聲明的變量沒有任何區別。
若是須要聲明初始值爲零值的變量,應該使用var關鍵字聲明變量;若是提供確切的非零值初始化變量或者使用函數返回值建立變量,應該使用簡化變量聲明運算符。
sync 包的 WaitGroup 跟蹤全部啓動的 goroutine。實際開發中,很是推薦使用 WaitGroup 來 跟蹤 goroutine 的工做是否完成。WaitGroup 是一個計數信號量,咱們能夠利用它來統計全部的 goroutine 是否是都完成了工做。
關鍵字 range 能夠用於迭代數組、字符串、切片、映射和通道。使用for range迭代切片時,每次迭代會返回兩個值。第一個值是迭代的元素在切片裏的索引位置,第二個值是元素值的一個副本。
若是要調用的函數返回多個值,而又不須要其中的某個值,就可使用下劃線標識符將其忽略。
一個 goroutine 是一個獨立於其餘函數運行的函數。使用關鍵字 go 啓動一個 goroutine,並對這個 goroutine 作併發調度。
關鍵字 defer 會安排隨後的函數調用在函數返回時才執行。在使用完文件後,須要主動關 閉文件。使用關鍵字 defer 來安排調用 Close 方法,能夠保證這個函數必定會被調用。哪怕函 數意外崩潰終止,也能保證關鍵字 defer 安排調用的函數會被執行。關鍵字 defer 能夠縮短打 開文件和關閉文件之間間隔的代碼行數,有助提升代碼可讀性,減小錯誤。
一個接口的行爲最終由在這個接口類型中聲明的方法決定。
命名接口的時候,也須要遵照 Go 語言的命名慣例。若是接口類型只包含一個方法,那麼這 個類型的名字以 er 結尾。咱們的例子裏就是這麼作的,因此這個接口的名字叫做 Matcher。如 果接口類型內部聲明瞭多個方法,其名字須要與其行爲關聯。
type Matcher interface {
Search(feed *Feed, searchTerm string) ([]*Result, error)
}
複製代碼
若是要讓一個用戶定義的類型實現一個接口,這個用戶定義的類型要實現接口類型裏聲明的 全部方法。
type defaultMatcher struct{}
複製代碼
上面的代碼使用一個空結構聲明瞭一個名叫 defaultMatcher 的結構類型。空結構在建立實例時,不會分配任何內存。這種結構很適合建立沒有任何狀態的類型。
全部的.go 文件,除了空行和註釋,都應該在第一行聲明本身所屬的包。每一個包都在一個單獨的目錄裏。不能把多個包放到同一個目錄中,也不能把同一個包的文件分拆到多個不一樣目錄中。這意味着,同一個目錄下的全部.go 文件必須聲明同一個包名。
標準庫中的包會在安裝 Go 的位置找到。Go 開發者建立的包會在 GOPATH 環境變量指定的目錄裏查找。 GOPATH 指定的這些目錄就是開發者的我的工做空間。編譯器會首先查找Go的安裝目錄,而後纔會按順序查找 GOPATH 變量裏列出的目錄。
用導入路徑編譯程序時,go build 命令會使用GOPATH 的設置,在磁盤上搜索這個包。事實上, 這個導入路徑表明一個 URL,指向 GitHub 上的代碼庫。若是路徑包含 URL,可使用 Go 工具鏈從 DVCS 獲取包,並把包的源代碼保存在 GOPATH 指向的路徑裏與 URL 匹配的目錄裏。這個獲取過程 使用 go get 命令完成。go get 將獲取任意指定的 URL 的包,或者一個已經導入的包所依賴的其 他包。因爲go get 的這種遞歸特性,這個命令會掃描某個包的源碼樹,獲取能找到的全部依賴包。
用於解決包名相同的問題。 命名導入是指,在 import 語句給出的包路徑的左側定義一個名字,將導入的包命名爲新名字。 例如,若用戶已經使用了標準庫裏的 fmt 包,如今要導入本身項目里名叫 fmt 的包,可參照以下代碼:
import (
"fmt"
myfmt "mylib/fmt"
)
複製代碼
下劃線字符(_)在 Go 語言裏稱爲空白標識符,有不少用法。這個標識符用來拋棄不 想繼續使用的值,如給導入的包賦予一個空名字,或者忽略函數返回的你不感興趣的值。
每一個包能夠包含任意多個 init 函數,這些函數都會在程序執行開始的時候被調用。全部被 編譯器發現的 init 函數都會安排在 main 函數以前執行。init 函數用在設置包、初始化變量 或者其餘要在程序運行前優先完成的引導工做。
運行代碼經過 go run 命令。
go vet 該 命令會幫開發人員檢測代碼的常見錯誤,例如:
這個工具能夠很好地捕獲一部分常見錯誤。每次對代碼先執行 go vet 再將其簽入源代碼庫是一個很好的習慣。
Go 代碼格式化 經過fmt命令。
go doc 能夠查看相關包下的文檔,例如go doc http。
瀏覽文檔 開發人員啓動本身的文檔服務器,只須要在終端會話中輸入以下命令: godoc -http=:6060 這個命令通知 godoc 在端口 6060 啓動 Web 服務器。若是瀏覽器已經打開,導航到 http://localhost:6060 能夠看到一個頁面,包含全部 Go 標準庫和你的GOPATH 下的 Go 源代碼的文檔。
Go 語言有 3 種數據結構可讓用戶管理集合數據:數組、切片和映射,數組是切片和映射的基礎數據結構。
數組是一個 長度固定 的數據類型,用於存儲一段 具備相同的類型 的元素的連續塊。數組存儲的類型能夠是內置類型,如整型或者字符串,也能夠是某種結構類型。
數組佔用的內存是連續分配的。因爲內存連續,CPU 能把正在使用的數據緩存更久的時間,同時訪問速度也會很快。
聲明數組時須要指定內部存儲的數據的類型,以及須要存儲的元素的數量。
var array [5]int // 聲明一個包含 5 個元素的整型數組
複製代碼
此外,一種快速建立數組並初始化的方式是使用數組字面量。數組字面量容許聲明數組裏元素的數 量同時指定每一個元素的值。
array:= [5]int{1,2,3,4,5}
// 聲明一個整型數組
// 用具體值初始化每一個元素
// 容量由初始化值的數量決定
array := [...]int{10, 20, 30, 40, 50}
// 聲明一個有 5 個元素的數組
// 用具體值初始化索引爲 1 和 2 的元素
// 其他元素保持零值
array := [5]int{1: 10, 2: 20}
複製代碼
// 聲明一個二維整型數組,兩個維度分別存儲 4 個元素和 2 個元素
var array [4][2]int
// 使用數組字面量來聲明並初始化一個二維整型數組
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
複製代碼
數組在函數間進行傳遞會進行完成複製操做,這樣不利於優化內存和性能,比較好的一種作法是使用指針,將數組的地址傳入函數,此時只須要分配8字節的內存給指針就能夠了。固然要意識到,由於如今傳遞的是指針,因此若是改變指針指向的值,會改變共享的內存。如你所見,使用切片能更好地處理這類共享問題。
原理:切片是一種數據結構,這種數據結構便於使用和管理數據集合。切片是圍繞動態數組的概念 構建的,能夠按需自動增加和縮小。切片的動態增加是經過內置函數 append 來實現的。這個函 數能夠快速且高效地增加切片。還能夠經過對切片再次切片來縮小一個切片的大小。由於切片的 底層內存也是在連續塊中分配的,因此切片還能得到索引、迭代以及爲垃圾回收優化的好處。
切片有 3 個字段的數據結構,分別是指向底層數組的指針、切片訪問的元素的個數(即長度)和切片容許增加到的元素個數(即容量)。
方法1:make和切片字面量
// 建立一個字符串切片
// 其長度和容量都是 5 個元素
slice := make([]string, 5)
// 建立一個整型切片
// 其長度爲 3 個元素,容量爲 5 個元素
slice:=make([]int,3,5)
// 建立字符串切片
// 其長度和容量都是 5 個元素
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
複製代碼
記住,若是在[]運算符裏指定了一個值,那麼建立的就是數組而不是切片。只有不指定值 的時候,纔會建立切片
// 建立有 3 個元素的整型數組
array := [3]int{10, 20, 30}
// 建立長度和容量都是 3 的整型切片
slice := []int{10, 20, 30}
複製代碼
對底層數組容量是 k 的切片 slice[i:j]來講
長度: j - i
容量: k - i
使用 append,須要一個被操做的切片和一個要追加的值。
注意:函數 append 會智能地處理底層數組的容量增加。在切片的容量小於 1000 個元素時,老是 會成倍地增長容量。一旦元素個數超過 1000,容量的增加因子會設爲 1.25,也就是會每次增長 25% 的容量。隨着語言的演化,這種增加算法可能會有所改變。
置函數 append 也是一個可變參數的函數。這意味着能夠在一次調用傳遞多個追加的值。 若是使用...運算符,能夠將一個切片的全部元素追加到另外一個切片裏。
// 建立兩個切片,並分別用兩個整數進行初始化
s1 := []int{1, 2}
s2 := []int{3, 4}
// 將兩個切片追加在一塊兒,並顯示結果
fmt.Printf("%v\n", append(s1, s2...))
Output:
[1 2 3 4]
複製代碼
對於切片,函數 len返回切片的長度,函數 cap 返回切片的容量
在 64 位架構的機器上,一個切片須要 24 字節的內存:指針字段須要 8 字節,長度和容量 字段分別須要 8 字節。因爲與切片關聯的數據包含在底層數組裏,不屬於切片自己,因此將切片 複製到任意函數的時候,對底層數組大小都不會有影響。複製時只會複製切片自己,不會涉及底 層數組,在函數間傳遞 24 字節的數據會很是快速、簡單。這也是切片效率高的地方。不須要傳遞指 針和處理複雜的語法,只須要複製切片,按想要的方式修改數據,而後傳遞迴一份新的切片副本。
映射是一種數據結構,用於存儲一系列無序的鍵值對。映射裏基於鍵來存儲值。映射功能強大的地方是,可以基於鍵快速檢索數據。鍵就像索引同樣,指向與該鍵關聯的值。 映射是無序的。
//建立一個映射,鍵的類型是 string,值的類型是 int
dict:=make(map[string]int)
// 建立一個映射,鍵和值的類型都是 string
// 使用兩個鍵值對初始化映射
dict := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}
複製代碼
映射的鍵能夠是任何值。這個值的類型能夠是內置的類型,也能夠是結構類型,只要這個值 可使用==運算符作比較。切片、函數以及包含切片的結構類型這些類型因爲具備引用語義, 不能做爲映射的鍵
從映射取值時有兩個選擇。第一個選擇是,能夠同時得到值,以及一個表示這個鍵是否存在 的標誌。
// 獲取鍵 Blue 對應的值
value, exists := colors["Blue"]
// 這個鍵存在嗎?
if exists {
fmt.Println(value)
}
複製代碼
經過鍵來索引映射時,即使這個鍵不存在也總會返回一個值。在這種狀況下,返回的是該值對應的類型的零值。
在函數間傳遞映射並不會製造出該映射的一個副本。實際上,當傳遞映射給一個函數,並對 這個映射作了修改時,全部對這個映射的引用都會察覺到這個修改。這個特性和切片相似,保證能夠用很小的成原本複製映射。
方法能給 用戶定義的類型 添加新的行爲。方法實際上也是函數,只是在聲明時,在關鍵字 func 和方法名之間增長了一個參數。
Go 語言裏有兩種類型的接收者:值接收者和指針接收者
// notify 使用值接收者實現了一個方法
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",
u.name,u.email)
}
複製代碼
若是使用值接收者聲明方法,調用時會使用這個值的一個副原本執行。
// changeEmail 使用指針接收者實現了一個方法
func (u *user) changeEmail(email string) {
u.email = email
}
複製代碼
值接收者使用值的副原本調用方法,而指針接受者使用實際值來調用方法。
內置類型是由語言提供的一組類型,當把這些類型的值傳遞給方法或者函數時,應該傳遞一個對應值的 副本。
引用類型,Go 語言裏的引用類型有以下幾個:切片、映射、通道、接口和函數類型。經過複製來傳遞一個引用類型的值的副本,本質上就是在共享底層數據結構。
Go 語言容許用戶擴展或者修改已有類型的行爲。這個功能對代碼複用很重要,在修改已有 類型以符合新類型的時候也很重要。這個功能是經過嵌入類型(type embedding)完成的。嵌入類 型是將已有的類型直接聲明在新的結構類型裏。被嵌入的類型被稱爲新的外部類型的內部類型。
type user3 struct {
name string
email string
}
type admin struct {
user3
level string
}
複製代碼
上面的代碼咱們聲明瞭一個名爲 user 的結構類型和另外一個名爲admin 的結構類型。在聲明 admin 類型的方法中,咱們將 user類型嵌入admin類型裏。要嵌入一個類型,只須要聲明這個類型的名字就能夠了。 一旦咱們將 user 類型嵌入 admin,咱們就能夠說 user 是外部類型 admin 的內部類型。
當一個標識符的名字以小寫字母開頭時,這個標識符就是未公開的,即對包外的代碼不可見。 若是一個標識符以大寫字母開頭,這個標識符就是公開的,即對包外的代碼可見。
Go 語言裏的併發指的是能讓某個函數獨立於其餘函數運行的能力。當一個函數建立爲 goroutine 時,Go 會將其視爲一個獨立的工做單元。這個單元會被調度到可用的邏輯處理器上執行。
Go 語言的併發同步模型來自一個叫做通訊順序進程(Communicating Sequential Processes,CSP) 的範型(paradigm)。CSP 是一種消息傳遞模型,經過在 goroutine 之間傳遞數據來傳遞消息,而不是 對數據進行加鎖來實現同步訪問。用於在 goroutine 之間同步和傳遞數據的關鍵數據類型叫做通道 (channel)。
併發(concurrency)不是並行(parallelism)。並行是讓不一樣的代碼片斷同時在不一樣的物理處 理器上執行。並行的關鍵是同時作不少事情,而併發是指同時管理不少事情,這些事情可能只作 了一半就被暫停去作別的事情了。在不少狀況下,併發的效果比並行好,由於操做系統和硬件的 總資源通常不多,但能支持系統同時作不少事情。
若是兩個或者多個 goroutine在沒有互相同步的狀況下,訪問某個共享的資源,並試圖同時讀和寫這個資源,就處於相互競爭的狀態,這種狀況被稱做競爭狀態(racecandition)。對一個共享資源的讀和寫操做必須是原子化的,換句話說,同一時刻只能有一個goroutine對共享資源進行讀和寫操做。
一種修正代碼、消除競爭狀態的辦法是,使用 Go 語言提供的鎖機制,來鎖住共享資源,從 而保證 goroutine 的同步狀態。Go 語言提供了傳統的同步 goroutine 的機制,就是對共享資源加鎖。若是須要順序訪問一個 整型變量或者一段代碼,atomic 和 sync 包裏的函數提供了很好的解決方案。
原子函數可以以很底層的加鎖機制來同步訪問整型變量和指針。
例如atmoic 包中的AddInt64,LoadInt64和StoreInt64這幾個原子函數。
互斥鎖用於在代碼上建立一個臨界區,保證同一時間只有一個 goroutine 能夠 執行這個臨界區代碼(Mutex)。
mutex.Lock()//加鎖
臨界區的代碼
mutex。Unlock()//解鎖
複製代碼
當一個資源須要在 goroutine 之間共享時,通道在 goroutine 之間架起了一個管道,並提供了 確保同步交換數據的機制。聲明通道時,須要指定將要被共享的數據的類型。能夠經過通道共享 內置類型、命名類型、結構類型和引用類型的值或者指針。 在 Go 語言中須要使用內置函數 make 來建立一個通道,例如:
// 無緩衝的整型通道
unbuffered := make(chan int)
// 有緩衝的字符串通道
buffered := make(chan string, 10)
複製代碼
向通道發送值或者指針須要用到<-操做符
// 經過通道發送一個字符串
buffered <- "Gopher"
// 從通道接收一個字符串
value := <-buffered
複製代碼
注意<- 在通道的位置,向通道發送時在右邊,從通道中接收時在左邊。
指在接收前沒有能力保存任何值的通道。要求發送和接收的goroutine必須同時準備好才能完成發送和接收的操做,不然會形成goroutine阻塞。
有緩衝的通道(buffered channel)是一種在被接收前能存儲一個或者多個值的通道。這種類 型的通道並不強制要求 goroutine 之間必須同時完成發送和接收。通道會阻塞發送和接收動做的 條件也會不一樣。只有在通道中沒有要接收的值時,接收動做纔會阻塞。只有在通道沒有可用緩衝 區容納被髮送的值時,發送動做纔會阻塞。這致使有緩衝的通道和無緩衝的通道之間的一個很大 的不一樣:無緩衝的通道保證進行發送和接收的 goroutine 會在同一時間進行數據交換;有緩衝的 通道沒有這種保證。
runner 包用於展現如何使用通道來監視程序的執行時間,若是程序運行時間太長,也能夠 用 runner 包來終止程序。當開發須要調度後臺處理任務的程序的時候,這種模式會頗有用。
Go 標準庫是一組核心包,用來擴展和加強語言的能力。這些包爲語言增長了大量不一樣的類型。
一個簡單的自定義日誌記錄器
package main
import (
"io"
"io/ioutil"
"log"
"os"
)
var (
Trace *log.Logger
Info *log.Logger
Warning *log.Logger
Error *log.Logger
)
func init() {
file, err := os.OpenFile("errors.txt",
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open error log file:", err)
}
Trace = log.New(ioutil.Discard, "TRACE:", log.Ldate|log.Ltime|log.Lshortfile)
Info = log.New(os.Stdout, "INFO:", log.Ldate|log.Ltime|log.Lshortfile)
Warning = log.New(os.Stdout, "WARNING:", log.Ldate|log.Ltime|log.Lshortfile)
Error = log.New(io.MultiWriter(file, os.Stderr), "ERROR:", log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
Trace.Println("I have something standard to say")
Info.Println("Special Information")
Warning.Println("There is something you need to know about")
Error.Println("Something has failed")
}
複製代碼
使用 json 包的 NewDecoder 函數以及 Decode方法進行解碼。
解析來自網絡的json數據
// 將 JSON 響應解碼到結構類型
var gr gResponse
err = json.NewDecoder(resp.Body).Decode(&gr)
if err != nil {
log.Println("ERROR:", err)
return
}
fmt.Println(gr)
複製代碼
有時,須要處理的 JSON 文檔會以 string 的形式存在。在這種狀況下,須要將 string 轉換 爲 byte 切片([]byte),並使用 json 包的 Unmarshal 函數進行反序列化的處理。
// 將 JSON 字符串反序列化到變量
var c Contact
err := json.Unmarshal([]byte(JSON), &c)
if err != nil {
log.Println("ERROR:", err)
return
}
複製代碼
沒法爲 JSON 的格式聲明一個結構類型,而是須要更加靈活的方式來處理 JSON 文檔。 在這種狀況下,能夠將 JSON 文檔解碼到一個 map 變量中。
// 將 JSON 字符串反序列化到 map 變量
var c map[string]interface{}
err := json.Unmarshal([]byte(JSON), &c)
if err != nil {
log.Println("ERROR:", err)
return
}
fmt.Println("Name:", c["name"])
fmt.Println("Title:", c["title"])
fmt.Println("Contact")
fmt.Println("H:", c["contact"].(map[string]interface{})["home"])
fmt.Println("C:", c["contact"].(map[string]interface{})["cell"])
複製代碼
使用json包的MarshalIndent函數進行編碼。這個函數能夠很方便地將 Go 語言的 map 類型的值或者結構類型的值轉換爲易讀格式的 JSON 文檔。
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
fmt.Println("ERROR:", err)
return
}
fmt.Println(string(data))
複製代碼
go test 命令能夠用來執行寫好的測試代碼,須要作的就是遵照一些規則來寫測試。並且,能夠將測試無縫地集成到代碼工程和持續集成系統裏。
Go中的單元測試有如下兩種:
注意點1 Go 語言的測試工具只會認爲以_test.go 結尾的文件是測試文件。
注意點2 一個測試函數必須是公開的函數,而且以 Test 單詞開頭。不但函數名字要以 Test 開頭,並且函數的簽名必須接收一個指向 testing.T 類型的指針,而且不返回任何值。
例如:
func TestDownload(t *testing.T)
const checkMark = "\u2713" //輸出對號
const ballotX = "\u2717" //輸出錯號
複製代碼
標準庫包含一個名爲 httptest 的包,它讓開發人員能夠模仿基於 HTTP 的網絡調用。
func mockServer() *httptest.Server {
f := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/xml")
fmt.Fprintln(w, feed)
}
return httptest.NewServer(http.HandlerFunc(f))
}
複製代碼
基準測試是一種測試代碼性能的方法。想要測試解決同一問題的不一樣方案的性能,以及查看 哪一種解決方案的性能更好時,基準測試就會頗有用。基準測試也能夠用來識別某段代碼的 CPU 或者內存效率問題,而這段代碼的效率可能會嚴重影響整個應用程序的性能。