以太坊客户端是用Go语言写的,所以首先要学会Go语言。
目前为止看到的最好的教程是下面这个网站:https://tour.golang.org
花上一天时间学习,Go语言的方方面面基本上心里就有数了。教程中间还穿插了几个练习题,比较有意思,摘出来汇总到这篇文章里。
1. fibonacci的三种写法
孔乙己的“回”字有三种写法,教程里也有三种不同的生成fibonacci数列的方法。
第一种:使用函数闭包实现,相比Java/C/C++,代码非常简洁
func fibonacci() func() int { x, y := 1, 0 return func() int { x, y = y, x+y return x } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
第二种:使用channel实现,也就是把所有结果先送到channel中,然后再依次读出来
func fibonacci2(n int, c chan int) { x, y := 1, 0 for i := 0; i < n; i++ { x, y = y, x+y c <- x } close(c) } func main() { c := make(chan int, 10) go fibonacci2(cap(c), c) for v := range c { fmt.Println(v) } }
第三种:使用select方式实现,其实还是使用channel,只不过是用select来监测channel的状态
func fibonacci3(c, quit chan int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci3(c, quit) }
2. rot13Reader字符加密
rot13是一种简单的字符加密算法,也就是把每个字符向后移动13位,具体参见下图:
上面的图只画了大写字母的情况,小写字母也是一样。这道题可以练习一下Go里面比较新颖的switch写法:
package main import ( "io" "os" "strings" ) type rot13Reader struct { r io.Reader } func (r13 *rot13Reader) Read(b []byte) (int, error) { n, err := r13.r.Read(b) if err == io.EOF { return 0, io.EOF } for i, c := range b { switch { case c < 'N': b[i] = c + 13 case c <= 'Z': b[i] = c - 13 case c < 'n': b[i] = c + 13 case c <= 'z': b[i] = c - 13 default: b[i] = c } } return n, nil } func main() { s := strings.NewReader("Lbh penpxrq gur pbqr!") r := rot13Reader{s} io.Copy(os.Stdout, &r) }
可以看到,如果switch后面什么都不加,条件判断就全部进入了case中,是不是非常方便?这样就不需要写很多if-else语句了,逻辑也比较清晰。
3. 比较二叉平衡树内容是否相同
这道题可以练习一下二叉树的遍历、channel的状态测试等知识点:
package main import "fmt" import "golang.org/x/tour/tree" // Walk walks the tree t sending all values // from the tree to the channel ch. func Walk(t *tree.Tree, ch chan int) { WalkInternal(t, ch) close(ch) } func WalkInternal(t *tree.Tree, ch chan int) { if t != nil { WalkInternal(t.Left, ch) ch <- t.Value WalkInternal(t.Right, ch) } } // Same determines whether the trees // t1 and t2 contain the same values. func Same(t1, t2 *tree.Tree) bool { c1, c2 := make(chan int), make(chan int) go Walk(t1, c1) go Walk(t2, c2) for { v1, ok1 := <-c1 v2, ok2 := <-c2 if ok1 != ok2 || v1 != v2 { return false } if ok1 == false { break } } return true } func main() { ch := make(chan int) go Walk(tree.New(1), ch) for i := range ch { fmt.Println(i) } fmt.Println(Same(tree.New(1), tree.New(1))) fmt.Println(Same(tree.New(1), tree.New(2))) }
4. 网络爬虫性能优化
这是一个fake的网络爬虫,数据是通过一个map提供的,并不是真的去网络上爬取。性能优化的目标有2个:
a) 每个URL只需要被爬取一次
b) 并行爬取多个URL
第2个目标实现很简单,URL爬取是一个循环,只需要在爬取函数前面加上go关键字,创建多个线程来爬取就可以了。
第1个目标实现需要通过一个map来记录已爬取过的URL,需要注意的是map不是线程安全的,需要用mutex进行保护。
优化后的代码如下:
package main import ( "fmt" "sync" "time" ) type Fetcher interface { // Fetch returns the body of URL and // a slice of URLs found on that page. Fetch(url string) (body string, urls []string, err error) } // Crawl uses fetcher to recursively crawl // pages starting with url, to a maximum of depth. func Crawl(url string, depth int, fetcher Fetcher) { // TODO: Fetch URLs in parallel. // TODO: Don't fetch the same URL twice. // This implementation doesn't do either: if depth <= 0 { return } if cache.has(url) { fmt.Println("Hit cache") return } body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { go Crawl(u, depth-1, fetcher) } return } type Cache struct { cache map[string]bool mutex sync.Mutex } func (cache *Cache) add(url string) { cache.mutex.Lock() cache.cache[url] = true cache.mutex.Unlock() } func (cache *Cache) has(url string) bool { cache.mutex.Lock() defer cache.mutex.Unlock() _, ok := cache.cache[url] if !ok { cache.cache[url] = true } return ok } var cache Cache = Cache{ cache: make(map[string]bool), } func main() { Crawl("https://golang.org/", 4, fetcher) time.Sleep(2000 * time.Millisecond) } // fakeFetcher is Fetcher that returns canned results. type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f fakeFetcher) Fetch(url string) (string, []string, error) { if res, ok := f[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } // fetcher is a populated fakeFetcher. var fetcher = fakeFetcher{ "https://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "https://golang.org/pkg/", "https://golang.org/cmd/", }, }, "https://golang.org/pkg/": &fakeResult{ "Packages", []string{ "https://golang.org/", "https://golang.org/cmd/", "https://golang.org/pkg/fmt/", "https://golang.org/pkg/os/", }, }, "https://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, "https://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, }
总体来说,Go语言是一种非常简洁的语言,相比Java/C/C++,可以用更少的代码实现复杂的功能。也许这也是以太坊选用Go作为开发语言的原因之一吧~