區塊鏈開發之Go語言—IO操作

linxinzhe發表於2019-02-20

本篇文章是對區塊鏈開發中的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在找到分界符前

  1. 快取陣列就滿了,則返回bufio.ErrBufferFull
  2. 遇到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 函式的簽名。

引數

  1. data 是還未處理的資料,
  2. atEOF 標識 Reader是否還有更多資料(是否到了EOF)。

返回值

  1. advance data裡下一個token開始位置
  2. token 表示當前token的結果資料
  3. 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的方法。

  1. ScanBytes 返回單個位元組作為一個 token。

  2. ScanRunes 返回單個 UTF-8 編碼的 rune 作為一個 token。返回的 rune 序列(token)和 range string型別 返回的序列是等價的,也就是說,對於無效的 UTF-8 編碼會解釋為 U+FFFD = “xefxbfxbd”。

  3. ScanWords 返回通過“空格”分詞的單詞。如:study golang,呼叫會返回study。注意,這裡的“空格”是 unicode.IsSpace(),即包括:` `, `
    `, `v`, `f`, `
    `, ` `, U+0085 (NEL), U+00A0 (NBSP)。

  4. ScanLines 返回一行文字,不包括行尾的換行符。這裡的換行包括了Windows下的”
    “和Unix下的”
    “。

Scanner 的使用方法
  1. NewScanner
  2. Split設定分割token的方法
  3. 迴圈scanner.Scan()
  4. 在迴圈裡用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

歡迎留言討論,也歡迎關注我,收穫更多區塊鏈開發相關的知識。

公眾號.jpg

相關文章