golang ioutil与io包详解

    技术2022-07-11  99

    golang ioutil与io包详解

    ioutilio错误变量接口结构体函数 前提概要,公共错误处理

    func checkErr(err error) { if err != nil { log.Fatil(err) } }

    输出文件目录 file1.txt中的内容为1行 1:街角魔族是最好看的动漫! file2.txt中的内容为2行 2:街角魔族是最好看的动漫! 还有一个输入文件夹in,不过是空的就不截图了,到时候底下的例子凡是关于write的都是放进in文件夹里。

    ioutil

    首先就从最简单的ioutil开始把。 先申明,ioutil的所有方法我仅举例子并说明方法的用处,至于返回值或函数传参,凡是关于io属性与os属性的详细用法请自行往下查找io相关内容或os相关博文。

    1、NopCloser(r io.Reader) io.ReadCloser用一个无操作的Close方法包装r返回一个ReadCloser接口。

    //打开文件 file, err := os.Open("./out/file1.txt") checkErr(err) //通过io.Reader获得一个io.ReadCloser readCloser := ioutil.NopCloser(file)

    注意这里的Close没有实际操作,源码如下⬇

    func (nopCloser) Close() error { return nil }

    2、ReadAll(r io.Reader) ([]byte, error)从r读取数据直到EOF或遇到error,返回读取的数据和可能的错误。成功的调用返回的err为nil而非EOF。因为本函数定义为读取r直到EOF,它不会将读取返回的EOF视为应报告的错误。

    file, err := os.Open("./out/file1.txt") checkErr(err) fileBytes, err := ioutil.ReadAll(file) checkErr(err) fmt.Println(string(fileBytes))

    正确的输出:

    1:街角魔族是最好看的动漫!

    3、ReadFile(filename string) ([]byte, error)从filename指定的文件中读取数据并返回文件的内容。对err的判断和ReadAll一样。

    fileBytes, err := ioutil.ReadFile("./out/file2.txt") checkErr(err) fmt.Println(string(fileBytes))

    输出

    2:街角魔族是最好看的动漫! 2:街角魔族是最好看的动漫!

    4、WriteFile(filename string, data []byte, perm os.FileMode) error函数向filename指定的文件中写入数据。如果文件不存在将按给出的perm权限创建文件,否则在写入数据之前清空文件。

    fileBytes, err := ioutil.ReadFile("./out/街角魔族1.jpg") checkErr(err) //以全权限写文件 ioutil.WriteFile("./in/街角魔族1.jpg", fileBytes, 0777)

    输出效果,没有字节损失,依旧如此高清可爱,完美。 5、ReadDir(dirname string) ([]os.FileInfo, error)读取dirname目录内的所有文件信息,注意此序列有序。

    osFiles, err := ioutil.ReadDir("./out") if err != nil { println(err) return } for i, osFile := range osFiles { //打印下标和文件名 fmt.Println(i, osFile.Name()) }

    输出:

    0 file1.txt 1 file2.txt 2 理直气壮.jpg 3 街角魔族1.jpg 4 街角魔族2.png

    6、TempDir(dir, prefix string) (name string, err error)在dir目录里创建一个新的、使用prfix作为前缀的临时文件夹,并返回文件夹的路径。如果dir是空字符串,TempDir使用默认用于临时文件的目录(参见os.TempDir函数)。 不同程序同时调用该函数会创建不同的临时目录,调用本函数的程序有责任在不需要临时文件夹时摧毁它。

    name, err := ioutil.TempDir("./in", "temp") checkErr(err) fmt.Println("已创建文件夹,目录:" + name) //等待1秒后删除创建的临时文件夹 time.Sleep(time.Second) err = os.Remove(name) checkErr(err) fmt.Println("已删除文件夹,目录:" + name)

    输出:

    已创建文件夹,目录:in\temp103398839 已删除文件夹,目录:in\temp103398839

    7、TempFile(dir, prefix string) (f *os.File, err error)在dir目录下创建一个新的、使用prefix为前缀的临时文件,以读写模式打开该文件并返回os.File指针。如果dir是空字符串,TempFile使用默认用于临时文件的目录(参见os.TempDir函数)。责任与TempDir相同。

    //在./in目录中创建一个前缀为temp,后缀为.png的临时文件 filePoint, err := ioutil.TempFile("./in", "temp*.png") checkErr(err) fmt.Println("已创建临时文件:目录", filePoint.Name()) fileBytes, err := ioutil.ReadFile("./out/街角魔族2.png") checkErr(err) _, err = filePoint.Write(fileBytes) //关闭文件流,不关将无法删除。 filePoint.Close() //等待10秒后删除创建的临时文件夹,文件留10秒截个效果图 time.Sleep(10 * time.Second) err = os.Remove(filePoint.Name()) checkErr(err) fmt.Println("已删除临时文件,目录", filePoint.Name())

    结果非常perfect,高清可爱。

    已创建临时文件:目录 in\temp224024071.png 已删除临时文件,目录 in\temp224024071.png

    好了到这里outil的所有内容都已经写完了,有没有发现ioutil的方法往往只需要一句,并不需要其他语句就能完成功能,非常简洁明了。毕竟ioutil包的创建初衷就只是对io包实用功能的二次封装而已,同样的由于高度的封装,是不是对里面很多参数都很懵逼呢,如:perm os.FileMode,io.ReadCloser,io.Reader这种,感兴趣的话就接着看吧。

    io

    io包提供了对I/O原语的基本接口。本包的基本任务是包装这些原语已有的实现(如os包里的原语),使之成为共享的公共接口,这些公共接口抽象出了泛用的函数并附加了一些相关的原语的操作,非并发安全包。 原语:计算机进程的控制通常由原语完成。所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断。

    错误变量

    首先来看看包中的错误变量⬇ var EOF = errors.New(“EOF”) 正常输入结束Read返回EOF,如果在一个结构化数据流中EOF在不期望的位置出现了,则应返回错误ErrUnexpectedEOF或者其它给出更多细节的错误。

    var ErrClosedPipe = errors.New(“io: read/write on closed pipe”) 当从一个已关闭的Pipe读取或者写入时,会返回ErrClosedPipe。

    var ErrNoProgress = errors.New(“multiple Read calls return no data or error”) 某些使用io.Reader接口的客户端如果多次调用Read都不返回数据也不返回错误时,就会返回本错误,一般来说是io.Reader的实现有问题的标志。

    var ErrShortBuffer = errors.New(“short buffer”) ErrShortBuffer表示读取操作需要大缓冲,但提供的缓冲不够大。

    var ErrShortWrite = errors.New(“short write”) ErrShortWrite表示写入操作写入的数据比提供的少,却没有显式的返回错误。

    var ErrUnexpectedEOF = errors.New(“unexpected EOF”) ErrUnexpectedEOF表示在读取一个固定尺寸的块或者数据结构时,在读取未完全时遇到了EOF。

    接口

    1、

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

    一个Reader接口,只要是实现了Read方法的结构体都代表实现了Reader接口,os的很多关于file的结构体都是实现了Reader接口。想要实现Reader接口非常简单,只要实现了Read方法即可。 Read方法读取len§字节数据写入p。它返回写入的字节数和遇到的任何错误。 很多时候我们也可以通过自己实现Reader接口来给需要io.Reader 传参的函数使用,举个例子。

    func main(){ //实例化一个实现了io.Reader接口的结构体 rs := ReaderStruct{"街角魔族是最好看的动漫!"} //实例化一个text长度的[]byte buf := make([]byte, len(rs.text)) //将rs读取至buf,返回长度 n, err := io.ReadFull(rs, buf) checkErr(err) fmt.Println(string(buf)) fmt.Println(n) } type ReaderStruct struct { text string } //实现Reader接口中的Read方法,总的功能就是将ReaderStruct中的text属性读取出来 func (rs ReaderStruct) Read(p []byte) (n int, err error) { bf := bytes.NewBufferString(rs.text) bytes := bf.Bytes() i := 0 for i < len(p) { p[i] = bytes[i] i++ } n = len(p) err = io.EOF return n, err }

    输出

    街角魔族是最好看的动漫! 34

    是不是很简单呢,对于字符操作有不懂的可以看这篇文章

    2、

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

    Writer接口用于包装基本的写入方法。 Write方法len( p )字节数据从p写入底层的数据流。它会返回写入的字节数(0 <= n <= len( p ))和遇到的任何导致写入提取结束的错误。Write必须返回非nil的错误,如果它返回的 n < len( p )。Write不能修改切片p中的数据,即使临时修改也不行。

    func main(){ //此处的buf为上一个例子Reader代码中实例化并赋值的 buffer := bytes.NewBuffer(buf) ws := WriterStruct{} //将buffer中的buf写入ws buffer.WriteTo(&ws) fmt.Println("writerStruct.bytes:", string(ws.bytes)) } type WriterStruct struct { bytes []byte } func (ws *WriterStruct) Write(p []byte) (int, error) { ws.bytes = append(ws.bytes, p...) return len(p), nil }

    这个例子要和上一个例子结合着看,里面的buf在上一个例子中有读ReaderStruct里的text值并赋值至buf。 输出:

    writerStruct.bytes: 街角魔族是最好看的动漫!

    3、

    type Closer interface { Close() error }

    Closer接口用于包装基本的关闭方法。 在第一次调用之后再次被调用时,Close方法的的行为是未定义的。某些实现可能会说明他们自己的行为。 在整个标准库内都没有对Closer的引用,只有实现,因此这个就不举例了,用法都是开启某某连接/流,在用完/报错后在进行Close的操作。

    4、

    type Seeker interface { Seek(offset int64, whence int) (int64, error) }

    Seeker接口用于包装基本的移位方法。 Seek方法设定下一次读写的位置:偏移量为offset,校准点由whence确定:0表示相对于文件起始;1表示相对于当前位置;2表示相对于文件结尾。Seek方法返回新的位置以及可能遇到的错误。 移动到一个绝对偏移量为负数的位置会导致错误。移动到任何偏移量为正数的位置都是合法的,但其下一次I/O操作的具体行为则要看底层的实现。 在整个标准库内都没有对Seeker的引用,只有实现,因此这个就不举例了。

    5、

    type ReadCloser interface { Reader Closer }

    见名知意,就是Reader+Closer,例如在ioutil中的NopCloser方法返回的就是一个ReadCloser,但是里面的Close就是个空函数,毫无作用。

    6、

    type ReadSeeker interface { Reader Seeker }

    ReadSeeker接口聚合了基本的读取和移位操作。

    7、

    type WriteCloser interface { Writer Closer }

    WriteCloser接口聚合了基本的写入和关闭操作。

    8、

    type WriteSeeker interface { Writer Seeker }

    WriteSeeker接口聚合了基本的写入和移位操作。

    9、

    type ReadWriter interface { Reader Writer }

    ReadWriter接口聚合了基本的读写操作。

    10、

    type ReadWriteCloser interface { Reader Writer Closer }

    ReadWriteCloser接口聚合了基本的读写和关闭操作。

    11、

    type ReadWriteSeeker interface { Reader Writer Seeker }

    ReadWriteSeeker接口聚合了基本的读写和移位操作

    12、

    type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error) }

    ReaderAt接口包装了的ReadAt方法。

    ReadAt从底层输入流的偏移量off位置读取len( p )字节数据写入p, 它返回读取的字节数(0 <= n <= len( p ))和遇到的任何错误。

    当ReadAt返回n<len( p )时,它返回一个非零错误,解释为什么没有返回更多的字节。在这方面,Readat比Read更严格。

    即使ReadAt方法返回值 n < len( p ),它在被调用时仍可能使用p的全部长度作为暂存空间。如果有部分可用数据,但不够len( p )字节,ReadAt会阻塞直到获取len( p )个字节数据或者遇到错误。在这方面,ReadAt和Read是不同的。

    如果ReadAt返回的n=len( p )字节位于输入源的末尾,那么ReadAt可以返回err== EOF或err ==nil。

    如果ReadAt是从某个有偏移量的底层输入流(的Reader包装)读取,ReadAt方法既不应影响底层的偏移量,也不应被底层的偏移量影响。

    ReadAt的调用者可以对同一个输入源执行并行ReadAt调用。实现不能保留p。

    13、

    type WriterAt interface { WriteAt(p []byte, off int64) (n int, err error) }

    WriterAt接口包装了基本的WriteAt方法。

    在偏移结束时,WriteAt将len§字节从p写入基础数据流。它返回从p(0<=n<=len§)写入的字节数以及导致写入提前停止的任何错误。如果写入错误返回n<len§,则必须返回非零错误。

    如果WriteAt使用查找偏移量写入目标,则WriteAt不应影响也不受基础查找偏移量的影响。

    如果写入范围不重叠,WriteAt的调用方可以在同一目标上执行并行WriteAt调用。实现不能保留p。

    14、

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

    ByteReader是基本的ReadByte方法的包装。

    ReadByte读取输入中的单个字节并返回。如果没有字节可读取,会返回错误。

    15、

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

    ByteReader是基本的ReadByte方法的包装。

    ReadByte读取输入中的单个字节并返回。如果没有字节可读取,会返回错误。

    16、

    type ByteScanner interface { ByteReader UnreadByte() error }

    ByteScanner接口在基本的ReadByte方法之外还添加了UnreadByte方法。

    UnreadByte方法让下一次调用ReadByte时返回之前调用ReadByte时返回的同一个字节。连续调用两次UnreadByte方法而中间没有调用ReadByte时,可能会导致错误。

    17、

    type ByteWriter interface { WriteByte(c byte) error }

    ByteWriter是包装WriteByte方法的接口。

    18、

    type RuneReader interface { ReadRune() (r rune, size int, err error) }

    RuneReader是基本的ReadRune方法的包装。

    ReadRune读取单个utf-8编码的字符,返回该字符和它的字节长度。如果没有有效的字符,会返回错误。

    19、

    type RuneScanner interface { RuneReader UnreadRune() error }

    RuneScanner接口在基本的ReadRune方法之外还添加了UnreadRune方法。

    UnreadRune方法让下一次调用ReadRune时返回之前调用ReadRune时返回的同一个utf-8字符。连续调用两次UnreadRune方法而中间没有调用ReadRune时,可能会导致错误。

    20、

    type ReaderFrom interface { ReadFrom(r Reader) (n int64, err error) }

    ReaderFrom是包装ReadFrom方法的接口。

    ReadFrom方法从r读取数据直到EOF或者遇到错误。返回值n是读取的字节数,执行时遇到的错误(EOF除外)也会被返回。

    21、

    type WriterTo interface { WriteTo(w Writer) (n int64, err error) }

    WriterTo是包装WriteTo方法的接口。

    WriterTo写入数据到w,直到没有更多的数据要写入或发生错误时。返回值n是写入的字节数。还将返回写入过程中遇到的任何错误。如果可用,复制函数将使用WriterTo。

    以上就是io包全部的21个公共接口了。

    结构体

    1、

    type LimitedReader struct { R Reader // underlying reader N int64 // max bytes remaining }

    LimitedReader从R读取,但将返回的数据量限制为N个字节。每次读取更新N以标记剩余可以读取的字节数。Read在N<=0时或基础R返回EOF时返回EOF。 具体实现方法有: 1)func (l *LimitedReader) Read(p []byte) (n int, err error) Reader接口的实现方法

    r := strings.NewReader("街角魔族是最好看的动漫!") //限制27字节,只读取9字 n := int64(27) limitReader := io.LimitReader(r, n) bs := make([]byte, n) _, err := lr.Read(bs) checkErr(err) fmt.Println(string(bs))

    输出:

    街角魔族是最好看的

    2、

    type PipeReader struct { // 内含隐藏或非导出字段 }

    PipeReader是一个管道的读取端。 具体实现方法有: 1)func (r *PipeReader) Read(data []byte) (n int, err error) Read实现了标准的读取接口:它从管道中读取数据,阻塞直到写入端到达或写入端被关闭。如果用错误关闭写入端,则返回错误为ERR;否则ERR为EOF。

    2)func (r *PipeReader) Close() error Close关闭读取器;关闭后如果对管道的写入端进行写入操作,就会返回(0, ErrClosedPip)。

    3)func (r *PipeReader) CloseWithError(err error) error CloseWithError类似Close方法,但将调用Write时返回的错误改为err。

    3、

    type PipeWriter struct { // 内含隐藏或非导出字段 }

    PipeWriter是一个管道的写入端。 具体实现方法有: 1)func (w *PipeWriter) Write(data []byte) (n int, err error) Write实现了标准的写接口:它将数据写入管道,直到一个或多个读取端消耗完所有数据或读取端关闭为止。如果以错误关闭读取端,则该错误将作为ERR返回;否则ERR将为ErrClosedPipe。

    2)func (w *PipeWriter) Close() error Close关闭写入器;关闭后如果对管道的读取端进行读取操作,就会返回(0, EOF)。

    3)func (w *PipeWriter) CloseWithError(err error) error CloseWithError类似Close方法,但将调用Read时返回的错误改为err。

    注:以上两个结构体PipeWriter与PipeReader是结合使用的需要用Pipe()方法进行创建。

    创建函数func Pipe() (*PipeReader, *PipeWriter) Pipe创建一个同步的内存管道。 可用于连接期望io.Reader的代码和期望io.Writer的代码。

    管道上的读和写是一对一匹配的,除非需要多次读取才能使用单次写入。也就是说,每次对PipeWriter的写入都将阻塞,直到它满足从PipeReader读取的一个或多个读取,这些读取会完全消耗已写入的数据。

    数据直接从Write复制到相应的Read (或Reads);没有内部缓冲。 对读的并行调用和对写的并行调用也是安全的:单个调用将按顺序执行。 举个例子⬇

    r, w := io.Pipe() //开启写协程 go func() { for i := 0; i < 3; i++ { buf := bytes.NewBufferString("街角魔族是最好看的动漫!\n") w.Write(buf.Bytes()) } w.Close() }() str := strings.Builder{} bs := make([]byte, 37) //循环读取直至读取结束EOF或其他错误 for { _, err := r.Read(bs) if err != nil { break } str.Write(bs) } r.Close() fmt.Println(str.String())

    输出:

    街角魔族是最好看的动漫! 街角魔族是最好看的动漫! 街角魔族是最好看的动漫!

    值得注意的是在没有读取完Writer写入的内容前,主协程main是完全阻塞的,上面的writer如果换一个其他的比如strings.Builder.WriteString()连协程都进不去,主协程就执行完毕了。

    4、

    type SectionReader struct { // contains filtered or unexported fields }

    SectionReader在ReaderAt的基础上实现了Read,Seek和ReadAt。 具体实现方法有:

    1)func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader 结构体SectionReader的创建方法 NewSectionReader返回一个SectionReader,它从r开始读取,偏移量为off,并在n个字节后以EOF停止。

    2)func (s *SectionReader) Read(p []byte) (n int, err error) 实现了接口Reader的Read方法

    3)func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) 实现了接口ReaderAt的ReadAt方法

    4)func (s *SectionReader) Seek(offset int64, whence int) (int64, error) 实现了接口Seeker的Seek方法 func (*SectionReader) Size

    5)func (s *SectionReader) Size() int64 Size返回以字节为单位的片段大小。 举个例子⬇

    //SectionReader Method NewReader r := strings.NewReader("真言:街角魔族是最好看的动漫!") s := io.NewSectionReader(r, 9, 33) fmt.Println("原Reader内容:", *r) fmt.Printf("创建SectionReader,跳过%d个字节读取%d个字节\n", 9, 33) ReadBytes := make([]byte, r.Len()) //SectionReader Method Read s.Read(ReadBytes) fmt.Println("Read:", string(ReadBytes)) //SectionReader Method ReadAt //s中的内容为 街角魔族是最好看的动漫 var off int64 off = 15 //注意bs的长度不能大于s.Size()-off,此值为最大值,大于将报错 ReadAtBytes := make([]byte, s.Size()-off) //从off的位置开始读取s中的内容存进bs n, err := s.ReadAt(ReadAtBytes, off) checkErr(err) fmt.Println("ReadAt:", string(ReadAtBytes), n) //SectionReader Method seek //在下一次的读写操作中设置相对于whence的偏移量offset //返回相对于文件开头的新偏移量n与可能的错误 //具体请看文章type Seeker的描述 n64, err := s.Seek(6, io.SeekStart) checkErr(err) fmt.Printf("从%d开始读6字节\n", n64) buf := make([]byte, 6) _, err = s.Read(buf) checkErr(err) fmt.Println("经过seek的Read", string(buf), n64) //注意这里的ReadAt是从第3字节开始读的,如果受seek偏移量的影响 //那么应该是,从 '族'开始,但ReadAt并不受偏移量影响所以从'角'开始读 _, err = s.ReadAt(buf, 3) checkErr(err) fmt.Println("经过seek的ReadAt", string(buf))

    输出:

    原Reader内容: {真言:街角魔族是最好看的动漫! 0 -1} 创建SectionReader,跳过9个字节读取33个字节 Read: 街角魔族是最好看的动漫 ReadAt: 最好看的动漫 186开始读6字节 经过seek的Read 魔族 6 经过seek的ReadAt 角魔

    可以看的出来为什么说SectionReader是根据ReaderAt实现的而非Seeker了,虽然两者的效果很像,但是ReaderAt读取内容是无视Seeker偏移量的。且在读取数据大小上ReadAt是要比Read严格的,同样的Bytes在Read上即使设大了也会没事,但在ReadAt会报错。 其实上面说的关于ReadAt的两个注意点在我写的ReaderAt接口的描述中都有说明,但怕你们没注意在举例上实验一遍,还有几个需要注意的点我没举例子实验,你们感兴趣可以往上翻看看ReaderAt接口的描述。 以上就是io包全部的4个公共结构体了。

    函数

    1、Copy(dst Writer, src Reader) (written int64, err error) 将副本从src复制到dst,直到在src上达到EOF或发生错误。它返回复制的字节数和复制时遇到的第一个错误(如果有)。 成功的复制将返回err == nil而不是err == EOF。因为复制被定义为从src读取直到EOF,所以它不会将读取的EOF视为要报告的错误。 如果src实现WriterTo接口,则通过调用src.WriteTo(dst)实现该副本。否则,如果dst实现了ReaderFrom接口,则通过调用dst.ReadFrom(src)实现该副本。

    r := strings.NewReader("街角魔族是世界上最好看的动漫!") w := new(strings.Builder) //通过r.WriteTo()实现该副本 written, err := io.Copy(w, r) checkErr(err) fmt.Println(w.String(), written)

    输出:

    街角魔族是世界上最好看的动漫! 43

    2、func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) CopyBuffer与Copy相同,区别在于CopyBuffer逐步遍历提供的缓冲区(如果需要),而不是分配临时缓冲区。如果buf为nil,则分配一个;如果长度为零,则CopyBuffer会panic报错。 如果src实现WriterTo或dst实现ReaderFrom,则buf将不用于执行复制。

    r := strings.NewReader("街角魔族是世界上最好看的动漫!") w := new(strings.Builder) //如果len(buf)==0会报错 written, err := io.CopyBuffer(w, r, make([]byte, 9)) checkErr(err) fmt.Println(w.String(), written)

    输出:

    街角魔族是世界上最好看的动漫! 43

    3、func CopyN(dst Writer, src Reader, n int64) (written int64, err error) CopyN将n个字节(或直到出错)从src复制到dst。它返回复制的字节数以及复制时遇到的最早错误。返回时,只有err == nil时,writte == n。 如果dst实现了ReaderFrom接口,则使用该接口实现副本。

    r := strings.NewReader("街角魔族是世界上最好看的动漫!") w := new(strings.Builder) //如果n大于len(r)会报错 written, err := io.CopyN(w, r, 36) checkErr(err) fmt.Println(w.String(), written)

    输出:

    街角魔族是世界上最好看的 36

    4、func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) ReadAtLeast从r读取到buf,直到它至少读取了min字节。它返回复制的字节数n,如果读取的字节数少则返回错误。仅当未读取任何字节时,错误才是EOF。如果在读取少于最小字节后发生EOF,则ReadAtLeast返回ErrUnexpectedEOF。如果min大于buf的长度,则ReadAtLeast返回ErrShortBuffer。返回时,当且仅当err == nil时,n> = min。

    r := strings.NewReader("街角魔族是最好看的动漫!") //min如果大于len(r)会报错 min := 27 //这边buf的len如果小于min也会报错 buf := make([]byte, min) n, err := io.ReadAtLeast(r, buf, min) checkErr(err) fmt.Println(string(buf), n)

    输出:

    街角魔族是最好看的 27

    5、func ReadFull(r Reader, buf []byte) (n int, err error) ReadFull将r中的len(buf)个字节准确地读取到buf中。它返回复制的字节数,如果读取的字节数少则返回错误。仅当未读取任何字节时,错误才是EOF。如果在读取了一些但不是全部字节后发生EOF,则ReadFull返回ErrUnexpectedEOF。返回时,当且仅当err == nil时,n == len(buf)。

    r := strings.NewReader("街角魔族是最好看的动漫!") buf := make([]byte, 12) _, err := io.ReadFull(r, buf) checkErr(err) fmt.Println(string(buf)) //buf大于io.Reader时,Err=EOF longBuf := make([]byte, 100) _, err = io.ReadFull(r, longBuf) checkErr(err)

    输出:

    街角魔族 2020/07/14 16:22:27 unexpected EOF

    6、func WriteString(w Writer, s string) (n int, err error) WriteString将字符串s的内容写入w,w接收一个字节切片。如果w实现了StringWriter,则直接调用其WriteString方法。否则,w.Write只会被调用一次。

    io.WriteString(os.Stdout, "街角魔族是世界上最好看的动漫!")

    输出:

    街角魔族是世界上最好看的动漫!

    7、func LimitReader(r Reader, n int64) Reader LimitReader返回一个从r读取但在n字节后以EOF停止的Reader。 基础实现是一个* LimitedReader。 例子在结构体1写了,很简单就不再重复了。

    8、func MultiReader(readers ...Reader) Reader MultiReader返回一个Reader,它是所提供的输入阅读器的逻辑串联。它们被顺序读取。一旦所有输入均返回EOF,读取将返回EOF。如果任何读取器返回非零,非EOF错误,则Read将返回该错误。

    r := strings.NewReader("街角魔族是最好看的动漫!") r2 := strings.NewReader("说得对") w := new(strings.Builder) mr := io.MultiReader(r, r2) n, err := io.Copy(w, mr) checkErr(err) fmt.Println(w.String(), n)

    输出:

    街角魔族是最好看的动漫!说得对 45

    9、func TeeReader(r Reader, w Writer) Reader TeeReader返回一个Reader,该Reader向w写入从r读取的内容。通过r执行的所有r读取均与对w的相应写入匹配。没有内部缓冲-写入必须在读取完成之前完成。写入时遇到的任何错误均报告为读取错误。

    r := strings.NewReader("街角魔族是最好看的动漫!") w := new(strings.Builder) tee := io.TeeReader(r, w) p := make([]byte, r.Len()) fmt.Println("读取前w:", w.String()) _, err := tee.Read(p) checkErr(err) fmt.Println("读取的值", string(p)) //所有对tee中的r io.Reader的读取都会往tee中的w io.Writer写入 fmt.Println("读取后w:", w.String())

    输出:

    读取前w: 读取的值 街角魔族是最好看的动漫! 读取后w: 街角魔族是最好看的动漫!

    10、func MultiWriter(writers ...Writer) Writer MultiWriter创建一个Writers,将其写入复制到所有提供的写入器中,类似于Unix tee(1)命令。 每个写入一次写入每个列出的写入器。如果列出的写程序返回错误,则整个写操作将停止并返回错误;它不会在列表中继续下去。

    r := strings.NewReader("街角魔族是世界上最好看的动漫!") builder1 := new(strings.Builder) builder2 := new(strings.Builder) ws := io.MultiWriter(builder1, builder2) _, err := io.Copy(ws, r) checkErr(err) fmt.Print(builder1.String()) fmt.Print(builder2.String())

    输出:

    街角魔族是最好看的动漫!街角魔族是最好看的动漫!

    以上就是io的全部的10个公共函数了 其实例子早就写完了,但是太忙一直没写博文,本来标题是 ioutil/io/bufio/os io流,文件操作大全,这几个一起用的相关度非常高,但是实在太忙没工夫写os和bufio了,等以后有机会吧。 以上结束。

    Processed: 0.013, SQL: 9