Golang | IO庫

_Liu_發表於2020-04-04

一、io

1、兩個介面

Go 的 io 包提供了 io.Readerio.Writer 介面,分別用於資料的輸入和輸出;

Go 官方提供了一些 API,支援對記憶體結構檔案網路連線等資源進行操作;

圍繞io.Reader/Writer,有幾個常用的實現:

  • net.Conn, os.Stdin, os.File: 網路、標準輸入輸出、檔案的流讀取
  • strings.Reader: 把字串抽象成Reader(類似流那樣,這樣就有各種API了)
  • bytes.Reader: 把[]byte抽象成Reader
  • bytes.Buffer: 把[]byte抽象成Reader和Writer
  • bufio.Reader/Writer: 抽象成帶緩衝的流讀取(比如按行讀寫)

什麼是流?流是一種資料的載體,通過它可以實現資料交換和傳輸; 比如說程式讀取檔案,程式通過流從檔案中讀取資料,也通過向流中寫入資料,最終寫入到檔案中; 程式碼中開啟檔案,就會返回一個流,這個流連通著檔案和程式;

關於io.EOF

EOF是End-Of-File的縮寫,表示輸入流的結尾. 因為每個檔案都有一個結尾;

2、io.Reader

該介面只有一個方法,Read(p []byte)。只要實現了 Read(p []byte) ,那它就是一個讀取器。

type Reader interface {
	//n是讀取到的位元組數,err是讀取時發生的錯誤;
	//如果資源內容已全部讀取完畢,應該返回 io.EOF 錯誤
    Read(p []byte) (n int, err error)
}

//實際使用
p := make([]byte, 4)
for {//迴圈讀取,每次讀取4個位元組
    n, err := reader.Read(p)
    if err != nil{
        if err == io.EOF {//讀取到空時,退出迴圈
            break
        }else{
       		os.Exit(1)
        }
    }
    fmt.Println(n, string(p[:n]))
}

//最後一次返回的 n 值有可能小於緩衝區大小;
複製程式碼

3、io.Writer

該介面只有一個方法,Write(p []byte)。只要實現了 Write(p []byte) ,那它就是一個編寫器。

type Writer interface {
    Write(p []byte) (n int, err error)
}
複製程式碼

4、輔助函式

//方便地將字串型別寫入一個Writer,本質是對Write([]byte(s))的封裝
func WriteString(w Writer, s string) (n int, err error)

//ReadFullr精確地讀取len(buf)位元組資料填充進buffunc ReadFull(r Reader, buf []byte) (n int, err error)

//將src的資料拷貝到dst,直到在src上到達EOF或發生錯誤。
//io.Copy()可以輕鬆地將資料從一個 Reader 拷貝到另一個 Writer。
//它抽象出for迴圈讀取的模式並正確處理io.EOF和 位元組計數。這樣就不用自己讀一點寫一點了;
func Copy(dst Writer, src Reader) (written int64, err error)
複製程式碼

5、PipeReader和PipeWriter

Pipe建立一個同步的記憶體中的管道,型別 io.PipeWriterio.PipeReader 在記憶體管道中模擬 io 操作。 資料被寫入管道的一端,並使用單獨的 goroutine 在管道的另一端讀取。

read:=strings.NewReader("hello world")

piper, pipew := io.Pipe()
// 將 read 寫入 pipew 這一端
go func() {
    defer pipew.Close()
    io.Copy(pipew, read)	//往通道寫入資料,在資料被讀走之前,該協程將是阻塞的
}()
io.Copy(os.Stdout, piper)	//從通道中讀取資料
piper.Close()

//原始碼剖析
func Pipe() (*PipeReader, *PipeWriter) {
	p := &pipe{
		wrCh: make(chan []byte),	//這裡是一個同步通道
		rdCh: make(chan int),
		done: make(chan struct{}),
	}
	return &PipeReader{p}, &PipeWriter{p}
}

複製程式碼

6、特殊Reader

func LimitReader(r Reader, n int64) Reader
//返回一個Reader,它從r中讀取n個位元組後以EOF停止。返回值介面的底層為*LimitedReader型別。

func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
//返回一個從r中的偏移量off處為起始,讀取n個位元組後以EOF停止的SectionReaderfunc MultiReader(readers ...Reader) Reader
//返回一個將多個Reader在邏輯上串聯起來的Reader介面。
//他們依次被讀取。當所有的輸入流都讀取完畢,Read才會返回EOF複製程式碼

二、io_ioutil

1、ioutil

io 包下面的一個子包 ioutil 封裝了一些非常方便的功能

func ReadFile(filename string) ([]byte, error)
//ReadFilefilename指定的檔案中讀取資料並返回檔案的內容(讀取整個檔案,考慮記憶體大小)

func WriteFile(filename string, data []byte, perm os.FileMode) error
//函式向filename指定的檔案中寫入資料。如果檔案不存在將按給出的許可權建立檔案,否則在寫入資料之前清空檔案。
複製程式碼

2、函式

//將Reader中的資料一次性讀取出來
func ReadAll(r io.Reader) ([]byte, error)

//將檔案中的資料一次性讀取出來
func ReadFile(filename string) ([]byte, error)

//將檔案中寫入資料,如果檔案不存在將按給出的許可權建立檔案
func WriteFile(filename string, data []byte, perm os.FileMode) error
複製程式碼

3、os.File

型別 os.File 表示本地系統上的檔案。它實現了 io.Readerio.Writer ,因此可以在任何 io 上下文中使用。

4、os.Stdoutos.Stdinos.Stderr

