- import java.nio.*;
- import java.nio.channel.*;
- import java.io.*;
- public static void copy(File source, File dest) throws IOException {
- FileChannel in = null, out = null;
- try {
- in = new FileInputStream(source).getChannel();
- out = new FileOutputStream(dest).getChannel();
- long size = in.size();
- MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size);
- out.write(buf);
- if (in != null) in.close();
- if (out != null) out.close();
- }
- }
import java.nio.*;
import java.nio.channel.*;
import java.io.*;
public static void copy(File source, File dest) throws IOException {
FileChannel in = null, out = null;
try {
in = new FileInputStream(source).getChannel();
out = new FileOutputStream(dest).getChannel();
long size = in.size();
MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size);
out.write(buf);
if (in != null) in.close();
if (out != null) out.close();
}
}
谈谈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的派生.毕竟这是一个很实用的类,如果允许派生,那么我就可以操作的就不仅仅限于
堆内存和文件了,我可以扩展到任何存储设备.
- public boolean copyTo(String strSourceFileName, String strDestDir) {
- File fileSource = new File(strSourceFileName);
- File fileDest = new File(strDestDir);
- // 如果源文件不存或源文件是文件夹
- if (!fileSource.exists() || !fileSource.isFile()) {
- System.out.println("错误: FileOperator.java copyTo函数,/n原因: 源文件["
- + strSourceFileName + "],不存在或是文件夹!");
- return false;
- }
- // 如果目标文件夹不存在
- if (!fileDest.isDirectory() || !fileDest.exists()) {
- if (!fileDest.mkdirs()) {
- System.out.println("错误: FileOperator.java copyTo函数,/n原因:目录文件夹不存,在创建目标文件夹时失败!");
- return false;
- }
- }
- try {
- String strAbsFilename = strDestDir + File.separator + fileSource.getName();
- FileInputStream fileInput = new FileInputStream(strSourceFileName);
- FileOutputStream fileOutput = new FileOutputStream(strAbsFilename);
- int i = 0;
- int count = -1;
- long nWriteSize = 0;
- long nFileSize = fileSource.length();
- byte[] data = new byte[BUFFER];
- while (-1 != (count = fileInput.read(data, 0, BUFFER))) {
- fileOutput.write(data, 0, count);
- nWriteSize += count;
- long size = (nWriteSize * 100) / nFileSize;
- long t = nWriteSize;
- String msg = null;
- if (size <= 100 && size >= 0) {
- msg = "/r拷贝文件进度: " + size + "% /t" + "/t 已拷贝: " + t;
- } else if (size > 100) {
- msg = "/r拷贝文件进度: " + 100 + "% /t" + "/t 已拷贝: " + t;
- }
- }
- fileInput.close();
- fileOutput.close();
- System.out.println("/n拷贝文件成功!");
- return true;
- } catch (Exception e) {
- System.out.println("异常信息:[");
- e.printStackTrace();
- return false;
- }
- }
public boolean copyTo(String strSourceFileName, String strDestDir) {
File fileSource = new File(strSourceFileName);
File fileDest = new File(strDestDir);
// 如果源文件不存或源文件是文件夹
if (!fileSource.exists() || !fileSource.isFile()) {
System.out.println("错误: FileOperator.java copyTo函数,/n原因: 源文件["
+ strSourceFileName + "],不存在或是文件夹!");
return false;
}
// 如果目标文件夹不存在
if (!fileDest.isDirectory() || !fileDest.exists()) {
if (!fileDest.mkdirs()) {
System.out.println("错误: FileOperator.java copyTo函数,/n原因:目录文件夹不存,在创建目标文件夹时失败!");
return false;
}
}
try {
String strAbsFilename = strDestDir + File.separator + fileSource.getName();
FileInputStream fileInput = new FileInputStream(strSourceFileName);
FileOutputStream fileOutput = new FileOutputStream(strAbsFilename);
int i = 0;
int count = -1;
long nWriteSize = 0;
long nFileSize = fileSource.length();
byte[] data = new byte[BUFFER];
while (-1 != (count = fileInput.read(data, 0, BUFFER))) {
fileOutput.write(data, 0, count);
nWriteSize += count;
long size = (nWriteSize * 100) / nFileSize;
long t = nWriteSize;
String msg = null;
if (size <= 100 && size >= 0) {
msg = "/r拷贝文件进度: " + size + "% /t" + "/t 已拷贝: " + t;
} else if (size > 100) {
msg = "/r拷贝文件进度: " + 100 + "% /t" + "/t 已拷贝: " + t;
}
}
fileInput.close();
fileOutput.close();
System.out.println("/n拷贝文件成功!");
return true;
} catch (Exception e) {
System.out.println("异常信息:[");
e.printStackTrace();
return false;
}
}