如果有人說X語言比Y語言好,兩方的支持者經(jīng)常會激烈地爭吵。如果你是某種語言老手,你就是那門語言的“傳道者”,下意識地會保護它。無論承認與否,你都已被困在一個隧道里,你看到的完全是局限的。《肖申克的救贖》對此有很好的注腳:
[Red] These walls are funny. First you hate ‘em, then you get used to ‘em. Enough time passes, you get so you depend on them. That's institutionalized.
這些墻很有趣。起初你恨它們,之后你習慣了它們。隨著時間流逝,你開始以來它們。這就是體制。
在你還沒有被完全“體制化”時,為何不多學些語言,哪怕只是淺嘗輒止,潛移默化中也許你的思維壁壘就松動了。不管是Golang還是Ruby還是其他語言,當看到一些語法習慣與之前熟悉的C和Java不同時,的確潛意識里就會產(chǎn)生抵觸情緒,覺得這不好,還是自己習慣的那套好。長此以往,如果不能沖破自己的心理,“坐以待斃”,被時間淘汰恐怕只是早晚的事兒。所以這里的關(guān)鍵也 不是非要學習Golang,而是要不斷地學!
Golang也有專門的IDE,但由于最近迷上了Sublime Text神器,所以這里還是用ST來學習Golang。配置步驟與在ST中使用其他語言開發(fā)都類似:
Golang版的HelloWorld來了!一眼望去,package和import的聲明方式與Java如出一轍,比較明顯的區(qū)別是:func關(guān)鍵字、每行末尾沒有分號、Println()大寫的函數(shù)名。這個例子雖小,卻“五臟俱全”,后面會逐一分析這個小例子中碰到的Golang語法點。
Golang提供了go run“解釋”執(zhí)行和go build編譯執(zhí)行兩種運行方式,所謂的“解釋”執(zhí)行其實也是編譯出了可執(zhí)行文件后才執(zhí)行的。
上面例子中我們使用的就是fmt包下的Println()函數(shù)。Golang約定:我們可以用./或../相對路徑來引自己的package;如果不是相對路徑,那么go會去$GOPATH/src下查找。
類似C、Java等語言,Golang的fmt包提供了格式化輸出功能,而且像%d、%s等占位符和\t、\r、\n轉(zhuǎn)義也幾乎完全一致。但Golang的Println不支持格式化,只有Printf支持,所以我們經(jīng)常會在后面加入\n換行。此外,Golang加入了%T打印值的類型,%v打印數(shù)組等集合的所有元素。
雖然Golang是靜態(tài)類型語言,卻用類似JavaScript中的var關(guān)鍵字聲明變量。而且像同樣是靜態(tài)語言的Scala一樣,支持類型自動推斷。有一點很重要的不同是:如果明確指明變量類型的話,類型要放在變量名后面。這有點別扭吧?!后面會看到函數(shù)的入?yún)⒑头祷刂档念愋鸵惨@樣聲明。
分號由詞法分析器在掃描源代碼過程自動插入的,分析器使用簡單的規(guī)則:如果在一個新行前方的最后一個標記是一個標識符(包括像int和float64這樣的單詞)、一個基本的如數(shù)值這樣的文字、或break continue fallthrough return ++ – ) }中的一個時,它就會自動插入分號。
分號的自動插入規(guī)則產(chǎn)生了“蝴蝶效應(yīng)”:所有控制結(jié)構(gòu)的左花括號不都能放在下一行。因為按照上面的規(guī)則,這樣做會導致分析器在左花括號的前方插入一個分號,從而引起難以預(yù)料的結(jié)果。所以Golang中是不能隨便換行的。
package main
import "fmt"
/**
* Array未初始化: [0 0 0 0 0]
* Array賦值: [0 10 0 20 0]
* Array初始化: [0 1 2 3 4 5]
* Array二維: [[0 1 2] [1 2 3]]
* Array切片: [2 3] [0 1 2 3] [2 3 4 5]
*
* Map哈希表:map[one:1 two:2 three:3],長度[3]
* Map刪除元素后:map[one:1 three:3],長度[2]
* Map打印:
* one => 1
* four => 4
* three => 3
* five => 5
*/
func main() {
testArray()
testMap()
}
func testArray() {
var a [5]int
fmt.Println("Array未初始化: ", a)
a[1] = 10
a[3] = 20
fmt.Println("Array賦值: ", a)
b := []int{0, 1, 2, 3, 4, 5}
fmt.Println("Array初始化: ", b)
var c [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
c[i][j] = i + j
}
}
fmt.Println("Array二維: ", c)
d := b[2:4] // b[3,4]
e := b[:4] // b[1,2,3,4]
f := b[2:] // b[3,4,5]
fmt.Println("Array切片:", d, e, f)
}
func testMap() {
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
m["three"] = 3
fmt.Printf("Map哈希表:%v,長度[%d]\n", m, len(m))
delete(m, "two")
fmt.Printf("Map刪除元素后:%v,長度[%d]\n", m, len(m))
m["four"] = 4
m["five"] = 5
fmt.Println("Map打印:")
for key, val := range m {
fmt.Printf("\t%s => %d\n", key, val)
}
fmt.Println()
}
package main
import "fmt"
/**
* 整數(shù)i=[10],指針pInt=[0x184000c0],指針指向*pInt=[10]
* 整數(shù)i=[3],指針pInt=[0x184000c0],指針指向*pInt=[3]
* 整數(shù)i=[5],指針pInt=[0x184000c0],指針指向*pInt=[5]
*
* Wild的數(shù)組指針: <nil>
* Wild的數(shù)組指針==nil[true]
*
* New分配的數(shù)組指針: &[]
* New分配的數(shù)組指針[0x18443010],長度[0]
* New分配的數(shù)組指針==nil[false]
* New分配的數(shù)組指針Make后: &[0 0 0 0 0 0 0 0 0 0]
* New分配的數(shù)組元素[3]: 23
*
* Make分配的數(shù)組引用: [0 0 0 0 0 0 0 0 0 0]
*/
func main() {
testPointer()
testMemAllocate()
}
func testPointer() {
var i int = 10;
var pInt *int = &i;
fmt.Printf("整數(shù)i=[%d],指針pInt=[%p],指針指向*pInt=[%d]\n",
i, pInt, *pInt)
*pInt = 3
fmt.Printf("整數(shù)i=[%d],指針pInt=[%p],指針指向*pInt=[%d]\n",
i, pInt, *pInt)
i = 5
fmt.Printf("整數(shù)i=[%d],指針pInt=[%p],指針指向*pInt=[%d]\n",
i, pInt, *pInt)
}
func testMemAllocate() {
var pNil *[]int
fmt.Println("Wild的數(shù)組指針:", pNil)
fmt.Printf("Wild的數(shù)組指針==nil[%t]\n", pNil == nil)
var p *[]int = new([]int)
fmt.Println("New分配的數(shù)組指針:", p)
fmt.Printf("New分配的數(shù)組指針[%p],長度[%d]\n", p, len(*p))
fmt.Printf("New分配的數(shù)組指針==nil[%t]\n", p == nil)
//Error occurred
//(*p)[3] = 23
*p = make([]int, 10)
fmt.Println("New分配的數(shù)組指針Make后:", p)
(*p)[3] = 23
fmt.Println("New分配的數(shù)組元素[3]:", (*p)[3])
var v []int = make([]int, 10)
fmt.Println("Make分配的數(shù)組引用:", v)
}
3.6 面向?qū)ο缶幊?/strong>
Golang的結(jié)構(gòu)體跟C有幾點不同:
結(jié)構(gòu)體可以有方法,其實也就相當于OOP中的類了。
支持帶名稱的初始化。
用指針訪問結(jié)構(gòu)中的屬性也用”.”而不是”->”,指針就像Java中的引用一樣。
沒有public,protected,private等訪問權(quán)限控制。C也沒有protected,C中默認是public的,private需要加static關(guān)鍵字限定。Golang中方法名大寫就是public的,小寫就是private的。
同時,Golang支持接口和多態(tài),而且接口有別于Java中繼承和實現(xiàn)的方式,而是采取了類似Ruby中更為新潮的Duck Type。只要struct與interface有相同的方法,就認為struct實現(xiàn)了這個接口。就好比只要能像鴨子那樣叫,我們就認為它是一只鴨子一樣。
package main
import (
"fmt"
"math"
)
// -----------------
// Struct
// -----------------
type Person struct {
name string
age int
email string
}
func (p *Person) getName() string {
return p.name
}
// -------------------
// Interface
// -------------------
type shape interface {
area() float64
}
type rect struct {
width float64
height float64
}
func (r *rect) area() float64 {
return r.width * r.height
}
type circle struct {
radius float64
}
func (c *circle) area() float64 {
return math.Pi * c.radius * c.radius
}
// -----------------
// Test
// -----------------
/**
* 結(jié)構(gòu)Person[{cdai 30 cdai@gmail.com}],姓名[cdai]
* 結(jié)構(gòu)Person指針[&{cdai 30 cdai@gmail.com}],姓名[cdai]
* 用指針修改結(jié)構(gòu)Person為[{carter 40 cdai@gmail.com}]
*
* Shape[0]周長為[13.920000]
* Shape[1]周長為[58.088048]
*/
func main() {
testStruct()
testInterface()
}
func testStruct() {
p1 := Person{"cdai", 30, "cdai@gmail.com"}
p1 = Person{name: "cdai", age: 30, email: "cdai@gmail.com"}
fmt.Printf("結(jié)構(gòu)Person[%v],姓名[%s]\n", p1, p1.getName())
ptr1 := &p1
fmt.Printf("結(jié)構(gòu)Person指針[%v],姓名[%s]\n", ptr1, ptr1.getName())
ptr1.age = 40
ptr1.name = "carter"
fmt.Printf("用指針修改結(jié)構(gòu)Person為[%v]\n", p1)
}
func testInterface() {
r := rect { width: 2.9, height: 4.8 }
c := circle { radius: 4.3 }
s := []shape{ &r, &c }
for i, sh := range s {
fmt.Printf("Shape[%d]周長為[%f]\n", i, sh.area())
}
}
3.7 異常處理
Golang中異常的使用比較簡單,可以用errors.New創(chuàng)建,也可以實現(xiàn)Error接口的方法來自定義異常類型,同時利用函數(shù)的多返回值特性可以返回異常類。比較復雜的是defer和recover關(guān)鍵字的使用。Golang沒有采取try-catch“包住”可能出錯代碼的這種方式,而是用 延遲處理 的方式。
用defer調(diào)用的函數(shù)會以后進先出(LIFO)的方式,在當前函數(shù)結(jié)束后依次順行執(zhí)行。defer的這一特點正好可以用來處理panic。當panic被調(diào)用時,它將立即停止當前函數(shù)的執(zhí)行并開始逐級解開函數(shù)堆棧,同時運行所有被defer的函數(shù)。如果這種解開達到堆棧的頂端,程序就死亡了。但是,也可以使用內(nèi)建的recover函數(shù)來重新獲得Go程的控制權(quán)并恢復正常的執(zhí)行。由于僅在解開期間運行的代碼處在被defer的函數(shù)之內(nèi),recover僅在被延期的函數(shù)內(nèi)部才是有用的。
package main
import (
"fmt"
"errors"
"os"
)
/**
* 自定義Error類型,實現(xiàn)內(nèi)建Error接口
* type Error interface {
* Error() string
* }
*/
type MyError struct {
arg int
msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.msg)
}
/**
* Failed[*errors.errorString]: Bad Arguments - negative!
* Success: 16
* Failed[*main.MyError]: 1000 - Bad Arguments - too large!
*
* Recovered! Panic message[Cannot find specific file]
* 4 3 2 1 0
*/
func main() {
// 1.Test error
args := []int{-1, 4, 1000}
for _, i := range args {
if r, e := testError(i); e != nil {
fmt.Printf("Failed[%T]: %v\n", e, e)
} else {
fmt.Println("Success: ", r)
}
}
// 2.Test defer
src, err := os.Open("control.go")
if (err != nil) {
fmt.Printf("打開文件錯誤[%v]\n", err)
return
}
defer src.Close()
// use src...
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// 3.Test panic/recover
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered! Panic message[%s]\n", r)
}
}()
_, err2 := os.Open("test.go")
if (err2 != nil) {
panic("Cannot find specific file")
}
}
func testError(arg int) (int, error) {
if arg < 0 {
return -1, errors.New("Bad Arguments - negative!")
} else if arg > 256 {
return -1, &MyError{ arg, "Bad Arguments - too large!" }
} else {
return arg * arg, nil
}
}
4.高級特性
上面介紹的只是Golang的基本語法和特性,盡管像控制語句的條件不用圓括號、函數(shù)多返回值、switch-case默認break、函數(shù)閉包、集合切片等特性相比Java的確提高了開發(fā)效率,但這些在其他語言中也都有,并不是Golang能真正吸引人的地方。不僅是Golang,我們學習任何語言當然都是從基本語法特性著手,但學習時要不斷地問自己:使這門語言區(qū)別于其他語言的”獨到之處“在哪?這種獨到之處往往反映了語言的設(shè)計思想、出發(fā)點、要解決的”痛點“,這才是一門語言或任何技術(shù)的立足之本。
4.1 goroutine
goroutine使用go關(guān)鍵字來調(diào)用函數(shù),也可以使用匿名函數(shù)。可以簡單的把go關(guān)鍵字調(diào)用的函數(shù)想像成pthread_create。如果一個goroutine沒有被阻塞,那么別的goroutine就不會得到執(zhí)行。也就是說goroutine阻塞時,Golang會切換到其他goroutine執(zhí)行,這是非常好的特性!Java對類似goroutine這種的協(xié)程沒有原生支持,像Akka最害怕的就是阻塞。因為協(xié)程不等同于線程,操作系統(tǒng)不會幫我們完成“現(xiàn)場”保存和恢復,所以要實現(xiàn)goroutine這種特性,就要模擬操作系統(tǒng)的行為,保存方法或函數(shù)在協(xié)程“上下文切換”時的Context,當阻塞結(jié)束時才能正確地切換回來。像Kilim等協(xié)程庫利用字節(jié)碼生成,能夠勝任,而Akka完全是運行時的。
注意:如果你要真正的并發(fā),需要調(diào)用runtime.GOMAXPROCS(CPU_NUM)設(shè)置。
package main
import "fmt"
func main() {
go f("goroutine")
go func(msg string) {
fmt.Println(msg)
}("going")
// Block main thread
var input string
fmt.Scanln(&input)
fmt.Println("done")
}
func f(msg string) {
fmt.Println(msg)
}
4.2 原子操作
像Java一樣,Golang支持很多CAS操作。運行結(jié)果是unsaftCnt可能小于200,因為unsafeCnt++在機器指令層面上不是一條指令,而可能是從內(nèi)存加載數(shù)據(jù)到寄存器、執(zhí)行自增運算、保存寄存器中計算結(jié)果到內(nèi)存這三部分,所以不進行保護的話有些更新是會丟失的。
package main
import (
"fmt"
"time"
"sync/atomic"
"runtime"
)
func main() {
// IMPORTANT!!!
runtime.GOMAXPROCS(4)
// thread-unsafe
var unsafeCnt int32 = 0
for i := 0; i < 10; i++ {
go func() {
for i := 0; i < 20; i++ {
time.Sleep(time.Millisecond)
unsafeCnt++
}
}()
}
time.Sleep(time.Second)
fmt.Println("cnt: ", unsafeCnt)
// CAS toolkit
var cnt int32 = 0
for i := 0; i < 10; i++ {
go func() {
for i := 0; i < 20; i++ {
time.Sleep(time.Millisecond)
atomic.AddInt32(&cnt, 1)
}
}()
}
time.Sleep(time.Second)
cntFinal := atomic.LoadInt32(&cnt)
fmt.Println("cnt: ", cntFinal)
}
神奇CAS的原理
Golang的AddInt32()類似于Java中AtomicInteger.incrementAndGet(),其偽代碼可以表示如下。二者的基本思想是一致的,本質(zhì)上是 樂觀鎖:首先,從內(nèi)存位置M加載要修改的數(shù)據(jù)到寄存器A中;然后,修改數(shù)據(jù)并保存到另一寄存器B;最終,利用CPU提供的CAS指令(Java通過JNI調(diào)用到)用一條指令完成:1)A值與M處的原值比較;2)若相同則將B值覆蓋到M處。
若不相同,則CAS指令會失敗,說明從內(nèi)存加載到執(zhí)行CAS指令這一小段時間內(nèi),發(fā)生了上下文切換,執(zhí)行了其他線程的代碼修改了M處的變量值。那么重新執(zhí)行前面幾個步驟再次嘗試。
ABA問題:即另一線程修改了M位置的數(shù)據(jù),但是從原值改為C,又從C改回原值。這樣上下文切換回來,CAS指令發(fā)現(xiàn)M處的值“未改變”(實際是改了兩次,最后改回來了),所以CAS指令正常執(zhí)行,不會失敗。這種問題在Java中可以用AtomicStampedReference/AtomicMarkableReference解決。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
4.3 Channel管道
通過前面可以看到,盡管goroutine很方便很高效,但如果濫用的話很可能會導致并發(fā)安全問題。而Channel就是用來解決這個問題的,它是goroutine之間通信的橋梁,類似Actor模型中每個Actor的mailbox。多個goroutine要修改一個狀態(tài)時,可以將請求都發(fā)送到一個Channel里,然后由一個goroutine負責順序地修改狀態(tài)。
Channel默認是阻塞的,也就是說select時如果沒有事件,那么當前goroutine會發(fā)生讀阻塞。同理,Channel是有大小的,當Channel滿了時,發(fā)送方會發(fā)生寫阻塞。Channel這種阻塞的特性加上goroutine可以很容易就能實現(xiàn)生產(chǎn)者-消費者模式。
用case可以給Channel設(shè)置阻塞的超時時間,避免一直阻塞。而default則使select進入無阻塞模式。
package main
import (
"fmt"
"time"
)
/**
* Output:
* received message: hello
* received message: world
*
* received from channel-1: Hello
* received from channel-2: World
*
* received message: hello
* Time out!
*
* Nothing received!
* received message: hello
* Nothing received!
* Nothing received!
* Nothing received!
* Nothing received!
* Nothing received!
* Nothing received!
* Nothing received!
* Nothing received!
* Nothing received!
* received message: world
* Nothing received!
* Nothing received!
* Nothing received!
*/
func main() {
listenOnChannel()
selectTwoChannels()
blockChannelWithTimeout()
unblockChannel()
}
func listenOnChannel() {
// Specify channel type and buffer size
channel := make(chan string, 5)
go func() {
channel <- "hello"
channel <- "world"
}()
for i := 0; i < 2; i++ {
msg := <- channel
fmt.Println("received message: " + msg)
}
}
func selectTwoChannels() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(time.Second)
c1 <- "Hello"
}()
go func() {
time.Sleep(time.Second)
c2 <- "World"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <- c1:
fmt.Println("received from channel-1: " + msg1)
case msg2 := <- c2:
fmt.Println("received from channel-2: " + msg2)
}
}
}
func blockChannelWithTimeout() {
channel := make(chan string, 5)
go func() {
channel <- "hello"
// Sleep 10 sec
time.Sleep(time.Second * 10)
channel <- "world"
}()
for i := 0; i < 2; i++ {
select {
case msg := <- channel:
fmt.Println("received message: " + msg)
// Set timeout 5 sec
case <- time.After(time.Second * 5):
fmt.Println("Time out!")
}
}
}
func unblockChannel() {
channel := make(chan string, 5)
go func() {
channel <- "hello"
time.Sleep(time.Second * 10)
channel <- "world"
}()
for i := 0; i < 15; i++ {
select {
case msg := <- channel:
fmt.Println("received message: " + msg)
default:
fmt.Println("Nothing received!")
time.Sleep(time.Second)
}
}
}
4.4 緩沖流
Golang的bufio包提供了方便的緩沖流操作,通過strings或網(wǎng)絡(luò)IO得到流后,用bufio.NewReader/Writer()包裝:
緩沖區(qū):Peek()或Read時,數(shù)據(jù)會從底層進入到緩沖區(qū)。緩沖區(qū)默認大小為4096字節(jié)。
切片和拷貝:Peek()和ReadSlice()得到的都是切片(緩沖區(qū)數(shù)據(jù)的引用)而不是拷貝,所以更加節(jié)約空間。但是當緩沖區(qū)數(shù)據(jù)變化時,切片也會隨之變化。而ReadBytes/String()得到的都是數(shù)據(jù)的拷貝,可以放心使用。
Unicode支持:ReadRune()可以直接讀取Unicode字符。有意思的是Golang中Unicode字符也要用單引號,這點與Java不同。
分隔符:ReadSlice/Bytes/String()得到的包含分隔符,bufio不會自動去掉。
Writer:對應(yīng)地,Writer提供了WriteBytes/String/Rune。
undo方法:可以將讀出的字節(jié)再放回到緩沖區(qū),就像什么都沒發(fā)生一樣。
package main
import (
"fmt"
"strings"
"bytes"
"bufio"
)
/**
* Buffered: 0
* Buffered after peek: 7
* ABCDE
* AxCDE
*
* abcdefghijklmnopqrst 20 <nil>
* uvwxyz1234567890 16 <nil>
* 0 EOF
*
* "ABC "
* "DEF "
* "GHI"
*
* "ABC "
* "DEF "
* "GHI"
*
* read unicode=[你], size=[3]
* read unicode=[好], size=[3]
* read(after undo) unicode=[好], size=[3]
*
* Available: 4096
* Buffered: 0
* Available after write: 4088
* Buffered after write: 8
* Buffer after write: ""
* Available after flush: 4096
* Buffered after flush: 0
* Buffer after flush: "ABCDEFGH"
*
* Hello,世界!
*/
func main() {
testPeek()
testRead()
testReadSlice()
testReadBytes()
testReadUnicode()
testWrite()
testWriteByte()
}
func testPeek() {
r := strings.NewReader("ABCDEFG")
br := bufio.NewReader(r)
fmt.Printf("Buffered: %d\n", br.Buffered())
p, _ := br.Peek(5)
fmt.Printf("Buffered after peek: %d\n", br.Buffered())
fmt.Printf("%s\n", p)
p[1] = 'x'
p, _ = br.Peek(5)
fmt.Printf("%s\n", p)
}
func testRead() {
r := strings.NewReader("abcdefghijklmnopqrstuvwxyz1234567890")
br := bufio.NewReader(r)
b := make([]byte, 20)
n, err := br.Read(b)
fmt.Printf("%-20s %-2v %v\n", b[:n], n, err)
n, err = br.Read(b)
fmt.Printf("%-20s %-2v %v\n", b[:n], n, err)
n, err = br.Read(b)
fmt.Printf("%-20s %-2v %v\n", b[:n], n, err)
}
func testReadSlice() {
r := strings.NewReader("ABC DEF GHI")
br := bufio.NewReader(r)
w, _ := br.ReadSlice(' ')
fmt.Printf("%q\n", w)
w, _ = br.ReadSlice(' ')
fmt.Printf("%q\n", w)
w, _ = br.ReadSlice(' ')
fmt.Printf("%q\n", w)
}
func testReadBytes() {
r := strings.NewReader("ABC DEF GHI")
br := bufio.NewReader(r)
w, _ := br.ReadBytes(' ')
fmt.Printf("%q\n", w)
w, _ = br.ReadSlice(' ')
fmt.Printf("%q\n", w)
s, _ := br.ReadString(' ')
fmt.Printf("%q\n", s)
}
func testReadUnicode() {
r := strings.NewReader("你好,世界!")
br := bufio.NewReader(r)
c, size, _ := br.ReadRune()
fmt.Printf("read unicode=[%c], size=[%v]\n", c, size)
c, size, _ = br.ReadRune()
fmt.Printf("read unicode=[%c], size=[%v]\n", c, size)
br.UnreadRune()
c, size, _ = br.ReadRune()
fmt.Printf("read(after undo) unicode=[%c], size=[%v]\n", c, size)
}
func testWrite() {
b := bytes.NewBuffer(make([]byte, 0))
bw := bufio.NewWriter(b)
fmt.Printf("Available: %d\n", bw.Available())
fmt.Printf("Buffered: %d\n", bw.Buffered())
bw.WriteString("ABCDEFGH")
fmt.Printf("Available after write: %d\n", bw.Available())
fmt.Printf("Buffered after write: %d\n", bw.Buffered())
fmt.Printf("Buffer after write: %q\n", b)
bw.Flush()
fmt.Printf("Available after flush: %d\n", bw.Available())
fmt.Printf("Buffered after flush: %d\n", bw.Buffered())
fmt.Printf("Buffer after flush: %q\n", b)
}
func testWriteByte() {
b := bytes.NewBuffer(make([]byte, 0))
bw := bufio.NewWriter(b)
bw.WriteByte('H')
bw.WriteByte('e')
bw.WriteByte('l')
bw.WriteByte('l')
bw.WriteByte('o')
bw.WriteString(",")
bw.WriteRune('世')
bw.WriteRune('界')
bw.WriteRune('!')
bw.Flush()
fmt.Println(b)
}
4.5 并發(fā)控制
sync包中的WaitGroup是個很有用的類,類似信號量。wg.Add()和Done()能夠加減WaitGroup(信號量)的值,而Wait()會掛起當前線程直到信號量變?yōu)?。下面的例子用WaitGroup的值表示正在運行的goroutine數(shù)量。在goroutine中,用defer Done()確保goroutine正常或異常退出時,WaitGroup都能減一。
package main
import (
"fmt"
"sync"
)
/**
* I'm waiting all goroutines on wg done
* I'm done=[0]
* I'm done=[1]
* I'm done=[2]
* I'm done=[3]
* I'm done=[4]
* I'm done=[5]
* I'm done=[6]
* I'm done=[7]
* I'm done=[8]
* I'm done=[9]
*/
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("I'm done=[%d]\n", id)
}(i)
}
fmt.Println("I'm waiting all goroutines on wg done")
wg.Wait()
}
4.6 網(wǎng)絡(luò)編程
Golang的net包的抽象層次還是挺高的,用不了幾行代碼就能實現(xiàn)一個簡單的TCP或HTTP服務(wù)端了。
4.6.1 Socket編程
package main
import (
"net"
"fmt"
"io"
)
/**
* Starting the server
* Accept the connection: 127.0.0.1:14071
* Warning: End of data EOF
*/
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:12345")
if err != nil {
panic("error listen: " + err.Error())
}
fmt.Println("Starting the server")
for {
conn, err := listener.Accept()
if err != nil {
panic("error accept: " + err.Error())
}
fmt.Println("Accept the connection: ", conn.RemoteAddr())
go echoServer(conn)
}
}
func echoServer(conn net.Conn) {
buf := make([]byte, 1024)
defer conn.Close()
for {
n, err := conn.Read(buf)
switch err {
case nil:
conn.Write(buf[0:n])
case io.EOF:
fmt.Printf("Warning: End of data %s\n", err)
return
default:
fmt.Printf("Error: read data %s\n", err)
return
}
}
}
4.6.2 Http服務(wù)器
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/hello", handleHello)
fmt.Println("serving on http://localhost:7777/hello")
log.Fatal(http.ListenAndServe("localhost:7777", nil))
}
func handleHello(w http.ResponseWriter, req *http.Request) {
log.Println("serving", req.URL)
fmt.Fprintln(w, "Hello, world!")
}
5.結(jié)束語
5.1 Golang初體驗
Golang的某些語法的確很簡潔,像行尾無分號、條件語句無括號、類型推斷、函數(shù)多返回值、異常處理、原生協(xié)程支持、DuckType繼承等,盡管很多并不是Golang首創(chuàng),但結(jié)合到一起寫起來還是很舒服的。
當然Golang也有讓人“不爽”的地方。像變量和函數(shù)中的類型聲明寫在后面簡直是“反人類”!同樣是顛覆,switch的case默認會break就很實用。另外,因為Golang主要還是想替代C做系統(tǒng)開發(fā),所以像類啊、包啊還是能看到C的影子,例如類聲明只有成員變量而不會包含方法實現(xiàn)等,支持全局函數(shù)等,所以有時看到aaa.bbb()還是有點迷糊,不知道aaa是包名還是實例名。
5.2 如何學習一門語言
當我們談到學習英語時,想到的可能是背單詞、學語法、練習聽說讀寫。對于編程語言來說,背單詞(關(guān)鍵字)、學語法(語法規(guī)則)少不了,可聽說讀寫只剩下了“寫”,因為我們說話的對象是“冷冰冰”的計算機。所以唯一的捷徑就是“寫”,不斷地練習!
此外,學的語言多了也能總結(jié)出一些規(guī)律。首先是基礎(chǔ)語法,包括了變量和常量、控制語句、函數(shù)、集合、OOP、異常處理、控制臺輸入輸出、包管理等。然后是高級特性就差別比較大了。專注高并發(fā)的語言就要看并發(fā)方面的特性,專注OOP的語言就要看有哪些抽象層次更高的特性等等。還是那句話,基礎(chǔ)語言只能說我們會用,而能夠區(qū)別一門語言的高級特性才是它的根本和靈魂,也是我們要著重學習和領(lǐng)悟的地方。
您可能感興趣的文章:
【玩轉(zhuǎn)Golang】slice切片的操作——切片的追加、刪除、插入等
一、一般操作
1,聲明變量,go自動初始化為nil,長度:0,地址:0,nil
func main(){
var ss []string;
fmt.Printf("length:%v \taddr:%p \tisnil:%v",len(ss),ss, ss==nil)
}
---
Running...
length:0 addr:0x0 isnil:true
Success: process exited with code 0.
2,切片的追加,刪除,插入操作

