Go 語言標准庫之 io & io/ioutil 包


io 包提供了對I/O原語的基本接口,其基本任務是包裝這些原語已有的實現(如 os 包里的原語),使之成為共享的公共接口,這些公共接口抽象出了泛用的函數並附加一些相關的原語的操作。

io 包常用接口

io.Reader 和 io.Writer 接口

io.Reader 接口

// io.Reader 接口用於包裝基本的讀取方法
type Reader interface {
    Read(p []byte) (n int, err error)
}

從底層輸入流讀取最多len(p)個字節數據寫入到 p 中,返回讀取的字節數 n 和遇到的任何錯誤 err。即使 Read 方法返回的n < len(p),在調用過程也會占用len(p)個字節作為暫存空間。若可讀取的數據不足len(p)個字節,Read 方法會返回可用數據,而不是等待更多數據。

當 Read 方法讀取n > 0個字節后遇到一個錯誤或 EOF (end-of-file),會返回讀取的字節數,同時在該次調用返回一個非 nil 錯誤或者在下一次的調用時返回 0 和該錯誤。一般情況下,io.Reader接口會在輸入流的結尾返回非 0 的字節數,返回值 err 為 nil 或者io.EOF,但下次調用必然返回(0, io.EOF)。調用者在考慮錯誤之前應當首先處理n > 0字節的返回數據,這樣做可以正確地處理在讀取一些字節后產生的I/O錯誤,同時允許 EOF 的出現。

☕️ 示例代碼

package main

import (
    "fmt"
    "io"
    "os"
    "strings"
)

func ReadFrom(reader io.Reader, num int) ([]byte, error) {
    p := make([]byte, num)
    n, err := reader.Read(p)
    if n > 0 {
        return p[:n], nil
    }
    return p, err
}

func main() {
    // 例子 1:從標准輸入中讀取 11 個字節
    // 控制台輸入:hello world!!!!
    data, err := ReadFrom(os.Stdin, 11)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", data) // hello world

    // 例子 2:從普通文件中讀取 9 個字節
    // 文件內容:hello world!!!!
    f, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    data, err = ReadFrom(f, 9)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", data) // hello wor

    // 例子 3:從字符串中讀取 12 個字節
    r := strings.NewReader("hello world!!!!")
    data, err = ReadFrom(r, 12)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", data) // hello world!
}

io.Writer 接口

// io.Writer 接口用於包裝基本的寫入方法
type Writer interface {
    Write(p []byte) (n int, err error)
}

len(p) 個字節數據從 p 中寫入底層的輸出流,返回寫入的字節數 n 和遇到的任何錯誤 err。如果 Write 方法返回的n < len(p),它就必須返回一個 非 nil 的錯誤。Write 方法不能修改切片 p 中的數據,即使臨時修改也不行。

⭐️ 示例代碼

package main

import (
    "fmt"
    "io"
    "os"
)

func WriteTo(write io.Writer, p []byte) (int, error) {
    n, err := write.Write(p)
    return n, err
}

func main() {
    p := []byte("hello world!!!\n")

    // 例子 1:將字符串寫入到標准輸出
    n, err := WriteTo(os.Stdout, p)
    if err != nil {
        panic(err)
    }
    fmt.Printf("write %d bytes\n", n)

    // 例子 2:將字符串寫入文件中
    f, err := os.Create("./test.txt")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    n, err = WriteTo(f, p)
    if err != nil {
        panic(err)
    }
    fmt.Printf("write %d bytes\n", n)
}

// 控制台輸出:
// hello world!!!
// write 15 bytes
// write 15 bytes

// test.txt 文件內容:
// hello world!!!

