Go语言中的文件读取技巧全解析

发表时间: 2024-05-24 09:58

1、ioutil读取整个文件(io/ioutil)

ioutil的方式能够读取整个文件, 只需传入文件名, 操作简单。该操作方式需要把文件读入内容, 效率高, 同样占用内存也高

func ReadFile(filename string) ([]byte, error)

ReadFile 从filename指定的文件中读取数据并返回文件的内容。成功的调用返回的err为nil而非EOF。因为本函数定义为读取整个文件, 它不会将读取返回的EOF视为应报告的错误。// ioutil.ReadFile读取整个文件

func main() {content, err := ioutil.ReadFile("./main.go")if err != nil {fmt.Println("read file failed, err:", err)return}fmt.Println(string(content))}
func ReadAll(r io.Reader) ([]byte, error)

ReadAll与ReadFile的区别在于传入的参数类型不同, 该方法需要传入一个io.Reader.ReadAll从r读取数据直到EOF或遇到error, 返回读取的数据和遇到的错误。

成功的调用返回的err为nil而非EOF。因为本函数定义为读取r直到EOF, 它不会将读取返回的EOF视为应报告的错误。

// ioutil.ReadAll读取整个文件func main() {    file,err := os.Open("./main.go")    if err != nil {        fmt.Println(err)        return    }    defer file.Close()    content, err := ioutil.ReadAll(file)    if err != nil {        fmt.Println("read file failed, err:", err)        return    }    fmt.Println(string(content))}

2、File.Read(属于os包)

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

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

