1.序言
Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis、类Evernote的云笔记leanote等。
1.1 为什么要学习
如果有人说X语言比Y语言好,两方的支持者经常会激烈地争吵。如果你是某种语言老手,你就是那门语言的“传道者”,下意识地会保护它。无论承认与否,你都已被困在一个隧道里,你看到的完全是局限的。《肖申克的救赎》对此有很好的注脚:
[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不同时,的确潜意识里就会产生抵触情绪,觉得这不好,还是自己习惯的那套好。长此以往,如果不能冲破自己的心理,“坐以待毙”,被时间淘汰恐怕只是早晚的事儿。所以这里的关键也 不是非要学习Golang,而是要不断地学!
1.2 用什么工具来开发
Golang也有专门的IDE,但由于最近迷上了Sublime Text神器,所以这里还是用ST来学习Golang。配置步骤与在ST中使用其他语言开发都类似:
安装智能提示插件GoSublime
创建编译配置脚本
点Preferences -> Package Settings -> GoSublime -> User Settings中写入(感觉保存时自动格式化出来的缩进、空格等风格有些“讨厌”,所以就禁掉了):
{ "fmt_enabled": false, "env": { "path":"D:\\Program Files (x86)\\Go\bin" } }点新建Build System产生go.sublime-build中写入:
{ "path": "D:\\Program Files (x86)\\Go\\bin", "cmd": ["go", "run", "${file}"], "selector": "source.go" }
Golang版的HelloWorld来了!一眼望去,package和import的声明方式与Java如出一辙,比较明显的区别是:func关键字、每行末尾没有分号、Println()大写的函数名。这个例子虽小,却“五脏俱全”,后面会逐一分析这个小例子中碰到的Golang语法点。
package mainimport "fmt"
func main() { fmt.Println("你好,世界!") }
Golang提供了go run“解释”执行和go build编译执行两种运行方式,所谓的“解释”执行其实也是编译出了可执行文件后才执行的。
$ go run helloworld.go
$ go build helloworld.go $ ls
$ ./helloworld
2.2 Package管理
上面例子中我们使用的就是fmt包下的Println()函数。Golang约定:我们可以用./或../相对路径来引自己的package;如果不是相对路径,那么go会去$GOPATH/src下查找。
2.3 格式化输出
类似C、Java等语言,Golang的fmt包提供了格式化输出功能,而且像%d、%s等占位符和\t、\r、\n转义也几乎完全一致。但Golang的Println不支持格式化,只有Printf支持,所以我们经常会在后面加入\n换行。此外,Golang加入了%T打印值的类型,%v打印数组等集合的所有元素。
package mainimport "fmt" import "math"
/** * This is Printer! * 布尔值:false * 二进制:11111111 * 八进制:377 * 十六进制:FF * 十进制:255 * 浮点数:3.141593 * 字符串:printer * * 对象类型:int,string,bool,float64 * 集合:[1 2 3 4 5] */ func main() { fmt.Println("This is Printer!")
fmt.Printf("布尔值:%t\n", 1 == 2) fmt.Printf("二进制:%b\n", 255) fmt.Printf("八进制:%o\n", 255) fmt.Printf("十六进制:%X\n", 255) fmt.Printf("十进制:%d\n", 255) fmt.Printf("浮点数:%f\n", math.Pi) fmt.Printf("字符串:%s\n", "printer")
fmt.Printf("对象类型:%T,%T,%T,%T\n", 1, "hello", true, math.E) fmt.Printf("集合:%v\n", [5]int{1, 2, 3, 4, 5}) }
3.1 变量和常量
虽然Golang是静态类型语言,却用类似JavaScript中的var关键字声明变量。而且像同样是静态语言的Scala一样,支持类型自动推断。有一点很重要的不同是:如果明确指明变量类型的话,类型要放在变量名后面。这有点别扭吧?!后面会看到函数的入参和返回值的类型也要这样声明。
package mainimport "fmt"
/** * 单变量声明:num[100], word[hello] * 多变量声明:i[1], i[2], k[3] * 推导类型:b1[true], b2[false] * 常量:age[20], pi[3.141593] */ func main() { var num int = 100 var word string = "hello" fmt.Printf("单变量声明:num[%d], word[%s]\n", num, word)
var i, j, k int = 1, 2, 3 fmt.Printf("多变量声明:i[%d], i[%d], k[%d]\n", i, j, k)
var b1 = true b2 := false fmt.Printf("推导类型:b1[%t], b2[%t]\n", b1, b2)
const age int = 20 const pi float32 = 3.1415926 fmt.Printf("常量:age[%d], pi[%f]\n", age, pi) }
作为最基本的语法要素,Golang的各种控制语句也是特点鲜明。在对C继承发扬的同时,也有自己的想法融入其中:
if/switch/for的条件部分都没有圆括号,但必须有花括号。
switch的case中不需要break。《C专家编程》里也“控诉”了C的fall-through问题。既然90%以上的情况都要break,为何不将break作为case的默认行为?而且编程语言后来者也鲜有纠正这一问题的。
switch的case条件可以是多个值。
Golang中没有while。
package mainimport "fmt"
/** * testIf: x[2] is even * testIf: x[3] is odd * * testSwitch: One * testSwitch: Two * testSwitch: Three, Four, Five [3] * testSwitch: Three, Four, Five [4] * testSwitch: Three, Four, Five [5] * * 标准模式:[0] [1] [2] [3] [4] [5] [6] * While模式:[0] [1] [2] [3] [4] [5] [6] * 死循环模式:[0] [1] [2] [3] [4] [5] [6] */ func main() { testIf(2) testIf(3) testSwitch(1) testSwitch(2) testSwitch(3) testSwitch(4) testSwitch(5) testFor(7) }
func testIf(x int) { if x % 2 == 0 { fmt.Printf("testIf: x[%d] is even\n", x) } else { fmt.Printf("testIf: x[%d] is odd\n", x) } }
func testSwitch(i int) { switch i { case 1: fmt.Println("testSwitch: One") case 2: fmt.Println("testSwitch: Two") case 3, 4, 5: fmt.Printf("testSwitch: Three, Four, Five [%d]\n", i) default: fmt.Printf("testSwitch: Invalid value[%d]\n", i) } }
func testFor(upper int) { fmt.Print("标准模式:") for i := 0; i < upper; i++ { fmt.Printf("[%d] ", i) } fmt.Println()
fmt.Print("While模式:") j := 0 for j < upper { fmt.Printf("[%d] ", j) j++ } fmt.Println()
fmt.Print("死循环模式:") k := 0 for { if (k >= upper) { break } fmt.Printf("[%d] ", k) k++ } fmt.Println() }
函数有几点不同:
func关键字。
最大的不同就是“倒序”的类型声明。
不需要函数原型,引用的函数可以后定义。这一点很好,真不喜欢C语言里要么将“最底层抽象”的函数放在最前面定义,要么写一堆函数原型声明在最前面。
3.4 集合
Golang提供了数组和Map作为基本数据结构:
数组中的元素会自动初始化,例如int数组元素初始化为0
切片(借鉴Python)的区间跟主流语言一样,都是 “左闭右开”
用 range()遍历数组和Map
package mainimport "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() }
Golang中可以使用指针,并提供了两种内存分配机制:
new:分配长度为0的空白内存,返回类型T*。
make:仅用于 切片、map、chan消息管道,返回类型T而不是指针。
package mainimport "fmt"
/** * 整数i=[10],指针pInt=[0x184000c0],指针指向*pInt=[10] * 整数i=[3],指针pInt=[0x184000c0],指针指向*pInt=[3] * 整数i=[5],指针pInt=[0x184000c0],指针指向*pInt=[5] * * Wild的数组指针: <nil> * Wild的数组指针==nil[true] * * New分配的数组指针: &[] * New分配的数组指针[0x18443010],长度[0] * New分配的数组指针==nil[false] * New分配的数组指针Make后: &[0 0 0 0 0 0 0 0 0 0] * New分配的数组元素[3]: 23 * * Make分配的数组引用: [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("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]\n", i, pInt, *pInt)
*pInt = 3 fmt.Printf("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]\n", i, pInt, *pInt)
i = 5 fmt.Printf("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]\n", i, pInt, *pInt) }
func testMemAllocate() { var pNil *[]int fmt.Println("Wild的数组指针:", pNil) fmt.Printf("Wild的数组指针==nil[%t]\n", pNil == nil)
var p *[]int = new([]int) fmt.Println("New分配的数组指针:", p) fmt.Printf("New分配的数组指针[%p],长度[%d]\n", p, len(*p)) fmt.Printf("New分配的数组指针==nil[%t]\n", p == nil)
//Error occurred //(*p)[3] = 23
*p = make([]int, 10) fmt.Println("New分配的数组指针Make后:", p) (*p)[3] = 23 fmt.Println("New分配的数组元素[3]:", (*p)[3])
var v []int = make([]int, 10) fmt.Println("Make分配的数组引用:", v) }
Golang的结构体跟C有几点不同:
结构体可以有方法,其实也就相当于OOP中的类了。
支持带名称的初始化。
用指针访问结构中的属性也用”.”而不是”->”,指针就像Java中的引用一样。
没有public,protected,private等访问权限控制。C也没有protected,C中默认是public的,private需要加static关键字限定。Golang中方法名大写就是public的,小写就是private的。
同时,Golang支持接口和多态,而且接口有别于Java中继承和实现的方式,而是采取了类似Ruby中更为新潮的Duck Type。只要struct与interface有相同的方法,就认为struct实现了这个接口。就好比只要能像鸭子那样叫,我们就认为它是一只鸭子一样。
package mainimport ( "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 // -----------------
/** * 结构Person[{cdai 30 cdai@gmail.com}],姓名[cdai] * 结构Person指针[&{cdai 30 cdai@gmail.com}],姓名[cdai] * 用指针修改结构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("结构Person[%v],姓名[%s]\n", p1, p1.getName())
ptr1 := &p1 fmt.Printf("结构Person指针[%v],姓名[%s]\n", ptr1, ptr1.getName())
ptr1.age = 40 ptr1.name = "carter" fmt.Printf("用指针修改结构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()) } }
Golang中异常的使用比较简单,可以用errors.New创建,也可以实现Error接口的方法来自定义异常类型,同时利用函数的多返回值特性可以返回异常类。比较复杂的是defer和recover关键字的使用。Golang没有采取try-catch“包住”可能出错代码的这种方式,而是用 延迟处理 的方式。
用defer调用的函数会以后进先出(LIFO)的方式,在当前函数结束后依次顺行执行。defer的这一特点正好可以用来处理panic。当panic被调用时,它将立即停止当前函数的执行并开始逐级解开函数堆栈,同时运行所有被defer的函数。如果这种解开达到堆栈的顶端,程序就死亡了。但是,也可以使用内建的recover函数来重新获得Go程的控制权并恢复正常的执行。由于仅在解开期间运行的代码处在被defer的函数之内,recover仅在被延期的函数内部才是有用的。
package mainimport ( "fmt" "errors" "os" )
/** * 自定义Error类型,实现内建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 } }
上面介绍的只是Golang的基本语法和特性,尽管像控制语句的条件不用圆括号、函数多返回值、switch-case默认break、函数闭包、集合切片等特性相比Java的确提高了开发效率,但这些在其他语言中也都有,并不是Golang能真正吸引人的地方。不仅是Golang,我们学习任何语言当然都是从基本语法特性着手,但学习时要不断地问自己:使这门语言区别于其他语言的”独到之处“在哪?这种独到之处往往反映了语言的设计思想、出发点、要解决的”痛点“,这才是一门语言或任何技术的立足之本。
4.1 goroutine
goroutine使用go关键字来调用函数,也可以使用匿名函数。可以简单的把go关键字调用的函数想像成pthread_create。如果一个goroutine没有被阻塞,那么别的goroutine就不会得到执行。也就是说goroutine阻塞时,Golang会切换到其他goroutine执行,这是非常好的特性!Java对类似goroutine这种的协程没有原生支持,像Akka最害怕的就是阻塞。因为协程不等同于线程,操作系统不会帮我们完成“现场”保存和恢复,所以要实现goroutine这种特性,就要模拟操作系统的行为,保存方法或函数在协程“上下文切换”时的Context,当阻塞结束时才能正确地切换回来。像Kilim等协程库利用字节码生成,能够胜任,而Akka完全是运行时的。
注意:如果你要真正的并发,需要调用runtime.GOMAXPROCS(CPU_NUM)设置。
package mainimport "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) }
像Java一样,Golang支持很多CAS操作。运行结果是unsaftCnt可能小于200,因为unsafeCnt++在机器指令层面上不是一条指令,而可能是从内存加载数据到寄存器、执行自增运算、保存寄存器中计算结果到内存这三部分,所以不进行保护的话有些更新是会丢失的。
package mainimport ( "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) }
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
通过前面可以看到,尽管goroutine很方便很高效,但如果滥用的话很可能会导致并发安全问题。而Channel就是用来解决这个问题的,它是goroutine之间通信的桥梁,类似Actor模型中每个Actor的mailbox。多个goroutine要修改一个状态时,可以将请求都发送到一个Channel里,然后由一个goroutine负责顺序地修改状态。
Channel默认是阻塞的,也就是说select时如果没有事件,那么当前goroutine会发生读阻塞。同理,Channel是有大小的,当Channel满了时,发送方会发生写阻塞。Channel这种阻塞的特性加上goroutine可以很容易就能实现生产者-消费者模式。
用case可以给Channel设置阻塞的超时时间,避免一直阻塞。而default则使select进入无阻塞模式。
package mainimport ( "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) } } }
Golang的bufio包提供了方便的缓冲流操作,通过strings或网络IO得到流后,用bufio.NewReader/Writer()包装:
缓冲区:Peek()或Read时,数据会从底层进入到缓冲区。缓冲区默认大小为4096字节。
切片和拷贝:Peek()和ReadSlice()得到的都是切片(缓冲区数据的引用)而不是拷贝,所以更加节约空间。但是当缓冲区数据变化时,切片也会随之变化。而ReadBytes/String()得到的都是数据的拷贝,可以放心使用。
Unicode支持:ReadRune()可以直接读取Unicode字符。有意思的是Golang中Unicode字符也要用单引号,这点与Java不同。
分隔符:ReadSlice/Bytes/String()得到的包含分隔符,bufio不会自动去掉。
Writer:对应地,Writer提供了WriteBytes/String/Rune。
undo方法:可以将读出的字节再放回到缓冲区,就像什么都没发生一样。
package mainimport ( "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) }
sync包中的WaitGroup是个很有用的类,类似信号量。wg.Add()和Done()能够加减WaitGroup(信号量)的值,而Wait()会挂起当前线程直到信号量变为0。下面的例子用WaitGroup的值表示正在运行的goroutine数量。在goroutine中,用defer Done()确保goroutine正常或异常退出时,WaitGroup都能减一。
package mainimport ( "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() }
Golang的net包的抽象层次还是挺高的,用不了几行代码就能实现一个简单的TCP或HTTP服务端了。
4.6.1 Socket编程
package mainimport ( "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 } } }
package mainimport ( "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.1 Golang初体验
Golang的某些语法的确很简洁,像行尾无分号、条件语句无括号、类型推断、函数多返回值、异常处理、原生协程支持、DuckType继承等,尽管很多并不是Golang首创,但结合到一起写起来还是很舒服的。
当然Golang也有让人“不爽”的地方。像变量和函数中的类型声明写在后面简直是“反人类”!同样是颠覆,switch的case默认会break就很实用。另外,因为Golang主要还是想替代C做系统开发,所以像类啊、包啊还是能看到C的影子,例如类声明只有成员变量而不会包含方法实现等,支持全局函数等,所以有时看到aaa.bbb()还是有点迷糊,不知道aaa是包名还是实例名。
5.2 如何学习一门语言
当我们谈到学习英语时,想到的可能是背单词、学语法、练习听说读写。对于编程语言来说,背单词(关键字)、学语法(语法规则)少不了,可听说读写只剩下了“写”,因为我们说话的对象是“冷冰冰”的计算机。所以唯一的捷径就是“写”,不断地练习!
此外,学的语言多了也能总结出一些规律。首先是基础语法,包括了变量和常量、控制语句、函数、集合、OOP、异常处理、控制台输入输出、包管理等。然后是高级特性就差别比较大了。专注高并发的语言就要看并发方面的特性,专注OOP的语言就要看有哪些抽象层次更高的特性等等。还是那句话,基础语言只能说我们会用,而能够区别一门语言的高级特性才是它的根本和灵魂,也是我们要着重学习和领悟的地方。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#nhooo.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。