io.Reader 和 io.Writer 接口實現類型

  • os.File同時實現了io.Readerio.Writer接口
  • strings.Reader實現了io.Reader接口
  • bufio.Readerbufio.Writer 分別實現了io.Readerio.Writer接口
  • bytes.Buffer同時實現了io.Readerio.Writer接口
  • bytes.Reader實現了io.Reader接口
  • compress/gzip.Readercompress/gzip.Writer分別實現了io.Readerio.Writer接口
  • crypto/cipher.StreamReadercrypto/cipher.StreamWriter分別實現了io.Readerio.Writer接口
  • crypto/tls.Conn同時實現了io.Readerio.Writer接口
  • encoding/csv.Readerencoding/csv.Writer分別實現了io.Readerio.Writer接口
  • mime/multipart.Part 實現了io.Reader接口
  • net/conn分別實現了io.Readerio.Writer接口(Conn 接口定義了 Read 和 Write 方法)

除此之外,io 包本身也有這兩個接口的實現類型。如:

  • 實現了io.Reader的類型:LimitedReaderPipeReaderSectionReader
  • 實現了io.Writer的類型:PipeWriter

以上類型中,常用的類型有:os.Filestrings.Readerbufio.Reader/Writerbytes.Bufferbytes.Reader


io.ReaderAt 和 io.WriterAt 接口

io.ReaderAt 接口

// io.ReaderAt 接口包裝了基本的 ReadAt 方法
type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}

從底層輸入流的偏移量 off 位置讀取最多len(p)字節數據寫入 p,返回讀取的字節數 n 和遇到的任何錯誤 err。當ReadAt方法返回的n < len(p)時,它會返回一個非 nil 的錯誤來說明沒有讀取更多的字節的原因。在這方面,ReadAt方法是比 Read 方法要嚴格的。

即使ReadAt方法返回的n < len(p),在調用過程也會占用len(p)個字節作為暫存空間。如果有部分可用數據,但不夠len(p)字節,ReadAt 方法會阻塞直到獲取len(p)個字節數據或者遇到錯誤。在這方面,ReadAt方法和 Read 方法是不同的。如果 ReadAt 方法返回時到達輸入流的結尾,返回的n == len(p),返回的 err 為 nil 或者io.EOF

如果ReadAt方法是從某個有偏移量的底層輸入流讀取,ReadAt方法既不應影響底層的偏移量,也不被它所影響。ReadAt方法的調用者可以對同一輸入流執行並行的ReadAt調用。

✏️ 示例代碼

package main

import (
    "fmt"
    "strings"
)

func main() {
    reader := strings.NewReader("Go語言中文網")
    p := make([]byte, 6)
    n, err := reader.ReadAt(p, 2)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Read %d bytes: %s\n", n, p) // Read 6 bytes: 語言
}

io.WriterAt 接口

// io.WriterAt 接口包裝了基本的 WriteAt 方法
type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error)
}

len(p)個字節數據從 p 中寫入偏移量 off 位置的底層輸出流中,返回寫入的字節數 n 和遇到的任何錯誤 err。當WriteAt方法返回的n < len(p)時,它就必須返回一個非 nil 的錯誤。

如果WriteAt方法寫入的目標是某個有偏移量的底層輸出流,WriteAt方法既不應影響底層的偏移量,也不被它所影響。ReadAt方法的調用者可以對同一輸入流執行並行的WriteAt調用。(前提是寫入范圍不重疊)

📚 示例代碼

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("writeAt.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    file.WriteString("Golang中文社區——這里是多余hh!!!")
    n, err := file.WriteAt([]byte("Go語言中文網"), 24)
    if err != nil {
        panic(err)
    }
    fmt.Printf("write %d bytes", n) // write 17 bytes
    file.WriteString("hello world!!!")
}

// test.txt 文件內容:
// Golang中文社區——Go語言中文網!!!hello world!!!

io.ReaderFrom 和 io.WriteTo 接口

io.ReaderFrom 接口

// io.ReaderFrom 接口包裝了基本的 ReadFrom 方法
type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}

從輸入流 r 中讀取數據,寫入底層輸出流,直到 EOF 或發生錯誤。其返回值 n 為讀取的字節數,除io.EOF之外,在讀取過程中遇到的其它錯誤 err 也將被返回。

注意ReadFrom方法不會返回err == io.EOF

