Golang IO

io包中最重要的是两个接口:Reader和Writer

Reader接口#####

type Reader interface {
    Read(p []byte) (n int ,err error)
}```
#####官方文档中关于该接口方法的说明#####
>Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p)) 以及任何遇到的错误。即使 Read 返回的 n < len(p),它也会在调用过程中使用 p 的全部作为暂存空间。若一些数据可用但不到 len(p) 个字节,Read 会照例返回可用的数据,而不是等待更多数据。

> Read 在成功读取 n > 0 个字节后遇到一个错误或 ```EOF (end-of-file)```,它就会返回读取的字节数。它会从相同的调用中返回(非nil的)错误或从随后的调用中返回错误(同时 n == 0)。 一般情况的一个例子就是 Reader 在输入流结束时会返回一个非零的字节数,同时返回的 ```err``` 不是 ```EOF``` 就是```nil```。无论如何,下一个 Read 都应当返回 ```0, EOF```。

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

*PS: 当```Read```方法返回错误时,不代表没有读取到任何数据,可能是数据被读完了时返回的```io.EOF```。* 

 Reader 接口的方法集([Method_sets](http://golang.org/ref/spec#Method_sets))只包含一个 Read 方法,因此,所有实现了 ```Read``` 方法的类型都实现了```io.Reader ```接口,也就是说,在所有需要``` io.Reader``` 的地方,可以传递实现了 ````Read()``` 方法的类型的实例。

#####Writer 接口#####
---

type Writer interface {
Write(p []byte) (n int, err error)
}```

官方文档中关于该接口方法的说明:#####

Write 将 len(p) 个字节从 p 中写入到基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误。若 Write 返回的 n < len(p),它就必须返回一个 非nil 的错误。

io.File#####

os.File同时实现了io.Readerio.Writer接口。
在os包中有三个可倒出的特殊文件(os.File)实例:StdinStdoutStderr,自然也实现了 io.Readerio.Writer.

实现了 io.Reader 或 io.Writer 接口的类型#####
  • os.File 同时实现了 io.Readerio.Writer
  • strings.Reader 实现了 io.Reader
  • bufio.Reader/Writer 分别实现了 io.Readerio.Writer
  • bytes.Buffer 同时实现了 io.Readerio.Writer
  • bytes.Reader 实现了 io.Reader
  • compress/gzip.Reader/Writer 分别实现了 io.Readerio.Writer
  • crypto/cipher.StreamReader/StreamWriter 分别实现了 io.Readerio.Writer
  • crypto/tls.Conn 同时实现了 io.Readerio.Writer
  • encoding/csv.Reader/Writer 分别实现了 io.Readerio.Writer
  • mime/multipart.Part 实现了 io.Reader
  • io.LimitedReader、io.PipeReader、io.SectionReader实现了io.Reader
  • io.PipeWriter实现了io.Writer

PS: Go接口的命名约定:接口名以 er 结尾。注意,这里并非强行要求,你完全可以不以 er 结尾。标准库中有些接口也不是以 er 结尾的。

ReaderAt 和 WriterAt 接口#####
ReaderAt 接口######

type ReaderAt interface {
  ReadAt(p []byte,off int64) (n int ,err error)
}```
官方文档说明
>ReadAt 从几本输入源的偏移量off处开始,将len(p)个字节读取到p 中,它返回读取的字节数 n(0 <= n <= len(p))以及任何遇到的错误。

>当 ReadAt 返回的 n < len(p) 时,它就会返回一个 非nil 的错误来解释 为什么没有返回更多的字节。在这一点上,ReadAt 比 Read 更严格。

>即使 ReadAt 返回的 n < len(p),它也会在调用过程中使用 p 的全部作为暂存空间。若一些数据可用但不到 len(p) 字节,ReadAt 就会阻塞直到所有数据都可用或产生一个错误。 在这一点上 ReadAt 不同于 Read。

>若 n = len(p) 个字节在输入源的的结尾处由 ReadAt 返回,那么这时 err == EOF 或者 err == nil。

