固然,當前在企業級應用開發中,Java依然佔有一席之地。
學習任何知識都會有一個學習背景linux
最近,咱們團隊乃至我司整個雲服務,上go的呼聲愈來愈高!新服務已經開始用go開發,部分現有Java版的服務重構爲go也只是時間問題而已,故相關技術積累勢在必行!在雲網絡的分佈式服務乃至在一切高併發,分佈式後臺服務中,golang都有着很大的優點。git
據我對國內互聯網行業的實際考察,瞭解,目前國內主流互聯網公司都在積極投入go的懷抱……github
青雲更是全棧使用了go……golang
還有火的一塌糊塗的docker。算法
它爲雲而生。docker
它爲併發而生。數據庫
還有go的安全、簡潔、高效編程
有良好的Java、C/C++背景,拿起go很容易。json
……windows
參考:
2017年編程語言排行榜,相比2016,go的飛速上升趨勢很明顯。而Java有很大份額的縮減……
固然,當前在企業級應用開發中,Java依然佔有一席之地。
而我的認爲:一個優秀的研發工程師乃至架構師……是不能僅僅侷限於一門語言的,語言只是拿來就用的工具,關鍵仍是實戰經驗和行業內功的修煉。可是對於任何編程語言,不學也不是憑空就會的。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安裝go
brew install go
看安裝成功以後的提示就能知道,go的運行須要一個環境變量——GOPATH,是go中很是重要的一個環境變量,必須配置好,假如不配,那麼安裝go的一些工具,或者本身寫的包,就會出問題。固然還有GOROOT。
PS:其實在目前的go版本中,這些環境變量已經在安裝的時候默認被配置ok了。只須要在機器的環境配置文件中作以下操做便可:
You may wish to add the GOROOT-based install location to your PATH: export PATH=$PATH:/usr/local/opt/go/libexec/bin ==> Summary 🍺 /usr/local/Cellar/go/1.9: 7,639 files, 293.7MB
下面查看go的環境變量配置,看看配置的狀況如何了。
發現查看環境變量的是go env命令
bash-3.2$ go env GOARCH="amd64" GOBIN="" GOEXE="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOOS="darwin" GOPATH="/Users/dashuai/go" GORACE="" GOROOT="/usr/local/Cellar/go/1.9/libexec" GOTOOLDIR="/usr/local/Cellar/go/1.9/libexec/pkg/tool/darwin_amd64" GCCGO="gccgo" CC="clang" GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build746511617=/tmp/go-build -gno-record-gcc-switches -fno-common" CXX="clang++" CGO_ENABLED="1" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config"
發現GOROOT、GOPATH等環境變量確實已經在安裝go 1.9 時,被默認設置。
而後,按照以前安裝go成功以後的提示,在.bash_profile文件中進行以下配置:
export PATH=$PATH:/usr/local/opt/go/libexec/bin
最後,source下便可。
PS:各個環境變量含義
$GOROOT
表示go的安裝位置,它的值通常都是 $HOME/go,固然也能夠安裝在別的地方,這個都無所謂的。$GOARCH
表示目標機器的處理器架構。$GOOS
表示目標機器的操做系統,它的值能夠是 darwin,freebsd,linux 或 windows。$GOBIN
表示編譯器和連接器的安裝位置,默認是 $GOROOT/bin,若是使用的是 Go 1.0.3 及之後的版本,通常狀況下能夠將它的值設置爲空,Go 將會使用前面提到的默認值。$GOPATH
表示工做的環境路徑,容許包含多個目錄。當有多個目錄時,Windows分隔符是分號,Linux是冒號,當有多個GOPATH時,默認會將go get
命令的內容放在第一個目錄下。$GOPATH
目錄約定有三個子目錄:
${GOPATH//://bin:}/bin
添加全部的bin目錄)不少Go 命令都依賴於此變量,例如go get
命令會將獲取到的包放到GOPATH中。1 package main 2 import "fmt" 3 4 /* 5 註釋風格和C/C++,JAVA相似 6 */ 7 func main() { 8 fmt.Println("hello world!"); //打印hello world! 9 } 10
wangyishuaideMacBook-Pro:goStu wangyishuai$ go build hello.go wangyishuaideMacBook-Pro:goStu wangyishuai$ ls hello hello.go wangyishuaideMacBook-Pro:goStu wangyishuai$ ./hello hello world!
固然,圖省事,直接使用go run命令執行也是ok的,可是這樣不會生成任何中間結果。
wangyishuaideMacBook-Pro:goStu wangyishuai$ rm hello wangyishuaideMacBook-Pro:goStu wangyishuai$ ls hello.go wangyishuaideMacBook-Pro:goStu wangyishuai$ go run hello.go hello world! wangyishuaideMacBook-Pro:goStu wangyishuai$ ls hello.go
有一個小坑,若是import的包不用,編譯會報錯。好比,fmt包導入了就必須使用,相似的包括變量等。當註釋掉第8行代碼,在執行會報錯,這點嚴謹性,比Java強多了。
wangyishuaideMacBook-Pro:goStu wangyishuai$ go run hello.go # command-line-arguments ./hello.go:2:8: imported and not used: "fmt"
如上,其實和其餘大部分語言都差很少。其中go、chan、以及select是go的特點和重點,又好比map相似Java的hashmap……其餘的也沒什麼新鮮的,接下來快速過。
go程序相似Java,都是經過package組織,而後每一個獨立的、可運行的go程序的入口必須都是main函數,也就是main包裏的main函數。
var是go最基本的定義變量方式 ,可是它的變量類型是放在了變量名後面。
以下,格式化打印語句
Printf
和C語言很是像,其實go就是C語言衍生改進而來,這也是爲何稱它爲互聯網世界中的C語言的緣由。
package main import ( "fmt" ) func main() { var x int; x = 1; fmt.Printf("%d", x); }
有時候會以爲老是多寫一個var比較煩,那麼能夠用:=來定義變量(我的感受也不是想象中的那麼簡潔……)
package main import ( "fmt" ) func main() { var x int; x = 1; fmt.Printf("%d", x); a, b, c := 1, 2, 3; // 自動判斷類型,相似Python fmt.Println(a, b, c); }
這種方式相似Python,省去了寫類型的步驟,在go程序中十分常見。可是這樣的寫法有一個缺點::= 沒法用於函數體外部,而var能夠。故var也是有存在的必要的。
eclipse直接提示變量y的定義錯誤,而變量x是ok的。
注意:
一、package和import必須挨着,也就是它們中間不能有其餘代碼,不然報錯。
二、對於沒有顯式初始化的變量,Go語言老是將零值賦值給該變量。
三、在Go語言中,聲明變量的時候,類型名老是在變量名的後面。
沒什麼可說的,和C語言同樣的用法,惟一不一樣的就是定義常量時,加類型或者不加均可以,可是注意,最好養成習慣,類型加在常量名後面。
func main() { fmt.Println(x); const dashuai string = "dashuai"; const gogogo = 888; }
並且,字符串數據類型和Java不太同樣,是徹底小寫的string。下面會說到。
注意:
一、當須要設置多個常量的時候,沒必要重複使用const
,可使用小括號設置 (var同理
)
package main import ( "fmt" ) func main() { var ( a int; b string; c bool; ) fmt.Println(a, b, c) // 0 false const ( aa = 1 bb = "dada" cc = true ) fmt.Println(aa, bb, cc) }
bool byte complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr
無類型的數值常量能夠兼容go內置的任何類型的數值
在不一樣類型數值之間進行運算或者比較操做,須要進行類型轉換。類型轉換採用type(value)
的方式,只要合法就能轉換成功,即便會致使數據精度丟失。
快速過一下,和Java對比,以下經常使用的幾個:
一、go獨有的 rune,其實就是表明了無符號32位整型 unit32。
二、有符號整型 int (依賴於os,多是int32或者int64)
也就是說,go是見名知意,go中能夠直接定義xxx位的整型。
三、字節 byte,等價於 uint8
四、無符號整型 uint ,依賴於不一樣os實現,能夠是uint32或者uint64
能夠經過unsafe.Sizeof
函數實現相似C語言裏sizeof
操做符的功能,查看類型的字節長度。
package main import ( "fmt" "unsafe" ) func main() { str := "dashuai" fmt.Println(unsafe.Sizeof(str)) // 16 a := 1 fmt.Println(unsafe.Sizeof(a)) // 8 var b int8 = 1 fmt.Println(unsafe.Sizeof(b)) // 1 var c int32 = 1 fmt.Println(unsafe.Sizeof(c)) // 4 var d int64 = 1 fmt.Println(unsafe.Sizeof(d)) // 8 }
unsafe
包含了用於獲取Go語言類型信息的方法。
一、float32 ,±3.402 823 466 385 288 598 117 041 834 845 169 254 40x1038 計算精度大概是小數點後7個十進制數
二、float64,±1.797 693 134 862 315 708 145 274 237 317 043 567 981x1038 計算精度大概是小數點後15個十進制數
注意區分Java的boolean/Boolean寫法,實際上是和C語言同樣,這裏也說下,學習go,就對比着C語言和Java語言,這樣用起來是很快的。
布爾值使用內置的true和false。Go語言支持標準的邏輯和比較操做,這些操做的結果都是布爾值。還能夠經過!b
的方式反轉變量b
的真假。也就是邏輯非。
這也是go獨有的, complex3二、complex64,在針對一些算法的實現時,很是方便,好比傅里葉變換。
func main() { var x complex64 = 10 + 9i; fmt.Print(x + x); }
complex32複數,實部和虛部都是float32
complex64複數,實部和虛部都是float64
經常使用是string定義,Go語言中的字符串是 UTF-8編碼格式(當字符爲 ASCII 碼時則佔用 1 個字節,其它字符根據須要佔用 2-4 個字節)。
UTF-8 是被普遍使用的編碼格式,是文本文件的標準編碼,其它包括 XML 和 JSON 在內,也都使用該編碼。因爲該編碼對佔用字節長度的不定性,Go 中的字符串也可能根據須要佔用 1 至 4 個字節,這與C++、Java 或者 Python 不一樣。
Go 這樣作的好處是:不只減小了內存和硬盤空間佔用,同時也不用像其它語言那樣須要對使用 UTF-8 字符集的文本進行編碼和解碼。
Go語言中字符串的可使用雙引號( " )或者反引號( ` )來建立。
雙引號用來建立可解析的字符串,所謂可解析的是指字符串中的一些符號能夠被格式化爲其餘內容,如 "\n" 在輸出時候會被格式化成換行符, 若是須要按照原始字符輸出必須進行轉義。
反引號建立的字符串原始是什麼樣,那輸出仍是什麼,不須要進行任何轉義。
package main import ( "fmt" ) func main() { str := "123" str1 := "\n" str2 := `\n` str3 := "\\n" fmt.Println(str) // 123 fmt.Println(str1) // 換行 fmt.Println(str2) // \n fmt.Println(str3) // \n }
注意
一、Go語言中的部分轉義字符
\\ 反斜線
\' 單引號
\" 雙引號
\n 換行符
\uhhhh 4個16進制數字給定的Unicode字符
二、在Go語言中單個字符可使用單引號 ' 來建立。並且,一個單一的字符能夠用一個單一的rune
來表示(rune是uint32類型)。這也是容易理解的,由於Go語言的字符串是UTF-8編碼,其底層使用4個字節表示,也就是32 bit。
目前爲止,使用了fmt.Printf
和fmt.Prinfln
,對於前者的使用,就像C語言中的printf函數同樣,經常使用的格式化指令以下:
格式化指令 | 含義 |
---|---|
%% | %字面量 |
%b | 一個二進制整數,將一個整數格式化爲二進制的表達方式 |
%c | 一個Unicode的字符 |
%d | 十進制數值 |
%o | 八進制數值 |
%x | 小寫的十六進制數值 |
%X | 大寫的十六進制數值 |
%U | 一個Unicode表示法表示的整形碼值,默認是4個數字字符 |
%s | 輸出以原生的UTF-8字節表示的字符,若是console不支持UTF-8編碼,則會輸出亂碼 |
%t | 以true或者false的方式輸出布爾值 |
%v | 使用默認格式輸出值,或者使用類型的String()方法輸出的自定義值,若是該方法存在的話 |
%T | 輸出值的類型 |
經常使用的格式化指令修飾符以下:
空白
若是輸出的數字爲負,則在其前面加上一個減號"-"。若是輸出的是整數,則在前面加一個空格。使用%x或者%X格式化指令輸出時,會在結果之間添加一個空格。例如fmt.Printf("% X", "實")輸出E5 AE 9E#
%#o
輸出以0開始的八進制數據%#x
輸出以0x開始的十六進制數據+
讓格式化指令在數值前面輸出+號或者-號,爲字符串輸出ASCII字符(非ASCII字符會被轉義),爲結構體輸出其字段名-
讓格式化指令將值向左對齊(默認值爲像右對齊)0
讓格式指令以數字0而非空白進行填充strings
包提供瞭如查找字符串,分割字符串,判斷先後綴,判斷字符串包含,字符串替換,統計字符串出現的次數等經常使用操做。
strconv
包提供了許多能夠在字符串和其餘類型的數據之間進行轉換的函數。例如能夠將數字轉換爲字符串,將數字樣式的字符串轉換爲數值(將字符串"12345"
轉換int
類型的整數)
這些直接查文檔就能夠了。
array能夠類比Python的元組,slice能夠類比Python的列表,Java的list,很好理解。
package main import ( "fmt" ) /* array用法 */ func main() { var x [10]int; x[0] = 100; x[8] = 800; // %v 能夠直接遍歷打印數組,很是方便,C語言是沒有的。很是高級的用法%v,意思是value,一樣數組也不能越界使用。 // 若是沒有初值,自動設爲0 fmt.Printf("%v \n", x); // 看長度,和Python的len函數同樣 fmt.Println(len(x)); var str [5]string; str[3] = "aaaa"; // 默認初值爲空串 fmt.Printf("%v \n", str); // 若是使用簡潔聲明,那麼能夠直接賦值 y := [3]int{1, 2, 3} fmt.Printf("%v \n", y); }
在看下slice,其實它就是一個動態數組
var x [10]int; // 這是一個靜態數組,array,以前說過了 var slice_x []int; // 很是明顯,不須要顯式聲明長度
還能夠經過從一個數組或在一個已經存在的slice中再次聲明slice
xx := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 一個靜態數組 z := xx[1:3]; //對xx切片(和Python同樣的道理),切片範圍是前閉後開區間,也就是截取了2,3。而後截取的結果賦值給數組z,go的數組一樣從下標0開始
其實,如今的z就變爲了動態數組slice,相似於指針。
一樣slice也能夠用簡潔方式聲明,而且配合關鍵字make去分配內存空間,make用於內建類型(map、slice 和channel)的內存分配,有些相似C語言的malloc。
package main import ( "fmt" ) func main() { x := make([]int, 3, 8); // 長度爲3,容量爲8 fmt.Printf("%v \n", x); // [0 0 0] // 會自動擴容 x = append(x, 1, 2, 3, 4, 5, 6, 7, 8); fmt.Printf("%v \n", x); // [0 0 0 1 2 3 4 5 6 7 8] fmt.Printf("%v %v \n", len(x), cap(x)); // 11 16 }
小結
一、總的來講,以下圖,能夠從一個array聲明slice,其實就是指針指向。
二、在Go語言中,字符串支持切片操做,可是須要注意的是:
若是字符串都是由ASCII字符組成,那能夠隨便使用切片進行操做
若是字符串中包含其餘非ASCII字符,直接使用切片獲取想要的單個字符時須要十分當心,由於對字符串直接使用切片時是經過字節進行索引的,可是非ASCII字符在內存中可能不是由一個字節組成。若是想對字符串中字符依次訪問,可使用range
操做符。
另外獲取字符串的長度可能有兩種含義,一種是指獲取字符串的字節長度,一種是指獲取字符串的字符數量。
三、字符串支持如下操做:
語法 | 描述 |
---|---|
s += t | 將字符串t追加到s末尾 |
s + t | 將字符串s和t級聯 |
s[n] | 從字符串s中索引位置爲n處的原始字節 |
s[n:m] | 從位置n到位置m-1處取得的字符(字節)串 |
s[n:] | 從位置n到位置len(s)-1處取得的字符(字節)串 |
s[:m] | 從位置0到位置m-1處取得的字符(字節)串 |
len(s) | 字符串s中的字節數 |
len([]rune(s)) | 字符串s中字符的個數,可使用更快的方法utf8.RuneCountInString() |
[ ]rune(s) | 將字符串s轉換爲一個unicode值組成的串 |
string(chars) | chars類型是[]rune或者[]int32, 將之轉換爲字符串 |
[ ]byte(s) | 無副本的將字符串s轉換爲一個原始的字節的切片數組,不保證轉換的字節是合法的UTF-8編碼字節 |
一般狀況下Go語言中的變量持有相應的值。也就是說,咱們能夠將一個變量想象成它所持有的值來使用。
其中有些例外:通道、函數、方法、map、切片slice 是引用變量,它們持有的都是引用,也即保存指針的變量。
在go中,值在傳遞給函數或者方法的時候會被複制一次,對於布爾類型和數值類型來講這so easy,可是對於大型變量卻代價很大。並且複製值傳參的方式,修改值只是修改了副本,這能保證原始變量不被修改,但也必定程度上增長了修改原始值的麻煩。(和C語言同樣的道理,一樣go也保留了C語言的指針)
Go語言中有指針,也沒什麼新鮮東西,一樣在每次傳遞給函數或者方法的只是變量的內存地址,這是很是高效的。並且一個被指針指向的變量能夠經過該指針來修改,這就很方便的在函數或者方法中經過指針修改原始變量。
Go語言中的指針操做符也是使用&
和*
操做符。
交換兩個變量
func swap1(x, y, p *int) { if *x > *y { *x, *y = *y, *x } *p = *x + *y }
類比Java的hashmap,在go中,經常使用於json解碼等。map和Java的哈希表同樣,必須申請內存,使用make,不然報錯
var a map[string]int32; a["aaaa"] = 111; fmt.Printf("%v \n", a);
執行出錯,nil就是null,零值。
panic: assignment to entry in nil map
map和slice不同,slice能夠不make直接用,底層會作處理,可是map不行。
var a map[string]int32; a = make(map[string]int32); a["111"] = 111; fmt.Printf("%v \n", a); // map[111:111]
仍是使用簡潔寫法爽,我的也推薦
a := make(map[string]int32);
這個東西幾乎每一個語言都有,下面看經常使用的幾個
package main import ( "fmt" ) func main() { x := 1; if x > 1 { // 注意條件表達式沒有括號包住() fmt.Println(x); } else if x < 1 { fmt.Println(x); } else { fmt.Println(x); } }
一、當作普通的for循環
package main import ( "fmt" ) func main() { var x int; sum := 0; for x = 1; x <= 10; x++ { sum += x; } fmt.Println(sum); }
二、當作while循環使用(由於go裏沒有while關鍵字)
package main import ( "fmt" ) func main() { var x int = 1; sum := 0; for x <= 100 { sum += x; x++; } fmt.Println(sum); }
注意
當:=
操做符用於多個逗號分隔的變量時,若是該變量已經存在,則只是簡單的修改它的值。可是當:=
操做符位於做用域的起始處時,Go語言會建立一個新的變量,無論該變量以前是否存在,如在if或者for語句中。
a, b, c := 2, 3, 5 for a := 7; a < 8; a++ { fmt.Println(a) }
例子先使用:=
聲明並賦值了三個變量。for
語句處又一次使用:=
操做符聲明瞭變量a
。而for
語句表明了一個新的做用域,因此:=
在這裏新聲明建立了一個變量a
,這個變量和以前的變量a
是徹底不一樣的兩個變量(內存地址不同)
package main import ( "fmt" ) func main() { var x int = 1; switch x { // 一樣沒有括號 case 1: fmt.Println(1); // 不須要break,go自身作了優化,默認給加了break語句。可是也能夠手動加上。 case 2: fmt.Println(2); default: fmt.Println(3); } }
若是想讓switch語句在case執行後不中斷,繼續執行接下來的流程,可使用fallthrough(穿透語句)
package main import ( "fmt" ) func main() { var x int = 1; switch x { // 一樣沒有括號 case 1: fmt.Println(1); // 不須要break,go自身作了優化,默認給加了break語句。可是也能夠手動加上。 fallthrough; case 2: fmt.Println(2); default: fmt.Println(3); } } // 打印 1 換行 2
相似Java的迭代器,能夠便捷的遍歷容器
package main import ( "fmt" ) func main() { x := [5]int{1, 2, 3, 4, 5} for i, v := range x { fmt.Println(i, v) // i至關於key,v至關於value,和Java的foreach很像 } // 固然,能夠不用關心key,只取value,go提供了一個符號 _,表明丟棄值 for _, v := range x { fmt.Println(v) } /* 1 2 3 4 5 */ // 還能夠遍歷字符串,默認打印的是ask碼 str := "abcdefg" for i, v := range str { fmt.Println(i, v) } /* 0 97 1 98 2 99 3 100 4 101 5 102 6 103 */ // 若是想打印字符串 for _, v := range str { fmt.Printf("%c \n", v) } /* a b c d e f g */ }
另外, "_" 也叫空標示符,它是一個佔位符,用於在賦值操做的時候將某個值賦值給空標示符號,從而達到丟棄該值的目的。空標示符不是一個新的變量,所以將它用於:=
操做符號的時候,必須同時爲至少另外一個值賦值。