✌ 示例代碼

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // test.txt 文件內容:hello world!!!
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 調用 file 的讀操作從文件中讀取文件,並寫入標准輸出
    writer := bufio.NewWriter(os.Stdout)
    n, err := writer.ReadFrom(file)
    if err != nil {
        panic(err)
    }
    fmt.Printf("read %d bytes\n", n)
    // 將緩存中的數據 flush 到控制台
    writer.Flush()
}

// 控制台輸出:
// hello world!!!
// read 15 bytes

io.WriteTo 接口

// io.WriterTo 接口包裝了基本的 WriteTo 方法
type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

從底層輸入流中讀取數據,寫入輸出流 w 中,直到沒有數據可寫或發生錯誤。其返回值 n 為寫入的字節數,在寫入過程中遇到的任何錯誤也將被返回。

✌ 示例代碼

package main

import (
    "bytes"
    "fmt"
    "os"
)

func main() {
    // 調用 r 的讀操作讀取數據,並寫入到標准輸出
    r := bytes.NewReader([]byte("Go語言中文網\n"))
    n, err := r.WriteTo(os.Stdout)
    if err != nil {
        panic(err)
    }
    fmt.Printf("write %d bytes\n", n)
}

// 控制台輸出:
// Go語言中文網
// write 18 bytes

io.Seeker 接口

// io.Seeker 接口用於包裝基本的讀/寫偏移量移動方法
type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}

設定下一次讀/寫的位置。offset 為相對偏移量,而 whence 決定相對位置:0 為相對文件開頭,1 為相對當前位置,2 為相對文件結尾。返回新的偏移量(相對開頭)和可能的錯誤。

移動到一個絕對偏移量為負數的位置會導致錯誤,移動到任何偏移量為正數的位置都是合法的,但其下一次I/O操作的具體行為則要看底層的實現。

// whence 的值,在 io 包中定義了相應的常量,推薦使用這些常量
const (
  SeekStart   = 0 // seek relative to the origin of the file
  SeekCurrent = 1 // seek relative to the current offset
  SeekEnd     = 2 // seek relative to the end
)

✍ 示例代碼

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Go語言中文網")
    fmt.Println(r.Len()) // 17
    n, err := r.Seek(-6, io.SeekEnd)
    if err != nil {
        panic(err)
    }
    fmt.Println(n) // 11
    c, _, _ := r.ReadRune()
    fmt.Printf("%c\n", c) // 文
}

io.Closer 接口

// io.Closer 接口用於包裝基本的關閉方法
type Closer interface {
    Close() error
}

該接口比較簡單,用於關閉數據流。文件 (os.File)、歸檔(壓縮包)、數據庫連接、Socket 等需要手動關閉的資源都實現了io.Closer接口。實際編程中,經常將 Close 方法的調用放在 defer 語句中。

💡 示例代碼

package main

import (
    "os"
)

func main() {
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
}

io.ByteReader、io.ByteWriter 和 io.ByteScanner 接口

// io.ByteReader 接口是基本的 ReadByte 方法的包裝
type ByteReader interface {
    ReadByte() (c byte, err error)
}

// io.RuneReader 接口是基本的 ReadRune 方法的包裝
type ByteWriter interface {
    WriteByte(c byte) error
}

// io.ByteScanner 接口在基本的 ReadByte 方法之外還添加了 UnreadByte 方法
type ByteScanner interface {
    ByteReader
    UnreadByte() error
}

io.ByteReaderio.ByteWriter接口用於讀/寫一個字節。在標准庫中,有如下類型實現了io.ByteReaderio.ByteWriter接口:

  • bufio.Readerbufio.Writer分別實現了io.ByteReaderio.ByteWriter接口
  • bytes.Buffer同時實現了io.ByteReaderio.ByteWriter接口
  • bytes.Reader實現了io.ByteReader接口
  • strings.Reader實現了io.ByteReader接口

io.ByteScanner接口內嵌了io.ByteReader接口之外還添加了UnreadByte方法,該方法會還原最近一次讀取操作讀出的最后一個字節,相當於讓讀偏移量向前移動一個字節。注意,連續兩次UnreadByte方法調用而中間沒有任何讀取操作,會返回錯誤。