這三者型別均為 *os.File,分別代表 系統標準輸入系統標準輸出系統標準錯誤 的檔案控制程式碼。

os.Stdout.Write([]byte("hello world"))	//直接輸出到終端
os.Stderr.Write([]byte("this is an error"))	//直接輸出到終端

p := make([]byte, 10)	//獲取終端輸入
reader := bufio.NewReader(os.Stdin)
_, err := reader.Read(p)
複製程式碼

三、bufio

bufio 包實現了快取IO。它包裝了 io.Reader 和 io.Writer 物件,建立了另外的Reader和Writer物件,它們也實現了io.Reader和io.Writer介面,不過它們是有快取的。 資料緩衝功能,能夠一定程度減少大塊資料讀寫帶來的開銷。

簡單說就是,把檔案讀取進緩衝(記憶體)之後再讀取的時候就可以避免檔案系統的io 從而提高速度。

1、Reader 型別和方法

type Reader struct {
    buf          []byte     // 快取
    rd           io.Reader  // 底層的io.Reader
    r, w         int
    err          error      // 讀過程中遇到的錯誤
    lastByte     int        // 最後一次讀到的位元組
    lastRuneSize int        // 最後一次讀到的Rune的大小
}

//將 rd 封裝成一個帶快取的 bufio.Reader 物件,快取大小由 size 指定
//如果rd已經帶用足夠的快取,則將rb轉化為*Reader型別,直接返回
func NewReaderSize(rd io.Reader, size int) *Reader

//預設快取大小,defaultBufSize=4096
func NewReader(rd io.Reader) *Reader {
	return NewReaderSize(rd, defaultBufSize)	
}

//Peek 返回快取的一個切片,該切片引用快取中前 n 個位元組的資料,
//該操作不會將資料讀出,只是引用.
func (b *Reader) Peek(n int) ([]byte, error)
複製程式碼
話說read的時候,還要我自定義p []byte,這個包有什麼鳥用?
底層的緩衝區有什麼鳥用?
p:=make([]byte,1024)
buff.Read(p)

底層的緩衝是為了避免多次訪問磁碟,提高效能;
上層的[]byte是。。。

//原始碼剖析
func (b *Reader) Read(p []byte) (n int, err error) {}
1、Read從b中讀取資料到p中,如果快取不為空,則只能讀取快取中的資料,不會從底層io.Reader中提取資料
2、如果快取為空:
(1len(p) >= 快取大小,則跳過快取,直接從底層io.Reader中提取數到p
(2len(p) < 快取大小,則先將資料從io.Reader讀取到快取中,再從快取copy到p中;(記憶體之間直接賦值是很快的)
快取中被讀取的部分都將被清空;
複製程式碼

1570714023755

2、Writer

func (b *Writer) Write(p []byte) (nn int, err error) // 寫入n byte資料

func (b *Writer) Reset(w io.Writer) // 重置當前緩衝區

func (b *Writer) Flush() error // 清空當前緩衝區,將資料寫入輸出
複製程式碼

3、Scanner

Scanner是有快取的,意思是Scanner底層維護了一個Slice用來儲存已經從Reader中讀取的資料; Scanner本身不負責關閉檔案描述符,需要自己在外面關閉;

這緩衝區是怎麼樣的?

file,err:=os.Open("./file.txt")

//和Reader類似,Scanner需要繫結到某個io.Reader上
scan:=bufio.NewScanner(file)

//設定緩衝區大小,預設是MaxScanTokenSize = 64 * 1024
//這裡緩衝區大小設定為2,不夠時會擴充為原來的2倍,最大為1024
buf:=make([]byte,2)
scan.Buffer(buf,1024)//要在scan之前呼叫,否則panic

//設定分割方式,要在scan之前呼叫,否則panic
//scan.Split(bufio.ScanWords)//輸出單個單詞,也就是按空格分割
//scan.Split(bufio.ScanLines)//一行一行輸出,也就是按行分割(這是預設的方式)
//scan.Split(bufio.ScanRunes)//一個字元一個字元輸出
//scan.Split(bufio.ScanBytes)//一個位元組一個位元組輸出
//自定義分割方式
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
        }
    }
    if !atEOF {
        return 0, nil, nil
    }
    return 0, data, bufio.ErrFinalToken
}
scan.Split(onComma)

//Scan方法獲取當前位置的token,並讓Scanner的掃描位置移動到下一個token。
//返回false時,表示EOF或者讀取時產生錯誤
//這裡的token是指分隔符與分隔符之間的這段字元
	for scan.Scan(){
		fmt.Println(scan.Text())//返回最近一次Scan呼叫生成的token
	}
//Scan返回false了,Err看一下是EOF還是其他錯誤;
//在Scan方法返回false後,Err方法將返回掃描時遇到的任何錯誤;除非是io.EOF,此時Err會返回nil。
	if err=scan.Err();err!=nil{
		fmt.Println(err)
	}
複製程式碼

四、bytes/strings

1、這兩個包是相當類似的,只是一個操作[]byte,一個操作string罷了

2、功能:equal,container,ToLower,ToUpper,repeat(返回n個串聯的字串),replace,Trim,Split,Join(連線多個string)

3、bytes還擁有buff功能

//簡單使用
buf := make([]byte, 1024)	//定義緩衝區大小
b := bytes.NewBuffer(buf)

file,err:=os.Open("./file.txt")

b.ReadFrom(file)//讀取資料到緩衝區
b.WriteTo(os.Stdout)//將緩衝區輸出到io.Writer
b.String()	//字串化
b.Reset()	//清空資料
複製程式碼

相關文章