作为一个初学者超级喜欢这本书,值得反复阅读,常读常新。因为读了好多时间了,因此有一个计划把课后习题全部做完,每天做五题~~
Golang版本:1.10.1
IDE: GoLand
操作系统: 10.13.4
练习1.1
修改echo程序输出os.Args[0],即命令的名字
1 2 3
| func TestOutputName(t *testing.T) { fmt.Println(os.Args[0]) }
|
练习1.2
修改echo程序,输出参数的索引和值,每行一个
1 2 3 4 5
| func TestListArgs(t *testing.T) { for k, v := range os.Args { fmt.Printf("%d\t%s\n", k, v) } }
|
练习1.3
尝试测量可能低效的程序和使用strings.Join的程序在执行时间上的差异
- 该用例大概显示相比
+=
,使用strings.Join要快1782倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| func BenchmarkStringJoin1(b *testing.B) { var a [100000]string for i := 0; i < 100000; i++ { a[i] = "1" }
for i := 0; i < b.N; i++ { var ret string for _, i := range a { ret += i } } }
func BenchmarkStringJoin2(b *testing.B) { var a [100000]string for i := 0; i < 100000; i++ { a[i] = "1" }
for i := 0; i < b.N; i++ { _ = strings.Join(a[:], "") } }
|
练习1.4
修改dup2程序,输出出现重复行的文件的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package main
import ( "os" "fmt" "bufio" )
type line struct { FileName string String string }
func main() { counts := make(map[line]int) files := os.Args[1:] if len(files) == 0 { countLines(os.Stdin, counts, "ARGS") } else { for _, arg := range files { f, err := os.Open(arg) if err != nil { fmt.Fprintf(os.Stderr, "dup2: %v\n", err) continue } countLines(f, counts, f.Name()) f.Close() } }
for line, n := range counts { if n > 1 { fmt.Printf("%s\t%s\t%d\n", line.FileName, line.String, n) } } }
func countLines(file *os.File, counts map[line]int, fileName string) { input := bufio.NewScanner(file) for input.Scan() { counts[line{ FileName: fileName, String: input.Text(), }]++ } }
|
练习1.5
改变利萨如程序的画板颜色为绿色黑底来增加真实性。使用color.RGBA{0xRR,0xGG,0xBB,0xff}创建一种web颜色#RRGGBB,每一对十六进制数组表示组成一个像素红、绿、蓝分量的亮度。
- 画板底色为palette的第一个元素,可采用
0x
表示十六进制数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package main
import ( "image/color" "math/rand" "time" "os" "net/http" "log" "io" "image/gif" "image" "math" )
var palette = []color.Color{color.Black, color.RGBA{ R: 0, G: 0xFF, B: 0, A: 0xFF, }}
func main() { rand.Seed(time.Now().UTC().UnixNano()) if len(os.Args) > 1 && os.Args[1] == "web" { handler := func(w http.ResponseWriter, r *http.Request) { lissajous(w) } http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe("localhost:8000", nil)) return } lissajous(os.Stdout)
} func lissajous(out io.Writer) { const ( cycles = 5 res = 0.001 size = 100 nframes = 64 delay = 8 ) freq := rand.Float64() * 3.0 anim := gif.GIF{LoopCount: nframes} phase := 0.0 for i := 0; i < nframes; i++ { rect := image.Rect(0, 0, 2*size+1, 2*size+1) img := image.NewPaletted(rect, palette) for t := 0.0; t < cycles*2*math.Pi; t += res { x := math.Sin(t) y := math.Sin(t*freq + phase) img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), 1) } phase += 0.1 anim.Delay = append(anim.Delay, delay) anim.Image = append(anim.Image, img) } gif.EncodeAll(out, &anim) }
|
练习1.6
通过在画板中添加更多颜色,然后通过有趣的方式改变SetColorIndex的第三个参数,修改利萨如程序来产生多彩的图片
- 没想到什么有趣的方式,随便用了个随机生成,
rand.Intn
,注意init在main前面被执行,因此随机种子需要放在init里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| package main
import ( "image/color" "math/rand" "time" "os" "net/http" "log" "io" "image/gif" "image" "math" )
var palette []color.Color
func init() { rand.Seed(time.Now().UTC().UnixNano()) for i := 0; i < 256; i++ { palette = append(palette, color.RGBA{ R: uint8(rand.Intn(256)), G: uint8(rand.Intn(256)), B: uint8(rand.Intn(256)), A: 0xFF, }) } }
func main() { if len(os.Args) > 1 && os.Args[1] == "web" { handler := func(w http.ResponseWriter, r *http.Request) { lissajous(w) } http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe("localhost:8000", nil)) return } lissajous(os.Stdout)
} func lissajous(out io.Writer) { const ( cycles = 5 res = 0.001 size = 100 nframes = 64 delay = 8 ) freq := rand.Float64() * 3.0 anim := gif.GIF{LoopCount: nframes} phase := 0.0 for i := 0; i < nframes; i++ { rect := image.Rect(0, 0, 2*size+1, 2*size+1) img := image.NewPaletted(rect, palette) for t := 0.0; t < cycles*2*math.Pi; t += res { x := math.Sin(t) y := math.Sin(t*freq + phase) img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), uint8(rand.Intn(len(palette)))) } phase += 0.1 anim.Delay = append(anim.Delay, delay) anim.Image = append(anim.Image, img) } gif.EncodeAll(out, &anim) }
|
练习1.7
函数io.Copy(dst,src)从src读,并且写入dst.使用它代替ioutil.ReadAll来复制响应内容到os.StdOut,这样不需要装下整个数据流的缓冲区.确保检查io.Copy返回的错误结果
- io.Copy代替ioutil.ReadAll更省内存,同时也可以把它看做是一个同步的操作,返回了最终传输数据的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package main
import ( "time" "os" "fmt" "net/http" "io" "io/ioutil" )
func main() { start := time.Now() ch := make(chan string) for _, url := range os.Args[1:] { go fetch(url, ch) } for range os.Args[1:] { fmt.Println(<-ch) } fmt.Printf("%.2f elapsed\n", time.Since(start).Seconds()) }
func fetch(url string, ch chan<- string) { start := time.Now() resp, err := http.Get(url) if err != nil { ch <- fmt.Sprint(err) return } nbytes, err := io.Copy(ioutil.Discard, resp.Body) resp.Body.Close() if err != nil { ch <- fmt.Sprintf("while reading %s: %v", url, err) return } secs := time.Since(start).Seconds() ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url) }
|
练习1.8
修改fetch程序添加一个http://前缀(假如该URL参数缺失协议前缀).可能会用到strings.HasPrefix
1 2 3 4 5 6 7
| func TestAddPrefix(t *testing.T) { inputURL := "z.cn" if ! strings.HasPrefix(inputURL, "http") { inputURL = "http://" + inputURL } fmt.Println(inputURL) }
|
练习1.9
修改fetch来输出http状态码,可以在resp.Status中找到它
1 2 3 4 5 6
| func TestOutputStatusCode(t *testing.T) { resp, _ := http.Get("http://z.cn") fmt.Println("StatusCode: ", resp.StatusCode) io.Copy(os.Stdout, resp.Body) resp.Body.Close() }
|
练习1.10
找一个产生大量数据的网站.连续两次运行fetchall,看报告的时间是否会有大的变化,调查缓存情况.每一次获取的内容一样吗?修改fetchall将内容输出到文件,这样可以检查它是否一致
- 这问题不是他傻就是我傻,我还是偏向是他傻。猜测有可能说的是访问网站第一次很慢,第二次会因为缓存策略加速传输(比如http运营商缓存啥的),亦或者是本机缓存304状态码(但是两次完全独立的访问和这个扯不上什么关系)
练习1.11
使用更长的参数列表来尝试fetchall,例如使用alexa.com排名前100万的网站,如果一个网站没有响应,程序的行为是则么样的
- Go net/http 超时机制完全手册
- 虽然每个URL都对应了一个chan,但是有可能某几个URL特别慢,那么所有的请求都需要等到最慢的那个完成,比如你在本地
nc -l 8000
监听端口,再直接使用http.Get
访问。它默认永不超时,直到tcp连接被中断
- 打开太多的链接会启用N多的goroutine,这样会达到系统上限,比如触发常见的
too many open files