☕️ 示例代碼

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var ch byte
    fmt.Scanf("%c\n", &ch)

    var b bytes.Buffer

    // 寫入一個字節
    err := b.WriteByte(ch)
    if err != nil {
        panic(err)
    }
    fmt.Println("寫入一個字節成功!准備讀取該字節……")

    // 讀取一個字節
    newCh, _ := b.ReadByte()
    fmt.Printf("讀取的字節:%c\n", newCh)

    // 還原最近一次讀取操作讀出的最后一個字節
    b.UnreadByte()
    newCh2, _ := b.ReadByte()
    fmt.Printf("再次讀取的字節:%c\n", newCh2)
}

// 控制台輸入:
// A
// 控制台輸出:
// 寫入一個字節成功!准備讀取該字節……
// 讀取的字節:A
// 再次讀取的字節:A

io.RuneReader 和 io.RuneScanner 接口

// io.RuneReader 是基本的 ReadRune 方法的包裝
type RuneReader interface {
    ReadRune() (r rune, size int, err error)
}

// io.RuneScanner 接口在基本的 ReadRune 方法之外還添加了 UnreadRune 方法
type RuneScanner interface {
    RuneReader
    UnreadRune() error
}

io.ByteReader/io.ByteScanner接口類似,不過io.RuneReader/io.RuneScanner接口操作的是一個字符。ReadRune方法讀取單個UTF-8編碼的字符,返回其 rune 和該字符占用的字節數。如果沒有有效的字符,會返回錯誤。

UnreadRune方法還原前一次ReadRune操作讀取的 unicode 碼值,相當於讓讀偏移量前移一個碼值長度。注意,UnreadRune方法調用前必須調用ReadRune方法,UnreadRune方法比UnreadByte嚴格很多。

⭐️ 示例代碼

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var ch rune
    fmt.Scanf("%c\n", &ch)

    var b bytes.Buffer

    // 寫入一個字符,WriteRune 方法是 bytes.Buffer 自定義的方法
    b.WriteRune(ch)
    fmt.Println("寫入一個字符成功!准備讀取該字符……")

    // 讀取一個字符
    newCh, _, _ := b.ReadRune()
    fmt.Printf("讀取的字符:%c\n", newCh)

    // 還原前一次 ReadRune 操作讀取的字符
    b.UnreadRune()
    newCh2, _, _ := b.ReadRune()
    fmt.Printf("再次讀取的字符:%c\n", newCh2)
}

// 控制台輸入:
// 您
// 控制台輸出:
// 寫入一個字符成功!准備讀取該字符……
// 讀取的字符:您
// 再次讀取的字符:您

其它聚合接口

// io.ReadWriter 接口聚合了基本的讀寫操作
type ReadWriter interface {
    Reader
    Writer
}

// io.ReadCloser 接口聚合了基本的讀取和關閉操作 
type ReadCloser interface {
    Reader
    Closer
}

// io.ReadSeeker 接口聚合了基本的讀取和移位操作
type ReadSeeker interface {
    Reader
    Seeker
}

// io.ReadWriteCloser 接口聚合了基本的讀寫和關閉操作
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// io.ReadWriteSeeker 接口聚合了基本的讀寫和移位操作
type ReadWriteSeeker interface {
    Reader
    Writer
    Seeker
}

// io.WriteSeeker 接口聚合了基本的寫入和移位操作
type WriteSeeker interface {
    Writer
    Seeker
}

// io.WriteCloser 接口聚合了基本的寫入和關閉操作
type WriteCloser interface {
    Writer
    Closer
}

io 包常用函數

文件復制 io.Copy/io.CopyN

// 將 src 的數據拷貝到 dst,直到在 src 上到達 EOF 或發生錯誤。返回拷貝的字節數和遇到的第一個錯誤
// 對成功的調用,返回值 err 為 nil 而非 EOF,因為 Copy 定義為從 src 讀取直到 EOF,它不會將讀取到 EOF視為應報告的錯誤
// 如果 src 實現了 WriterTo 接口,會調用 src.WriteTo(dst) 進行拷貝;如果 dst 實現了 ReaderFrom 接口,會調用 dst.ReadFrom(src) 進行拷貝
func Copy(dst Writer, src Reader) (written int64, err error)

