作为一个初学者超级喜欢这本书,值得反复阅读,常读常新。因为读了好多时间了,因此有一个计划把课后习题全部做完,每天做五题~~

Golang版本:1.10.1
IDE: GoLand
操作系统: 10.13.4

练习1.1

修改echo程序输出os.Args[0],即命令的名字

  • os.Args是一个slice
1
2
3
func TestOutputName(t *testing.T) {
fmt.Println(os.Args[0])
}

练习1.2

修改echo程序,输出参数的索引和值,每行一个

  • for range结构的使用
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) {
// 1 1581441327 ns/op
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) {
// 2000 844351 ns/op
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程序,输出出现重复行的文件的名称

  • 用结构体代替原有的string类型
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 // 完整的x振荡器变化的个数
res = 0.001 // 角度分辨率
size = 100 // 图像画布包含 [-size..+size]
nframes = 64 // 动画中的帧数
delay = 8 // 以10ms为单位的帧间延迟
)
freq := rand.Float64() * 3.0 // y振荡器的相对频率
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 // 完整的x振荡器变化的个数
res = 0.001 // 角度分辨率
size = 100 // 图像画布包含 [-size..+size]
nframes = 64 // 动画中的帧数
delay = 8 // 以10ms为单位的帧间延迟
)
freq := rand.Float64() * 3.0 // y振荡器的相对频率
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