在〈從標準輸入、輸出認識 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)
}