io

Go中输入和输出操作都采用原语来实现,原语会将数据模拟成可读的或可写的字节流。为方便开发使用Go将I/O操作封装在不同包中,而io包是I/O原语(I/O Primitives)提供的基本接口。

读写库 描述
io 底层接口定义库,定义基本接口和常量。
io/ioutil 包含在io目录下,作为io库的工具包,封装了实用的函数。
bufio io库的添加缓存后的封装,实现带缓冲的I/O。
fmt 实现了格式化I/O,类似C语言中的printfscanf
os 与操作系统打交道

常用实现

Go中很多原生的结构都围绕io.Readerio.Writer两个接口展开,通过这两个接口可以在多种不同的I/O之间进行过渡和转化。任何实现了Read()函数的对象都可以作为io.Reader,任何实现了Write()函数的对象都可以作为io.Writer

io转化关系

io.Readerio.Writer常用实现

常用实现 描述
net.Conn 网络
os.Stdin 标准输入输出
os.File 文件流读取
strings.Reader 将字符串抽象成为读取器
bytes.Reader 将字节数组抽象成读取器
bytes.Buffer 将字节数组抽象成读写器
bufio.Reader/bufio.Writer 抽象带缓冲的流读写器

基础接口

Go中输入和输出操作使用原语实现,原语将数据模拟称为可读或可写的字节流。

为此,io包提供了对I/O原语的基本接口,包装了原语的已有实现,使之成为共享的公共接口,抽象出泛用的函数并附加了相关的原语操作。这些接口和原语是对底层实现完全不同的低水平操作的包装,除非得到其它方面的通知,客户端不应该假设它们是并发执行安全的。

io包提供了最重要的两个接口io.Readerio.Writer,分别用于数据的输入和输出。

接口 名称 描述
io.Reader 读取器 将数据从某个资源读取到传输缓冲区,被流式传输和使用。
io.Writer 写入器 从缓冲区读取数据后写入到目标资源
io

io.Reader

io.Reader表示一个读取器,将数据从某个资源读取到传输缓冲区。在缓冲区中,数据可以被流式传输和使用。

io.Reader

io.Reader接口可用于包装基本的读取方法,利用io.Reader可实现流式数据传输。

type Reader interface {
  Read(p []byte) (n int, err error)
}

Read

对于要用作读取器的类型,必须实现io.Reader接口的唯一方法Read(p []byte)。只要实现了Read(p []byte)它就是一个读取器。

Read(p []byte) (n int, err error)

Read()入参p是一个字节切片[]byte,当数据读取时会写入该切片中,返回类型中整型n表示读取到的字节数量,err错误则表示读取过程中遇到的任何异常。当资源内容读取完毕,err会为io.EOF表示读取内容结束。

返回值 类型 描述
n int 读取到的字节数
err error 发生错误时的信息,资源读取完毕会返回io.EOF错误。

Read()会将长度len(p)个字节读取到p变量中,返回读取的字节数量n和错误err

读取机制

首先由于读取的字节数量n的取值范围是0 <= n <= len(p),因此要尽可能读取。

Read()读取过程中n < len(p) 会在调用过程中使用len(p)作为暂存空间(缓冲区),若读取完毕n < len(p)则会返回可用数据n,而不会等待更多数据。

var str string
var buf []byte
var n int
var err error
var reader io.Reader

str = "hello world"
reader = strings.NewReader(str)

buf = make([]byte, 32)
n, err = reader.Read(buf)
fmt.Printf("%d, %+v, %s\n", n, err, buf) // 11, , hello world                     

buf = make([]byte, 32)
n, err = reader.Read(buf)
fmt.Printf("%d, %+v, %s\n", n, err, buf) // 0, EOF
读取结果

Read()在成功读取n > 0后遇到Error或EOF,本次调用结果一定会返回已读取到的n,但可能返回的是(n, err), 或在下次调用中返回(0, err)

因此当Read()返回Error时并不能代表没有读取到任何数据,调用者应该先处理返回的数据后,再处理可能的错误。

读取结果 描述
(n, EOF) 下次调用Read()一定会返回(0, EOF)
(n, nil) 下次调用Read()一定会返回(0, EOF)
(0, nil) 表示被阻塞,调用者应该将此种情况视为未进行操作。

例如:读取数据时突然关闭TCP套接字,根据情况可选择将字节保持在p中或重试。

