《go语言圣经》练习答案--第七章
练习 7.1: 使用来自ByteCounter的思路,实现一个针对对单词和行数的计数器。你会发现bufio.ScanWords非常的有用。
package main
import (
"bufio"
"bytes"
"fmt"
)
type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) {
scan := bufio.NewScanner(bytes.NewReader(p))
scan.Split(bufio.ScanWords)
for scan.Scan() {
*c++
}
return len(p), nil
}
func main() {
var c ByteCounter
c.Write([]byte("hello"))
fmt.Println(c)
c = 0
var name = "Dolly"
fmt.Fprintf(&c, "hello, %s", name)
fmt.Println(c)
}
输出
1
2
练习 7.2: 写一个带有如下函数签名的函数CountingWriter,传入一个io.Writer接口类型,返回一个新的Writer类型把原来的Writer封装在里面和一个表示写入新的Writer字节数的int64类型指针
package main
import (
"io"
)
var bytes int64
type Counts struct {
w io.Writer
}
func (c *Counts) Write(p []byte) (n int, err error) {
n, err = c.w.Write(p)
bytes += int64(n)
return
}
func main() {
}
func CountingWriter(w io.Writer) (io.Writer, *int64) {
return &Counts{w}, &bytes
}
练习 7.3: 为在gopl.io/ch4/treesort (§4.4)的*tree类型实现一个String方法去展示tree类型的值序列。
package main
import (
"bytes"
"fmt"
"strconv"
)
type tree struct {
value int
left, right *tree
}
func (t *tree) String() string {
var buf bytes.Buffer
queue := make([]*tree, 0)
if t != nil {
queue = append(queue, t)
}
//使用 bfs 遍历树
buf.WriteByte('[')
for len(queue) != 0 {
size := len(queue)
for i := 0; i < size; i++ {
node := queue[i]
if node.left != nil {
queue = append(queue, node.left)
}
if node.right != nil {
queue = append(queue, node.right)
}
buf.WriteString(strconv.Itoa(node.value))
//不在最后一个值后面加空格
if len(queue) == size && i == size-1 {
continue
}
buf.WriteByte(' ')
}
queue = queue[size:]
}
buf.WriteByte(']')
return buf.String()
}
func main() {
t := new(tree)
t.value = 1
t.left = &tree{value: 2}
t.right = &tree{value: 3}
fmt.Println(t)
}
输出
[1 2 3]
练习 7.4: strings.NewReader函数通过读取一个string参数返回一个满足io.Reader接口类型的值(和其它值)。实现一个简单版本的NewReader,并用它来构造一个接收字符串输入的HTML解析器(§5.2)
package main
import (
"fmt"
"io"
)
type S struct {
str string
current int
}
func (s *S) Read(p []byte) (n int, err error) {
if s.current > len(s.str) {
return 0, io.EOF
}
n = copy(p, s.str)
s.current = n
return n, nil
}
func newReader(s string) io.Reader {
return &S{s, 0}
}
func main() {
r := newReader("123")
p := make([]byte, 3)
r.Read(p)
fmt.Println(string(p))
}
输出
123
练习 7.5: io包里面的LimitReader函数接收一个io.Reader接口类型的r和字节数n,并且返回另一个从r中读取字节但是当读完n个字节后就表示读到文件结束的Reader。实现这个LimitReader函数:
func LimitReader(r io.Reader, n int64) io.Reader
package main
import (
"fmt"
"io"
"strings"
)
type LimitR struct {
R io.Reader
N int64
}
func (l *LimitR) Read(p []byte) (n int, err error) {
if l.N <= 0 {
return 0, io.EOF
}
if int64(len(p)) > l.N {
p = p[:l.N]
}
n, err = l.R.Read(p)
l.N -= int64(n)
return
}
func LimitReader(r io.Reader, n int64) io.Reader {
return &LimitR{r, n}
}
func main() {
p := make([]byte, 10)
str := "12345"
r := strings.NewReader(str)
l := LimitReader(r, 4)
l.Read(p)
fmt.Println(string(p))
}
输出
1234
练习 7.11: 增加额外的handler让客服端可以创建,读取,更新和删除数据库记录。例如,一个形如 /update?item=socks&price=6
的请求会更新库存清单里一个货品的价格并且当这个货品不存在或价格无效时返回一个错误值。(注意:这个修改会引入变量同时更新的问题)
只写一个 update 方法。注意由于可能同时有很多请求,所以要使用锁。
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"sync"
)
func main() {
db := database{map[string]dollars{"shoes": 50, "socks": 5}, sync.Mutex{}}
mux := http.NewServeMux()
mux.Handle("/list", http.HandlerFunc(db.list))
mux.Handle("/price", http.HandlerFunc(db.price))
mux.Handle("/update", http.HandlerFunc(db.update))
log.Fatal(http.ListenAndServe("localhost:80", mux))
}
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database struct {
R map[string]dollars
sync.Mutex
}
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db.R {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price, ok := db.R[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
}
func (db database) update(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price, err := strconv.ParseFloat(req.URL.Query().Get("price"), 64)
if err != nil {
fmt.Fprintf(w, "invalid price")
}
db.Mutex.Lock()
defer db.Mutex.Unlock()
if _, ok := db.R[item]; ok {
db.R[item] = dollars(price)
fmt.Fprintf(w, "update success")
} else {
fmt.Fprintf(w, "invalid item")
}
}