课程地址 go-class-slides/xmas-2020 at trunk · matt4biz/go-class-slides (github.com)
主讲老师 Matt Holiday
11-Homework #2
package main
import (
\"bytes\"
\"fmt\"
\"os\"
\"strings\"
\"golang.org/x/net/html\"
)
var raw = `
<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<p>HTML <a href=\"https://www.w3schools.com/html/html_images.asp\">images</a> are defined with the img tag:</p>
<img src=\"xxx.jpg\" width=\"104\" height=\"142\">
</body>
</html>
`
func visit(n *html.Node, words, pics *int) {
if n.Type == html.TextNode {
*words += len(strings.Fields(n.Data))
} else if n.Type == html.ElementNode && n.Data == \"img\" {
*pics++
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
visit(c, words, pics)
}
}
func countWordsAndImages(doc *html.Node) (int, int) {
var words, pics int
visit(doc, &words, &pics)
return words, pics
}
func main() {
doc, err := html.Parse(bytes.NewReader([]byte(raw)))
if err != nil {
fmt.Fprintf(os.Stderr, \"parse failed:%s\\n\", err)
os.Exit(-1)
}
words, pics := countWordsAndImages(doc)
fmt.Printf(\"%d words and %d images\\n\", words, pics)
}
14 words and 1 images
假如我去访问一个网站,我会得到一个字节的片段,将它放到阅读器中。
doc, err := html.Parse(bytes.NewReader([]byte(raw)))
返回的\\(doc\\)是树节点,我们可以用 \\(for\\) 循环通过节点的 \\(FirstChild、NextSibling\\) 属性遍历整棵树。
11-Reader
Reader interface
上文出现了阅读器这个概念,我感到很模糊,于是查找相关资料进行学习。
type Reader interface {
Read(p []byte) (n int ,err error)
}
官方文档中关于该接口方法的说明
Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p)) 以及任何遇到的错误。即使 Read 返回的 n < len(p),它也会在调用过程中使用 p 的全部作为暂存空间。若一些数据可用但不到 len(p) 个字节,Read 会照例返回可用的数据,而不是等待更多数据。
Read 在成功读取 n > 0 个字节后遇到一个错误或
EOF (end-of-file)
,它就会返回读取的字节数。它会从相同的调用中返回(非nil的)错误或从随后的调用中返回错误(同时 n == 0)。 一般情况的一个例子就是 Reader 在输入流结束时会返回一个非零的字节数,同时返回的err
不是EOF
就是nil
。无论如何,下一个 Read 都应当返回0, EOF
。
调用者应当总在考虑到错误 err 前处理 n > 0 的字节。这样做可以在读取一些字节,以及允许的 EOF 行为后正确地处理 I/O 错误
PS: 当Read
方法返回错误时,不代表没有读取到任何数据,可能是数据被读完了时返回的io.EOF
。
Reader 接口的方法集(Method_sets)只包含一个 Read 方法,因此,所有实现了 Read
方法的类型都实现了io.Reader
接口,也就是说,在所有需要 io.Reader
的地方,可以传递实现了 Read()
方法的类型的实例。
NewReader func
Reader Struct
NewReader创建一个从s读取数据的Reader
type Reader struct {
s string //对应的字符串
i int64 // 当前读取到的位置
prevRune int
}
Len 、Size,Read func
Len作用: 返回未读的字符串长度
Size的作用:返回字符串的长度
read的作用: 读取字符串信息
r := strings.NewReader(\"abcdefghijklmn\")
fmt.Println(r.Len()) // 输出14 初始时,未读长度等于字符串长度
var buf []byte
buf = make([]byte, 5)
readLen, err := r.Read(buf)
fmt.Println(\"读取到的长度:\", readLen) //读取到的长度5
if err != nil {
fmt.Println(\"错误:\", err)
}
fmt.Println(buf) //adcde
fmt.Println(r.Len()) //9 读取到了5个 剩余未读是14-5
fmt.Println(r.Size()) //14 字符串的长度
Practice
任何实现了 Read()
函数的对象都可以作为 Reader
来使用。
围绕io.Reader/Writer
,有几个常用的实现
net.Conn
,os.Stdin
,os.File
: 网络、标准输入输出、文件的流读取strings.Reader
: 把字符串抽象成Readerbytes.Reader
: 把[]byte抽象成Readerbytes.Buffer
: 把[]byte抽象成Reader和Writerbufio.Reader/Writer
: 抽象成带缓冲的流读取(比如按行读写)
我们编写一个通用的阅读器至标准输出流方法,并分别传入对象 \\(os.File、net.Conn、strings.Reader\\)
func readerToStdout(r io.Reader, bufSize int) {
buf := make([]byte, bufSize)
for {
n, err := r.Read(buf)
if err == io.EOF {
break
}
if err != nil {
fmt.Println(err)
break
}
if n > 0 {
fmt.Println(string(buf[:n]))
}
}
}
在\\(readerToStdout\\) 方法中,我们传入实现了 \\(io.Reader\\) 接口的对象,并规定一个每次读取数据的缓冲字节切片的大小。
需要注意的是,由于是分段读取,需要使用 \\(for\\) 循环,通过判断 \\(io.EOF\\) 退出循环,同时还需要考虑其他错误。输出至 \\(os.Stdin\\) 标准流时需要对字节切片进行字符串类型转换,同时字节切片应该被索引截取。\\(n\\)是本次读取到的字节数。
如果输出时切片不被索引截取会出现什么情况。
func fileReader() {
f, err := os.Open(\"book.txt\")
if err != nil {
panic(err)
}
defer f.Close()
buf := make([]byte, 3)
for {
n, err := f.Read(buf)
if err == io.EOF {
break
}
if err != nil {
fmt.Println(err)
break
}
if n > 0 {
fmt.Println(buf)
}
}
}
book.txt 内容为 abcd
[97 98 99]
[100 98 99]
第一次循环缓冲切片被正常填满,而第二次由于还剩一个字节,便将这一个字节读入缓冲切片中,而后面元素未被改变。假定文件字节数很小,缓冲切片很大,那么第一次就可以读取完成,这会导致输出字节数组后面的 \\(0\\) 或一些奇怪的内容。
func connReader() {
conn, err := net.Dial(\"tcp\", \"example.com:80\")
if err != nil {
panic(err)
}
defer conn.Close()
fmt.Fprint(conn, \"GET /index.html HTTP/1.0\\r\\n\\r\\n\")
readerToStdout(conn, 20)
}
这里我们通过 \\(net.Dial\\) 方法创建一个 \\(tcp\\) 连接,同时我们需要使用 \\(fmt.Fprint\\) 方法给特定连接发送请求。\\(conn\\) 实现了 \\(io.Reader\\) 接口,可以传入 \\(readerToStdout\\) 方法。
func stringsReader() {
s := strings.NewReader(\"very short but interesting string\")
readerToStdout(s, 5)
}
func fileReader() {
f, err := os.Open(\"book.txt\")
if err != nil {
panic(err)
}
defer f.Close()
readerToStdout(f, 3)
}
我们给定 \\(string\\) 对象来构造 \\(strings.Reader\\),并传入 \\(readerToStdout\\) 方法。我们使用 \\(os.Open\\) 打开文件,所得到的 \\(File\\) 对象也实现了 \\(os.Reader\\) 方法。
来源:https://www.cnblogs.com/linxiaoxu/p/16104861.html
本站部分图文来源于网络,如有侵权请联系删除。