首页>>后端>>Golang->golangwriter

golangwriter

时间:2023-12-02 本站 点击:0

Golang:I/O 操作,千万不要小瞧这些知识点

I/O 操作也叫输入输出操作。其中 I 是指 Input,O 是指 Output,用于读或者写数据的,有些语言中也叫流操作,是指数据通信的通道。

Golang 标准库对 IO 的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。

io 包中提供 I/O 原始操作的一系列接口。它主要包装了一些已有的实现,如 os 包中的那些,并将这些抽象成为实用性的功能和一些其他相关的接口。

在 io 包中最重要的是两个接口:Reader 和 Writer 接口,首先来介绍这读的操作。

Reader 接口的定义,Read() 方法用于读取数据。

Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 = n = len(p))以及任何遇到的错误。即使 Read 返回的 n len(p),它也会在调用过程

中使用 p 的全部作为暂存空间。若一些数据可用但不到 len(p) 个字节,Read 会照例返回可用的东西,而不是等待更多。

当 Read 在成功读取 n 0 个字节后遇到一个错误或 EOF 情况,它就会返回读取的字节数,这种一般情况的一个例子就是 Reader 在输入流结束时会返回一个非零的字节数,可能的返回不是 err == EOF 就是 err == nil。无论如何,下一个 Read 都应当返回 0、EOF。

调用者应当总在考虑到错误 err 前处理 n 0 的字节。这样做可以在读取一些字节,以及允许的 EOF 行为后正确地处理 I/O 错误。

Read 的实现会阻止返回零字节的计数和一个 nil 错误,调用者应将这种情况视作空操作。

ReaderFrom接口的定义,封装了基本的 ReadFrom 方法。

ReadFrom 从 r 中读取数据到对象的数据流中,直到 r 返回 EOF 或 r 出现读取错误为止,返回值 n 是读取的字节数,返回值 err 就是 r 的返回值 err。

定义ReaderAt接口,ReaderAt 接口封装了基本的 ReadAt 方法

ReadAt 从对象数据流的 off 处读出数据到 p 中,忽略数据的读写指针,从数据的起始位置偏移 off 处开始读取,如果对象的数据流只有部分可用,不足以填满 p,则 ReadAt 将等待所有数据可用之后,继续向 p 中写入,直到将 p 填满后再返回。

在这点上 ReadAt 要比 Read 更严格,返回读取的字节数 n 和读取时遇到的错误,如果 n len(p),则需要返回一个 err 值来说明,为什么没有将 p 填满(比如 EOF),如果 n = len(p),而且对象的数据没有全部读完,则 err 将返回 nil,如果 n = len(p),而且对象的数据刚好全部读完,则 err 将返回 EOF 或者 nil(不确定)

file 类是在 os 包中的,封装了底层的文件描述符和相关信息,同时封装了 Read 和 Write 的实现。

读取文件中的数据:

Writer 接口的定义,Write() 方法用于写出数据。

Write 将 len(p) 个字节从 p 中写入到基本数据流中。它返回从 p 中被写入的字节数 n(0 = n = len(p))以及任何遇到的引起写入提前停止的错误。若 Write 返回的 n len(p),它就必须返回一个非 nil 的错误。Write 不能修改此切片的数据,即便它是临时的。

Seeker接口的定义,封装了基本的 Seek 方法。

Seeker 用来移动数据的读写指针,Seek 设置下一次读写操作的指针位置,每次的读写操作都是从指针位置开始的。

whence 的含义:

如果 whence 为 0:表示从数据的开头开始移动指针

如果 whence 为 1:表示从数据的当前指针位置开始移动指针

如果 whence 为 2:表示从数据的尾部开始移动指针

offset 是指针移动的偏移量

返回移动后的指针位置和移动过程中遇到的任何错误

WriterTo接口的定义,封装了基本的 WriteTo 方法。

WriterTo 将对象的数据流写入到 w 中,直到对象的数据流全部写入完毕或遇到写入错误为止。返回值 n 是写入的字节数,返回值 err 就是 w 的返回值 err。

定义WriterAt接口,WriterAt 接口封装了基本的 WriteAt 方法

WriteAt 将 p 中的数据写入到对象数据流的 off 处,忽略数据的读写指针,从数据的起始位置偏移 off 处开始写入,返回写入的字节数和写入时遇到的错误。如果 n len(p),则必须返回一个 err 值来说明为什么没有将 p 完全写入

file 类是在 os 包中的,封装了底层的文件描述符和相关信息,同时封装了 Read 和 Write 的实现。

写出数据到本地文件:

Golang将日志同时输出到控制台和文件

日常开发当中需要将golang的log包打印的日志同时输出到控制台和文件,应该如何解决这个问题?

