一、io
1、兩個介面
Go 的 io
包提供了 io.Reader
和 io.Writer
介面,分別用於資料的輸入和輸出;
Go 官方提供了一些 API
,支援對記憶體結構,檔案,網路連線等資源進行操作;
圍繞io.Reader/Writer
,有幾個常用的實現:
net.Conn
,os.Stdin
,os.File
: 網路、標準輸入輸出、檔案的流讀取strings.Reader
: 把字串抽象成Reader(類似流那樣,這樣就有各種API了)bytes.Reader
: 把[]byte抽象成Readerbytes.Buffer
: 把[]byte抽象成Reader和Writerbufio.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)
//ReadFull從r精確地讀取len(buf)位元組資料填充進buf。
func 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.PipeWriter
和 io.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停止的SectionReader。
func MultiReader(readers ...Reader) Reader
//返回一個將多個Reader在邏輯上串聯起來的Reader介面。
//他們依次被讀取。當所有的輸入流都讀取完畢,Read才會返回EOF。
複製程式碼
二、io_ioutil
1、ioutil
io
包下面的一個子包 ioutil
封裝了一些非常方便的功能
func ReadFile(filename string) ([]byte, error)
//ReadFile 從filename指定的檔案中讀取資料並返回檔案的內容(讀取整個檔案,考慮記憶體大小)
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.Reader
和 io.Writer
,因此可以在任何 io 上下文中使用。
4、os.Stdout
,os.Stdin
和 os.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、如果快取為空:
(1)len(p) >= 快取大小,則跳過快取,直接從底層io.Reader中提取數到p
(2)len(p) < 快取大小,則先將資料從io.Reader讀取到快取中,再從快取copy到p中;(記憶體之間直接賦值是很快的)
快取中被讀取的部分都將被清空;
複製程式碼
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() //清空資料
複製程式碼