«

golang怎么通过io包进行文件读写

时间:2024-5-20 08:53     作者:韩俊     分类: Go语言


本文小编为大家详细介绍“golang怎么通过io包进行文件读写”,内容详细,步骤清晰,细节处理妥当,希望这篇“golang怎么通过io包进行文件读写”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

在 

golang
 语言中,实现io的读与写,只要实现了如下的读写接口即可:

// go 1.19/src/io/io.go

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

golang
语言自身提供了很多实现这两个接口的结构体,比较典型的有:
io、os、bytes.buffer、strings、bufio
等,这些解决了文件的读写,字节、字符串的读写,或者是带缓冲的读写等。

今天我们先来看看

io包
中提供的这些接口,简单了解下相关原理。

1.io包的读写函数

典型的相关函数声明如下:

// 读操作
func ReadAll(r Reader) ([]byte, error) {...}

// 写操作
func Copy(dst Writer, src Reader) (written int64, err error) {...}

func WriteString(w Writer, s string) (n int, err error) {...}

在上面的 

ReadAll()
 函数中,接收一个 
Reader
 类型的参数,比如 
os.File
 类型,又或者是其他的实现了 
io.Reader
 接口的结构体类型,输出读取到的内容,以字节数组形式输出,外附一个 
error
 错误,我们进一步看看其内部实现:

func ReadAll(r Reader) ([]byte, error) {
        // 新建字节数组,cap=512
    b := make([]byte, 0, 512)
        // 开启循环读取内容
    for {
                // 
        if len(b) == cap(b) {
            // Add more capacity (let append pick how much).
            b = append(b, 0)[:len(b)]
        }
                // 读取 reader中的内容,填充到对应部分
        n, err := r.Read(b[len(b):cap(b)])
        b = b[:len(b)+n]
                // 如果读到了结尾,可以返回数据
        if err != nil {
            if err == EOF {
                err = nil
            }
            return b, err
        }
    }
}

从上面源码可以知道,

io.ReadAll()
函数,通过一次读取
Reader
中的所有内容,如果是小文件无所谓,占用内存有限,但如果是好几个G的文件呢,是不是,本来服务器内存有限,以下占用几G内存,这样读取不太合理,当然,
golang
也为我们提供了带缓冲的读取,这是后话,后面再讨论。

接下来看看写入操作的源码实现:

// io.Copy()
func Copy(dst Writer, src Reader) (written int64, err error) {
    return copyBuffer(dst, src, nil) // 实际通过调用此函数实现
}

func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
    if buf != nil && len(buf) == 0 { // 当buf非空或者长度0,panic
        panic("empty buffer in CopyBuffer")
    }
    return copyBuffer(dst, src, buf) // 继续往下调用
}

func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
    // If the reader has a WriteTo method, use it to do the copy.
    // Avoids an allocation and a copy.
    if wt, ok := src.(WriterTo); ok { // 源实现了writeTo就可以直接调用结束
        return wt.WriteTo(dst)
    }
    // Similarly, if the writer has a ReadFrom method, use it to do the copy.
    if rt, ok := dst.(ReaderFrom); ok { // 目标实现了readFrom,也可以直接调用结束
        return rt.ReadFrom(src)
    }
    if buf == nil {
        size := 32 * 1024 
        if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
            if l.N < 1 {
                size = 1
            } else {
                size = int(l.N)
            }
        }
        buf = make([]byte, size) // buf空时,创建buf
    }
        // 开启循环内容读取
    for {
        nr, er := src.Read(buf) // 从源中读取内容到buf中
        if nr > 0 {
            nw, ew := dst.Write(buf[0:nr]) // 目标w从buf中写入内容
            if nw < 0 || nr < nw {
                nw = 0
                if ew == nil {
                    ew = errInvalidWrite
                }
            }
            written += int64(nw) // 更新写入长度
            if ew != nil { // 写入返回err,退出循环
                err = ew
                break
            }
            if nr != nw { // 读取与写入长度不等,退出
                err = ErrShortWrite
                break
            }
        }
        if er != nil { // 读err非空,退出
            if er != EOF {
                err = er
            }
            break
        }
    }
    return written, err // 返回写入长度及err
}

通过源码走读,我们可以看到,在函数中传入

