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只是一部分。