golang io中io.go解读
根据golang io源码包解读io.go文件。
1. 整体大纲

分别从接口,函数以及结构体去解读golang io 包中io.go文件。
2. 接口
在源代码中,对于 IO 流,定义了四个基本操作原语,分别用 Reader,Writer,Closer,Seeker 接口表达二进制流读、写、关闭、寻址等操作。根据其中的性质来区分,将分为读,写,关闭以及寻址等解读。
读
详细实现参考: bytes.Buffer
Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader 接口包装了基本的 Read 方法,用于输出自身(实现者)的数据到p。Read 方法用于将对象的数据流读入到 p 中,返回读取的字节数和遇到的错误。实现者不能包含p。
- 在没有遇到读取错误的情况下:
- 如果读到了数据(n > 0),则应该返回 n,nil。
- 如果数据被读空,没有数据可读(n == 0),则应该返回 0,EOF[1];
- 遇到读取错误,则 err 应该返回相应的错误信息(如果在读取过程中发了错误即n>0,那么要考虑处理这种情况,返回错误为ErrUnexpectedEOF[2]);
- 返回0,nil,那么代表什么都没有发生。
buf := bytes.NewBuffer([]byte("Hello World!"))
b := make([]byte, buf.Len())
n, err := buf.Read(b)
fmt.Printf("%s %v\n", b[:n], err) // output: Hello World! <nil>
ReaderFrom
type ReaderFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
ReaderFrom 接口包装了基本的 ReadFrom 方法,用于从 r 中读取数据存入自身(即实现者本身带有p)。 直到遇到 EOF 或读取出错为止,返回读取的字节数和遇到的错误。
buf := bytes.NewBuffer([]byte("Hello World!"))
dst := bytes.Buffer{}
dst.ReadFrom(buf)
dst.WriteTo(os.Stdout) // output: Hello World!
ReaderAt
type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}
ReaderAt 接口包装了基本的 ReadAt 方法,用于将自身的数据写入 p 中。ReadAt 忽略之前的读写位置,从起始位置的 off 偏移处开始读取。
返回写入的字节数和遇到的错误:
- 如果 p 被写满,则 err 会返回 nil;
- 如果 p 没 有被写满,则会返回一个错误信息用于说明为什么没有写满(比如 io.EOF)。在这方面 ReadAt 比 Read 更严格。
- 如果 p 被写满的同时,自身的数据也刚好被读完,则 err 即可以返回 nil 也可以返回 io.EOF。
即使不能将 p 填满,ReadAt 在被调用时也可能会使用整个 p 的空间作为缓存空间。如果 ReadAt 自身的数据是从其它地方(比如网络)获取数的,那么在写入 p 的时候,如果没有把 p 写满(比如网络延时),则 ReadAt 会阻塞,直到获取更多的数据把 p 写满,或者所有数据都获取完毕,或者遇到读取错误(比如超时)时才返回。
在这方面,ReadAt 和 Read 是不同的。
如果 ReadAt 读取的对象是某个有偏移量的底层数据流时,则 ReadAt 方法既不能影响底层的偏移量,也不应该被底层的偏移量影响。
ReadAt 的调用者可以对同一数据流并行执行 ReadAt 方法。
ReaderAt 的实现者不应该持有 p。
ByteReader
type ByteReader interface {
ReadByte() (byte, error)
}
ByteReader 接口包装了基本的 ReadByte 方法,用于从自身读出一个字节。
返回读出的字节和遇到的错误。如果返回错误,那么没有任何输入byte被消费,所返回的byte也是无效的。
buf := bytes.NewBuffer([]byte("Hello World!"))
c, err := buf.ReadByte()
fmt.Printf("%c %s %v\n", c, buf.String(), err) // output: H ello World! <nil>
ByteScanner
type ByteScanner interface {
ByteReader
UnreadByte() error
}
ByteScanner 在 ByteReader 的基础上增加了一个 UnreadByte 方法,用于撤消最后一次的 ReadByte 操作,以便下次的 ReadByte 操作可以读出与前一次一样的数据。
UnreadByte 之前必须是 ReadByte 才能撤消成功,否则可能会返回一个错误信息(根 据不同的需求,UnreadByte 也可能返回 nil,允许随意调用 UnreadByte,但只有最后一次的 ReadByte 可以被撤销,其它 UnreadByte 不执行任何操作)。
buf := bytes.NewBuffer([]byte("Hello World!"))
c, err := buf.ReadByte()
fmt.Printf("%c %s %v\n", c, buf.String(), err)// output: H ello World! <nil>
err = buf.UnreadByte()
fmt.Printf("%s %v\n", buf.String(), err)//output: Hello World! <nil>
RuneReader
type RuneReader interface {
ReadRune() (r rune, size int, err error)
}
RuneReader 接口包装了基本的 ReadRune 方法,用于从自身读取一个 UTF-8 编码的字符到 r 中。
返回读取的字符、字符的编码长度和遇到的错误。
buf := bytes.NewBuffer([]byte("爱Hello World!"))
c,s, err := buf.ReadRune()
fmt.Printf("%c %d %s %v\n", c,s, buf.String(), err) // output: 爱 3 Hello World! <nil>
RuneScanner
type RuneScanner interface {
RuneReader
UnreadRune() error
}
RuneScanner 在 RuneReader 的基础上增加了一个 UnreadRune 方法,用于撤消最后一次的 ReadRune 操作,以便下次的 ReadRune 操作可以读出与前一次一样的数据。UnreadRune(操作) 之前必须是 ReadRune(操作) 才能撤消成功,否则可能会返回一个错误信息(根据不同的需求,UnreadRune 也可能返回 nil,允许随意调用 UnreadRune,但只有最后一次的 ReadRune 可以被撤销,其它 UnreadRune 不执行任何操作)。
buf := bytes.NewBuffer([]byte("爱Hello World!"))
c,s, err := buf.ReadRune()
fmt.Printf("%c %d %s %v\n", c,s, buf.String(), err) // output: 爱 3 Hello World! <nil>
err = buf.UnreadRune()
fmt.Printf("%c %d %s %v\n", c,s, buf.String(), err)// output: 爱 3 爱Hello World! <nil>
写
Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
Writer 接口包装了基本的 Write 方法,用于将数据存入自身。Write 方法用于将 p 中的数据写入到对象的数据流中,返回写入的字节数和遇到的错误。
- 如果 p 中的数据全部被写入,则 err 应该返回 nil。
- 如果 p 中的数据无法被全部写入,则 err 应该返回相应的错误信息。
WriterTo
type WriterTo interface {
WriteTo(w Writer) (n int64, err error)
}
WriterTo 接口包装了基本的 WriteTo 方法,用于将自身的数据写入 w 中。
直到数据全部写入完毕或遇到错误为止,返回写入的字节数和遇到的错误。
WriterAt
type WriterAt interface {
WriteAt(p []byte, off int64) (n int, err error)
}
WriterAt 接口包装了基本的 WriteAt 方法,用于将 p 中的数据写入自身。
ReadAt 忽略之前的读写位置,从起始位置的 off 偏移处开始写入。
返回写入的字节数和遇到的错误。如果 p 没有被读完,则必须返回一个 err 值来说明为什么没有读完。
如果 WriterAt 写入的对象是某个有偏移量的底层数据流时,则 ReadAt 方法既不能影响底层的偏移量,也不应该被底层的偏移量影响。
WriterAt 的调用者可以对同一数据流的不同区段并行执行 WriteAt 方法。WriterAt 的实现者不应该持有 p。
ByteWriter
type ByteWriter interface {
WriteByte(c byte) error
}
ByteWriter 接口包装了基本的 WriteByte 方法,用于将一个字节写入自身。
返回遇到的错误
关闭
Closer
type Closer interface {
Close() error
}
Closer 接口包装了基本的 Close 方法,用于关闭数据读写。
Close 一般用于关闭文件,关闭通道,关闭连接,关闭数据库等
寻址
Seeker
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
Seeker 接口包装了基本的 Seek 方法,用于移动数据的读写指针。
Seek 设置下一次读写操作的指针位置,每次的读写操作都是从指针位置开始的。
whence 的含义:
- 如果 whence 为 0:表示从数据的开头开始移动指针。
- 如果 whence 为 1:表示从数据的当前指针位置开始移动指针。
- 如果 whence 为 2:表示从数据的尾部开始移动指针。
offset 是指针移动的偏移量。返回新指针位置和遇到的错误。
r := strings.NewReader("Hello World!")
n, err := io.CopyN(os.Stdout, r, 5) // output: Hello
fmt.Printf("\n%d %v\n\n", n, err) // output: 5 <nil>
r.Seek(0, 0)
n, err = io.Copy(os.Stdout, r) // output: Hello World!
fmt.Printf("\n%d %v\n\n", n, err) // output: 12 <nil>
3. 函数
读
ReadFull
func ReadFull(r Reader, buf []byte) (n int, err error) {
return ReadAtLeast(r, buf, len(buf))
}
这个函数可以把对象 r 中的数据读出来,然后存入一个缓冲区 buf 中,以便其它代码可以处理 buf 中的数据。
如果没有数据读取,那么久返回拷贝的字节数和一个错误。
- 返回n,EOF代表没有字节可以读取了
- 返回ErrUnexpectedEOF,如果在读取数据的过程中发生了err
- 返回 n == len(buf) 或者 err == nil,代表err不存在
// 定义一个 Ustr 类型
type Ustr struct {
s string // 数据流
i int // 读写位置
}
// 根据字符串创建 Ustr 对象
func NewUstr(s string) *Ustr {
return &Ustr{s, 0}
}
// 获取未读取部分的数据长度
func (s *Ustr) Len() int {
return len(s.s) - s.i
}
// 实现 Ustr 类型的 Read 方法
func (s *Ustr) Read(p []byte) (n int, err error) {
for ; s.i < len(s.s) && n < len(p); s.i++ {
c := s.s[s.i]
// 将小写字母转换为大写字母,然后写入 p 中
if 'a' <= c && c <= 'z' {
p[n] = c + 'A' - 'a'
} else {
p[n] = c
}
n++
}
// 根据读取的字节数设置返回值
if n == 0 {
return n, io.EOF
}
return n, nil
}
func main() {
s := NewUstr("Hello World!") // 创建 Ustr 对象 s
buf := make([]byte, s.Len()) // 创建缓冲区 buf
n, err := io.ReadFull(s, buf) // 将 s 中的数据读取到 buf 中
fmt.Printf("%s\n", buf) //output: HELLO WORLD!
fmt.Println(n, err) //output: 12 <nil>
}
ReadAtLeast
func ReadFull(r Reader, buf []byte) (n int, err error) {
return ReadAtLeast(r, buf, len(buf))
}
ReadAtLeast 从 r 中读取数据到 buf 中,要求至少读取 min 个字节。
返回读取的字节数和遇到的错误。
如果 min 超出了 buf 的容量,则 err 返回 io.ErrShortBuffer,否则:
- 读出的数据长度 == 0 ,则 err 返回 EOF[1:1];
- 读出的数据长度 < min,则 err 返回 io.ErrUnexpectedEOF[2:1];
- 读出的数据长度 >= min,则 err 返回 nil。
r := strings.NewReader("Hello World!") // 数据长度为12
b := make([]byte, 15)
n, err := io.ReadAtLeast(r, b, 12) // 要求读取至少12个字节
fmt.Printf("%q %d %v\n", b[:n], n, err) // output: "Hello World!" 12 <nil>
LimitReader
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
LimitReader 对 r 进行封装,使其最多只能读取 n 个字节的数据。相当于对 r 做了一个切片 r[:n] 返回。底层实现是一个 *LimitedReader(只有一个 Read 方法)。
r := strings.NewReader("Hello World!")
lr := io.LimitReader(r, 5)
n, err := io.Copy(os.Stdout, lr) // Hello
fmt.Printf("\n%d %v\n", n, err) //// output: 5 <nil>
MultiReader
func MultiReader(readers ...Reader) Reader {
r := make([]Reader, len(readers))
copy(r, readers)
return &multiReader{r}
}
MultiReader 将多个 Reader 封装成一个单独的 Reader,多个 Reader 会按顺序读取,当多个 Reader 都返回 EOF 之后,单独的 Reader 才返回 EOF,否则返回读取过程中遇到的任何错误。
r1 := strings.NewReader("Hello World!")
r2 := strings.NewReader("ABCDEFG")
r3 := strings.NewReader("abcdefg")
b := make([]byte, 15)
mr := io.MultiReader(r1, r2, r3)
for n, err := 0, error(nil); err == nil; {
n, err = mr.Read(b)
fmt.Printf("%q\n", b[:n])
}
// "Hello World!"
// "ABCDEFG"
// "abcdefg"
// ""
r1.Seek(0, 0)
r2.Seek(0, 0)
r3.Seek(0, 0)
mr = io.MultiReader(r1, r2, r3)
io.Copy(os.Stdout, mr) // output: Hello World!ABCDEFGabcdefg
TeeReader
func TeeReader(r Reader, w Writer) Reader {
return &teeReader{r, w}
}
TeeReader 对 r 进行封装,使 r 在读取数据的同时,自动向 w 中写入数据。它是一个无缓冲的 Reader,所以对 w 的写入操作必须在 r 的 Read 操作结束之前完成。所有写入时遇到的错误都会被作为 Read 方法的 err 返回。
r := strings.NewReader("Hello World!")
b := make([]byte, 15)
tr := io.TeeReader(r, os.Stdout) // 会在屏幕输出
n, err := tr.Read(b) // output: Hello World!
fmt.Printf("\n%s %v\n", b[:n], err) //output: Hello World! <nil>
写
WriteString
func WriteString(w Writer, s string) (n int, err error) {
if sw, ok := w.(StringWriter); ok {
return sw.WriteString(s)
}
return w.Write([]byte(s))
}
WriteString 将字符串 s 写入到 w 中,返回写入的字节数和遇到的错误。
如果 w 实现了 WriteString 方法,则优先使用该方法将 s 写入 w 中。否则,将 s 转换为 []byte,然后调用 w.Write 方法将数据写入 w 中。
io.WriteString(os.Stdout, "Hello World!\n") // output: Hello World!
MultiWriter
func MultiWriter(writers ...Writer) Writer {
allWriters := make([]Writer, 0, len(writers))
for _, w := range writers {
if mw, ok := w.(*multiWriter); ok {
allWriters = append(allWriters, mw.writers...)
} else {
allWriters = append(allWriters, w)
}
}
return &multiWriter{allWriters}
}
MultiReader 将向自身写入的数据同步写入到所有 writers 中。
r := strings.NewReader("Hello World!\n")
mw := io.MultiWriter(os.Stdout, os.Stdout, os.Stdout)
r.WriteTo(mw)
// output: Hello World!
// output: Hello World!
// output: Hello World!
复制
CopyN
func CopyN(dst Writer, src Reader, n int64) (written int64, err error) {
written, err = Copy(dst, LimitReader(src, n))
if written == n {
return n, nil
}
if written < n && err == nil {
// src stopped early; must have been EOF.
err = EOF
}
return
}
CopyN 从 src 中复制 n 个字节的数据到 dst 中,返回复制的字节数和遇到的错误。
- 只有当 written = n 时,err 才返回 nil。
如果 dst 实现了 ReadFrom 方法,则优先调用该方法执行复制操作。
r := strings.NewReader("Hello World!")
n, err := io.CopyN(os.Stdout, r, 5) // output:Hello
fmt.Printf("\n%d %v\n\n", n, err) // output:5 <nil>
CopyBuffer
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
if buf != nil && len(buf) == 0 {
panic("empty buffer in io.CopyBuffer")
}
return copyBuffer(dst, src, buf)
}
CopyBuffer 相当于 Copy,只不过Copy 在执行的过程中会创建一个临时的缓冲区来中转数据,而 CopyBuffer 则可以单独提供一个缓冲区,让多个复制操作共用同一个缓冲区,避免每次复制操作都创建新的缓冲区。如果 buf == nil,则 CopyBuffer 会自动创建缓冲区。
r := strings.NewReader("Hello World!")
buf := make([]byte, 32)
n, err := io.CopyBuffer(os.Stdout, r, buf) // output: Hello World!
fmt.Printf("\n%d %v\n", n, err) // output: 12 <nil>
Copy
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
Copy 从 src 中复制数据到 dst 中,直到所有数据都复制完毕,返回复制的字节数和遇到的错误。
如果复制过程成功结束,则 err 返回 nil,而不是 EOF,因为 Copy 的定义为“直到所有数据都复制完毕”,所以不会将 EOF 视为错误返回。
如果 src 实现了 WriteTo 方法,则调用 src.WriteTo(dst) 复制数据,否则如果 dst 实现了 ReadeFrom 方法,则调用 dst.ReadeFrom(src) 复制数据。
r := strings.NewReader("Hello World!")
n, err := io.Copy(os.Stdout, r) // output: Hello World!
fmt.Printf("\n%d %v\n\n", n, err) // output: 12 <nil>
4. 结构体
SectionReader
type SectionReader struct {
r ReaderAt
base int64
off int64
limit int64
}
实现了 Read, Seek, and ReadAt 接口
NewSectionReader
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader {
return &SectionReader{r, off, off, off + n}
}
NewSectionReader 对 r 进行封装,使其只能从 off 位置开始读取,最多只能读取 n个字节的的数据。相当于对 r 做了一个切片 r[off:off+n] 返回。底层实现是一个 *SectionReader。
Size
func (s *SectionReader) Size() int64 { return s.limit - s.base }
Size 返回允许读取部分的大小(即切片的长度 n)
使用示例
r := strings.NewReader("Hello World!")
sr := io.NewSectionReader(r, 6, 5)
n, err := io.Copy(os.Stdout, sr) // output: World
fmt.Printf("\n%d %d %v\n", sr.Size(), n, err) // output: 5 5 <nil>
LimitedReader
type LimitedReader struct {
R Reader // underlying reader
N int64 // max bytes remaining
}
实现Read接口
使用示例
r := strings.NewReader("Hello World!")
sr := io.LimitedReader{r,2}
buf := make ([]byte,12)
n, err := sr.Read(buf)
fmt.Printf("\n%d %s %v\n", n,buf[:n], err) // output: 2 He <nil>
teeReader
type teeReader struct {
r Reader
w Writer
}
结合TeeReader函数使用
5. 备注
EOF is the error returned by Read when no more input is available.Functions should return EOF only to signal a graceful end of input.If the EOF occurs unexpectedly in a structured data stream,the appropriate error is either ErrUnexpectedEOF or some other error↩︎ ↩︎ErrUnexpectedEOF means that EOF was encountered in the middle of reading a fixed-size block or data structure.↩︎ ↩︎
golang io中io.go解读的更多相关文章
- Java基础---IO(三)--IO包中的其他类
第一讲 对象序列化 一.概述 将堆内存中的对象存入硬盘,保留对象中的数据,称之为对象的持久化(或序列化).使用到的两个类:ObjectInputStream和ObjectOutputStrea ...
- JAVA中IO总结
JAVA中IO流主要分为两大类: 字节流:InputStream+OutputStream 字符流:Reader+Writer 字节流: InputStream是所有字节输入流的父类 OutputSt ...
- socket.io 中文手册 socket.io 中文文档
socket.io 中文手册,socket.io 中文文档转载于:http://www.cnblogs.com/xiezhengcai/p/3956401.html 服务端 io.on('connec ...
- java中IO写文件工具类
以下是一些依据经常使用java类进行组装的对文件进行操作的类,平时,我更喜欢使用Jodd.io中提供的一些对文件的操作类,里面的方法写的简单易懂. 当中jodd中提供的JavaUtil类中提供的方法足 ...
- Java中IO流的总结
有关Java中IO流总结图 流分类 按方向分 输入流 输出流 按单位分 字节流 字符流 按功能分 节点流 处理流(过滤流) 其他 所有的流继承与这四类流:InputSteam.OutputStream ...
- Python中IO概述
Python中的io模块是用来处理各种类型的I/O操作流.主要有三种类型的I/O类型:文本I/O(Text I/O),二进制I/O(Binary I/O)和原始I/O(Raw I/O).它们都是通用类 ...
- Java中IO流中的装饰设计模式(BufferReader的原理)
本文粗略的介绍下JavaIO的整体框架,重在解释BufferReader/BufferWriter的演变过程和原理(对应的设计模式) 一.JavaIO的简介 流按操作数据分为两种:字节流与字符流. 流 ...
- 简述C#中IO的应用 RabbitMQ安装笔记 一次线上问题引发的对于C#中相等判断的思考 ef和mysql使用(一) ASP.NET/MVC/Core的HTTP请求流程
简述C#中IO的应用 在.NET Framework 中. System.IO 命名空间主要包含基于文件(和基于内存)的输入输出(I/O)服务的相关基础类库.和其他命名空间一样. System.I ...
- 一头扎进 Java IO中
Java IO 概述 在这一小节,我会试着给出Java IO(java.io)包下所有类的概述.更具体地说,我会根据类的用途对类进行分组.这个分组将会使你在未来的工作中,进行类的用途判定时,或者是为某 ...
随机推荐
- ASP.NET Core基于K8S的微服务电商案例实践--学习笔记
摘要 一个完整的电商项目微服务的实践过程,从选型.业务设计.架构设计到开发过程管理.以及上线运维的完整过程总结与剖析. 讲师介绍 产品需求介绍 纯线上商城 线上线下一体化 跨行业 跨商业模式 从0开始 ...
- WPF的DataGrid的某个列绑定数据的三种方法(Binding、Converter、DataTrigger)
最近在使用WPF的时候,遇到某个列的值需要根据内容不同进行转换显示的需求.尝试了一下,大概有三种方式可以实现: 1.传统的Binding方法,后台构造好数据,绑定就行. 2.转换器方法(Convert ...
- elasticsearch 心得
1.es 一台机器一般为一个节点.一台机器不设置的情况下是无法创建副本集的,副本集和主本必须不在一个节点下,方便故障转移等 2.es7.x后一个索引后只能创建一个类型,可以通过修改更改 出现这个的原因 ...
- SpringBoot(十一):SpringBoot整合Redis
详解springboot整合redis:https://blog.csdn.net/qq_36781505/article/details/86612988 一.环境准备 Redis-x64-3.2. ...
- MD5哈希算法及其原理
- MD5功能 MD5算法对任意长度的消息输入,产生一个128位(16字节)的哈希结构输出.在处理过程中,以512位输入数据块为单位. - MD5用途及特征 MD5通常应用在以下场景: 1.防篡改,保 ...
- Activit 5.13 工作流部署新版本后回退到上一个版本
有时因为某些原因Activit流程部署新版本后,还没有发起流程,回退到上一个版本.操作过程: 1.查询版本更新记录,记录字段ID_值,假设值为100: select to_char(t.deploy_ ...
- 去掉没用的参数的warning
#define UNUSED(x) ((void)(x)) void fun(int noused_arg, int b){ UNUSED(noused_arg); printf("%d\n ...
- 记一次接口调试错误: {"timestamp":"2019-09-11T03:04:30.036+0000","status":500,"error":"Internal Server Error","message":"Could not write JSON: Object is null; nested exception is com.fasterxml.jackson
接口测试中用postman测试返回是正常的,但是使用其他人去调用就出错了,找了半天,才想起来使用了nginx,用于端口的代理转发.然后根据错误信息发现json格式的某个字段为null,结合日志中的报文 ...
- AtCoder - 2140 (思维)
题意 https://vjudge.net/problem/AtCoder-2140 每次告诉你新的a:b,计算最后最小的a+b. 思路 如 3 2 3 1 1 3 2 先令a=2,b=3,发现新的为 ...
- 5. this关键字
一.this关键字概述 1. this作为对象的引用,它总是指向调用该方法的对象 2. this的最大作用:让类中的一个方法访问该类中的另一个方法或实例变量 二.this关键字的两种用法 1. 在方法 ...