写程序时经常需要从一个文件读取数据,然后输出到另一个文件。最典型的例子比如,从stdin读取,然后输出到stdout。go提供了几种读取文件的方式,我们逐个来看一看~
fmt.Scan
scss
func fmtScan() {
str := ""
for {
fmt.Scan(&str)
fmt.Printf("[fmt.scan]: %q (%d)\n", str, len(str))
}
}
// input: abc def ghi
// output:
//[fmt.scan]: "abc" (3)
//[fmt.scan]: "def" (3)
//[fmt.scan]: "ghi" (3)
fmt.Scan 从stdin读取数据,输出到内存变量。它会将空白符作为分割符,把分割后的字符串,依次赋值给传入参数。在上面的例子中,就是循环了3次分别赋值给str。
os.Stdin.Read
lua
func osStdinRead() {
p := make([]byte, 5)
for {
nn, err := os.Stdin.Read(p)
if err != nil && err != io.EOF {
panic(err)
}
fmt.Printf("[os.stdin.read]: %q (%d)\n", p, nn)
p = make([]byte, 5) // 清空p
}
}
// input: abcdefghijk
// output:
//[os.stdin.read]: "abcde" (5)
//[os.stdin.read]: "fghij" (5)
//[os.stdin.read]: "k\n\x00\x00\x00" (2)
最多读取len(p)大小的数据,如果输入的内容超出了len(p),则Read会在下一轮循环中接着读取。
io系列
ReadAll
scss
func ioReadAll() {
for {
p, err := io.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
fmt.Printf("[io.ReadAll]: %q (%d)\n", p, len(p))
}
}
// input: abc\nEOF
// output: [io.ReadAll]: "abc\n" (4)
ReadAll 读取全部,不把EOF当作错误返回。
ReadFull
go
func ioReadFull() {
p := make([]byte, 5)
for {
nn, err := io.ReadFull(os.Stdin, p)
if err != nil && err != io.EOF {
fmt.Printf("[io.ReadFull]: %q (%d)\n", p, nn)
panic(err)
}
fmt.Printf("[io.ReadFull]: %q (%d)\n", p, nn)
p = make([]byte, 5)
}
}
// input: aaaaaa
// output: [io.ReadFull]: "aaaaa" (5)
// input: aa\n\x00
// output: [io.ReadFull]: "aa\n\x00\x00" (3)
ReadFull 的目标是把p填满,也就是说正常情况下只有读到的数据长度nn==len(p)才返回。这里有两种情况需要注意:
- 没有数据可读,ReadFull返回 (0, EOF)
- 有一些数据可读,但在填满p前遇到了EOF,ReadFull返回 (读取的长度, ErrUnexpectedEOF)
bufio系列
bufio给io操作提供了一个缓冲,避免了每次都要访问磁盘。
Read
go
func bufioRead() {
br := bufio.NewReader(os.Stdin)
p := make([]byte, 5)
for {
nn, err := br.Read(p)
if err != nil && err != io.EOF{
panic(err)
}
fmt.Printf("[bufio.read]: %q (%d)\n", p, nn)
}
}
// input: abcdefghijk
// output:
//[bufio.read]: "abcde" (5)
//[bufio.read]: "fghij" (5)
//[bufio.read]: "k\n\x00\x00\x00" (2)
最多读取len(p)大小的数据,如果输入的内容超出了len(p),则Read会在下一轮循环中接着读取。
ReadBytes
scss
func bufioReadBytes() {
br := bufio.NewReader(os.Stdin)
for {
p, err := br.ReadBytes('\n')
if err != nil && err != io.EOF{
panic(err)
}
fmt.Printf("[bufio.ReadBytes]: %q (%d)\n", p, len(p))
}
}
// input: abcdef
// output: [bufio.ReadBytes]: "abcdef\n" (7)
ReadBytes 读取直到遇到给定的分界符,这里是'\n'(返回的p包含\n)。如果在读到分界符之前遇到了err,则返回读到的数据和err(一般是EOF)。
ReadString
scss
func bufioReadString() {
br := bufio.NewReader(os.Stdin)
for {
str, err := br.ReadString('\n')
if err != nil && err != io.EOF {
panic(err)
}
fmt.Printf("[bufio.ReadString]: %q (%d)\n", str, len(str))
}
}
// input: abcdef
// output: [bufio.ReadString]: "abcdef\n" (7)
ReadString 与 ReadBytes 类似,都是直到读到分界符才返回。
ReadRune
go
func bufioReadRune() {
br := bufio.NewReader(os.Stdin)
for {
r, sz, err := br.ReadRune()
if err != nil && err != io.EOF {
panic(err)
}
fmt.Printf("[bufio.ReadRune]: %q (%d)\n", r, sz)
}
}
// input: abc
// output:
//[bufio.ReadRune]: 'a' (1)
//[bufio.ReadRune]: 'b' (1)
//[bufio.ReadRune]: 'c' (1)
//[bufio.ReadRune]: '\n' (1)
ReadRune 一次读取一个UTF8字符。
Scan
scss
func bufioScan() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
text := scanner.Text()
fmt.Printf("[bufio.Scan]: %q (%d)\n", text, len(text))
}
if scanner.Err() != nil {
fmt.Print(scanner.Err().Error())
}
}
Scan 扫描文件到下一个token并返回true,随后可以通过Text,Bytes读取token。当扫描停止时,Scan返回false,这时可以通过Err获取错误,如果是因为EOF而扫描停止,Err=nil。
scanner在创建时,默认使用 ScanLines 作为分割函数。它会将输入文本分割成一系列的行,每行作为一个token。除此之外还有其他分割函数:
ScanLines
(默认):按行分割。ScanWords
:按空白字符分割的单词。ScanBytes
:按字节分割。ScanRunes
:按UTF-8编码的字符分割。
总结
如果只是简单从stdin读取,fmt.Scan 足够短小好用。当文件很大时 io.ReadAll, io.ReadFull 感觉在使用时不是很灵活,还是推荐用bufio的一系列方法操作。