Go語(yǔ)言區(qū)別于其他語(yǔ)言的特性
前言:
隨著編程語(yǔ)言的發(fā)展,Go 還很年輕。它于 2009 年 11 月 10 日首次發(fā)布。其創(chuàng)建者Robert Griesemer Rob Pike
和 Ken Thompson
在 Google
工作,在那里大規(guī)模擴(kuò)展的挑戰(zhàn)激勵(lì)他們將 Go 設(shè)計(jì)為一種快速有效的編程解決方案,用于具有大型代碼庫(kù)、管理由多個(gè)開(kāi)發(fā)人員,具有嚴(yán)格的性能要求,并跨越多個(gè)網(wǎng)絡(luò)和處理核心。 Go 的創(chuàng)始人在創(chuàng)建他們的新語(yǔ)言時(shí)也借此機(jī)會(huì)學(xué)習(xí)了其他編程語(yǔ)言的優(yōu)點(diǎn)、缺點(diǎn)和漏洞。結(jié)果是一種干凈、清晰和實(shí)用的語(yǔ)言,具有相對(duì)較少的命令和功能集。
1. Go 總是在構(gòu)建中包含二進(jìn)制文件
Go 運(yùn)行時(shí)提供內(nèi)存分配、垃圾收集、并發(fā)支持和網(wǎng)絡(luò)等服務(wù)。它被編譯到每個(gè) Go 二進(jìn)制文件中。這與許多其他語(yǔ)言不同,其中許多語(yǔ)言使用需要與程序一起安裝才能正常工作的虛擬機(jī)。
將運(yùn)行時(shí)直接包含在二進(jìn)制文件中使得分發(fā)和運(yùn)行 Go 程序變得非常容易,并避免了運(yùn)行時(shí)和程序之間的不兼容問(wèn)題。Python
、Ruby
和 JavaScript
等語(yǔ)言的虛擬機(jī)也沒(méi)有針對(duì)垃圾收集和內(nèi)存分配進(jìn)行優(yōu)化,這解釋了 Go 相對(duì)于其他類似語(yǔ)言的優(yōu)越速度。例如,Go 將盡可能多的存儲(chǔ)在堆棧中,其中數(shù)據(jù)按順序排列以便比堆更快地訪問(wèn)。稍后會(huì)詳細(xì)介紹。
關(guān)于 Go 的靜態(tài)二進(jìn)制文件的最后一件事是,因?yàn)椴恍枰\(yùn)行外部依賴項(xiàng),所以它們啟動(dòng)得非???。如果您使用Google App Engine 之類的服務(wù),這是一種在 Google Cloud
上運(yùn)行的平臺(tái)即服務(wù),它可以將您的應(yīng)用程序縮減到零實(shí)例以節(jié)省云成本,這將非常有用。當(dāng)收到新請(qǐng)求時(shí),App Engine
可以在眨眼間啟動(dòng) Go 程序的一個(gè)實(shí)例。在 Python
或 Node 中的相同體驗(yàn)通常會(huì)導(dǎo)致 3-5 秒(或更長(zhǎng)時(shí)間)的等待,因?yàn)樗璧奶摂M環(huán)境也會(huì)與新實(shí)例一起啟動(dòng)。
2. Go 沒(méi)有針對(duì)程序依賴的集中托管服務(wù)
為了訪問(wèn)已發(fā)布的 Go 程序,開(kāi)發(fā)人員不依賴集中托管的服務(wù),例如Java 的Maven Central
或JavaScript
的NPM注冊(cè)表。相反,項(xiàng)目通過(guò)其源代碼存儲(chǔ)庫(kù)(最常見(jiàn)的是 Github
)共享。在go install命令行允許以這種方式下載庫(kù)。 為什么我喜歡這個(gè)功能?我一直認(rèn)為像 Maven Central
、PIP
和 NPM 這樣的集中托管的依賴服務(wù)有點(diǎn)令人生畏的黑盒子,也許可以抽象出下載和安裝依賴項(xiàng)的麻煩,但不可避免地會(huì)在依賴項(xiàng)錯(cuò)誤時(shí)引發(fā)可怕的心跳停止發(fā)生。
此外,將的的模塊提供給其他人就像將其放入版本控制系統(tǒng)一樣簡(jiǎn)單,這是分發(fā)程序的一種非常簡(jiǎn)單的方式。
3. Go 是按值調(diào)用的
在 Go 中,當(dāng)你提供一個(gè)原始值(數(shù)字、布爾值或字符串)或一個(gè)結(jié)構(gòu)體(類對(duì)象的粗略等價(jià)物)作為函數(shù)的參數(shù)時(shí),Go 總是會(huì)復(fù)制變量的值。
在 Java
、Python
和 JavaScript
等許多其他語(yǔ)言中,原語(yǔ)是按值傳遞的,但對(duì)象(類實(shí)例)是按引用傳遞的,這意味著接收函數(shù)實(shí)際上接收的是指向原始對(duì)象的指針,而不是其副本。在接收函數(shù)中對(duì)對(duì)象所做的任何更改都會(huì)反映在原始對(duì)象中。
在 Go 中,結(jié)構(gòu)體和原語(yǔ)默認(rèn)按值傳遞,可以選擇傳遞指針,通過(guò)使用星號(hào)運(yùn)算符:
// 按值傳遞 func MakeNewFoo(f Foo ) (Foo, error) { f.Field1 = "New val" f.Field2 = f.Field2 + 1 return f, nil }
上述函數(shù)接收 Foo 的副本并返回一個(gè)新的 Foo 對(duì)象。
// 通過(guò)引用傳遞 func MutateFoo(f *Foo ) error { f.Field1 = "New val" f.Field2 = 2 return nil }
上面的函數(shù)接收一個(gè)指向 Foo 的指針并改變?cè)紝?duì)象。
按值調(diào)用與按引用調(diào)用的這種明顯區(qū)別使您的意圖顯而易見(jiàn),并降低了調(diào)用函數(shù)無(wú)意中改變傳入對(duì)象的可能性(當(dāng)它不應(yīng)該發(fā)生時(shí)(許多初學(xué)者開(kāi)發(fā)人員很難做到這一點(diǎn))握緊)。
正如麻省理工總結(jié)的:“可變性使得理解你的程序在做什么變得更加困難,并且更難以執(zhí)行契約”
此外,按值調(diào)用顯著減少了垃圾收集器的工作,這意味著應(yīng)用程序更快、內(nèi)存效率更高。這篇文章得出的結(jié)論是,指針追蹤(從堆中檢索指針值)比從連續(xù)堆棧中檢索值慢 10 到 20 倍。要記住的一個(gè)很好的經(jīng)驗(yàn)法則是:從內(nèi)存中讀取的最快方法是順序讀取,這意味著將隨機(jī)存儲(chǔ)在 RAM 中的指針數(shù)量減少到最少。
4. 'defer' 關(guān)鍵字
在NodeJS
中,在我開(kāi)始使用knex.js之前,我會(huì)通過(guò)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)池來(lái)手動(dòng)管理我的代碼中的數(shù)據(jù)庫(kù)連接,然后在每個(gè)函數(shù)中從池中打開(kāi)一個(gè)新連接,一旦所需的數(shù)據(jù)庫(kù) CRUD 功能已完成。
這有點(diǎn)像維護(hù)噩夢(mèng),因?yàn)槿绻覜](méi)有在每個(gè)函數(shù)結(jié)束時(shí)釋放連接,未釋放的數(shù)據(jù)庫(kù)連接的數(shù)量會(huì)慢慢增長(zhǎng),直到池中沒(méi)有更多可用連接,然后中斷應(yīng)用程序。
現(xiàn)實(shí)情況是,程序經(jīng)常需要釋放、清理和拆除資源、文件、連接等,因此 Go 引入了defer
關(guān)鍵字作為管理這些的有效方式。
任何以defer
開(kāi)頭的語(yǔ)句都會(huì)延遲對(duì)它的調(diào)用,直到周圍的函數(shù)退出。這意味著您可以將清理/拆卸代碼放在函數(shù)的頂部(很明顯),知道一旦函數(shù)完成它就會(huì)如此。
func main() { if len(os.Args) < 2 { log.Fatal("no file specified") } f, err := os.Open(os.Args[1]) if err != nil { log.Fatal(err) } defer f.Close() data := make([]byte, 2048) for { count, err := f.Read(data) os.Stdout.Write(data[:count]) if err != nil { if err != io.EOF { log.Fatal(err) } break } } }
在上面的例子中,文件關(guān)閉方法被推遲了。我喜歡這種在函數(shù)頂部聲明你的內(nèi)務(wù)處理意圖的模式,然后忘記它,知道一旦函數(shù)退出它就會(huì)完成它的工作。
5. Go 采用了函數(shù)式編程的最佳特性
函數(shù)式編程是一種高效且富有創(chuàng)造性的范式,幸運(yùn)的是 Go 采用了函數(shù)式編程的最佳特性。在Go中:
- 函數(shù)是值,這意味著它們可以作為值添加到映射中,作為參數(shù)傳遞給其他函數(shù),設(shè)置為變量,并從函數(shù)返回(稱為“高階函數(shù)”,在 Go 中經(jīng)常使用裝飾器創(chuàng)建中間件圖案)。
- 可以創(chuàng)建和自動(dòng)調(diào)用匿名函數(shù)。
- 在其他函數(shù)內(nèi)聲明的函數(shù)允許閉包(在函數(shù)內(nèi)聲明的函數(shù)能夠訪問(wèn)和修改在外部函數(shù)中聲明的變量)。在慣用的 Go 中,閉包被廣泛使用來(lái)限制函數(shù)的范圍,并設(shè)置函數(shù)然后在其邏輯中使用的狀態(tài)。
func StartTimer (name string) func(){ t := time.Now() log.Println(name, "started") return func() { d := time.Now().Sub(t) log.Println(name, "took", d) } } func RunTimer() { stop := StartTimer("My timer") defer stop() time.Sleep(1 * time.Second) }
上面是一個(gè)閉包的例子。'StartTimer
' 函數(shù)返回一個(gè)新函數(shù),它通過(guò)閉包可以訪問(wèn)在其出生范圍內(nèi)設(shè)置的 't' 值。然后,此函數(shù)可以將當(dāng)前時(shí)間與“t”的值進(jìn)行比較,從而創(chuàng)建一個(gè)有用的計(jì)時(shí)器。感謝Mat Ryer的這個(gè)例子。
6. Go 有隱式接口
任何閱讀過(guò)有關(guān)SOLID
編碼和設(shè)計(jì)模式的文獻(xiàn)的人都可能聽(tīng)說(shuō)過(guò)“優(yōu)先組合勝過(guò)繼承”的口頭禪。簡(jiǎn)而言之,這表明您應(yīng)該將業(yè)務(wù)邏輯分解為不同的接口,而不是依賴于來(lái)自父類的屬性和邏輯的分層繼承。
另一個(gè)流行的方法是“為接口編程,而不是實(shí)現(xiàn)”: API 應(yīng)該只發(fā)布其預(yù)期行為的契約(其方法簽名),而不是有關(guān)如何實(shí)現(xiàn)該行為的詳細(xì)信息。
這兩者都表明接口在現(xiàn)代編程中的重要性。
因此,Go 支持接口也就不足為奇了。事實(shí)上,接口是 Go 中唯一的抽象類型。
然而,與其他語(yǔ)言不同,Go 中的接口不是顯式實(shí)現(xiàn)的,而是隱式實(shí)現(xiàn)的。具體類型不聲明它實(shí)現(xiàn)了接口。相反,如果為該具體類型設(shè)置的方法集包含底層接口的所有方法集,則Go 認(rèn)為該對(duì)象實(shí)現(xiàn)了 interface
。
這種隱式接口實(shí)現(xiàn)(正式稱為結(jié)構(gòu)類型)允許 Go 強(qiáng)制執(zhí)行類型安全和解耦,保持動(dòng)態(tài)語(yǔ)言中表現(xiàn)出的大部分靈活性。
相比之下,顯式接口將客戶端和實(shí)現(xiàn)綁定在一起,例如,在 Java 中替換依賴項(xiàng)比在 Go 中困難得多。
// 這是一個(gè)接口聲明(稱為L(zhǎng)ogic) type Logic interface { Process (data string) string } type LogicProvider struct {} // 這是 LogicProvider 上名為“Process”的方法 struct func (lp LogicProvider) Process (data string) string { // 業(yè)務(wù)邏輯 } // 這是具有 Logic 接口作為屬性的客戶端結(jié)構(gòu) type Client struct { L Logic } func(c Client) Program() { // 從某處獲取數(shù)據(jù) cLProcess(data) } func main() { c := Client { L: LogicProvider{}, } c.Program() }
LogicProvider
中沒(méi)有任何聲明表示它符合Logic
接口。這意味著客戶端將來(lái)可以輕松替換其邏輯提供程序,只要該邏輯提供程序包含底層接口 ( Logic ) 的所有方法集。
7.錯(cuò)誤處理
Go 中的錯(cuò)誤處理方式與其他語(yǔ)言大不相同。簡(jiǎn)而言之,Go 通過(guò)返回一個(gè) error 類型的值作為函數(shù)的最后一個(gè)返回值來(lái)處理錯(cuò)誤。
當(dāng)函數(shù)按預(yù)期執(zhí)行時(shí),錯(cuò)誤參數(shù)返回nil,否則返回錯(cuò)誤值。調(diào)用函數(shù)然后檢查錯(cuò)誤返回值,并處理錯(cuò)誤,或拋出自己的錯(cuò)誤。
// 函數(shù)返回一個(gè)整數(shù)和一個(gè)錯(cuò)誤 func calculateRemainder(numerator int, denominator int) ( int, error ) { // if denominator == 0 { return 9, errors.New("denominator is 0" } // 沒(méi)有錯(cuò)誤返回 return numerator / denominator, nil }
Go 以這種方式運(yùn)行是有原因的:它迫使編碼人員考慮異常并正確處理它們。傳統(tǒng)的 try-catch
異常還會(huì)在代碼中添加至少一個(gè)新的代碼路徑,并以難以遵循的方式縮進(jìn)代碼。Go 更喜歡將“快樂(lè)路徑”視為非縮進(jìn)代碼,在“快樂(lè)路徑”完成之前識(shí)別并返回任何錯(cuò)誤。
8.并發(fā)
可以說(shuō)是 Go 最著名的特性,并發(fā)允許處理在機(jī)器或服務(wù)器上的可用內(nèi)核數(shù)量上并行運(yùn)行。當(dāng)單獨(dú)的進(jìn)程不相互依賴(不需要順序運(yùn)行)并且時(shí)間性能至關(guān)重要時(shí),并發(fā)性最有意義。這通常是 I/O 要求的情況,其中讀取或?qū)懭氪疟P(pán)或網(wǎng)絡(luò)的速度比除最復(fù)雜的內(nèi)存中進(jìn)程之外的所有進(jìn)程慢幾個(gè)數(shù)量級(jí)。 函數(shù)調(diào)用前的“ go ”關(guān)鍵字將同時(shí)運(yùn)行該函數(shù)。
func process(val int) int { // 用 val 做一些事情 } // 對(duì)于 'in' 中的每個(gè)值,同時(shí)運(yùn)行 process 函數(shù), // 并將 process 的結(jié)果讀取到 'out' func runConcurrently(in <-chan int, out chan<- int){ go func() { for val := range in { result := process(val) out <- result } } }
Go 中的并發(fā)是一項(xiàng)深入且相當(dāng)高級(jí)的功能,但在有意義的地方,它提供了一種有效的方法來(lái)確保程序的最佳性能。
9. Go 標(biāo)準(zhǔn)庫(kù)
Go 有一個(gè)“包含電池”的理念,現(xiàn)代編程語(yǔ)言的許多要求都被納入標(biāo)準(zhǔn)庫(kù),這使程序員的生活變得更加簡(jiǎn)單。 如前所述,Go 是一種相對(duì)年輕的語(yǔ)言,這意味著標(biāo)準(zhǔn)庫(kù)中可以滿足現(xiàn)代應(yīng)用程序的許多問(wèn)題/要求。
一方面,Go 為網(wǎng)絡(luò)(特別是 HTTP/2)和文件管理提供了世界一流的支持。它還提供原生 JSON 編碼和解碼。因此,設(shè)置服務(wù)器來(lái)處理 HTTP 請(qǐng)求并返回響應(yīng)(JSON 或其他)非常簡(jiǎn)單,這解釋了 Go 在基于 REST 的 HTTP Web
服務(wù)開(kāi)發(fā)中的流行。
正如Mat Ryer
還指出的那樣,標(biāo)準(zhǔn)庫(kù)是開(kāi)源的,是學(xué)習(xí) Go 最佳實(shí)踐的絕佳方式。
到此這篇關(guān)于Go 有別于其他語(yǔ)言的九個(gè)特性 的文章就介紹到這了,更多相關(guān)Go 有別于其他語(yǔ)言的特性 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Go進(jìn)行單元測(cè)試的實(shí)現(xiàn)
這篇文章主要介紹了使用Go進(jìn)行單元測(cè)試的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11淺析Go使用定時(shí)器時(shí)如何避免潛在的內(nèi)存泄漏陷阱
這篇文章來(lái)和大家一起探討一下Go?中如何高效使用?timer,特別是與select?一起使用時(shí),如何防止?jié)撛诘膬?nèi)存泄漏問(wèn)題,感興趣的可以了解下2024-01-01Go語(yǔ)言kube-scheduler深度剖析與開(kāi)發(fā)之pod調(diào)度
這篇文章主要為大家介紹了Go語(yǔ)言kube-scheduler深度剖析與開(kāi)發(fā),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Go語(yǔ)言中make和new函數(shù)的用法與區(qū)別
這篇文章介紹了Go語(yǔ)言中make和new函數(shù)的用法與區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07golang中l(wèi)og包自定義輸出日志格式與寫(xiě)入到文件
這篇文章主要給大家介紹了關(guān)于golang中l(wèi)og包自定義輸出日志格式與寫(xiě)入到文件的相關(guān)資料,日志輸出在任何項(xiàng)目中都極其重要,是有助于后續(xù)我們排查解決程序BUG,需要的朋友可以參考下2023-06-06RabbitMQ延時(shí)消息隊(duì)列在golang中的使用詳解
延時(shí)隊(duì)列常使用在某些業(yè)務(wù)場(chǎng)景,使用延時(shí)隊(duì)列可以簡(jiǎn)化系統(tǒng)的設(shè)計(jì)和開(kāi)發(fā)、提高系統(tǒng)的可靠性和可用性、提高系統(tǒng)的性能,下面我們就來(lái)看看如何在golang中使用RabbitMQ的延時(shí)消息隊(duì)列吧2023-11-11