go语言文件读取操作(附源码浅析)

507 阅读4分钟

go语言学习日记—文件的读取(附源码浅析)

前言

​ 为什么大学生放假了还是这么忙。小挑我负责前端,最近在画原型图,和项目的基本搭建。再一个就是字节青训营的项目,还有就是在准备数据结构和算法为了蓝桥杯。md,蓝桥杯国一学院三千,梭哈了兄弟。期末考试的成绩也在慢慢出来,还行吧,这次,通识课少就是爽。

os库操作读取文件

​ os库是go语言自带的一个库,读取文件时我们主要使用的是里面的Open()方法。

func Open

func Open(name string) (file *File, err error)

Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError

​ Open方法传入一个字符串,这个字符串是我们要打开文件的位置。绝对路径和相对路径都可以。函数返回一个*File类型的变量 file和error类型的变量 err。

​ 既然我在后面加了源码那肯定是要继续往下看的

File类型

// File represents an open file descriptor.
type File struct {
	*file // os specific
}

​ File类型是一个结构体,里面存放了一个*file类型的变量。go结构体里的字段如果没有字段名,称为匿名字段,匿名字段的名称就是它的类型。那么file类型又是啥。

// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
	pfd        poll.FD
	name       string
	dirinfo    *dirInfo // nil unless directory being read
	appendMode bool     // whether file is opened for appending
}

​ 可以看到file也是一个结构体体,里面还包含着其它的结构体,这里就不继续翻下去了(出现看不懂的了)。

error类型

type error interface {
	Error() string
}

​ error是一个结构,里面包含了一个 返回值为string类型的函数。

fileObj, err := os.Open("./main.go")
	if err != nil {
		fmt.Println("open file failed", err)
	}

	// 关闭文件
	defer fileObj.Close()

​ 打开文件后 记得通过 defer在函数结束的时候关闭文件。

​ 然后通过我们得到的fileObj中的Read方法读取文件里面的内容。

func (*File) Read

func (f *File) Read(b []byte) (n int, err error)

Read方法从f中读取最多len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值err为io.EOF。

​ Read方法需要我们传入一个 字符数组,也就是我们一次性读取多少个字节的数据。返回此次读取的字节数 n和错误 err。文件终止读取的标志是 返回的字节数为0。

​ 这样其实有一个问题,例如我们读取完最后1个字节是,由于n 不等于 0 ,然后Read会继续读取,然后发现没有字节可以读了,然后返回err,报错了。因此我们需要加一个判断,当返回的字节数小于我们规定时,说明这一次已经是最后一次了。直接退出就可以。

package main

import (
	"fmt"
	"os"
)

func main() {
	fileObje, err := os.Open("./main.go")
	if err != nil {
		fmt.Println("open file failed:", err)
	}
	defer fileObje.Close()
	var temp [128]byte
	// 循环调用Read方法
	for {
		n, err := fileObje.Read(temp[:])
		if err != nil {
			fmt.Println("read file fail:", err)
			return
		}
        fmt.Println(string(temp[:n]))
		// 如果这次字节读取的长度小于我们规定每次读取的长度,即文件已读取完毕
		if n < 128 {
			return
		}
	}
}

友情提示:读取文件之类的操作 必须先使用go build生成exe文件,再运行exe文件

bufio库操作读取文件

​ bufio也是一个操作文件的库,它在os库上做了一层简单的封装。

func main() {
	fileObj, err := os.Open("./main.go")
	if err != nil {
		fmt.Println("open file failed", err)
		return
	}
	defer fileObj.Close()

	reader := bufio.NewReader(fileObj)
	for {
		line, err := reader.ReadString('\n')
		if err == io.EOF {
			return
		}
		if err != nil {
			fmt.Println("read line failed err:", err)
			return
		}
		fmt.Println(line)
	}
}

1.通过 os.Open方法打开文件

2.调用bufio的NewReader方法,传入fileObj。

// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) *Reader {
	return NewReaderSize(rd, defaultBufSize)
}

NewReader返回一个带有默认缓存区的函数NewReaderSize。并且传入参数rd和defaultBufSize。defaultBufSize是一个常量。大小为4096个字节,也就是4M。

image-20220119103158992