// 從 src 拷貝 n 個字節數據到 dst,直到在 src 上到達 EOF 或發生錯誤。返回復制的字節數和遇到的第一個錯誤
// 只有 err 為 nil 時,written 才會等於 n。如果 dst 實現了 ReaderFrom 接口,本函數會調用它實現拷貝
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

✏️ 示例代碼

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    // 打開原始文件
    originalFile, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer originalFile.Close()

    // 創建新的文件作為目標文件
    newFile, err := os.Create("./test_copy.txt")
    if err != nil {
        panic(err)
    }
    defer newFile.Close()

    // 從源中復制字節到目標文件
    writtenNum, err := io.Copy(newFile, originalFile)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Copied %d bytes.", writtenNum)

    // 將文件內容 flush 到硬盤中
    err = newFile.Sync()
    if err != nil {
        panic(err)
    }
}

讀取至少 N 個字節 io.ReadAtLeast

// 從 r 至少讀取 min 字節數據填充進 buf。函數返回寫入的字節數和錯誤(如果沒有讀取足夠的字節)
// 只有沒有讀取到字節時才可能返回 io.EOF;如果讀取了有但不夠的字節時遇到了 EOF,函數會返回ErrUnexpectedEOF
// 如果 min 比 buf 的長度還大,函數會返回 ErrShortBuffer。只有返回值 err 為 nil 時,返回值 n 才會不小於 min
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)

📚 示例代碼

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    // 打開文件,只讀
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    byteSlice := make([]byte, 512)
    minBytes := 8
    // io.ReadAtLeast() 在不能得到最小的字節的時候會返回錯誤,但已讀的文件數據仍保留在 byteSlice 中
    numBytesRead, err := io.ReadAtLeast(file, byteSlice, minBytes)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Number of bytes read: %d\n", numBytesRead)
    fmt.Printf("Data read: %s\n", byteSlice)
}

讀取正好 N 個字節 io.ReadFull

// 從 r 精確地讀取 len(buf) 字節數據填充進 buf。函數返回寫入的字節數和錯誤(如果沒有讀取足夠的字節)
// 只有沒有讀取到字節時才可能返回 io.EOF;如果讀取了有但不夠字節數時遇到了 EOF,函數會返回ErrUnexpectedEOF
// 只有返回值 err 為 nil 時,返回值 n 才會等於 len(buf)
func ReadFull(r Reader, buf []byte) (n int, err error)

✌ 示例代碼

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    // 打開文件,只讀
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // file.Read() 可以讀取一個小文件到大的 byte slice 中,
    // 但是 io.ReadFull() 在文件的字節數小於 byte slice 字節數的時候會返回錯誤
    byteSlice := make([]byte, 2)
    numBytesRead, err := io.ReadFull(file, byteSlice)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Number of bytes read: %d\n", numBytesRead)
    fmt.Printf("Data read: %s\n", byteSlice)
}

字符串寫入 io.WriteString

// 將字符串 s 的內容寫入 w 中,返回寫入的字節數和遇到的任何錯誤。如果 w 已經實現了 WriteString 方法,函數會直接調用該方法
func WriteString(w Writer, s string) (n int, err error)

✍ 示例代碼

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    file, err := os.Create("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 寫入字符串
    n, err := io.WriteString(file, "hello world!!!!")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Write %d btyes\n", n)
}

合並多個輸入流 io.MultiReader

// 返回一個將提供的 Readers 在邏輯上串聯起來的 Reader 接口,它們依次被讀取
// 當所有的輸入流都讀取完畢,Read 才會返回 EOF。如果 readers 中任一個返回了非 nil 非 EOF 的錯誤,Read 方法會返回該錯誤
func MultiReader(readers ...Reader) Reader

✍ 示例代碼

package main

