丨文件io实现高效正确的读写并非易事_第1页
丨文件io实现高效正确的读写并非易事_第2页
丨文件io实现高效正确的读写并非易事_第3页
丨文件io实现高效正确的读写并非易事_第4页
丨文件io实现高效正确的读写并非易事_第5页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

有一个项目需要 的对账文件定时对账,原先一直是单机处理的,没什么问题。后来为了提升性能,使用双节点同时处理对账,每一个节点处理部分对账数据,但新增的节点在处理文件文的时候总是到乱码。为模拟这个场景,我们使用GBK编码把“你好hi”写入一个名为 o.txt的文本文件,123o.txt"),"你好1113:06:28.955[main]INFO虽然我们打开文本文件时看到的是“你好hi”,但不管是什么文字,计算机中都是按照一定的规则将其以二进制保存的。这个规则就是字符集,字符集枚举了所有支持的字符映射成二进制的映射表。在处理文件读写的时候,如果是在字节层面进行操作,那么不会涉及字符编码问题;而如果需要在字符层面进行读写的话,就需要明确字符的编码方式也就是字符集了。12345678char[]chars=newchar[10];Stringcontent="";try(FileReaderfileReader=newinto.txt"))while((count=fileReader.read(chars))!=-1){content+=newString(chars,0,count);}}99("result:{}",可以看到,是使用了FileReader类以字符方式进行文件,日志中出来的“你1113:06:28.961[main]INFO显然,这里并没有指定以什么字符集来文件中的字符。查看JDK文档可以发现,FileReader是以当前机器的默认字符集来文件的,如果希望指定字符集的话,需要直接使用InputStreamReader和FileInputStream。到这里我们就明白了,FileR("charset:{}",o2.txt"),"你好1113:06:28.961[main]INFO213:06:28.962[main]INFO可以看到,当前机器默认字符集是UTF-8,当然无法GBK编码的汉字。UTF-8编码的“你好”的十六进制是E4BDA0E5A5BD,每一个汉字需要三个字节;而GBK编码的汉字,每一个汉字两个字节。字节长度都不一样,以GBK编码后保存的汉字,以UTF8进行FileInputStream然后使用InputStreamReader字符流,并指定字符集为GBK:123456789privatestaticvoidright1()throwsIOException{char[]chars=newchar[10];Stringcontent=try(FileInputStreamfileInputStream=newInputStreamReaderinputStreamReader=newInputStreamReader(fileInputSintcount;while((count=inputStreamReader.read(chars))!=-1){content+=newString(chars,0,count);}}("result:{}",}从日志中看到,修复后的代码正确到了“你好Hi”1113:06:28.963[main]INFOJDK1.7FilesreadAllL("result:{}",o.txt"),但这种方式有个问题是,超出内存大小的大文件时会出现OOM。为什么呢publicstaticList<String>readAllLines(Pathpath,Charsetcs)throwstry(BufferedReaderreader=newBufferedReader(path,cs))List<String>result=newfor(;;)5Stringline=6if(line==789}return12}Filelineslines与readAllLines方法返回List<String>不同,lines方法返回的是Stream<String取到内存中,因此避免了OOM。接下来,我通过一段代码测试一下。我们尝试一个1亿1万行的文件,文件占用磁盘空间超过4GB。如果使用-Xmx512m-Xms512m启动JVM控制最大堆内存为512M的话,肯定无法这样的大文件,但通过Files.lines方法就没问题。在下面的代码中,首先输出这个文件的大小,然后计算20万行数据和200万行数据代码代码123456789("filesize:{}",StopWatchstopWatch=("lines{}",("lines200000lines");AtomicLongAtomicLongatomicLong=("totallines{}",可以看到,实现了全文件的、统计了整个文件的行数,并没有出现OOM;200万行数据耗时760ms,20万行数据仅需267ms。这些都可以说明,File.lines方法问题在于完文件后没有关闭。我们通常会认为静态方法的调用不涉及资源释放,因为方法调用结束自然代表资源使用完成,由API释放资源,但对于Files类的一些返回tream的方法并不是这样。这,是一个很容易被忽略的严重问题。我就曾遇到过一个案例:程序在生产上运行一段时间后就会出现toomanyfiles的错误,OSFiles.lines方法上。我们来重现一下这个问题,随便写入10行数据到一个demo.txtIntStream.rangeClosed(1,10).mapToObj(i->,UTF_8,CREATE,然后使用Files.lines方法这个文件100万次,每一行计数器代代123456789LongAdderlongAdder=newtry).forEach(i->Files.lines(Paths.get("demo.txt")).forEach(line->}catch(IOExceptione){}("total:{}",1234java.nio.file.FileSystemException:demo.txt:Toomanyopen使用lsof1万多个1-23zhuyezhuye67lsof-p63937|grepdemo.txt|wc- 其实,在修复方式很简单,使用try来包裹Stream代代123456789LongAdderlongAdder=new).forEach(i->try(Stream<String>lines=Files.lines(Paths.get("demo.txt"))){lines.forEach(line->longAdder.increment());}catch(IOExceptione){}("total:{}",修改后的代码不再出现错误日志,因为了100万次包含10行数据的文件,所以最正确输出了10001114:19:29.410[main]INFO查看lines方法源码可以发现,Stream的close了一个回调,来关12123456789publicstaticStream<String>lines(Pathpath,Charsetcs)throws{BufferedReaderbr=Files.newBufferedReader(path,cs);try{return}catch(Error|RuntimeException{try}catch(IOException{try}catch(Throwableignore)}throw}}privatestaticRunnableasUncheckedRunnable(Closeable{return()->try}catch(IOExceptione)thrownew}}比如,文件操作就是一大块数据(比如8KB)到缓冲区,后续的可以直接从缓冲区返回数据,而不是每次都直接对应文件IO。写操作也是类似。如果每次写几十字节到文件都对应一次IO操作,那么写一个几百兆的大文件可能就需要千万次的IO操接下来,我就通过几个实验,和你说明使用缓冲Buffer的重要性,并对比下不同使用方式的文件读写性能,来帮助你用对、用好Buffer。我曾遇到过这么一个案例,一段先进行文件读入再简单处理后写入另一个文件的业务代码,由于开发人员使用了单字节的写入方式,导致执行得巨慢,业务量上来后需要数小时才能完成。10035MB123).mapToObj(i->,UTF_8,CREATE,当时开发人员写的文件处理代码大概是这样的:使用FileInputStream获得一个文件输入流,然后调用其read方法每次一个字节,最后通过一个FileOutputStream文件输出当于文件:代代privatestaticvoidperByteOperation()throwsIOExceptiontry(FileInputStreamfileInputStream=newFileOutputStreamfileOutputStream=newintwhile((i=fileInputStream.read())!=-1) 9这样的实现,一个35MB的文件居然耗时190秒显然,每一个字节、每写入一个字节都进行一次IO操作,代价太大了。解决方案就是,考虑使用缓冲区作为过渡,从原文件一定数量的数据到缓冲区,写入100FileInputStreambyte一定字节的数据,同时使用FileOutputStream的byte[]的重载实现从缓冲区写入privatestaticvoidbufferOperationWith100Buffer()throwsIOExceptiontry(FileInputStreamfileInputStream=newFileOutputStreamfileOutputStream=newbyte[]buffer=newintlen=while((len=fileInputStream.read(buffer))!=-1)fileOutputStream.write(buffer,0, 10仅仅使用了100个字节的缓冲区作为过渡,完成35M文件的耗时缩短到了26秒,是无缓冲时性能的7倍;如果把缓冲区放大到1000字节,耗时可以进一步缩短到342毫秒。可以看到,在进行文件IO处理的时候,使用合适的缓冲区可以明显提高性能。你可能会说,实现文件读写还要自己new一个缓冲区出来,太麻烦了,不是有一个和是的,它们在内部实现了一个默认8KB大小的缓冲区。但是,在使用BufferedInputStreamBufferedOutputStream直接使用BufferedInputStream和BufferedOutputStream;privateprivatestaticvoidbufferedStreamByteOperation()throwsIOExceptiontry(BufferedInputStreambufferedInputStream=newBufferedOutputStreambufferedOutputStream=newintwhile((i=bufferedInputStream.read())!=-1) 10privatestaticvoidbufferedStreamBufferOperation()throwsIOExceptiontry(BufferedInputStreambufferedInputStream=newBufferedOutputStreambufferedOutputStream=newbyte[]buffer=newintlen=while((len=bufferedInputStream.read(buffer))!=-1)bufferedOutputStream.write(buffer,0,}}}privatestaticvoidlargerBufferOperation()throwsIOExceptiontry(FileInputStreamfileInputStream=newFileOutputStreamfileOutputStream=newbyte[]buffer=newintlen=while((len=fileInputStream.read(buffer))!=-1)fileOutputStream.write(buffer,0,}}3232 Task 456可以看到,第式虽然使用了缓冲流,但逐字节的操作因为方法调用次数实在太多还是慢,耗时1.4秒;后面两种方式的性能差不多,耗时110毫秒左右。虽然第三种方式没有使用缓冲流,但使用了8KB大小的缓冲区,和缓冲流默认的缓冲区大小相同。有一个固定大小较大的缓冲,也就是使用BufferedInputStream和BufferedOutputStream做为后备的稳定的二次缓冲,就非常有意义了。FileChannel的transfreTo方法进行流的。在一些操作系统(比如高版本的Linux和UNIX)上可以实现DMA(直接内存),也就是数据从磁盘经过总线直接发送到目标文件,无需经过内存和CPU进行数据中转:privatestaticvoidfileChannelOperation()throwsIOExceptionFileChannelin=FileChannel.open(Paths.get("src.txt"),FileChannelout=FileChannel.open(Paths.get("dest.txt"),CREATE,in.transferTo(0,in.size(),5你可以通过这篇文章,了解transferTo方法的细节35MB12 Task3456789可以看到,最慢的是单字节读写文件流的方式,耗时183秒,最快的是Filestry-with-resourcesStream,确保底层文件资源可以释放,避免产生toomanyopenfiles的问题。量读写减少IO次数,性能会好很多。一般可以考虑直接使用缓冲输入输出流BufferedXXXStream,追求极限性能的话可以考虑使用FileChannel进行流转发。最后我要强调的是,文件操作因为涉及操作系统和文件系统的实现,JDK并不能确保所有IOAPI在所有平台的逻辑一致性,代码迁移到新的操作系统或文件系统时,要重新进行功今天用到的代码,我都放在了上,你

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论