例如:将io.Reader作为参数即可以从任意地方读取数据,比如从标准输入、文件、字符串等读取数据,只要来源实现io.Reader接口。

func Read(reader io.Reader, size int) ([]byte, error) {
    p := make([]byte, size)
    n, err := reader.Read(p)
    if n > 0 {
        return p[:n], nil
    }
    return p, err
}

根据io.EOF变量的定义var EOF = errors.New("EOF")可知它是一个error类型,因此当n>0且数据被读完的情况下返回的error可能是io.EOF,也有可能是nil

bts, err := Read(os.Stdin, 11) //从标准输入读取
//bts,err := Read(strings.NewReader("hello world"), 12)//从字符串读取
fmt.Println(bts, err)

Read()内部是被循环调用的,每次迭代,会从数据源读取一块数据放入缓冲区p中,直到返回io.EOF错误时停止。

例如:创建字符串读取器,流式地按字节读取。

str := "clear is better than clever"
//创建字符串读取器
reader := strings.NewReader(str)
//字节缓存区
buf := make([]byte, 4)
//循环读取
for{
    //读取到缓存区
    n,err := reader.Read(buf)
    if err!=nil{
        //读取结束
        if err!=io.EOF{
            fmt.Println("EOF:", n)
            break
        }
        fmt.Println(err)
        os.Exit(1)
    }

    result := string(buf[:n])
    fmt.Println(n, result)
}
4 clea
4 r is
4  bet
4 ter 
4 than
4  cle
3 ver
EOF

例如:实现从流中过滤掉非字母字符

//过滤函数
func alpha(c byte) byte{
    if (c>='a'&&c<='z') || (c>='A'&&c<='Z'){
        return c
    }
    return 0
}
type AlphaReader struct{
    src string //资源
    i int//当前读取的位置
}
func NewAlphaReader(src string) *AlphaReader{
    return &AlphaReader{src:src}
}
func (r *AlphaReader) Read(p []byte) (int,error){
    //判断是否已经读取到结尾
    if r.i >= len(r.src){
        return 0, io.EOF
    }
    //剩余未读取长度
    x := len(r.src) - r.i
    //缓存区填满大小
    bound := 0
    //判断剩余长度是否超过缓存区大小
    if x >= len(p){
        //完全填满缓冲区
        bound = len(p)
    }else if x < len(p){
        //缓存区无法补满
        bound = x
    }
    //创建缓存区
    buf := make([]byte, bound)
    //迭代读取
    i := 0
    for bound > i {
        //每次读取一个字节
        char := r.src[r.i]
        //执行过滤函数
        if c := alpha(char); c!=0{
            buf[i] = c
        }
        i++
        r.i++
    }
    //将缓存内容赋值到
    copy(p, buf)
    return i,nil
}

测试

str := "Hello! It's 9am, where is the sun?"

buf := make([]byte, 4)
reader := NewAlphaReader(str)
for {
    n,err := reader.Read(buf)
    if err==io.EOF{
        break
    }
    data := string(buf[:n])
    fmt.Print(data)
}
  • 组合多个Reader可重用和屏蔽下层实现的复杂度

标准库已经实现了多个Reader,可使用一个Reader作为另一个Reader的实现是一种常见用法,可让一个Reader重用另一个Reader的逻辑。

例如:修改AlphaReader以接受io.Reader作为其源