func main() {    file, err := os.Open("test.txt") // For read access.    if err != nil {    log.Fatal(err)    }    defer file.Close()    var content []byte    var tmp = make([]byte, 128) // 一次读取多少个字节    for {        n, err := file.Read(tmp)        if err == io.EOF {        break    }    if err != nil {        fmt.Println("read file failed, err:", err)        return    }    content = append(content, tmp[:n]...)    }    fmt.Println(string(content))}

3、bufio读取文件(bufio包)

该操作同样可看作按行读取, 将数据读入缓冲区

func (b *Reader) ReadString(delim byte) (line string, err error)

ReadString读取直到第一次遇到delim字节, 返回一个包含已读取的数据和delim字节的字符串。如果ReadString方法在读取到delim之前遇到了错误, 它会返回在错误之前读取的数据以及该错误(一般是io.EOF)。

当且仅当ReadString方法返回的切片不以delim结尾时, 会返回一个非nil的错误。

// bufio按行读取示例func main() {    file, err := os.Open("./test.txt")    if err != nil {        fmt.Println("open file failed, err:", err)        return    }    defer file.Close()    var content string    reader := bufio.NewReader(file)    for {        line, err := reader.ReadString('\n')        if err != nil {            if err == io.EOF {            		break    				}	            fmt.Println("read file failed, err:", err)            return    	}   		 content += line    }    fmt.Println(content)}

另外一种方式为ReadBytes, 与readString的区别为返回格式为切片类型

func (b *Reader) ReadBytes(delim byte) (line []byte, err error)

ReadBytes读取直到第一次遇到delim字节, 返回一个包含已读取的数据和delim字节的切片。如果ReadBytes方法在读取到delim之前遇到了错误, 它会返回在错误之前读取的数据以及该错误(一般是io.EOF)。

当且仅当ReadBytes方法返回的切片不以delim结尾时, 会返回一个非nil的错误。

// bufio按行读取示例func main() {    file, err := os.Open("./test.txt")    if err != nil {        fmt.Println("open file failed, err:", err)        return    }    defer file.Close()    var content []byte    reader := bufio.NewReader(file)    for {        line, err := reader.ReadBytes('\n')        if err != nil {            if err == io.EOF {            break   		 			}        fmt.Println("read file failed, err:", err)        return   		 }    content = append(content,line...)    }    fmt.Println(string(content))}

func NewScanner(r io.Reader) *Scanner

NewScanner函数用于初始化一个Scanner对象, NewScanner返回一个新的Scanner来扫描r, 默认匹配函数为ScanLines(行匹配函数)

package mainimport (    "bufio"    "fmt"    "os")func main() {    file, err := os.Open("abc.txt")    if err != nil {        panic(err)    }    scanner := bufio.NewScanner(file)    for scanner.Scan() {        fmt.Println(scanner.Text())    }}

结论:

当文件较小(KB 级别)时, ioutil > bufio > os。

当文件大小比较常规(MB 级别)时, 三者差别不大, 但 bufio 又是已经显现出来。

当文件较大(GB 级别)时, bufio > os > ioutil。

注意: ioutil.ReadAll(data) // io.Reader 带缓冲区(512)的读取


汇总: 效率高的读写方式

package mainimport (    "bufio"    "fmt"    "io"    "io/ioutil"    "os")// 小文件读取方式// 第一种 ioutil.ReadFile 读取小文件的方式 效率高的方式func ReadFile(path string) (buf []byte, err error) {    buf, err = ioutil.ReadFile(path)    if err != nil {        return []byte{}, err    }    return buf, nil}// 第二种 ioutil.ReadAll 读取小文件的方式func ReadAll(path string) (buf []byte, err error) {    f, err := os.Open(path)    if err != nil {        return []byte{}, err    }    defer f.Close()    buf, err = ioutil.ReadAll(f)    if err != nil {        return []byte{}, err    }    return buf, nil}// 超大文件读取方式// 第一种方式 Read方式 分片读取 读取超大文件 (读取的是二进制文件, 没有换行符的时候)func ReadBuf(path string) (buf []byte, err error) {    f, err := os.Open(path)    if err != nil {    		return []byte{}, err    }    defer f.Close()    buf = make([]byte, 4096)    reader := bufio.NewReader(f) // 设置缓冲区    // 参照标准包的 ReadFile 函数    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 size < 512 {        size = 512    }    data := make([]byte, size) // 初始化一个切片, 容量大小为文件的大小    for {        n, err := reader.Read(buf[:]) // 这里传递 读取片段        if err != nil { // 遇到任何错误立即返回,并忽略 EOF 错误信息            if err == io.EOF {            break            }        break    }    data = append(data, buf[:n]...)    }    return data, nil}// 第二种方式 逐行读取 ReadLine 方式 读取超大的方式 文件中有明确的换行符/*注意: 不同的操作系统换行符不一样, 测试的结果误差相差很大// windows平台相当于 "\r\n";// unix\linux平台相当于 "\n";// mac平台相当于 "\r";*/func ReadLine(path string) (buf []byte, err error) {    f, err := os.Open(path)    if err != nil {    		return []byte{}, err    }    defer f.Close()    // 参照标准包的 ReadFile 函数    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 size < 512 {    		size = 512    }    data := make([]byte, size) // 根据文件的大小设置切片的容量    r := bufio.NewReader(f) // 设置缓冲区    for {        // 相对于ReadString(或ReadBytes) 函数, ReadLine 读取文件更快, 原因是由于 ReadString 后端调用 ReadBytes, 而 ReadBytes 多次使用 copy 方法造成大量耗时。        // line, err := r.ReadBytes('\n') // 返回值: []byte line, err := r.ReadString('\n') // 返回值: string        line, _, err := r.ReadLine()        if err != nil {            if err == io.EOF { // 读取到文件结尾            break        }        // log.Println("read file failed, err:", err)        break // break 跳出 for 循环, 也可以使用 return, 但是不容易理解        }    data = append(data, line...)    }    return data, nil}func main() {    /*    //小文件读取方式 自带的系统函数直接读取    buf, err := ioutil.ReadFile(path)    if err != nil {    fmt.Println(err)    }    fmt.Println(string(buf))    */    /*    data, _ := ReadBuf("version.txt")    fmt.Println(string(data))    */    buf, err := ReadLine("version.txt")    if err != nil {        fmt.Println(err)    }    fmt.Println(string(buf))}

结论: 小文件读取的方式: 使用自带的 ioutil.ReadAll 方法效率最高, 分析其源代码其本质上是 f.Read 的读取方式, 但是没有使用 bufio 缓冲区;

大文件读取的方式: 使用分块和逐行读取方式, 一般而言采用分块读取的方式, 而逐行读取的方式适用于日志文件(有换行符);

小文件的读取不必使用 bufio 缓冲区;