发布网友 发布时间:2024-10-02 00:47
共1个回答
热心网友 时间:2024-10-29 18:07
Golang:I/O操作,千万不要小瞧这些知识点I/O操作也叫输入输出操作。其中I是指Input,O是指Output,用于读或者写数据的,有些语言中也叫流操作,是指数据通信的通道。
Golang标准库对IO的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。
io包中提供I/O原始操作的一系列接口。它主要包装了一些已有的实现,如os包中的那些,并将这些抽象成为实用性的功能和一些其他相关的接口。
在io包中最重要的是两个接口:Reader和Writer接口,首先来介绍这读的操作。
Reader接口的定义,Read()方法用于读取数据。
Read将len(p)个字节读取到p中。它返回读取的字节数n(0=n=len(p))以及任何遇到的错误。即使Read返回的nlen(p),它也会在调用过程
中使用p的全部作为暂存空间。若一些数据可用但不到len(p)个字节,Read会照例返回可用的东西,而不是等待更多。
当Read在成功读取n0个字节后遇到一个错误或EOF情况,它就会返回读取的字节数,这种一般情况的一个例子就是Reader在输入流结束时会返回一个非零的字节数,可能的返回不是err==EOF就是err==nil。无论如何,下一个Read都应当返回0、EOF。
调用者应当总在考虑到错误err前处理n0的字节。这样做可以在读取一些字节,以及允许的EOF行为后正确地处理I/O错误。
Read的实现会阻止返回零字节的计数和一个nil错误,调用者应将这种情况视作空操作。
ReaderFrom接口的定义,封装了基本的ReadFrom方法。
ReadFrom从r中读取数据到对象的数据流中,直到r返回EOF或r出现读取错误为止,返回值n是读取的字节数,返回值err就是r的返回值err。
定义ReaderAt接口,ReaderAt接口封装了基本的ReadAt方法
ReadAt从对象数据流的off处读出数据到p中,忽略数据的读写指针,从数据的起始位置偏移off处开始读取,如果对象的数据流只有部分可用,不足以填满p,则ReadAt将等待所有数据可用之后,继续向p中写入,直到将p填满后再返回。
在这点上ReadAt要比Read更严格,返回读取的字节数n和读取时遇到的错误,如果nlen(p),则需要返回一个err值来说明,为什么没有将p填满(比如EOF),如果n=len(p),而且对象的数据没有全部读完,则err将返回nil,如果n=len(p),而且对象的数据刚好全部读完,则err将返回EOF或者nil(不确定)
file类是在os包中的,封装了底层的文件描述符和相关信息,同时封装了Read和Write的实现。
读取文件中的数据:
Writer接口的定义,Write()方法用于写出数据。
Write将len(p)个字节从p中写入到基本数据流中。它返回从p中被写入的字节数n(0=n=len(p))以及任何遇到的引起写入提前停止的错误。若Write返回的nlen(p),它就必须返回一个非nil的错误。Write不能修改此切片的数据,即便它是临时的。
Seeker接口的定义,封装了基本的Seek方法。
Seeker用来移动数据的读写指针,Seek设置下一次读写操作的指针位置,每次的读写操作都是从指针位置开始的。
whence的含义:
如果whence为0:表示从数据的开头开始移动指针
如果whence为1:表示从数据的当前指针位置开始移动指针
如果whence为2:表示从数据的尾部开始移动指针
offset是指针移动的偏移量
返回移动后的指针位置和移动过程中遇到的任何错误
WriterTo接口的定义,封装了基本的WriteTo方法。
WriterTo将对象的数据流写入到w中,直到对象的数据流全部写入完毕或遇到写入错误为止。返回值n是写入的字节数,返回值err就是w的返回值err。
定义WriterAt接口,WriterAt接口封装了基本的WriteAt方法
WriteAt将p中的数据写入到对象数据流的off处,忽略数据的读写指针,从数据的起始位置偏移off处开始写入,返回写入的字节数和写入时遇到的错误。如果nlen(p),则必须返回一个err值来说明为什么没有将p完全写入
file类是在os包中的,封装了底层的文件描述符和相关信息,同时封装了Read和Write的实现。
写出数据到本地文件:
Golang将日志同时输出到控制台和文件日常开发当中需要将golang的log包打印的日志同时输出到控制台和文件,应该如何解决这个问题?
log包可以通过SetOutput()方法指定日志输出的方式(Writer),但是只能指定一个输出的方式(Writer)。我们利用io.MultiWriter()将多个Writer拼成一个Writer使用的特性,把log.Println()输出的内容分流到控制台和文件当中。
原文地址
详解golang中bufio包的实现原理最近用golang写了一个处理文件的脚本,由于其中涉及到了文件读写,开始使用golang中的io包,后来发现golang中提供了一个bufio的包,使用这个包可以大幅提高文件读写的效率,于是在网上搜索同样的文件读写为什么bufio要比io的读写更快速呢?根据网上的资料和阅读源码,以下来详细解释下bufio的高效如何实现的。
bufio包介绍?
bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
以上为官方包的介绍,在其中我们能了解到的信息如下:
bufio是通过缓冲来提高效率
简单的说就是,把文件读取进缓冲(内存)之后再读取的时候就可以避免文件系统的io从而提高速度。同理,在进行写操作时,先把文件写入缓冲(内存),然后由缓冲写入文件系统。看完以上解释有人可能会表示困惑了,直接把内容-文件和内容-缓冲-文件相比,缓冲区好像没有起到作用嘛。其实缓冲区的设计是为了存储多次的写入,最后一口气把缓冲区内容写入文件。下面会详细解释
bufio封装了io.Reader或io.Writer接口对象,并创建另一个也实现了该接口的对象
io.Reader或io.Writer接口实现read()和write()方法,对于实现这个接口的对象都是可以使用这两个方法的
bufio包实现原理
bufio源码分析
Reader对象
bufio.Reader是bufio中对io.Reader的封装
//Readerimplementsbufferingforanio.Readerobject.
typeReaderstruct{
??buf????[]byte
??rd?????io.Reader//readerprovidedbytheclient
??r,w????int???//bufreadandwritepositions
??err????error
??lastByte??int
??lastRuneSizeint
}
bufio.Read(p[]byte)相当于读取大小len(p)的内容,思路如下:
当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区
当缓存区没有内容的时候且len(p)len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可
当缓存区没有内容的时候且len(p)len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容)
以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区(此时和情况1一样)
以下是源码
//Readreadsdataintop.
//Itreturnsthenumberofbytesreadintop.
//ThebytesaretakenfromatmostoneReadontheunderlyingReader,
//hencenmaybelessthanlen(p).
//AtEOF,thecountwillbezeroanderrwillbeio.EOF.
func(b*Reader)Read(p[]byte)(nint,errerror){
??n=len(p)
??ifn==0{
????return0,b.readErr()
??}
??ifb.r==b.w{
????ifb.err!=nil{
??????return0,b.readErr()
????}
????iflen(p)=len(b.buf){
??????//Largeread,emptybuffer.
??????//Readdirectlyintoptoavoidcopy.
??????n,b.err=b.rd.Read(p)
??????ifn0{
????????panic(errNegativeRead)
??????}
??????ifn0{
????????b.lastByte=int(p[n-1])
????????b.lastRuneSize=-1
??????}
??????returnn,b.readErr()
????}
????//Oneread.
????//Donotuseb.fill,whichwillloop.
????b.r=0
????b.w=0
????n,b.err=b.rd.Read(b.buf)
????ifn0{
??????panic(errNegativeRead)
????}
????ifn==0{
??????return0,b.readErr()
????}
????b.w+=n
??}
??//copyasmuchaswecan
??n=copy(p,b.buf[b.r:b.w])
??b.r+=n
??b.lastByte=int(b.buf[b.r-1])
??b.lastRuneSize=-1
??returnn,nil
}
说明:
reader内部通过维护一个r,w即读入和写入的位置索引来判断是否缓存区内容被全部读出
Writer对象
bufio.Writer是bufio中对io.Writer的封装
//Writerimplementsbufferingforanio.Writerobject.
typeWriterstruct{
??errerror
??buf[]byte
??n?int
??wrio.Writer
}
bufio.Write(p[]byte)的思路如下
判断buf中可用容量是否可以放下p
如果能放下,直接把p拼接到buf后面,即把内容放到缓冲区
如果缓冲区的可用容量不足以放下,且此时缓冲区是空的,直接把p写入文件即可
如果缓冲区的可用容量不足以放下,且此时缓冲区有内容,则用p把缓冲区填满,把缓冲区所有内容写入文件,并清空缓冲区
判断p的剩余内容大小能否放到缓冲区,如果能放下(此时和步骤1情况一样)则把内容放到缓冲区
如果p的剩余内容依旧大于缓冲区,(注意此时缓冲区是空的,情况和步骤2一样)则把p的剩余内容直接写入文件
//Writewritesthecontentsofpintothebuffer.
//Itreturnsthenumberofbyteswritten.
//Ifnnlen(p),italsoreturnsanerrorexplaining
//whythewriteisshort.
func(b*Writer)Write(p[]byte)(nnint,errerror){
??forlen(p)b.Available()b.err==nil{
????varnint
????ifb.Buffered()==0{
??????//Largewrite,emptybuffer.
??????//Writedirectlyfromptoavoidcopy.
??????n,b.err=b.wr.Write(p)
????}else{
??????n=copy(b.buf[b.n:],p)
??????b.n+=n
??????b.flush()
????}
????nn+=n
????p=p[n:]
??}
??ifb.err!=nil{
????returnnn,b.err
??}
??n:=copy(b.buf[b.n:],p)
??b.n+=n
??nn+=n
??returnnn,nil
}
说明:
b.wr存储的是一个io.writer对象,实现了Write()的接口,所以可以使用b.wr.Write(p)将p的内容写入文件
b.flush()会将缓存区内容写入文件,当所有写入完成后,因为缓存区会存储内容,所以需要手动flush()到文件
b.Available()为buf可用容量,等于len(buf)-n
下图解释的是其中一种情况,即缓存区有内容,剩余p大于缓存区
golang文件操作摘抄
译者按:rename和move原理一样
译者按:熟悉Linux的读者应该很熟悉权限模式,通过Linux命令chmod可以更改文件的权限
补充了原文未介绍的flag
一个普通的文件是一个指向硬盘的inode的地方。硬链接创建一个新的指针指向同一个地方。只有所有的链接被删除后文件才会被删除。硬链接只在相同的文件系统中才工作。你可以认为一个硬链接是一个正常的链接。
symboliclink,又叫软连接,和硬链接有点不一样,它不直接指向硬盘中的相同的地方,而是通过名字引用其它文件。他们可以指向不同的文件系统中的不同文件。并不是所有的操作系统都支持软链接。
复制文件
可以使用os包写入一个打开的文件。因为Go可执行包是静态链接的可执行文件,你import的每一个包都会增加你的可执行文件的大小。其它的包如io、`ioutil`、`bufio`提供了一些方法,但是它们不是必须的。
ioutil包有一个非常有用的方法WriteFile()可以处理创建/打开文件、写字节slice和关闭文件一系列的操作。如果你需要简洁快速地写字节slice到文件中,你可以使用它。
bufio包提供了带缓存功能的writer,所以你可以在写字节到硬盘前使用内存缓存。当你处理很多的数据很有用,因为它可以节省操作硬盘I/O的时间。在其它一些情况下它也很有用,比如你每次写一个字节,把它们攒在内存缓存中,然后一次写入到硬盘中,减少硬盘的磨损以及提升性能。
读取最多N个字节
os.File提供了文件操作的基本功能,而io、ioutil、bufio提供了额外的辅助函数。
有缓存写也有缓存读。缓存reader会把一些内容缓存在内存中。它会提供比os.File和io.Reader更多的函数,缺省的缓存大小是4096,最小缓存是16。
Scanner是bufio包下的类型,在处理文件中以分隔符分隔的文本时很有用。通常我们使用换行符作为分隔符将文件内容分成多行。在CSV文件中,逗号一般作为分隔符。os.File文件可以被包装成bufio.Scanner,它就像一个缓存reader。我们会调用Scan()方法去读取下一个分隔符,使用Text()或者Bytes()获取读取的数据。
分隔符可以不是一个简单的字节或者字符,有一个特殊的方法可以实现分隔符的功能,以及将指针移动多少,返回什么数据。如果没有定制的SplitFunc提供,缺省的ScanLines会使用newline字符作为分隔符,其它的分隔函数还包括ScanRunes和ScanWords,皆在bufio包中。
打包(zip)文件
其它
临时文件和目录
ioutil提供了两个函数:TempDir()和TempFile()。使用完毕后,调用者负责删除这些临时文件和文件夹。有一点好处就是当你传递一个空字符串作为文件夹名的时候,它会在操作系统的临时文件夹中创建这些项目(/tmponLinux)。os.TempDir()返回当前操作系统的临时文件夹。
上面的例子复制整个文件内容到内存中,传递给hash函数。另一个方式是创建一个hashwriter,使用Write、WriteString、Copy将数据传给它。下面的例子使用md5hash,但你可以使用其它的Writer。
聊聊golang的zap的ZapKafkaWriter本文主要研究一下golang的zap的ZapKafkaWriter
WriteSyncer内嵌了io.Writer接口,定义了Sync方法;Sink接口内嵌了zapcore.WriteSyncer及io.Closer接口;ZapKafkaWriter实现Sink接口及zapcore.WriteSyncer接口,其Write方法直接将data通过kafka发送出去。