极大提高java I/O效率的方法

来源:互联网 发布:php curl post https 编辑:程序博客网 时间:2024/06/05 14:12
Java代码 复制代码
  1. import java.nio.*;   
  2. import java.nio.channel.*;   
  3. import java.io.*;   
  4. public static void copy(File source, File dest) throws IOException {   
  5.  FileChannel in = null, out = null;   
  6.  try {    
  7.   in = new FileInputStream(source).getChannel();   
  8.   out = new FileOutputStream(dest).getChannel();   
  9.     
  10.   long size = in.size();   
  11.   MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size);   
  12.     
  13.   out.write(buf);   
  14.   if (in != null) in.close();   
  15.   if (out != null) out.close();   
  16.  }   
  17. }  


 

谈谈MappedByteBuffer JDK1.4中加入了一个新的包:NIO(java.nio.*).这个库最大的功能就是增加了对异步套接字的支持. 其实在其他语言中,包括在最原始的SOCKET实现(BSD SOCKET),这是一个早有的功能:异步回调读/写事件,
通过选择器动态选择感兴趣的事件,等等.不过好在SUN终于也开始支持它了.
我想这也是开放的好处之一吧(NIO是作为JSR-51项目引入的). 这里简单讲一下操作流程: 通过把一个套接字通道(SocketChannel)注册到一个选择器(Selector)中,不时调用后者的选择(select)方法就能返回
满足的选择键(SelectionKey),键中包含了SOCKET事件信息. 
异步套接字对服务器程序来说更具吸引力.一般同步SOCKET服务器的实现都是采用线程池来处理客户请求的,
基于请求超时时间和并发线程数目的限制,如果并发处理能力能够达到上千就已经是不错了.异步服务器的能力则至少是
它的数倍(有人测试一个简单的ECHO服务程序,说可以达到上万个并发,不知道是否真的能达到). SocketChannel的读
写是通过一个类叫ByteBuffer(java.nio.ByteBuffer)来操作的.这个类本身的设计是不错的,比直接操作byte[]方便多了. ByteBuffer有两种模式:直接/间接.间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即操作堆内存(byte[]).
但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存.这时就必须使用"直接"模式,
即MappedByteBuffer,文件映射. 先中断一下,谈谈操作系统的内存管理.一般操作系统的内存分两部分:物理内存;虚拟内
存.虚拟内存一般使用的是页面映像文件,即硬盘中的某个(某些)特殊的文件.操作系统负责页面文件内容的读写,这个过程叫
"页面中断/切换". MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer.这是一
个很好的设计,除了一点,令人头疼的一点. MappedByteBuffer只能通过调用FileChannel的map()取得,再没有其他方式
.但是令人奇怪的是,SUN提供了map()却没有提供unmap().这样会导致什么后果呢? 
举个例子,文件test.tmp是一个临时构建的文件,在业务处理(通过SocketChannel发送)完之后将不再有效.一般的做法都
是这样的: 
(1)File file = new File("test.tmp"); FileInputStream in = new FileInputStream(file); FileChannel ch = in.getChannel(); MappedByteBuffer buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); (2)SocketChannel sch = 已经构造好了; while (buf.hasRemaining()) sch.write(buf); (3)ch.close(); in.close(); file.delete(); 上面的操作都会正常的完成,除了最后一步:文件无法删除!即使你通过资源管理器直接强制删除也不行,说"文件正在使用". 为什么会出现这种情况? 说"文件正在使用",说明文件句柄没有清零,还有在使用它的地方---就是被MappedByteBuffer占用了!尽管FileChannel,
FileInputStream都已经关闭了,但是在map里还打开着一个文件句柄.但是在外部看不见也无法操作它.那么这个句柄在什
么时候才会正常地关闭呢?根据JAVADOC的说明,是在垃圾收集的时候.而众所周知垃圾收集是程序根本无法控制的. 既然MappedByteBuffer是从FileChannel中map()出来的,为什么它又不提供unmap()呢?SUN自己也没有讲清楚为什
么.O'Reilly的<<Java NIO>>中说是因为"安全"的原因,但是到底unmap()会怎么不安全,作者也没有讲清楚. 在SUN的BUG库中,这个问题在02年就有人提交了BUG报告,但是SUN自己不认为是BUG,而只是一个
RFE(Request For Enhancement),有待改进. 好在网上牛人多.在BUG报告(http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038)中,有网友
提出了一个解决的办法(具体参看上面的URL),可行,至少我在WINDOWS2000下测试是可以的.唯一的不足是并不是每次
都能马上生效(文件彻底被删除),有的时候要延迟一会再试. 再抱怨两句.对于网友们的BUG报告,SUN似乎不怎么重视.粗看一下上面的BUG报告,会发现居然上世纪90年代的报告还赫然
在列.有兴趣的朋友不妨仔细研究研究. 还有一点忘了说了.ByteBuffer是无法派生的.因为这个抽象类中定义了几个包抽象方法,即实现类只能位于java.nio包中.
本来自己实现MappedByteBuffer也不难,只是效率比SUN实现的肯定要低一些.毕竟后者是可以直接与操作系统打交道的.而
要是自己实现的化,只能通过一个中间的堆缓冲区进行过渡. 我不知道为什么SUN不提供ByteBuffer的派生.毕竟这是一个很实用的类,如果允许派生,那么我就可以操作的就不仅仅限于
堆内存和文件了,我可以扩展到任何存储设备. 
Java代码 复制代码
  1. public boolean copyTo(String strSourceFileName, String strDestDir) {   
  2. File fileSource = new File(strSourceFileName);   
  3. File fileDest = new File(strDestDir);   
  4.     
  5. // 如果源文件不存或源文件是文件夹   
  6. if (!fileSource.exists() || !fileSource.isFile()) {   
  7. System.out.println("错误: FileOperator.java copyTo函数,/n原因: 源文件["  
  8. + strSourceFileName + "],不存在或是文件夹!");   
  9. return false;   
  10. }   
  11.     
  12. // 如果目标文件夹不存在   
  13. if (!fileDest.isDirectory() || !fileDest.exists()) {   
  14. if (!fileDest.mkdirs()) {   
  15. System.out.println("错误: FileOperator.java copyTo函数,/n原因:目录文件夹不存,在创建目标文件夹时失败!");   
  16. return false;   
  17. }   
  18. }   
  19.     
  20. try {   
  21. String strAbsFilename = strDestDir + File.separator + fileSource.getName();   
  22.     
  23. FileInputStream fileInput = new FileInputStream(strSourceFileName);   
  24. FileOutputStream fileOutput = new FileOutputStream(strAbsFilename);   
  25.     
  26. int i = 0;   
  27. int count = -1;   
  28.     
  29. long nWriteSize = 0;   
  30. long nFileSize = fileSource.length();   
  31.     
  32. byte[] data = new byte[BUFFER];   
  33.     
  34. while (-1 != (count = fileInput.read(data, 0, BUFFER))) {   
  35. fileOutput.write(data, 0, count);   
  36. nWriteSize += count;   
  37. long size = (nWriteSize * 100) / nFileSize;   
  38. long t = nWriteSize;   
  39. String msg = null;   
  40. if (size <= 100 && size >= 0) {   
  41. msg = "/r拷贝文件进度: " + size + "% /t" + "/t 已拷贝: " + t;   
  42. else if (size > 100) {   
  43. msg = "/r拷贝文件进度: " + 100 + "% /t" + "/t 已拷贝: " + t;   
  44. }   
  45. }   
  46.     
  47. fileInput.close();   
  48. fileOutput.close();   
  49.     
  50. System.out.println("/n拷贝文件成功!");   
  51. return true;   
  52.     
  53. catch (Exception e) {   
  54. System.out.println("异常信息:[");   
  55. e.printStackTrace();   
  56. return false;   
  57. }   
  58. }  
原创粉丝点击