//过滤函数
func alpha(c byte) byte{
    if (c>='a'&&c<='z') || (c>='A'&&c<='Z'){
        return c
    }
    return 0
}
type AlphaReader struct{
    reader io.Reader//组合标准读取器
}
func NewAlphaReader(reader io.Reader) *AlphaReader{
    return &AlphaReader{reader:reader}
}
func (r *AlphaReader) Read(p []byte) (int,error){
    n,err := r.reader.Read(p)
    if err!=nil{
        return n,err
    }

    buf := make([]byte, n)
    for i:=0; i

测试

str := "Hello! It's 9am, where is the sun?"
reader := strings.NewReader(str)

buf := make([]byte, 4)
r := NewAlphaReader(reader)
for {
    n,err := r.Read(buf)
    if err==io.EOF{
        break
    }
    data := string(buf[:n])
    fmt.Print(data)
}

例如:与os.File结束过滤掉文件中的非字母字符

$ vim ./test.txt
Hello! It's 9am, where is the sun?
file,err := os.Open("./test.txt")
if err!=nil{
    fmt.Println(err)
    os.Exit(1)
}
defer file.Close()

r := NewAlphaReader(file)
buf := make([]byte, 4)
for {
    n,err := r.Read(buf)
    if err==io.EOF{
        break
    }
    data := string(buf[:n])
    fmt.Print(data)
}

io.Writer

  • io.Writer表示一个编写器,从缓存区读取数据,并将数据写入目标资源。
io.Writer
  • 对于要作为编写器的类型必须实现io.Writer接口唯一的方法Write(p []byte),只要实现了Write(p []byte)就是一个编写器。
type Writer interface{
  Write(p []byte) (n int, err error)
}
  • Write()方法具有两个返回值,一个是写入到目标资源的字节数,一个是发生错误时的错误。
返回值 类型 描述
n int 写入到目标资源的字节数
err error 发生错误时的错误

例如:使用bytes.Buffer类型作为io.Writer将数据写入内存缓存区

proverbs := []string{
    "many hands make light work",
    "measure for measure",
    "murder will out",
    "never say die",
}

var writer bytes.Buffer
for _,p := range proverbs{
    n,err := writer.Write([]byte(p))
    if err!=nil{
        fmt.Println(err)
        os.Exit(1)
    }
    if n != len(p){
        fmt.Println("failed to write data")
        os.Exit(1)
    }
}
result := writer.String()
fmt.Println(result)

例如:实现自定义的io.Writer将其内容作为字节序列写入通道

//ChanWriter 通道写入器
type ChanWriter struct{
    ch chan byte//目标资源
}
//NewChanWriter 创建通道写入器
func NewChanWriter() *ChanWriter{
    return &ChanWriter{ch:make(chan byte, 1024)}
}
//Chan 获取目标资源
func (w *ChanWriter) Chan() <-chan byte{
    return w.ch
}
//Close 关闭目标资源
func (w *ChanWriter) Close(){
    close(w.ch)
}
//Write 写入目标资源
func (w *ChanWriter) Write(p []byte) (int, error){
    n := 0
    //遍历输入数据按字节写入目标资源
    for _,b := range p{
        w.ch<-b
        n++
    }
    return n,nil
}

测试

writer := NewChanWriter()

go func(){
    defer writer.Close()
    writer.Write([]byte("money can talk"))
}()

for char := range writer.Chan(){
    fmt.Printf("%c\n", char)
}

io.EOF

  • EOF是End-Of-File的缩写,根据Go惯例大写字母缩写表示常量,不过io.EOF被定义成了变量。
  • io.EOFio包中的一个变量,表示文件结束的错误。
1package io23var EOF = errors.New("EOF")
  • io.EOF是Go中最重要的错误变量,用于表示输入流的结尾,因为每个文件都有一个结尾,所以io.EOF很多时候并不能算是一个错误,更重要是表示输入流结束了。
var EOF = errors.New("EOF")

io.Closer

  • Closer是包装基本Close()方法的接口用于关闭数据流,第一次调用后Closer的行为是未定义的。
  • 文件、归档(压缩包)、数据库连接、Socket等均需手动关闭的资源都实现了Closer接口。
  • 编程中经常会将Close方法的调用放在defer语句中
type Closer interface{
  Close() error //关闭数据流
}

例如:

file,err := os.Open("test.txt")
if err!=nil{

}
defer file.Close()

io.ReadCloser

  • response.Body的类型为io.ReadCloser

io.Copy

func Copy(dst Writer, src Reader) (writtern int64, err error){
  return copyBuffer(dst, src, nil)
}
  • io.Copy方法支持在两个文件指针之间直接内容拷贝
  • io.Copy将副本从源(读取器)复制到目标(写入器),直到源读取器达到文件末尾(EOF)或发生错误。
  • io.Copy按默认32KB的缓存区循环操作的将源读取器复制到目标写入器
  • io.Copy会返回复制的字节数和操作错误
  • io.Copy抽象出for循环模式并能正确地处理io.EOF和字节计数
  • io.Copy可轻松实现将数据从一个Reader拷贝到另一个Writer

例如:当使用net/http中下载文件时,首先会使用http.get()创建远程请求,若直接使用ioutil.ReadAll(resp.Body)将响应的正文直接写入文件会发现有个问题,当下载小文件时没有问题,但文件较大时,可能出现内存不足(内存溢出)的问题。

func download(url string, filepath string){
    //远程请求获取内容
    resp,err := http.Get(url)
    if err!=nil {
        panic(err)
    }
    reader := resp.Body
    defer reader.Close()
    //创建本地文件
    file,err := os.Create(filepath)
    if err!=nil{
        panic(err)
    }
    defer file.Close()
    writer := bufio.NewWriter(file)
    //分块拷贝防止内存溢出
    _,err = io.Copy(writer, reader)
    if err!=nil{
        panic(err)
    }
    writer.Flush()
}

例如:使用io.Copy可以简化文件复制操作

//cp 复制文件
func cp(dstPath, srcPath string) (int, error){
    //打开源文件读取数据
    srcFile,err := os.Open(srcPath)
    if err!=nil {
        return 0, err
    }
    defer srcFile.Close()
    //打开目标文件准备写入
    dstFile, err := os.OpenFile(dstPath, os.O_WRONLY | os.O_CREATE, 0777)
    if err!=nil {
        return 0, err
    }
    defer dstFile.Close()
    //复制操作
    written := 0
    buf := make([]byte, 1024)
    for{
        //从源文件读取
        n,err := srcFile.Read(buf)
        if err!=nil && err!=io.EOF{
            return 0,err
        }else if err==io.EOF{
            break
        }
        //写入目标文件
        dstFile.Write(buf[:n])
        written += n
    }
    return written, nil
}
//cp 复制文件
func cp(dstPath, srcPath string) (int64, error){
    //打开源文件读取数据
    srcFile,err := os.Open(srcPath)
    if err!=nil {
        return 0, err
    }
    defer srcFile.Close()
    //打开目标文件准备写入
    dstFile, err := os.OpenFile(dstPath, os.O_WRONLY | os.O_CREATE, 0777)
    if err!=nil {
        return 0, err
    }
    defer dstFile.Close()
    //复制操作
    return io.Copy(dstFile, srcFile)
}

例如:实现从文件读取打印到标准输出中

func PrintFile(filename string){
    file,err := os.Open(filename)
    if err!=nil {
        panic(err)
    }
    defer file.Close()

    n,err := io.Copy(os.Stdout, file)
    if err!=nil {
        fmt.Println(n)
        os.Exit(1)
    }
}

io.CopyN

  • io.CopyN会将n字节从源复制到目标,返回复制到目标的字节数以及操作错误。
  • 若源数据字节数小于指定复制的n字节限制,则会panicEOF错误。
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)
参数 类型 描述
dst Writer 写入器接口实例
src Reader 读取器接口实例
n int64 传送内容的字节数