func main(){
var ss []string;
fmt.Printf("[ local print ]\t:\t length:%v\taddr:%p\tisnil:%v\n",len(ss),ss, ss==nil)
print("func print",ss)
//切片尾部追加元素append elemnt
for i:=0;i<10;i++{
ss=append(ss,fmt.Sprintf("s%d",i));
}
fmt.Printf("[ local print ]\t:\tlength:%v\taddr:%p\tisnil:%v\n",len(ss),ss, ss==nil)
print("after append",ss)
//刪除切片元素remove element at index
index:=5;
ss=append(ss[:index],ss[index+1:]...)
print("after delete",ss)
//在切片中間插入元素insert element at index;
//注意:保存后部剩余元素,必須新建一個臨時切片
rear:=append([]string{},ss[index:]...)
ss=append(ss[0:index],"inserted")
ss=append(ss,rear...)
print("after insert",ss)
}
func print(msg string,ss []string){
fmt.Printf("[ %20s ]\t:\tlength:%v\taddr:%p\tisnil:%v\tcontent:%v",msg,len(ss),ss, ss==nil,ss)
fmt.Println()
}
------
Running...
[ local print ] : length:0 addr:0x0 isnil:true
[ func print ] : length:0 addr:0x0 isnil:true content:[]
[ local print ] : length:10 addr:0xc208056000 isnil:false
[ after append ] : length:10 addr:0xc208056000 isnil:false content:[s0 s1 s2 s3 s4 s5 s6 s7 s8 s9]
[ after delete ] : length:9 addr:0xc208056000 isnil:false content:[s0 s1 s2 s3 s4 s6 s7 s8 s9]
[ after insert ] : length:10 addr:0xc208056000 isnil:false content:[s0 s1 s2 s3 s4 inserted s6 s7 s8 s9]
Success: process exited with code 0.

