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。



// 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的深入进去看了一下,虽然不能完全看懂,但还是知道个大概,知道它在干什么。也是第一次觉得库也不是很难写嘛。对基础库经行封装,优化。使其变得更加好用。虽然里面有些东西我还不是很清楚,在看源码的时候也对自己的基础知识就行了一定程度的查漏补缺。