Go的I/O特点
Go的I/O分网络I/O和文件I/O:
网络fd可以用epoll管理事件,实现异步I/O 文件fd不能用epoll管理事件,只能同步I/O
简而言之,Go是网络I/O密集型程序,I/O操作是Go最核心内容之一
I/O接口
I/O就是读写,go用interface
来定义I/O,具体代码参考io/io.go
,最基本的就是Reader
和Writer
接口,不涉及到具体的实现
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
I/O主要可以分为三类:
基础类型: Reader
Writer
Closer
ReaderAt
Seeker
ByteReader
ByteWriter
RuneReader
StringWriter
等. 这是最基本的接口,自己可以实现这些接口,定义新的I/O相关的包,但是实现之前一定要参考相关注释组合类型: ReaderCloser
WriterCloser
WriteSeeker
等. 这些是把基本的接口组合起来进阶类型: TeeReader
LimitReader
SectionReader
MultiReader
MultiWriter
PipeReader
PipeWriter
等. 这种基于基础接口之上,加了一些有趣的实现
I/O还有一些通用的函数:
Copy
: 把一个Reader
读出来,写到Writer
中去,直到其中一方出错为止(最常见是EOF)CopyN
: 和Copy
类似,但是多了一个结束条件:数据拷贝不会超过NCopyBuffer
: 也是拷贝函数,但是能让用户指定使用多大的Buffer内存,使用恰当可以优化性能
I/O工具包
I/O工具包主要指的是io/ioutil
, 这个库位于src/io
目录之下,目录路径为src/io/ioutil
。他的核心是提供一些方便使用的函数,如
ReadFile
: 给一个路径,把文件读到内存(open,close,read都统一封装进来)WriteFile
: 给一个路径,把内存写入文件ReadDir
: 给一个目录路径,把这个路径下的文件列表一起读取ReadAll
: 给一个Reader
流,全都读到内存
这个工具包只是简便操作,但是并不关心客观条件是否满足,比如ReadAll
,只是读取所有内容,但是并不关心内存是否足够容纳
I/O拓扑
Go的I/O封装了其他的相关的包,比如文件I/O,网络I/O,bufio,bytes,strings都是实现基础类型的接口,所以I/O包丰富多彩,I/O的主体可以随意定制
bytes
库
bytes
库,主要把[]byte
转换成Reader
或Writer
,从而把内存块作为读写的数据流。主要类型有bytes.Reader
(仅仅为Reader
)和bytes.Buffer
(Reader
和Writer
).
buffer := make([]byte, 1024)
readerFromBytes := bytes.NewReader(buffer)
n, err := io.Copy(ioutil.Discard, readerFromBytes)
fmt.Printf("n=%v, err=%v\n", n, err)
strings
库
strings.Reader
可以把字符串转换成Reader
,直接把字符串作为读源
data := "hello world"
readerFromBytes := strings.NewReader(data)
n, err := io.Copy(ioutil.Discard, readerFromBytes)
fmt.Printf("n=%v, err=%v\n", n, err)
net
库
网络可以作为读写源,抽象成了Reader
Writer
形式,是net.Conn
封装出来的
func handleConn(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 4096)
conn.Read(buf)
conn.Write([]byte("pong: "))
conn.Write(buf)
}
func main() {
// 创建套接字并监听
server, err := net.Listen("tcp", ":9999")
if err != nil {
log.Fatalf("err:%v", err)
}
for {
// 接收连接,可以reader也可以writer
c, err := server.Accept()
if err != nil {
log.Fatalf("err:%v", err)
}
go handleConn(c)
}
}
func main() {
// 创建连接,可以作为reader也可以作为writer
conn, err := net.Dail("tcp", ":9999")
if err != nil {
panic(err)
}
conn.Write([]byte("hello world\n"))
io.Copy(os.Stdout, conn)
}
Go的net
封装了底层的系统调用:syscall.Socket
syscall.Connect
syscall.Listen
syscall.GetsockoptInt
.创建之后就会设置成nonblock
模式,从而使用I/O多路复用;然后把socket fd放到epoll池里,监听事件;最后封装好读写事件到来的函数回调
os
库
os
库通常指文件I/O。Go封装了一个File
结构体,对外实现了Read
Write
ReadAt
WriteAt
可以把其当作Reader
和Writer
来使用
os.Stdin
os.Stdout
os.Stderr
分别是标准输入,标准输出和标准错误输出,这三个变量也是File
的变量,定义在src/os/file.go
文件里。这三个也是Linux的 0, 1, 2文件句柄
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
bufio
库
c语言的缓冲I/O有三种模式:
全缓冲:数据填满buffer才会调用底层I/O 行缓冲:遇到换行符,就会调用底层I/O 不缓冲:每次都直接调用底层I/O
buffer I/O主要是为了减少底层I/O调用次数,但是会损失数据一致性,还可能出现脏读的情况
bufio
就是提供缓冲I/O的功能,对一个Reader
Writer
携带一个buffer,做了一层封装,达到聚合的目的。
bufio.NewWriter // 带buffer的writer,默认大小是4096
bufio.NewWriterSize // 手动指定size
总结
Go的I/O核心库是 src/io
,如果要实现其中的interface
,一定要先阅读注视除了基础的I/O功能,还实现了通用函数,如 Copyio/ioutil
实现挺复杂,但真香内存字节数组可以作为 Reader
Writer
,实现在bytes
库中字符串可以作为 Reader
,实现在strings
库中网络句柄可以作为 Reader
Writer
, 实现在net
库中,net.Conn文件句柄可以作为 Reader
Writer
, 实现在os
库中,os.File标准输入,标准输出,标准错误可以作为 Reader
Writer
实现在os
库中,os.Stdio
os.Stdout
os.Stderr
参考资料
浅析Go IO的知识框架





