本篇文章是對區塊鏈開發中的Go語言中常用的io操作的庫做一個梳理
io,最基本的io
Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
複製程式碼
實現了Reader介面的都可以用read方法,將資料讀入到p位元組陣列,n表示讀取了幾個位元組,err返回錯誤。
如果讀到了檔案尾EOF,則err返回EOF。
注意,當檔案最後一小段已經無法填滿p這個位元組陣列時,不會產生EOF的錯誤,只會在下一次讀取時產生n=0,err=io.EOF的錯誤
舉例
func main() {
file, _ := os.Open("main.go")
var a [128]byte
count:=0
for {
n, err := file.Read(a[:])
count+=1
if err != nil {
if err == io.EOF {
break
} else {
os.Exit(1)
}
}
fmt.Printf("%s
", a[:n])
}
fmt.Printf("%d
", count)
}
複製程式碼
Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
複製程式碼
Write 將 len(p) 個位元組從 p 中寫入到基本資料流中。它返回從 p 中被寫入的位元組數 n(0 <= n <= len(p))以及任何遇到的引起寫入提前停止的錯誤。若 Write 返回的 n < len(p),它就必須返回一個 非nil 的錯誤。
常見錯誤原因有磁碟滿了
ReaderAt 和 WriterAt 介面
和Reader,Writer類似,但是需要自己調控偏移量。
注意:接近檔案尾巴時,當n小於陣列大小時也觸發了err.EOF,需要自行把最後n小於陣列大小的這點資料處理一下。
舉例:
func main() {
file, _ := os.Open("main.go")
var a [128]byte
count := 0
var pos int64 = 0
for {
n, err := file.ReadAt(a[:], pos)
count += 1
pos += int64(n)
if err != nil {
if err == io.EOF {
fmt.Printf("%s", a[:n]) //區別在這裡
break
} else {
os.Exit(1)
}
}
fmt.Printf("%s", a[:n])
}
fmt.Println()
fmt.Printf("%d", count)
}
複製程式碼
ReaderFrom 和 WriterTo 介面
一次性讀完直到EOF,或者寫入全部資料
Seeker 介面
type Seeker interface {
Seek(offset int64, whence int) (ret int64, err error)
}
複製程式碼
用來設定偏移量,也就是從哪開始讀,offset由whence解釋。
- 0 表示相對於檔案的起始處
- 1 表示相對於當前的偏移,
- 2 表示相對於其結尾處。
ByteReader 和 ByteWriter
讀或寫一個位元組
ioutil — 方便的IO操作函式集
ReadAll
一次性讀取資料
ReadDir
讀取目錄並返回排好序的檔案和子目錄名
ReadFile 和 WriteFile
func WriteFile(filename string, data []byte, perm os.FileMode) error
複製程式碼
這裡特別注意的是寫檔案的許可權問題,perm的數值,和linux規則一致
四位(777):
模式 | 數字 |
---|---|
rwx | 7 |
rw- | 6 |
r-x | 5 |
r– | 4 |
-wx | 3 |
-w- | 2 |
–x | 1 |
— | 0 |
組合如0666,表示rw-rw-rw-
bufio,帶快取的io
是io庫的包裝,提供帶快取的方法
ReadSlice、ReadBytes、ReadString 和 ReadLine 方法
後三個方法最終都是呼叫ReadSlice來實現的
ReadSlice
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
複製程式碼
示例:
reader := bufio.NewReader(strings.NewReader("http://studygolang.com.
It is the home of gophers"))
line, _ := reader.ReadSlice(`
`)
fmt.Printf("the line:%s
", line)
// 這裡可以換上任意的 bufio 的 Read/Write 操作
n, _ := reader.ReadSlice(`
`)
fmt.Printf("the line:%s
", line)
fmt.Println(string(n))
複製程式碼
輸出:
the line:http://studygolang.com.
the line:It is the home of gophers
It is the home of gophers
複製程式碼
注意ReadSlice每次返回的line是指向同一個快取陣列,因此ReadSlice的實現是反覆覆蓋重寫快取陣列。
如果ReadSlice在找到分界符前
- 快取陣列就滿了,則返回bufio.ErrBufferFull
- 遇到EOF了,則返回ErrEOF
ReadBytes
func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
複製程式碼
返回的byte是copy的一份陣列
從以下實驗可看出來
reader := bufio.NewReader(strings.NewReader("http://studygolang.com.
It is the home of gophers"))
line, _ := reader.ReadBytes(`
`)
fmt.Printf("the line:%s
", line)
// 這裡可以換上任意的 bufio 的 Read/Write 操作
n, _ := reader.ReadBytes(`
`)
fmt.Printf("the line:%s
", line)
fmt.Println(string(n))
複製程式碼
輸出
the line:http://studygolang.com.
the line:http://studygolang.com.
It is the home of gophers
複製程式碼
ReadString
是對ReadBytes的封裝,將返回的line轉換成string
ReadLine
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
複製程式碼
這裡要說的是isPrefix,用於讀取的一行超過了快取大小,則isPrefix為true,下次還讀這行餘下的部分,直到讀完這行才isPrefix返回false
ReadLine返回的文字不會包含行結尾(”
“或者”
“)
Peek
該方法只是“窺探”一下Reader中沒有讀取的n個位元組。好比棧資料結構中的取棧頂元素,但不出棧。
func (b *Reader) Peek(n int) ([]byte, error)
複製程式碼
同上面介紹的ReadSlice一樣,返回的[]byte只是buffer中的引用。所以在併發的時候有可能就被別人給改了
Scanner 型別和方法
用於方便的按token讀取資料,token的分詞規則用SplitFunc定義。預設按行分詞,會去掉末尾換行符。
瞭解Scanner前要先了解SplitFunc
SplitFunc
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
複製程式碼
SplitFunc 定義了 用於對輸入進行分詞的 split 函式的簽名。
引數
- data 是還未處理的資料,
- atEOF 標識 Reader是否還有更多資料(是否到了EOF)。
返回值
- advance data裡下一個token開始位置
- token 表示當前token的結果資料
- err 則代表可能的錯誤。
舉例
func main() {
// Comma-separated list; last entry is empty.
const input = "1,2,3,4,"
scanner := bufio.NewScanner(strings.NewReader(input))
// Define a split function that separates on commas.
onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == `,` {
return i + 1, data[:i], nil
}
}
// There is one final token to be delivered, which may be the empty string.
// Returning bufio.ErrFinalToken here tells Scan there are no more tokens after this
// but does not trigger an error to be returned from Scan itself.
return 0, data, bufio.ErrFinalToken
}
scanner.Split(onComma)
// Scan.
for scanner.Scan() {
fmt.Printf("%q ", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
}
複製程式碼
輸出
"1" "2" "3" "4" "5"
複製程式碼
你也可以用系統定義好的幾個分割token的方法。
-
ScanBytes 返回單個位元組作為一個 token。
-
ScanRunes 返回單個 UTF-8 編碼的 rune 作為一個 token。返回的 rune 序列(token)和 range string型別 返回的序列是等價的,也就是說,對於無效的 UTF-8 編碼會解釋為 U+FFFD = “xefxbfxbd”。
-
ScanWords 返回通過“空格”分詞的單詞。如:study golang,呼叫會返回study。注意,這裡的“空格”是 unicode.IsSpace(),即包括:` `, `
`, `v`, `f`, `
`, ` `, U+0085 (NEL), U+00A0 (NBSP)。 -
ScanLines 返回一行文字,不包括行尾的換行符。這裡的換行包括了Windows下的”
“和Unix下的”
“。
Scanner 的使用方法
- NewScanner
- Split設定分割token的方法
- 迴圈scanner.Scan()
- 在迴圈裡用scanner.Text()取token
示例
const input = "This is The Golang Standard Library.
Welcome you!"
scanner := bufio.NewScanner(strings.NewReader(input))
scanner.Split(bufio.ScanWords)
count := 0
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
fmt.Println(count)
複製程式碼
Writer
帶快取的writer,記得在最終的寫入操作執行完後flush一下,確保全部快取都真正寫入。
參考
1.《Go語言標準庫》The Golang Standard Library by Example
關於我:
linxinzhe,全棧工程師,目前供職於某500強通訊企業。人工智慧,區塊鏈愛好者。
GitHub:https://github.com/linxinzhe
歡迎留言討論,也歡迎關注我,收穫更多區塊鏈開發相關的知識。