在〈从标准输入、输出认识 io〉中谈到了io.Reader
、io.Writer
,在 Go 中,这两个接口抽象化了输入、输出,认识这两个接口分别定义的Read
、Write
行为,是掌握 Go 中输入、输出的基础。
io.Reader
定义的Read
行为,可以在type Reader
查看:
type Reader interface {
Read(p []byte) (n int, err error)
}
对于调用者来说,Read
会将数据读入p
,并返回读入的字节数n
,n
会是 0 到不大于len(p)
的整数,如果n
不是 0 但不足len(p)
,应该先处理已读取的字节,这时err
可能不是nil
(例如文件结尾,可能会返回io.EOF
),无论如何,在这之后Read
,n
会是 0 而err
会是io.EOF
。
例如,若要读取一个文本文件,其中以 UTF-8 存储中文,可以如下:
package main
import (
"fmt"
"io"
"os"
)
func printUTF8TC(r io.Reader) (err error) {
var (
buf = make([]byte, 3)
n int
)
for err == nil {
n, err = r.Read(buf)
fmt.Print(string(buf[:n]))
}
if err == io.EOF {
err = nil
}
return
}
func main() {
fmt.Print("文件来源:")
var filename string
fmt.Scanf("%s", &filename)
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
printUTF8TC(f)
}
io.Writer
定义的Write
行为,可以在type Writer
查看:
type Writer interface {
Write(p []byte) (n int, err error)
}
Write
会将p
输出并返回实际输出的字节,n
会是 0 到不大于len(p)
的整数,如果n < len(p)
,那么err
不会是nil
。
来写个Copy
函数好了,可以将io.Reader
的数据直接写到io.Writer
:
package main
import (
"fmt"
"io"
"os"
)
func write(w io.Writer, buf []byte, n int) (err error) {
nw, ew := w.Write(buf[:n])
if ew != nil {
return ew
}
if n != nw {
return io.ErrShortWrite
}
return nil
}
func Copy(w io.Writer, r io.Reader) (err error) {
buf := make([]byte, 32 * 1024)
for {
nr, er := r.Read(buf)
if nr > 0 {
err = write(w, buf, nr)
if err != nil {
return
}
}
if er != nil {
if er != io.EOF {
err = er
}
return
}
}
}
func main() {
fmt.Print("文件来源:")
var filename string
fmt.Scanf("%s", &filename)
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
Copy(os.Stdout, f)
}
在这个例子中,可以将指定的文件读入并显示在控制台中,这是因为os.Stdout
具有io.Writer
的行为。实际上,io.Copy
就提供了这个功能:
package main
import (
"fmt"
"io"
"os"
)
func main() {
fmt.Print("文件来源:")
var filename string
fmt.Scanf("%s", &filename)
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
io.Copy(os.Stdout, f)
}