一文详解golang通过io包进行文件读写
作者:进击的davis 发布时间:2024-05-09 10:07:52
在 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:\\demo1\\src\\demo23\\go-io\\file\\file.txt")
defer f.Close()
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
b, err := io.ReadAll(f)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
fmt.Printf("ioRW read content: \n%s\n", 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\n", 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\n", err)
return
}
fmt.Printf("ioRW write size: %d\n", 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:\\demo1\\src\\demo23\\go-io\\file\\file.txt")
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
fmt.Printf("ioutilRW read content: \n%s\n", b)
// write
err = ioutil.WriteFile("ioutilRW_w.txt", b, 664)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
fmt.Println("ioutilRW write err: ", err)
}
上面的读取与写入操作实际很简单,代码操作没难度,相信你一看就懂。
今天我们通过 io.Reader/io.Writer
接口了解了实现了该接口的都可以作为相关对象,通过 io包
也实现了文件的读写,后面我们会陆续分享基于 os/bufio/bytes/strings
的读写操作,毕竟 IO
范围还是很广,文件IO
只是一部分。
来源:https://juejin.cn/post/7225862001188225085


猜你喜欢
- 背景重装系统,发现之前装在E盘的python可以直接使用,就只是将python的安装目录加入到环境变量中,也一直没有管它,今天跟天软交互的时
- 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档协程:协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文
- 安装官网下载http://ffmpeg.org/选择需要的版本在这个网址下载ffmpeg,https://github.com/BtbN/F
- 故障:数据库报错:“MSSQL Server 2000 附加数据库错误823”,附加数据库失败。故障
- JS怎样知道Flash广告条被网友点击过? 1、Flash广告条不是我做的,它的链接是写在里面的。 2、我想统计这个Flash被网友点击了多
- Python入门 本系列为Python学习相关笔记整理所得,IT人
- 我的电脑本来是有手动CMake+make安装的OpenCV3的,以及系统自带的python2.x,但是现在想用python3+OpenCV3
- 在本教程中,我们将学习使用OpenCV跟踪对象。OpenCV 3.0开始引入跟踪API。我们将学习如何和何时使用OpenCV 4.2中可用的
- 本文实例讲述了python获取从命令行输入数字的方法。分享给大家供大家参考。具体如下:#--------------------------
- 问题描述:两个 go 程轮流打印一个切片。Golang 实现:使用两个 channel,只用来判断package mainimport (
- 部署网站前查看一下系统是否已经安装CGI1、启动iis服务器,打开IIS服务器打开IIS服务器,点击网站,右击“添加网站”2、创建网站点击“
- 除非您正在对服务进行原型设计,否则您可能会关心应用程序的内存使用情况。占用更小的内存,会使基础设施成本降低,扩展变得更容易。尽管 Go 以不
- 查看并打印matplotlib中所有的colormap(cmap)类型代码如下:方法一import matplotlib.pyplot as
- 一、结论语法结构: limit offset, rows结论:rows 相同条件下,offset 值越大,limit 语句性能越差二、测试执
- 目录前言魔法方法__init__方法__new__方法__call__方法__str___方法__del___方法__enter__ &am
- 总是记不住API。昨晚写的时候用到了这些,但是没记住,于是就索性整理一下吧:python中对文件、文件夹(文件操作函数)的操作需要涉及到os
- 最近在学习python过程中,对print()打印输出函数进行了进一步学习。python 2.6中,print输出内容需要使用引号。pyth
- 注:此功能依赖GD2图形库最近要用php生成缩略图,在网上找了一下,发现了这篇文章:PHP生成图片缩略图试用了一下后,发现有这样几个问题:1
- 安装Pytorch在pytorch官网上选择相应选项,我的是OS X, pip, python2.7, none CUDA。(之所以用pyt
- 一般网站发布信息会在具体实现范围内发布,我们在进行网络爬虫的过程中,可以通过设置定时爬虫,定时的爬取网站的内容。使用python爬虫框架Sc