// NewReaderSize returns a new Reader whose buffer has at least the specified
// size. If the argument io.Reader is already a Reader with large enough
// size, it returns the underlying Reader.
func NewReaderSize(rd io.Reader, size int) *Reader {
	// Is it already a Reader?
	b, ok := rd.(*Reader)
	if ok && len(b.buf) >= size {
		return b
	}
	if size < minReadBufferSize {
		size = minReadBufferSize
	}
	r := new(Reader)
	r.reset(make([]byte, size), rd)
	return r
}

​ NewReaderSize函数返回一个新的带有具体的默认读取字节数的新的Reader。

// ReadString reads until the first occurrence of delim in the input,
// ReadString函数读取数据知道第一次出现终止符
// returning a string containing the data up to and including the delimiter.
// 返回一个包含数据和终止符的字符串
// If ReadString encounters an error before finding a delimiter,
// 如果ReadString在终止符之前遇到了错误
// it returns the data read before the error and the error itself (often io.EOF).
// ReadString返回遇到错误之前读取的数据和错误
// ReadString returns err != nil if and only if the returned data does not end in
// delim.
// For simple uses, a Scanner may be more convenient.
func (b *Reader) ReadString(delim byte) (string, error) {
	full, frag, n, err := b.collectFragments(delim)
	// Allocate new buffer to hold the full pieces and the fragment.
	// 分配新的缓冲区保存完整的片段和数据
	var buf strings.Builder
	buf.Grow(n)
	// Copy full pieces and fragment in.
	for _, fb := range full {
		buf.Write(fb)
	}
	buf.Write(frag)
	return buf.String(), err
}

ioutil读取文件

​ ioutil也是也该读取文件的包,它的使用方法比前面两个更加简单,其实也就是它帮我们做了更多的事情。

func main() {
	ret, err := ioutil.ReadFile("./main.go")
	if err != nil {
		fmt.Println("read file failed", err)
	}
	fmt.Println(string(ret))
}

ReadFile方法

// ReadFile reads the file named by filename and returns the contents.
// ReadFile 读取名称为filename的文件,并且返回其内容
// A successful call returns err == nil, not err == EOF. Because ReadFile
// reads the whole file, it does not treat an EOF from Read as an error
// to be reported.
// 一次成功的调用返回 err =nil 不是 err =EOF,因为ReadFile读取整个文件,它不会讲来自Read的EOF视为错误
// As of Go 1.16, this function simply calls os.ReadFile.
func ReadFile(filename string) ([]byte, error) {
	return os.ReadFile(filename)
}

​ 我们传入ReadFile的字符串,就是我们所要读取文件的位置。

// ReadFile reads the named file and returns the contents.
// A successful call returns err == nil, not err == EOF.
// Because ReadFile reads the whole file, it does not treat an EOF from Read
// as an error to be reported.
// name:文件的位置,返回字符数组和错误
func ReadFile(name string) ([]byte, error) {
	// 这里因为已经在os库里,所以没有加os的前缀
	f, err := Open(name)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	// 定义size
	var size int
	if info, err := f.Stat(); err == nil {
		size64 := info.Size()
		if int64(int(size64)) == size64 {
			size = int(size64)
		}
	}
	size++ // one byte for final read at EOF

	// If a file claims a small size, read at least 512 bytes.
	// In particular, files in Linux's /proc claim size 0 but
	// then do not work right if read in small pieces,
	// so an initial read of 1 byte would not work correctly.
	// 如果size<512 设置为512
	if size < 512 {
		size = 512
	}
	// make一个字符数组
	data := make([]byte, 0, size)
	for {
		if len(data) >= cap(data) {
			d := append(data[:cap(data)], 0)
			data = d[:len(data)]
		}
		n, err := f.Read(data[len(data):cap(data)])
		data = data[:len(data)+n]
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return data, err
		}
	}
}

​ 前半部分和我们在前面使用os库打开文件一样,后面部分主要是帮助我们经行了一些读取文件的配置,如设定默认读取的字节数目。

文末留言

​ 这是我第一次尝试遍学习函数遍看源码,以前总是记住API就算了,也不追求深究,这次我把每个API的深入进去看了一下,虽然不能完全看懂,但还是知道个大概,知道它在干什么。也是第一次觉得库也不是很难写嘛。对基础库经行封装,优化。使其变得更加好用。虽然里面有些东西我还不是很清楚,在看源码的时候也对自己的基础知识就行了一定程度的查漏补缺。