import (
    "bytes"
    "fmt"
    "io"
    "strings"
)

func main() {
    readers := []io.Reader{
        strings.NewReader("from strings reader!!!"),
        bytes.NewBufferString("from bytes buffer!!!"),
    }
    reader := io.MultiReader(readers...)
    data := make([]byte, 0, 128)
    buf := make([]byte, 10)

    for n, err := reader.Read(buf); err != io.EOF; n, err = reader.Read(buf) {
        if err != nil {
            panic(err)
        }
        data = append(data, buf[:n]...)
    }
    fmt.Printf("%s\n", data) // from strings reader!!!from bytes buffer!!!
}

合並多個輸出流 io.MultiWriter

// 創建一個 Writer 接口,會將提供給其的數據同時寫入 Writers 中的所有輸出流
func MultiWriter(writers ...Writer) Writer

💡 示例代碼

package main

import (
    "io"
    "os"
)

func main() {
    file, err := os.Create("./text.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    writers := []io.Writer{
        file,
        os.Stdout,
    }
    writer := io.MultiWriter(writers...)

    // 會同時在 text.txt 文件和控制台中輸出:Go語言中文網
    writer.Write([]byte("Go語言中文網"))
}

讀取並自動寫入 io.TeeReader

// 返回一個將其從 r 讀取的數據寫入 w 的 Reader 接口,所有通過該接口對 r 的讀取都會執行對應的對 w 的寫入
// 沒有內部的緩沖,寫入必須在讀取完成前完成,寫入時遇到的任何錯誤都會作為讀取錯誤返回
func TeeReader(r Reader, w Writer) Reader

☕️ 示例代碼

package main

import (
    "io"
    "os"
)

func main() {
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    r := io.TeeReader(file, os.Stdout)
    // r 讀取內容后,會自動寫入到 os.Stdout,所以控制台會顯示文件中內容
    r.Read(make([]byte, 100))
}

io/ioutil 包常用函數

雖然 io 包提供了不少類型、方法和函數,但有時候使用起來不是那么方便,比如讀取一個文件中的所有內容。為此,Go 語言在io/ioutil包提供了一些常用、方便的I/O操作函數。

接口類型轉換 ioutil.NopCloser

// 用一個無操作的 Close 方法包裝 r,返回一個 io.ReadCloser 接口。Close 方法什么也不做,只是返回 nil
func NopCloser(r io.Reader) io.ReadCloser

⭐️ 示例代碼

package main

import (
    "fmt"
    "io/ioutil"
    "strings"
)

func main() {
    r := strings.NewReader("hello world")
    nc := ioutil.NopCloser(r)
    // Close 方法什么也不做,只是返回 nil
    nc.Close()

    p := make([]byte, 20)
    n, err := nc.Read(p)
    if err != nil {
        panic(err)
    }
    fmt.Printf("read %d bytes:%s", n, p) // read 11 bytes:hello world
}

讀取全部字節 ioutil.ReadAll

// 從 r 讀取數據直到 EOF 或遇到 error,返回讀取的數據和遇到的錯誤
// 成功的調用返回的 err 為 nil 而非 EOF。因為本函數定義為讀取 r 直到 EOF,它不會將讀取返回的 EOF 視為應報告的錯誤
func ReadAll(r io.Reader) ([]byte, error)

✏️ 示例代碼

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    file, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // os.File.Read(), io.ReadFull() 和 io.ReadAtLeast() 在讀取之前都需要一個固定大小的 byte slice
    // 但 ioutil.ReadAll() 會讀取 reader (這個例子中是file) 的每一個字節,然后把字節 slice 返回
    data, err := ioutil.ReadAll(file)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Data as hex: %x\n", data)
    fmt.Printf("Data as string: %s\n", data)
    fmt.Println("Number of bytes read:", len(data))
}

讀取目錄 ioutil.ReadDir

// 讀取 dirname 目錄,並返回排好序的文件和子目錄名
func ReadDir(dirname string) ([]os.FileInfo, error)

📚 示例代碼

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    // 輸出當前目錄下的所有文件(包括子目錄)
    listAll(".", 0)
}