log包可以通过SetOutput()方法指定日志输出的方式(Writer),但是只能指定一个输出的方式(Writer)。我们利用io.MultiWriter()将多个Writer拼成一个Writer使用的特性,把log.Println()输出的内容分流到控制台和文件当中。

原文地址

详解golang中bufio包的实现原理

最近用golang写了一个处理文件的脚本,由于其中涉及到了文件读写,开始使用golang中的 io 包,后来发现golang 中提供了一个bufio的包,使用这个包可以大幅提高文件读写的效率,于是在网上搜索同样的文件读写为什么bufio 要比io的读写更快速呢?根据网上的资料和阅读源码,以下来详细解释下bufio的高效如何实现的。

bufio 包介绍 

bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。

以上为官方包的介绍,在其中我们能了解到的信息如下:

bufio 是通过缓冲来提高效率

简单的说就是,把文件读取进缓冲(内存)之后再读取的时候就可以避免文件系统的io 从而提高速度。同理,在进行写操作时,先把文件写入缓冲(内存),然后由缓冲写入文件系统。看完以上解释有人可能会表示困惑了,直接把 内容-文件 和 内容-缓冲-文件相比, 缓冲区好像没有起到作用嘛。其实缓冲区的设计是为了存储多次的写入,最后一口气把缓冲区内容写入文件。下面会详细解释

bufio 封装了io.Reader或io.Writer接口对象,并创建另一个也实现了该接口的对象

io.Reader或io.Writer 接口实现read() 和 write() 方法,对于实现这个接口的对象都是可以使用这两个方法的

bufio 包实现原理

bufio 源码分析

Reader对象

bufio.Reader 是bufio中对io.Reader 的封装

// Reader implements buffering for an io.Reader object.

type Reader struct {

  buf     []byte

  rd      io.Reader // reader provided by the client

  r, w     int    // buf read and write positions

  err     error

  lastByte   int

  lastRuneSize int

}

bufio.Read(p []byte) 相当于读取大小len(p)的内容,思路如下:

当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区

当缓存区没有内容的时候且len(p)len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可

当缓存区没有内容的时候且len(p)len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容)

以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区(此时和情况1一样)

以下是源码

// Read reads data into p.

// It returns the number of bytes read into p.

// The bytes are taken from at most one Read on the underlying Reader,

// hence n may be less than len(p).

// At EOF, the count will be zero and err will be io.EOF.

func (b *Reader) Read(p []byte) (n int, err error) {

  n = len(p)

  if n == 0 {

    return 0, b.readErr()

  }

  if b.r == b.w {

    if b.err != nil {

      return 0, b.readErr()

    }

    if len(p) = len(b.buf) {

      // Large read, empty buffer.

      // Read directly into p to avoid copy.

      n, b.err = b.rd.Read(p)

      if n 0 {

        panic(errNegativeRead)

      }

      if n 0 {

        b.lastByte = int(p[n-1])

        b.lastRuneSize = -1

      }

      return n, b.readErr()

    }

    // One read.

    // Do not use b.fill, which will loop.

    b.r = 0

    b.w = 0

    n, b.err = b.rd.Read(b.buf)

    if n 0 {

      panic(errNegativeRead)

    }

    if n == 0 {

      return 0, b.readErr()

    }

    b.w += n

  }

  // copy as much as we can

  n = copy(p, b.buf[b.r:b.w])

  b.r += n

  b.lastByte = int(b.buf[b.r-1])

  b.lastRuneSize = -1

  return n, nil

}

说明:

reader内部通过维护一个r, w 即读入和写入的位置索引来判断是否缓存区内容被全部读出

Writer对象

bufio.Writer 是bufio中对io.Writer 的封装

// Writer implements buffering for an io.Writer object.

type Writer struct {

  err error

  buf []byte

  n  int

  wr io.Writer

}

bufio.Write(p []byte) 的思路如下

判断buf中可用容量是否可以放下 p

如果能放下,直接把p拼接到buf后面,即把内容放到缓冲区

如果缓冲区的可用容量不足以放下,且此时缓冲区是空的,直接把p写入文件即可

如果缓冲区的可用容量不足以放下,且此时缓冲区有内容,则用p把缓冲区填满,把缓冲区所有内容写入文件,并清空缓冲区

判断p的剩余内容大小能否放到缓冲区,如果能放下(此时和步骤1情况一样)则把内容放到缓冲区

如果p的剩余内容依旧大于缓冲区,(注意此时缓冲区是空的,情况和步骤2一样)则把p的剩余内容直接写入文件

// Write writes the contents of p into the buffer.

// It returns the number of bytes written.

// If nn len(p), it also returns an error explaining