>若 ReadAt 按查找偏移量从输入源读取,ReadAt 应当既不影响
基本查找偏移量也不被它所影响。

>ReadAt 的客户端可对相同的输入源并行执行 ReadAt 调用。

######io.WriterAt 接口######
---

type Writer interface {
WriterAt(p []byte, off int64) (n int, err error) {
}```
官方文档说明

WriteAt 从 p 中将 len(p) 个字节写入到偏移量 off 处的基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误。若 WriteAt 返回的 n < len(p),它就必须返回一个 非nil 的错误。

若 WriteAt 按查找偏移量写入到目标中,WriteAt 应当既不影响基本查找偏移量也不被它所影响。

若区域没有重叠,WriteAt 的客户端可对相同的目标并行执行 WriteAt 调用。

ReaderFrom 和 WriterTo 接口#####
ReaderFrom######

type ReaderFrom interface {
    ReaderFrom(r Reader) (n int64, err error)
}```
官方文档说明:
>ReadFrom 从 r 中读取数据,直到 EOF 或发生错误。其返回值 n 为读取的字节数。除 io.EOF 之外,在读取过程中遇到的任何错误也将被返回。

>如果 ReaderFrom 可用,Copy 函数就会使用它。

*PS: ```ReadFrom``` 方法不会返回 ```err == EOF```。*

下面的例子简单的实现将文件中的数据全部读取(显示在标准输出):

file, err := os.Open("writeAt.txt")
if err != nil {
panic(err)
}
defer file.Close()
writer := bufio.NewWriter(os.Stdout)
writer.ReadFrom(file)
writer.Flush()```
也可以通过ioutil.ReadFile 函数获取文件全部内容, ioutil.ReadFile 内部通过ReadFrom方法实现。

如果不通过 ReadFrom 接口来做这件事,而是使用 io.Reader 接口,我们有两种思路:######
  • 先获取文件的大小(File 的 Stat 方法),之后定义一个该大小的 []byte,通过 Read 一次性读取
  • 定义一个小的 []byte,不断的调用 Read 方法直到遇到 EOF,将所有读取到的 []byte 连接到一起
WriterTo#####

type WriterTo interface {
    WriterTo(w Writer) (n int64, err error)
}```
官方文档说明
>WriteTo 将数据写入 w 中,直到没有数据可写或发生错误。其返回值 n 为写入的字节数。 在写入过程中遇到的任何错误也将被返回。

>如果 WriterTo 可用,Copy 函数就会使用它

如果有“一次性从某个地方读或写到某个地方去。”这样的需求,可以考虑使用这两个接口:``` io.ReaderFrom``` 和 ```io.WriterTo```.
#####Seeker 接口#####
---

type Seeker interface {
Seek(offset int64, whence int) (ret int64, err error)
}```
官方文档说明:

Seek 设置下一次 Read 或 Write 的偏移量为 offset,它的解释取决于 whence: 0 表示相对于文件的起始处,1 表示相对于当前的偏移,而 2 表示相对于其结尾处。 Seek 返回新的偏移量和一个错误,如果有的话。

也就是说,Seek 方法用于设置偏移量的,这样可以从某个特定位置开始操作数据流。听起来和 ReaderAt/WriteAt 接口有些类似,不过 Seeker 接口更灵活,可以更好的控制读写数据流的位置。

简单的示例代码:获取倒数第二个字符(需要考虑 UTF-8 编码,这里的代码只是一个示例)

reader := strings.NewReader("Go语言学习园地")
reader.Seek(-6, os.SEEK_END)
r, _, _ := reader.ReadRune()
fmt.Printf("%c\n", r)```

whence 的值,在 os 包中定义了相应的常量

const (
SEEK_SET int = 0 // seek relative to the origin of the file
SEEK_CUR int = 1 // seek relative to the current offset
SEEK_END int = 2 // seek relative to the end
)```

Closer接口#####

type Closer interface {
    Close() error
}```
该接口只有一个 Close() 方法,用于关闭数据流。

#####ByteReader 和 ByteWriter#####
这组接口的用途:读或写一个字节。接口定义如下:

---

type ByteReader interface {
ReadByte() (c byte, err error)
}

type ByteWriter interface {
WriteByte(c byte) error
}```

在标准库中,有如下类型实现了 io.ByteReader 或 io.ByteWriter:

  • bufio.Reader/Writer 分别实现了io.ByteReader 和 io.ByteWriter
  • bytes.Buffer 同时实现了 io.ByteReader 和 io.ByteWriter
  • bytes.Reader 实现了 io.ByteReader
  • strings.Reader 实现了 io.ByteReader

eg:

var ch byte
fmt.Scanf("%c\n", &ch)

buffer := new(bytes.Buffer)
err := buffer.WriteByte(ch)
if err == nil {
    fmt.Println("写入一个字节成功!准备读取该字节……")
    newCh, _ := buffer.ReadByte()
    fmt.Printf("读取的字节:%c\n", newCh)
} else {
    fmt.Println("写入错误")
}```
接口的使用.... 在二进制数据或归档压缩时用的比较多


#####ByteScanner、RuneReader 和 RuneScanner#####
######ByteScanner 接口的定义:######
------

type ByteScanner interface {
ByteReader // 内嵌了 ByteReader 接口
UnreadByte() error
}```
UnreadByte 方法的意思是:将上一次 ReadByte 的字节还原,使得再次调用 ReadByte 返回的结果和上一次调用相同,也就是说,UnreadByte 是重置上一次的 ReadByte。注意,UnreadByte 调用之前必须调用了 ReadByte,且不能连续调用 UnreadByte。

RuneReader 接口和 ByteReader 类似,只是 ReadRune 方法读取单个 UTF-8 字符,返回其 rune 和该字符占用的字节数。该接口在 [regexp](http://golang.org/pkg/rege
xp) 包有用到。

RuneScanner 接口和 ByteScanner 类似

ReadCloser、ReadSeeker、ReadWriteCloser、ReadWriteSeeker、ReadWriter、WriteCloser 和 WriteSeeker 接口#####

这些接口是上面介绍的接口的两个或三个组合而成的新接口。例如 ReadWriter 接口:

type ReadWriter interface {
    Reader
    Writer
}```
这是 Reader 接口和 Writer 接口的简单组合(内嵌)。

这些接口的作用是:有些时候同时需要某两个接口的所有功能,即必须同时实现了某两个接口的类型才能够被传入使用。可见,io 包中有大量的“小接口”,这样方便组合为“大接口”。

######SectionReader 类型#####
---
SectionReader 是一个 struct(没有任何导出的字段),实现了 Read, Seek 和 ReadAt,同时,内嵌了 ReaderAt 接口。结构定义如下:

type SectionReader struct {
r ReaderAt // 该类型最终的 Read/ReadAt 最终都是通过 r 的 ReadAt 实现
base int64 // NewSectionReader 会将 base 设置为 off
off int64 // 从 r 中的 off 偏移处开始读取数据
limit int64 // limit - off = SectionReader 流的长度
}```
从名称我们可以猜到,该类型读取数据流中部分数据。看一下

func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader```

的文档说明就知道了:
>NewSectionReader 返回一个 SectionReader,它从 r 中的偏移量 off 处读取 n 个字节后以 EOF 停止。

也就是说,SectionReader 只是内部(内嵌)ReaderAt 表示的数据流的一部分:从 off 开始后的 n 个字节。

这个类型的作用是:方便重复操作某一段 (section) 数据流;或者同时需要 ReadAt 和 Seek 的功能。


######LimitedReader 类型######
---

type LimitedReader struct {
R Reader // underlying reader,最终的读取操作通过 R.Read 完成
N int64 // max bytes remaining
}```
文档说明如下:

从 R 读取但将返回的数据量限制为 N 字节。每调用一次 Read 都将更新 N 来反应新的剩余数量。
也就是说,最多只能返回 N 字节数据。

LimitedReader 只实现了 Read 方法(Reader 接口)。

使用示例如下:

content := "This Is LimitReader Example"
reader := strings.NewReader(content)
limitReader := &io.LimitedReader{R: reader, N: 8}
for limitReader.N > 0 {
    tmp := make([]byte, 2)
    limitReader.Read(tmp)
    fmt.Printf("%s", tmp)
}```
输出:

This Is```
可见,通过该类型可以达到 只允许读取一定长度数据 的目的。

在 io 包中,LimitReader 函数的实现其实就是调用 LimitedReader:

func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }```

######PipeReader 和 PipeWriter 类型######
PipeReader(一个没有任何导出字段的 struct)是管道的读取端。它实现了 io.Reader 和 io.Closer 接口。

关于 Read 方法的说明:从管道中读取数据。该方法会堵塞,直到管道写入端开始写入数据或写入端关闭了。如果写入端关闭时带上了 error(即调用 CloseWithError 关闭),该方法返回的 err 就是写入端传递的error;否则 err 为 EOF。

PipeWriter(一个没有任何导出字段的 struct)是管道的写入端。它实现了 io.Writer 和 io.Closer 接口。

关于 Write 方法的说明:写数据到管道中。该方法会堵塞,直到管道读取端读完所有数据或读取端关闭了。如果读取端关闭时带上了 error(即调用 CloseWithError 关闭),该方法返回的 err 就是读取端传递的error;否则 err 为 ErrClosedPipe。

######io 包 管道 (pipe) 源码分析######
PipeWriter 和 PipeReader 都只有i一个不可导出的成员```p *pipe```,这两种类型的所有方法都是调用了 pipe 类型对应的方法实现的。

pipe类型的定义:

// A pipe is the shared pipe structure underlying PipeReader and PipeWriter.
type pipe struct {
rl sync.Mutex // gates readers one at a time
wl sync.Mutex // gates writers one at a time
l sync.Mutex // protects remaining fields
data []byte // data remaining in pending write
rwait sync.Cond // waiting reader
wwait sync.Cond // waiting writer
rerr error // if reader closed, error to give writes
werr error // if writer closed, error to give reads
}```

字段说明:

  • rl/wl 用于控制同一时刻只能有一个读取器或写入器
  • l 用于保护其他字段
  • data 在管道中的数据
  • rwait/wwait sync.Cond 类型(后续会讲解),分别控制读取器或写入器等待
  • rerr/werr 读取器(写入器)关闭,该错误会被 Write (Read) 方法返回
    .
    .
    .
Copy 和 CopyN 函数######

Copy 函数的签名:

func Copy(dst Writer, src Reader) (written int64, err error)
文档说明:

Copy 将 src 复制到 dst,直到在 src 上到达 EOF 或发生错误。它返回复制的字节数,如果有的话,还会返回在复制时遇到的第一个错误。

成功的 Copy 返回 err == nil,而非 err == EOF。由于 Copy 被定义为从 src 读取直到 EOF 为止,因此它不会将来自 Read 的 EOF 当做错误来报告。

若 dst 实现了 ReaderFrom 接口,其复制操作可通过调用 dst.ReadFrom(src) 实现。此外,若 src 实现了 WriterTo 接口,其复制操作可通过调用 src.WriteTo(dst) 实现。

eg1:

io.Copy(os.Stdout, strings.NewReader("Hello World!))```
**eg2:**

// 直接将内容输出(写入 Stdout 中)
func main() {
io.Copy(os.Stdout, os.Stdin)
fmt.Println("Got EOF -- bye")
}```

CopyN 函数的签名:######

func CopyN(dst Writer, src Reader, n int64) (written int64, err error)```

函数文档:
>CopyN 将 n 个字节从 src 复制到 dst。 它返回复制的字节数以及在复制时遇到的最早的错误。由于 Read 可以返回要求的全部数量及一个错误(包括 EOF),因此 CopyN 也能如此。
若 dst 实现了 ReaderFrom 接口,复制操作也就会使用它来实现。

#####ReadAtLeast 和 ReadFull 函数#####
######ReadAtLeast 函数签名######
---

func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)```
函数文档:

ReadAtLeast 将 r 读取到 buf 中,直到读了最少 min 个字节为止。它返回复制的字节数,如果读取的字节较少,还会返回一个错误。若没有读取到字节,错误就只是 EOF。如果一个 EOF 发生在读取了少于 min 个字节之后,ReadAtLeast 就会返回 ErrUnexpectedEOF。若 min 大于 buf 的长度,ReadAtLeast 就会返回 ErrShortBuffer。对于返回值,当且仅当 err == nil 时,才有 n >= min。

一般可能不太会用到这个函数。使用时需要注意返回的 error 判断。

ReadFull函数签名######

func ReadFull(r Reader,buf []byte) (n int,err error)```
函数文档:
>ReadFull 精确地从 r 中将 len(buf) 个字节读取到 buf 中。它返回复制的字节数,如果读取的字节较少,还会返回一个错误。若没有读取到字节,错误就只是 EOF。如果一个 EOF 发生在读取了一些但不是所有的字节后,ReadFull 就会返回 ErrUnexpectedEOF。对于返回值,当且仅当 err == nil 时,才有 n == len(buf)。

