固然,当前在企业级应用开发中,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 */ }
另外, "_" 也叫空标示符,它是一个占位符,用于在赋值操做的时候将某个值赋值给空标示符号,从而达到丢弃该值的目的。空标示符不是一个新的变量,所以将它用于:=
操做符号的时候,必须同时为至少另外一个值赋值。