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