我的编程空间,编程开发者的网络收藏夹
学习永远不晚

一文详解golang通过io包进行文件读写

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

一文详解golang通过io包进行文件读写

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

// go 1.19/class="lazy" data-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, class="lazy" data-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, class="lazy" data-src Reader) (written int64, err error) {
	return copyBuffer(dst, class="lazy" data-src, nil) // 实际通过调用此函数实现
}

func CopyBuffer(dst Writer, class="lazy" data-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, class="lazy" data-src, buf) // 继续往下调用
}


func copyBuffer(dst Writer, class="lazy" data-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 := class="lazy" data-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(class="lazy" data-src)
	}
	if buf == nil {
		size := 32 * 1024 
		if l, ok := class="lazy" data-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 := class="lazy" data-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\\class="lazy" data-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\\class="lazy" data-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只是一部分。

到此这篇关于一文详解golang通过io包进行文件读写的文章就介绍到这了,更多相关golang通过io包文件读写内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

一文详解golang通过io包进行文件读写

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

一文详解golang通过io包进行文件读写

这篇文章主要介绍了golang通过io包进行文件读写文中有详细的代码示例。对学习或工资有很好的帮助,需要的小伙伴可以参考阅读一下
2023-05-17

java进行文件读写操作详解

在Java中,文件读写操作可以使用Java的输入/输出流(IO)类来实现。Java提供了多种IO类来进行文件读写操作,包括字节流和字符流。字节流类是以字节为单位进行读写的,适用于处理二进制文件,如图片、音频和视频等。常用的字节流类有:1.
2023-09-14

Android无需读写权限通过临时授权读写用户文件详解

这篇文章主要为大家介绍了Android无需读写权限通过临时授权读写用户文件详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-19

C#中如何使用文件IO和流操作进行数据读写及解决方法

C#中如何使用文件IO和流操作进行数据读写及解决方法在开发过程中,我们常常需要进行文件的读写操作。C#提供了丰富的文件IO和流操作,使得数据的读写变得更加灵活和高效。本文将探讨C#中如何使用文件IO和流操作进行数据读写,并提供具体的代码示例
2023-10-22

Android编程之在SD卡上进行文件读写操作实例详解

本文实例讲述了Android编程之在SD卡上进行文件读写操作的方法。分享给大家供大家参考,具体如下: 很多知识只有真正理解掌握之后才能运用自如,举一反三。对Java中的文件操作和android系统SD卡里面的文件操作,你觉得有区别吗,显然没
2022-06-06

node.js基于fs模块对系统文件及目录进行读写操作的方法详解

本文实例讲述了node.js基于fs模块对系统文件及目录进行读写操作的方法。分享给大家供大家参考,具体如下: 如果要用这个模块,首先需要引入,fs已经属于node.js自带的模块,所以直接引入即可var fs = require('fs')
2022-06-04

如何在 golang 程序中运行二进制文件并通过发送一些输入并等待输出来保持与其交互?

你在学习Golang相关的知识吗?本文《如何在 golang 程序中运行二进制文件并通过发送一些输入并等待输出来保持与其交互?》,主要介绍的内容就涉及到,如果你想提升自己的开发能力,就不要错过这篇文章,大家要知道编程理论基础和实战操作都是不
如何在 golang 程序中运行二进制文件并通过发送一些输入并等待输出来保持与其交互?
2024-04-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录