io包和操作
Some checks failed
Release / deploy (push) Failing after 1m40s

This commit is contained in:
2024-03-23 18:09:44 +08:00
parent 71f300dbca
commit ac55a0c6a2
7 changed files with 330 additions and 242 deletions

View File

@@ -0,0 +1,148 @@
# Golang IO相关操作
- io包提供了基础的io操作几乎所有的io操作都是基于io.Reader和io.Writer接口的。
- ioutil 包提供了一些方便的io操作函数但是在go 1.16中已经被废弃放进了io包。
- bufio实现了缓冲io可以提高io效率。
- bytes 包中提供了Buffer类型可以用来做io操作。
## 核心接口
### Reader/Writer/Seeker
Reader和Writer是io包中最重要的接口它们定义了io操作的基本行为。像一些文件操作网络操作等等都是基于这两个接口的。
Seeker接口定义了Seek方法可以在Reader/Writer中定位到指定的位置文件操作中常用。
## 常用函数
下面我们来看一些常用的io操作函数更多的函数可以查看官方文档我只是列出一些我常用到的。
- io.ReadeAll 读取所有数据返回一个bytes
- io.MultiWriter/Reader 可以将多个Reader/Writer合并成一个
- io.LimitReader 限制Reader的读取长度读取n个长度后返回io.EOF
- io.TeeReader 读取数据的同时写入到另一个Writer中
- io.Pipe 创建同步内存管道
- io.Copy/CopyN 复制Reader到Writer
- io.NopCloser 给一个io.Reader加上Close方法
- bytes.NewBuffer 创建一个Buffer
- bufio.NewReader/Writer 创建一个缓冲Reader/Writer
## 常见小坑
> 我暂时能想到的坑记录下来,欢迎补充
### io.EOF
io.EOF是io包中定义的一个错误表示读取到文件末尾。在读取文件时当读取到文件末尾时会返回io.EOF错误。
请注意当读取到文件末尾时返回的数据可能不是nil而是文件的最后一部分数据需要注意处理。
### 大文件的操作
在处理大文件时需要注意内存的使用尽量使用io.Reader和io.Writer来处理文件避免一次性读取整个文件到内存中。
### bufio.Writer.Flush
在使用bufio.Writer时需要注意Flush方法因为bufio.Writer是带缓冲的数据并不是实时写入到Writer中的需要调用Flush方法来刷新缓冲区。
### io.Pipe
io.Pipe是一个同步的内存管道请注意在使用时Reader与Writer必须在不同的goroutine中且需要注意Reader与Writer的速度需要一致否则会造成死锁或线程阻塞。
并且需要使用Close方法来关闭Reader或Writer否则会造成内存泄漏。
## 高级用例
下面介绍一些高级用例你也可以从中学习到一些高级的io操作技巧。
### 计算上传文件的hash
下面的例子是计算上传文件的md5和sha1的hash值我们使用io.TeeReader将文件内容同时写入到md5和sha1的hash计算器中。
像我们在http上传文件时可以在上传的同时计算文件的hash值这样可以保证文件的完整性。同时也可以打开文件再将文件的Writer放到io.Copy中这样可以同时写入文件和计算hash值。
```go
func TestIOHash(t *testing.T) {
file := bytes.NewBuffer([]byte("hello world"))
md5 := md5.New()
sha1 := sha1.New()
r := io.TeeReader(file, io.MultiWriter(md5, sha1))
_, _ = io.Copy(io.Discard, r)
t.Logf("md5: %x\n", md5.Sum(nil))
t.Logf("sha1: %x\n", sha1.Sum(nil))
}
```
### 读取文件的部分内容
下面的例子是读取文件的部分内容我们使用io.LimitReader限制读取的长度读取n个长度后返回io.EOF这在解析文件头部信息时非常有用。
```go
func TestLimitReader(t *testing.T) {
file := bytes.NewBuffer([]byte("hello world"))
lr := io.LimitReader(file, 5)
data, _ := io.ReadAll(lr)
t.Logf("%s\n", data)
}
```
### 大文件按行读取
下面的例子是读取大文件的每一行我们使用bufio.Reader来读取文件的每一行这样可以避免一次性读取整个文件到内存中。
在读一些io比较慢的文件时网络文件也可以使用这种方式来读取文件。写文件也是类似的可以使用bufio.Writer来写文件提高磁盘io效率避免频繁的io操作。
```go
func TestBufIo(t *testing.T) {
file := bytes.NewBufferString("hello world\ngolang")
buf := bufio.NewReader(file)
for {
line, err := buf.ReadString('\n')
if err == io.EOF {
t.Logf("%s", line)
break
}
t.Logf("%s", line)
}
}
```
### 使用Pipe将上传文件解密写入文件
有时候上传的文件是一个加密的我们需要在上传的同时解密文件然后同时写入到磁盘中这时候可以使用io.Pipe来实现。
```go
// 凯撒加密
type decode struct {
r io.Reader
}
func (d *decode) Read(p []byte) (int, error) {
// 读取数据
n, err := d.r.Read(p)
if err != nil {
if err != io.EOF {
return n, err
}
}
// 解密
for i := 0; i < n; i++ {
p[i]++
}
return n, err
}
func TestDecodePipe(t *testing.T) {
body := bytes.NewBuffer([]byte("gdkkn\u001Fvnqkc"))
r, w := io.Pipe()
defer r.Close()
go func() {
defer w.Close()
// 读取加密数据解密
_, _ = io.Copy(w, &decode{r: body})
}()
// 读取解密数据,保存文件
data, _ := io.ReadAll(r)
t.Logf("%s\n", data)
}
```