課程地址 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) 根據空格、制表符分割,拿到的是字串切片,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/455545.html
標籤:Go