注意该函数和 ReadAtLeast 的区别:ReadFull 将 buf 读满;而 ReadAtLeast 是最少读取 min 个字节。

#####WriteString 函数#####
---

func WriteString(w Writer, s string) (n int, err error)```
当 w 实现了 WriteString 方法时,直接调用该方法,否则执行 w.Write([]byte(s))。

MultiReader 和 MultiWriter 函数#####

在 io 包中定义了两个非导出类型:mutilReader 和 multiWriter,它们分别实现了 io.Reader 和 io.Writer 接口。类型定义为:

type multiReader struct {
    readers []Reader
}

type multiWriter struct {
    writers []Writer
}```

MultiReader 和 MultiWriter 定义:

func MultiReader(readers ...Reader) Reader
func MultiWriter(writers ...Writer) Writer```
它们接收多个 Reader 或 Writer,返回一个 Reader 或 Writer。
MultiReader 只是逻辑上将多个 Reader 组合起来,并不能通过调用一次 Read 方法获取所有 Reader 的内容。在所有的 Reader 内容都被读完后,Reader 会返回 EOF。

TeeReader函数#####

函数签名如下:

func TeeReader(r Reader, w Writer) Reader```

TeeReader 返回一个 Reader,它将从 r 中读到的数据写入 w 中。所有经由它处理的从 r 的读取都匹配于对应的对 w 的写入。它没有内部缓存,即写入必须在读取完成前完成。任何在写入时遇到的错误都将作为读取错误返回。
也就是说,我们通过 Reader 读取内容后,会自动写入到 Writer 中去。例子代码如下:

reader := io.TeeReader(strings.NewReader("Hello Go!"), os.Stdout)reader.Read(make([]byte, 9))```
输出结果:

Hello Go!```
这种功能的实现其实挺简单,无非是在 Read 完后执行 Write。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • golang标准库对io的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。这篇文章结合一个实际的例子来...
    icexin阅读 10,404评论 1 16
  • 作者 丨icexin Golang 标准库对 IO 的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。...
    那个小码哥阅读 782评论 0 3
  • 摘要 Java I/O是Java技术体系中非常基础的部分,它是学习Java NIO的基础。而深入理解Java NI...
    biakia阅读 7,598评论 7 81
  • golang的io包中,稍微有点儿晦涩的就是Pipe方法,今天我们就一起来看一看这个Pipe。 函数定义如下: f...
    suoga阅读 15,367评论 9 10
  • 大学卒业后有过一段铭肌镂骨的恋爱,之后和许多男同胞一样,对恋爱有种莫明其妙的恐怖,很难承受另一段情感,出于心理需求...
    住你家隔壁偷窥阅读 127评论 0 0