Writer
作为目标写入对象,传入
Reader
作为源读取对象,如果
Reader
对象实现
WriteTo
方法,我们可以直接调用此完成
copy
, 或者是
Writer
对象实现了
ReadFrom
方法,我们可以直接调用此完成
copy
,都没有实现,就只有通过
buf
作为中转,通过循环,先从源读取内容,再写入目标对象中,最后返回整个内容的长度,当然其中也有相关错误处理,这里不做讨论。

接下来

io包
中提供的另外的写入操作实现:

// io.WriteString()
func WriteString(w Writer, s string) (n int, err error) {
    if sw, ok := w.(StringWriter); ok { // 如w实现了 StringWriter,则调用相关方法实现写入
        return sw.WriteString(s)
    }
    return w.Write([]byte(s)) // 作为兜底,w对象是肯定实现了 Write() 方法,所以调用此方法实现写入
}

从上面代码可以看到,该实现逻辑简单,具体见注释。

io包实现读写操作
 准备:

    file.txt,作为读取源,然后再写入其他文件中 写操作,我们实现了2种,一种 

    io.Copy()
    ,一种 
    io.WriteString()
    ,具体使用见下面代码:

func ioRW()  {
    // read
    f, err := os.Open("D:demo1srcdemo23go-iofilefile.txt")
    defer f.Close()
    if err != nil {
        fmt.Printf("err: %s
", err)
        return
    }
    b, err := io.ReadAll(f)
    if err != nil {
        fmt.Printf("err: %s
", err)
        return
    }
    fmt.Printf("ioRW read content: 
%s
", b)

    // write
    // 1.io.Copy()
    fw, err := os.Create("io_w.txt")
    defer fw.Close()
    n, err := io.Copy(fw, strings.NewReader(string(b)))
    if err != nil {
        fmt.Printf("err: %s
", err)
        return
    }
    // 2.io.WriteString()
    fw_1, _ := os.Create("io_w_1.txt")
    defer fw_1.Close()
    n, err := io.WriteString(fw_1, string(b))
    if err != nil {
        fmt.Printf("err: %s
", err)
        return
    }

    fmt.Printf("ioRW write size: %d
", n)
}

2.io.ioutil包实现读写操作

所谓

util
,肯定是作为工具使用,怎么方便怎么来,我们甚至通过文件名就可以完成操作,具体实现细节我们不关心,总之它实现了这些功能。

注意
由于我使用的
go 1.19
,在源码中,明确声明在
go 1.16
后,逐渐弃用了,所以使用高版本的
go
时要注意这个问题,这里为了增加源码的了解熟悉,也拿出来作为分享。

读操作

// Deprecated: As of Go 1.16, this function simply calls io.ReadAll.
func ReadAll(r io.Reader) ([]byte, error) {
    return io.ReadAll(r)
}

// Deprecated: As of Go 1.16, this function simply calls os.ReadFile.
func ReadFile(filename string) ([]byte, error) {
    return os.ReadFile(filename)
}

在 

ReadAll
 中,实际也是调用上面的 
io.ReadAll()
,这里不赘述,在 
ReadFile
 中,我们传入个文件名,就可以读取到整个字节内容,实际它也是调用 
os.ReadFile()
 实现,后面再讨论 
os包

写操作

// Deprecated: As of Go 1.16, this function simply calls os.WriteFile.
func WriteFile(filename string, data []byte, perm fs.FileMode) error {
    return os.WriteFile(filename, data, perm)
}

可以看到,这里也是调用了 

os.WriteFile()
 实现文件的写入,只需要我们传入待写入的文件名,写入的字节数组,以及写入文件的权限,是不是很简单。

代码实用

func ioutilRW()  {
    // read
    b, err := ioutil.ReadFile("D:demo1srcdemo23go-iofilefile.txt")
    if err != nil {
        fmt.Printf("err: %s
", err)
        return
    }

    fmt.Printf("ioutilRW read content: 
%s
", b)

    // write
    err = ioutil.WriteFile("ioutilRW_w.txt", b, 664)
    if err != nil {
        fmt.Printf("err: %s
", err)
        return
    }
    fmt.Println("ioutilRW write err: ", err)
}

标签: golang

热门推荐