// why the write is short.

func (b *Writer) Write(p []byte) (nn int, err error) {

  for len(p) b.Available() b.err == nil {

    var n int

    if b.Buffered() == 0 {

      // Large write, empty buffer.

      // Write directly from p to avoid copy.

      n, b.err = b.wr.Write(p)

    } else {

      n = copy(b.buf[b.n:], p)

      b.n += n

      b.flush()

    }

    nn += n

    p = p[n:]

  }

  if b.err != nil {

    return nn, b.err

  }

  n := copy(b.buf[b.n:], p)

  b.n += n

  nn += n

  return nn, nil

}

说明:

b.wr 存储的是一个io.writer对象,实现了Write()的接口,所以可以使用b.wr.Write(p) 将p的内容写入文件

b.flush() 会将缓存区内容写入文件,当所有写入完成后,因为缓存区会存储内容,所以需要手动flush()到文件

b.Available() 为buf可用容量,等于len(buf) - n

下图解释的是其中一种情况,即缓存区有内容,剩余p大于缓存区

golang文件操作摘抄

译者按: rename 和 move 原理一样

译者按:熟悉Linux的读者应该很熟悉权限模式,通过Linux命令chmod可以更改文件的权限

补充了原文未介绍的flag

一个普通的文件是一个指向硬盘的inode的地方。 硬链接创建一个新的指针指向同一个地方。只有所有的链接被删除后文件才会被删除。硬链接只在相同的文件系统中才工作。你可以认为一个硬链接是一个正常的链接。

symbolic link,又叫软连接,和硬链接有点不一样,它不直接指向硬盘中的相同的地方,而是通过名字引用其它文件。他们可以指向不同的文件系统中的不同文件。并不是所有的操作系统都支持软链接。

复制文件

可以使用os包写入一个打开的文件。 因为Go可执行包是静态链接的可执行文件,你import的每一个包都会增加你的可执行文件的大小。其它的包如io、`ioutil`、`bufio`提供了一些方法,但是它们不是必须的。

ioutil包有一个非常有用的方法WriteFile()可以处理创建/打开文件、写字节slice和关闭文件一系列的操作。如果你需要简洁快速地写字节slice到文件中,你可以使用它。

bufio包提供了带缓存功能的writer,所以你可以在写字节到硬盘前使用内存缓存。当你处理很多的数据很有用,因为它可以节省操作硬盘I/O的时间。在其它一些情况下它也很有用,比如你每次写一个字节,把它们攒在内存缓存中,然后一次写入到硬盘中,减少硬盘的磨损以及提升性能。

读取最多N个字节

os.File提供了文件操作的基本功能, 而io、ioutil、bufio提供了额外的辅助函数。

有缓存写也有缓存读。 缓存reader会把一些内容缓存在内存中。它会提供比os.File和io.Reader更多的函数,缺省的缓存大小是4096,最小缓存是16。

Scanner是bufio包下的类型,在处理文件中以分隔符分隔的文本时很有用。 通常我们使用换行符作为分隔符将文件内容分成多行。在CSV文件中,逗号一般作为分隔符。 os.File文件可以被包装成bufio.Scanner,它就像一个缓存reader。 我们会调用Scan()方法去读取下一个分隔符,使用Text()或者Bytes()获取读取的数据。

分隔符可以不是一个简单的字节或者字符,有一个特殊的方法可以实现分隔符的功能,以及将指针移动多少,返回什么数据。 如果没有定制的SplitFunc提供,缺省的ScanLines会使用newline字符作为分隔符,其它的分隔函数还包括ScanRunes和ScanWords,皆在bufio包中。

打包(zip) 文件

其它

临时文件和目录

ioutil提供了两个函数: TempDir() 和 TempFile()。 使用完毕后,调用者负责删除这些临时文件和文件夹。 有一点好处就是当你传递一个空字符串作为文件夹名的时候,它会在操作系统的临时文件夹中创建这些项目(/tmp on Linux)。 os.TempDir()返回当前操作系统的临时文件夹。

上面的例子复制整个文件内容到内存中,传递给hash函数。 另一个方式是创建一个hash writer, 使用Write、WriteString、Copy将数据传给它。 下面的例子使用 md5 hash,但你可以使用其它的Writer。

聊聊golang的zap的ZapKafkaWriter

本文主要研究一下golang的zap的ZapKafkaWriter

WriteSyncer内嵌了io.Writer接口,定义了Sync方法;Sink接口内嵌了zapcore.WriteSyncer及io.Closer接口;ZapKafkaWriter实现Sink接口及zapcore.WriteSyncer接口,其Write方法直接将data通过kafka发送出去。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Golang/10076.html