07-Formatted & File I/O、 I/O steams、 formatted I/O、 fmt functions、 file I/O、 Practice ① I/O、 Always check the err、 Practice ② I/O、 Pra... ...
課程地址 go-class-slides/xmas-2020 at trunk · matt4biz/go-class-slides (github.com)
主講老師 Matt Holiday
07-Formatted & File I/O
I/O steams
操作系統具有三個標準 io 流,標準輸入、標準輸出、標準錯誤。它們分別可以重定向。
formatted I/O
Println
將參數預設輸出到標準輸出流,如果會用 Fprintln
可以指定輸出到某個流,比如 os.Stderr
fmt functions
Sprintln
格式化字元串並返回。
package main
import "fmt"
func main() {
a, b := 12, 345
c, d := 1.2, 3.45
fmt.Printf("%d %d\n", a, b)
fmt.Printf("%x %x\n", a, b)
fmt.Printf("%#x %#x\n", a, b)
fmt.Printf("%f %.2f", c, d)
fmt.Println()
fmt.Printf("|%6d|%6d|\n", a, b)
fmt.Printf("|%-6d|%-6d|\n", a, b)
fmt.Printf("|%06d|%06d|\n", a, b)
fmt.Printf("|%9f|%9.2f|\n", c, d) // ^ 當數字過大時也會超出
}
12 345
c 159
0xc 0x159
1.200000 3.45
| 12| 345|
|12 |345 |
|000012|000345|
| 1.200000| 3.45|
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3}
a := [3]rune{'a', 'b', 'c'}
m := map[string]int{"and": 1, "or": 2}
ss := "a string"
b := []byte(ss)
fmt.Printf("%T\n", s)
fmt.Printf("%v\n", s)
fmt.Printf("%#v\n", s) // ^ %#v 更符合初始化時輸入的形式
fmt.Println()
fmt.Printf("%T\n", a)
fmt.Printf("%v\n", a)
fmt.Printf("%q\n", a) // ^ 註意這個%q將rune從int32轉化成了字元串
fmt.Printf("%#v\n", a)
fmt.Println()
fmt.Printf("%T\n", m)
fmt.Printf("%v\n", m)
fmt.Printf("%#v\n", m)
fmt.Println()
fmt.Printf("%T\n", ss)
fmt.Printf("%v\n", ss)
fmt.Printf("%q\n", ss)
fmt.Printf("%#v\n", ss)
fmt.Printf("%v\n", b)
fmt.Printf("%v\n", string(b)) // ^ 將位元組切片轉換為字元串
}
[]int
[1 2 3]
[]int{1, 2, 3}
[3]int32
[97 98 99]
['a' 'b' 'c']
[3]int32{97, 98, 99}
map[string]int
map[and:1 or:2]
map[string]int{"and":1, "or":2}
string
a string
"a string"
"a string"
[97 32 115 116 114 105 110 103]
a string
file I/O
Practice ① I/O
編寫一個類似 Unix cat 的程式,將多個文件輸出到標準輸出流中,並輸出為一個文件。
package main
import (
"fmt"
"io"
"os"
)
func main() {
for _, fname := range os.Args[1:] {
file, err := os.Open(fname)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if _, err := io.Copy(os.Stdout, file); err != nil {
fmt.Fprint(os.Stderr, err)
continue
}
fmt.Fprint(os.Stdout, "\n") // ^ 每個文件內容末尾添加換行符
file.Close()
}
}
io.copy
是一個很棒的功能,它知道如何緩衝、如何以塊的形式讀入並寫會,它不會嘗試把整個文件讀取到記憶體中也不會一次讀取一個字元。
file.Close
大多數操作系統對程式中打開多少個文件有限制,所以文件使用完成後需要進行關閉。
在當前目錄新建 txt
文件,寫入內容。執行下麵三條命令。
go run . a.txt
go run . a.txt b.txt c.txt
go run . a.txt b.txt c.txt > new.txt
第二條指令結果
[]int{1, 2, 3}
go go go
people car
cat
apple
banana
第三條指令在當前目錄生成了 new.txt 文件,內容是 標準輸出流 的內容。
Always check the err
Practice ② I/O
編寫一個簡短的程式計算文件大小。一次性讀取(小文件情況下)
我們前面知道, io/ioutil
包可以對整個文件進行讀取,存入記憶體中。我們可以使用它計算文件大小。
原先的 io.Copy
返回的是複製的位元組數,而 ReadAll
將返回整個 data
,位元組切片和一個err。
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
for _, fname := range os.Args[1:] {
file, err := os.Open(fname)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
data, err := ioutil.ReadAll(file)
if err != nil {
fmt.Fprint(os.Stderr, err)
continue
}
fmt.Println("The file has", len(data), "bytes")
file.Close()
}
}
go run . a.txt b.txt c.txt
The file has 30 bytes
The file has 20 bytes
The file has 18 bytes
data, err := ioutil.ReadAll(file)
從 if
中取出單獨成行,是因為需要 data
這個變數。如果放在 if
短聲明裡會導致作用域只在 if
語句塊內。
Practice ③ I/O
編寫一個 wc 程式(word counter),輸出lines、words、characters數量。使用緩衝 buffio(大文件情況下)
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
for _, fname := range os.Args[1:] {
var lc, wc, cc int
file, err := os.Open(fname)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
scan := bufio.NewScanner(file)
for scan.Scan() {
s := scan.Text()
wc += len(strings.Fields(s)) // ^ 根據空格、製表符分割 a slice of words
cc += len(s)
lc++
}
fmt.Printf("%7d %7d %7d %s\n", lc, wc, cc, fname)
file.Close()
}
}
go run . a.txt b.txt c.txt
3 7 26 a.txt
2 5 18 b.txt
3 3 14 c.txt
bufio.NewScanner(file)
創建一個掃描器按行掃描。考慮到多行需要用 for
迴圈 scan.Scan
。
strings.Fields(s)
根據空格、製表符分割,拿到的是字元串切片。