3,copy的使用。
在使用copy復制切片之前,要保證目標切片有足夠的大小,注意是大小,而不是容量,還是看例子:
func main() {
var sa = make ([]string,0);
for i:=0;i<10;i++{
sa=append(sa,fmt.Sprintf("%v",i))
}
var da =make([]string,0,10);
var cc=0;
cc= copy(da,sa);
fmt.Printf("copy to da(len=%d)\t%v\n",len(da),da)
da = make([]string,5)
cc=copy(da,sa);
fmt.Printf("copy to da(len=%d)\tcopied=%d\t%v\n",len(da),cc,da)
da = make([]string,10)
cc =copy(da,sa);
fmt.Printf("copy to da(len=%d)\tcopied=%d\t%v\n",len(da),cc,da)
}
---
Running...
copy to da(len=0) []
copy to da(len=5) copied=5 [0 1 2 3 4]
copy to da(len=10) copied=10 [0 1 2 3 4 5 6 7 8 9]
從上面運行結(jié)果,明顯看出,目標切片大小0,容量10,copy不能復制。目標切片大小小于源切片大小,copy就按照目標切片大小復制,不會報錯。
二、初始大小和容量
當我們使用make初始化切片的時候,必須給出size。go語言的書上一般都會告訴我們,當切片有足夠大小的時候,append操作是非常快的。但是當給出初始大小后,我們得到的實際上是一個含有這個size數(shù)量切片類型的空元素,看例子:
func main(){
var ss=make([]string,10);
ss=append(ss,"last");
print("after append",ss)
}
---
Running...
[ after append ] : length:11 addr:0xc20804c000 isnil:false content:[ last]
實際上,此時我們應(yīng)該先用下標為切片元素負值。但是如果我們既想有好的效率,有想繼續(xù)使用append函數(shù)而不想?yún)^(qū)分是否有空的元素,此時就要請出make的第三個參數(shù),容量,也就是我們通過傳遞給make,0的大小和足夠大的容量數(shù)值就行了。
func main(){
var ss=make([]string,0,10);
ss=append(ss,"last");
print("after append",ss)
}
---
Running...
[ after append ] : length:1 addr:0xc20804a000 isnil:false content:[last]
三、切片的指針。
1,當我們用append追加元素到切片時,如果容量不夠,go就會創(chuàng)建一個新的切片變量,看下面程序的執(zhí)行結(jié)果:
func main() {
var sa []string
fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
for i:=0;i<10;i++{
sa=append(sa,fmt.Sprintf("%v",i))
fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
}
fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
}
---
Running ...
addr:0x0 len:0 content:[]
addr:0x1030e0c8 len:1 content:[0]
addr:0x10328120 len:2 content:[0 1]
addr:0x10322180 len:3 content:[0 1 2]
addr:0x10322180 len:4 content:[0 1 2 3]
addr:0x10342080 len:5 content:[0 1 2 3 4]
addr:0x10342080 len:6 content:[0 1 2 3 4 5]
addr:0x10342080 len:7 content:[0 1 2 3 4 5 6]
addr:0x10342080 len:8 content:[0 1 2 3 4 5 6 7]
addr:0x10324a00 len:9 content:[0 1 2 3 4 5 6 7 8]
addr:0x10324a00 len:10 content:[0 1 2 3 4 5 6 7 8 9]
addr:0x10324a00 len:10 content:[0 1 2 3 4 5 6 7 8 9]
//很明顯,切片的地址經(jīng)過了數(shù)次改變。
2,如果,在make初始化切片的時候給出了足夠的容量,append操作不會創(chuàng)建新的切片:
func main() {
var sa = make ([]string,0,10);
fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
for i:=0;i<10;i++{
sa=append(sa,fmt.Sprintf("%v",i))
fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
}
fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
}
addr:0x10304140 len:0 content:[]
addr:0x10304140 len:1 content:[0]
addr:0x10304140 len:2 content:[0 1]
addr:0x10304140 len:3 content:[0 1 2]
addr:0x10304140 len:4 content:[0 1 2 3]
addr:0x10304140 len:5 content:[0 1 2 3 4]
addr:0x10304140 len:6 content:[0 1 2 3 4 5]
addr:0x10304140 len:7 content:[0 1 2 3 4 5 6]
addr:0x10304140 len:8 content:[0 1 2 3 4 5 6 7]
addr:0x10304140 len:9 content:[0 1 2 3 4 5 6 7 8]
addr:0x10304140 len:10 content:[0 1 2 3 4 5 6 7 8 9]
addr:0x10304140 len:10 content:[0 1 2 3 4 5 6 7 8 9]
//可見,切片的地址一直保持不變
3, 如果不能準確預(yù)估切片的大小,又不想改變變量(如:為了共享數(shù)據(jù)的改變),這時候就要請出指針來幫忙了,下面程序中,sa就是osa這個切片的指針,我們共享切片數(shù)據(jù)和操作切片的時候都使用這個切片地址就ok了,其本質(zhì)上是:append操作亦然會在需要的時候構(gòu)造新的切片,不過是將地址都保存到了sa中,因此我們通過該指針始終可以訪問到真正的數(shù)據(jù)。
func main() {
var osa = make ([]string,0);
sa:=&osa;
for i:=0;i<10;i++{
*sa=append(*sa,fmt.Sprintf("%v",i))
fmt.Printf("addr of osa:%p,\taddr:%p \t content:%v\n",osa,sa,sa);
}
fmt.Printf("addr of osa:%p,\taddr:%p \t content:%v\n",osa,sa,sa);
}
---
Running...
addr of osa:0xc20800a220, addr:0xc20801e020 content:&[0]
addr of osa:0xc20801e0a0, addr:0xc20801e020 content:&[0 1]
addr of osa:0xc20803e0c0, addr:0xc20801e020 content:&[0 1 2]
addr of osa:0xc20803e0c0, addr:0xc20801e020 content:&[0 1 2 3]
addr of osa:0xc208050080, addr:0xc20801e020 content:&[0 1 2 3 4]
addr of osa:0xc208050080, addr:0xc20801e020 content:&[0 1 2 3 4 5]
addr of osa:0xc208050080, addr:0xc20801e020 content:&[0 1 2 3 4 5 6]
addr of osa:0xc208050080, addr:0xc20801e020 content:&[0 1 2 3 4 5 6 7]
addr of osa:0xc208052000, addr:0xc20801e020 content:&[0 1 2 3 4 5 6 7 8]
addr of osa:0xc208052000, addr:0xc20801e020 content:&[0 1 2 3 4 5 6 7 8 9]
addr of osa:0xc208052000, addr:0xc20801e020 content:&[0 1 2 3 4 5 6 7 8 9]
posted on 2016-01-13 11:18
思月行云 閱讀(273)
評論(0) 編輯 收藏 引用 所屬分類:
Golang