例如:从字符串中读取后输入到控制台

str := "hello world\n"
reader := strings.NewReader(str)

writer := os.Stdout

n,err := io.CopyN(writer, reader, 50)
if err!=nil {
    panic(err)//panic: EOF
}

fmt.Printf("\n%d bytes\n", n)

io.WriteString

  • io.WriteString()可方便地将字符串写入一个io.Writer
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))
}

为了操作更加安全,io.WriteString()中进行了类型断言,若传入的Writer对象拥有WriteString()方法的话,则就直接调用它的WriteString()。若没有则调用Write()方法同时显示的进行类型转换。

参数 类型 描述
w Writer 实现io.Writer接口的写入器对象
s string 待写入的字符串
返回值 类型 描述
n int 当前写入字符长度
err error 错误信息

例如:将字符串写入文件

//FileExist 判断文件是否存在
func FileExist(filepath string) bool{
    if _,err := os.Stat(filepath); os.IsNotExist(err){
        return false
    }
    return true
}
//WriteFile 写入字符串到文件
func WriteFile(filepath string, content string){
    var file *os.File
    var err error
    //判断文件是否存在
    if ok := FileExist(filepath); !ok{
        //创建文件
        file,err = os.Create(filepath)
        if err!=nil {
            panic(err)
        }
    }else{
        //打开文件
        file,err = os.OpenFile(filepath, os.O_APPEND, 0666)
        if err!=nil {
            panic(err)
        }
    }
    defer file.Close()

    //写入文件
    _,err = io.WriteString(file, content)
    if err!=nil {
        panic(err)
    }
}

例如:向浏览器访问的页面中输出字符串

func WriteHandler(w http.ResponseWriter, r *http.Request){
    str := "hello world"

    w.Write([]byte(str))

    io.WriteString(w, str)
    
    fmt.Fprintf(w, str)

    return
}

你可能感兴趣的:(io)