func listAll(path string, curHeight int) {
    fileInfos, err := ioutil.ReadDir(path)
    if err != nil {
        fmt.Println(err)
        return
    }

    for _, info := range fileInfos {
        if info.IsDir() {
            for tmpHeight := curHeight; tmpHeight > 0; tmpHeight-- {
                fmt.Printf("|\t")
            }
            fmt.Println(info.Name(), "\\")
            listAll(path+"/"+info.Name(), curHeight+1)
        } else {
            for tmpHeight := curHeight; tmpHeight > 0; tmpHeight-- {
                fmt.Printf("|\t")
            }
            fmt.Println(info.Name())
        }
    }
}

快讀文件 ioutil.ReadFile

io/ioutil包提供ReadFile函數可以處理打開文件、讀取文件和關閉文件一系列的操作。如果需要簡潔快速地讀取文件到字節切片中,可以使用它。該方法定義如下:

// 從 filename 指定的文件中讀取數據並返回文件的內容
// 成功的調用返回的 err 為 nil 而非 EOF。因為本函數定義為讀取整個文件,它不會將讀取返回的 EOF 視為應報告的錯誤
func ReadFile(filename string) ([]byte, error)

✌ 示例代碼

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    // 讀取文件到字節 slice 中
    data, err := ioutil.ReadFile("./test.txt")
    if err != nil {
        panic(err)
    }

    fmt.Printf("Data read: %s\n", data)
}

快寫文件 ioutil.WriteFile

ReadFile函數對應,io/ioutil包提供WriteFile函數可以處理創建或者打開文件、寫入字節切片和關閉文件一系列的操作。如果需要簡潔快速地寫字節切片到文件中,可以使用它。該方法定義如下:

// 向 filename 指定的文件中寫入數據。如果文件不存在將按給出的權限創建文件,否則在寫入數據之前清空文件
func WriteFile(filename string, data []byte, perm os.FileMode) error

✍ 示例代碼

package main

import (
    "io/ioutil"
)

func main() {
    // 向文件寫入 "hello world!!!"
    err := ioutil.WriteFile("./test.txt", []byte("hello world!!!"), 0666)
    if err != nil {
        panic(err)
    }
}

創建臨時目錄 ioutil.TempDir

// 在 dir 目錄里創建一個新的、使用 prfix 作為前綴的臨時文件夾,並返回文件夾的路徑
// 如果 dir 是空字符串,表明使用系統默認的臨時目錄(參見 os.TempDir 函數)中創建臨時目錄
// 不同程序同時調用該函數會創建不同的臨時目錄,調用本函數的程序有責任在不需要臨時文件夾時摧毀它
func TempDir(dir, prefix string) (name string, err error)

💡 示例代碼

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    // 創建臨時目錄
    name, err := ioutil.TempDir("F:", "temp")
    if err != nil {
        panic(err)
    }
    // 刪除臨時目錄
    defer os.Remove(name)
    fmt.Println(name) // F:\temp2305919538
}

創建臨時文件 ioutil.TempFile

// 在 dir 目錄下創建一個新的、使用 prefix 為前綴的臨時文件,以讀寫模式打開該文件並返回 os.File 指針
// 如果 dir 是空字符串,表明使用系統默認的臨時目錄(參見 os.TempDir 函數)中創建臨時文件
// 不同程序同時調用該函數會創建不同的臨時文件,調用本函數的程序有責任在不需要臨時文件時摧毀它
func TempFile(dir, prefix string) (f *os.File, err error)

💡 示例代碼

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    // 創建並打開臨時文件
    f, err := ioutil.TempFile("F:", "temp")
    if err != nil {
        panic(err)
    }
    // 刪除臨時文件
    defer func() {
        f.Close()
        os.Remove(f.Name())
    }()
    fmt.Println(f.Name()) // F:\temp2379571275
}

參考

  1. io — 基本的 IO 接口
  2. ioutil — 方便的IO操作函數集
  3. Go語言的30個常用文件操作,總有一個你會用到


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM