谈谈对Golang IO读写的困惑

 更新时间:2020-09-24 15:01:25   作者:佚名   我要评论(0)

前言


Golang的IO读写提供了很多种方式,目前本人知道的有io库、os库、ioutil库、bufio库、bytes/strings库等。
虽然库多是一件好事,意味着选择性多,但让我困惑

前言

Golang的IO读写提供了很多种方式,目前本人知道的有io库、os库、ioutil库、bufio库、bytes/strings库等。

虽然库多是一件好事,意味着选择性多,但让我困惑的一点是:什么场景下该用哪个库? 为什么?

在给出结论前,我先想给出Golang内置IO库的项目结构,主要方便理解和引用:

# 只列举了核心的目录及文件
src:
 - bufio
  - bufio.go
 - bytes
  - buffer.go
  - reader.go
 - io
  - ioutil
   - ioutil.go
  - io.go
 - os
  - file.go
 - strings 
  - reader.go

1.io库属于底层接口定义库,其作用是是定义一些基本接口和一些基本常量,并对这些接口的作用给出说明,常见的接口有Reader、Writer等。一般用这个库只是为了调用它的一些常量,比如io.EOF。

2.ioutil库包含在io目录下,它的主要作用是作为一个工具包,里面有一些比较实用的函数,比如 ReadAll(从某个源读取数据)、ReadFile(读取文件内容)、WriteFile(将数据写入文件)、ReadDir(获取目录)

3.os库主要是跟操作系统打交道,所以文件操作基本都会跟os库挂钩,比如创建文件、打开一个文件等。这个库往往会和ioutil库、bufio库等配合使用

4.bufio库可以理解为在io库上再封装一层,加上了缓存功能。它可能会和ioutil库和bytes.Buffer搞混。
4.1 bufio VS ioutil库:两者都提供了对文件的读写功能,唯一的不同就是bufio多了一层缓存的功能,这个优势主要体现读取大文件的时候(ioutil.ReadFile是一次性将内容加载到内存,如果内容过大,很容易爆内存)

4.2 bufio VS bytes.Buffer:两者都提供一层缓存功能,它们的不同主要在于 bufio 针对的是文件到内存的缓存,而 bytes.Buffer 的针对的是内存到内存的缓存(个人感觉有点像channel,你也可以发现 bytes.Buffer 并没有提供接口将数据写到文件)。

5.bytes和strings库:这两个库有点迷,首先它们都实现了Reader接口,所以它们的不同主要在于针对的对象不同,bytes针对的是字节,strings针对的是字符串(它们的方法实现原理很相似)。另一个区别就是 bytes还带有Buffer的功能,但是 strings没提供。

注:关于Reader和Writer接口,可以简单理解为读取源和写入源,即只要实现Reader里面的Read方法,这个东西就可以作为一个读取源,里面可以包含数据并被我们读取;Writer亦是如此。

以上就是个人的一些结论,下面会针对以上结论做进一步说明,如果有错误的地方麻烦请留言指正,比心❤️!

窥探 io 库

io库比较常用的接口有三个,分别是Reader,Writer和Close。

// Read方法会接收一个字节数组p,并将读取到的数据存进该数组,最后返回读取的字节数n。
// 注意n不一定等于读取的数据长度,比如字节数组p的容量太小,n会等于数组的长度
type Reader interface {
  Read(p []byte) (n int, err error)
}

// Write 方法同样接收一个字节数组p,并将接收的数据保存至文件或者标准输出等,返回的n表示写入的数据长度。
// 当n不等于len(p)时,返回一个错误。
type Writer interface {
  Write(p []byte) (n int, err error)
}

// 关闭操作
type Closer interface {
  Close() error
}

关于 Read 方法的具体实现,可以在strings库中看到:

// 定义一个Reader接口体
type Reader struct {
  s    string
  i    int64 // current reading index
  prevRune int  // index of previous rune; or < 0
}

// 通过NewReader方法得到 reader 对象,这里有个关键的地方是传入的字符串被赋值到 s 变量中
func NewReader(s string) *Reader { 
 return &Reader{s, 0, -1} 
}

// Read方法: 核心是 copy 方法,参数b虽然是切片,但是copy方法会影响到它的底层数组
func (r *Reader) Read(b []byte) (n int, err error) {
  if r.i >= int64(len(r.s)) {
    return 0, io.EOF
  }
 r.prevRune = -1
 // 核心方法
  n = copy(b, r.s[r.i:])
  r.i += int64(n)
  return
}

窥探 ioutil 库

上面提到,ioutil 库就是一个工具包,里面主要是比较实用的函数,比如ReadFile、WriteFile等,唯一需要注意的是它们都是一次性读取和一次性写入,所以当读取的时候注意文件不能过大。

从文件读取数据:

func readByFile() {
  data, err := ioutil.ReadFile( "./lab8_io/file/test.txt")
  if err != nil {
    log.Fatal("err:", err)
    return
  }
  fmt.Println("data", string(data)) // hello world!
}

把数据写入到文件:

func writeFile() {
  err := ioutil.WriteFile("./lab8_io/file/write_test.txt", []byte("hello world!"), 0644)
  if err != nil {
    panic(err)
    return
  }
}

遍历目录:遍历目录有一个需要注意的是它的排序并不是自然排序方式。

窥探bufio库

bufio 库在上面也提到过,它主要是在io库上加了一层缓存的功能,以下是bufio读取大文件的例子:

func readBigFile(filePath string) error {
  f, err := os.Open(filePath)
  defer f.Close()

  if err != nil {
    log.Fatal(err)
    return err
  }

  buf := bufio.NewReader(f)
  count := 0
  for {
    count += 1
    line, err := buf.ReadString('\n')
    line = strings.TrimSpace(line)
    if err != nil {
      return err
    }
  fmt.Println("line", line)
  // 这里是避免全部打印
    if count > 100 {
      break
    }
  }
  return nil
}

注:

1.bufio 的ReadLine/ReadBytes/ReadString/ReadSlice: ReadString和ReadBytes等同,ReadBytes和ReadLine都调用了ReadSlice

窥探bytes/strings库

前面提过,就单纯实现Reader接口,bytes和strings底层函数的实现方式是差不多的,可以查看其源码得证:

// bytes/reader.go
// Read implements the io.Reader interface.
func (r *Reader) Read(b []byte) (n int, err error) {
  if r.i >= int64(len(r.s)) {
    return 0, io.EOF
  }
  r.prevRune = -1
  n = copy(b, r.s[r.i:])
  r.i += int64(n)
  return
}

// strings/reader.go
func (r *Reader) Read(b []byte) (n int, err error) {
  if r.i >= int64(len(r.s)) {
    return 0, io.EOF
  }
  r.prevRune = -1
  n = copy(b, r.s[r.i:])
  r.i += int64(n)
  return
}

参考/推荐

详解golang中bufio包的实现原理
Golang 超大文件读取的两个方案
https://gist.github.com/suntong/032173e96247c0411140

到此这篇关于谈谈对Golang IO读写的困惑的文章就介绍到这了,更多相关Golang IO读写内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
  • Golang实现对map的并发读写的方法示例
  • Golang通道的无阻塞读写的方法示例
  • 详解golang RWMutex读写互斥锁源码分析
  • Golang读写Excel的方法教程
  • golang简单读写文件示例

相关文章

  • 谈谈对Golang IO读写的困惑

    谈谈对Golang IO读写的困惑

    前言 Golang的IO读写提供了很多种方式,目前本人知道的有io库、os库、ioutil库、bufio库、bytes/strings库等。 虽然库多是一件好事,意味着选择性多,但让我困惑
    2020-09-24
  • Go 实现热重启的详细介绍

    Go 实现热重启的详细介绍

    最近在优化公司框架 trpc 时发现了一个热重启相关的问题,优化之余也总结沉淀下,对 go 如何实现热重启这方面的内容做一个简单的梳理。 1.什么是热重启? 热重启(H
    2020-09-24
  • 详解Go 结构体格式化输出

    详解Go 结构体格式化输出

    在软件系统中定位问题时日志不可或缺,但是当一个系统功能繁多,需要打印的日志也多如牛毛,此时为了提高我们浏览日志的效率,便于阅读的输出格式必不可少。 打印结
    2020-09-24
  • Linux shell实现压缩多个文件代码实例

    Linux shell实现压缩多个文件代码实例

    Linux环境下写一个脚本 从键盘让用户输入几个文件,脚本能够将此几个文件归档压缩成一个文件: 1.首先介绍一下case语句格式 2.脚本如下: DEST读取的是压缩后文件的
    2020-09-24
  • 详解Go 并发

    详解Go 并发

    golang 天生语言层面支持并发, 非常棒的语言, 有时我们业务开发时, 遇到复杂场景, 需要用于并发, 将多个请求使用协程组完成并发, 当遇到嵌套循环,还存在上下文关系需
    2020-09-24
  • Bash技巧:把变量赋值为换行符(判断文件是否以换行符结尾)

    Bash技巧:把变量赋值为换行符(判断文件是否以换行符结尾)

    变量赋值为换行符 在 bash 中,如果要把变量赋值为换行符,写为 '\n' 没有效果,需要写为 $'\n'。具体举例如下: $ newline='\n' $ echo $newline \n $ newline=$
    2020-09-24
  • MacOS下本地golang环境搭建详细教程

    MacOS下本地golang环境搭建详细教程

    安装golang 使用homebrew安装golang。homebrew是MacOS 平台下的软件包管理工具,拥有安装、卸载、更新、查看、搜索等功能。开发者不需要关心依赖和文件路径。如果系
    2020-09-24
  • Linux shell传递参数实现原理及代码实例

    Linux shell传递参数实现原理及代码实例

    Shell 传递参数 我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数
    2020-09-24
  • 浅析Go 字符串指纹

    浅析Go 字符串指纹

    写项目时,有时我们需要缓存, 缓存就会需要唯一的key. 常规是对字符串求md5指纹. 在golang里我们也可以使用, 目前可以计算一个字符串的crc32, md5, sha1的指纹. md5
    2020-09-24
  • Shell脚本调用另一个脚本的三种方法

    Shell脚本调用另一个脚本的三种方法

    假如有脚本 first.sh: #!/bin/bash #------------------ #@file:first.sh #------------------ echo 'your are in first file' 如何在其它Shell脚本中调用first
    2020-09-24

最新评论