1.NIO概述
Channel与Stream的不同:
- Stream是具有方向性的,有输入流 or 输出流;Channel是双向的,既可以读又可以写。
- Stream的读和写都是阻塞式的;但是Channel有两种模式,既可以阻塞式读写,又可以非阻塞式读写。
Note:如果一个任务单线程就可以执行,那么往往比使用多线程效率要高,NIO就是一个例子。多线程不一定更有效率,因为:
- 如果需要处理线程的数量多于CPU处理器的数量,会出现“上下文交换”。CPU的每一次切换都需要先保存当前线程的状态,之后重新执行该线程时,要加载原先线程的状态。在各个线程发生的交换过程,需要消耗系统资源;
- 每创建一个线程,系统都要为其分配一定的系统资源。
2.Buffer简析
3.Channel简析
4.使用BIO和NIO实战:本地文件拷贝
interface FileCopyRunner { void copyFile(File source, File target); } public class FileCopyDemo { private static final int ROUNDS = 3; // 每种方法都运行3次来评估性能 // 执行不同方法的函数,并评估性能 private static void benchmark(FileCopyRunner test, File source, File target) { long elapsed = 0L; // 总时间 for (int i = 0; i < ROUNDS; i++) { long startTime = System.currentTimeMillis(); test.copyFile(source, target); elapsed += System.currentTimeMillis() - startTime; if (i != ROUNDS - 1) { target.delete(); // 每次拷贝后再删除 } } System.out.println(test + ":" + elapsed / ROUNDS); } // 关闭各种实现了Closeable接口的资源 private static void colse(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { // 一、不使用缓冲的流的拷贝(一个字节一个字节地拷贝) FileCopyRunner noBufferStreamCopy = new FileCopyRunner() { @Override public void copyFile(File source, File target) { InputStream fin = null; OutputStream fout = null; try { fin = new FileInputStream(source); fout = new FileOutputStream(target); int result; try { // 如果读到数据的结尾时,会返回-1 while ((result = fin.read()) != -1) { // 每读到一个字节,就把字节写到文件输出流中 fout.write(result); } } catch (IOException e) { e.printStackTrace(); } } catch (FileNotFoundException e) { e.printStackTrace(); } finally { colse(fin); colse(fout); } } @Override public String toString() { return "noBufferStreamCopy"; } }; // 二、使用缓冲区的流的拷贝 FileCopyRunner bufferedStreamCopy = new FileCopyRunner() { @Override public void copyFile(File source, File target) { InputStream fin = null; OutputStream fout = null; try { fin = new BufferedInputStream(new FileInputStream(source)); fout = new BufferedOutputStream(new FileOutputStream(target)); // 缓冲区大小可以自己定义,例如定义为1024字节. byte[] buffer = new byte[1024]; int result; while ((result = fin.read(buffer)) != -1) { // 一次可读1024个字节,而不是1个字节了. result返回当前从buffer中读取的字节数 fout.write(buffer,0,result); } } catch (IOException e) { e.printStackTrace(); } finally { colse(fin); colse(fout); } } @Override public String toString() { return "bufferedStreamCopy"; } }; // 三、channel与buffer做数据交换 nio FileCopyRunner nioBufferCopy = new FileCopyRunner() { @Override public void copyFile(File source, File target) { FileChannel fin = null; FileChannel fout = null; // 由文件得到对应的文件通道 try { fin = new FileInputStream(source).getChannel(); fout = new FileOutputStream(target).getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); // 把数据从通道中读入到缓冲区 while ((fin.read(buffer)) != -1) { // 把buffer从写模式切换到读模式,内部通过调整position指针和limit指针来实现 buffer.flip(); // 加循环的作用:为了保证把buffer中所有的数据都读取到目标文件通道中 while (buffer.hasRemaining()) { fout.write(buffer); } // 读模式调整为写模式,内部通过使position指针回到初始位置,limit回到最远端 buffer.clear(); } } catch (IOException e) { e.printStackTrace(); } finally { colse(fin); colse(fout); } } @Override public String toString() { return "nioBufferCopy"; } }; // 四、通道与通道间传输数据 nio FileCopyRunner nioTransferCopy = new FileCopyRunner() { @Override public void copyFile(File source, File target) { FileChannel fin = null; FileChannel fout = null; try { fin = new FileInputStream(source).getChannel(); fout = new FileOutputStream(target).getChannel(); long transfered = 0L; // 目前为止已经拷贝了多少字节的数据 long size = fin.size(); // 要复制文件的大小 // transferTo()函数不能保证把原通道所有数据都传输到目标通道 while (transfered != size) { transfered += fin.transferTo(0, size, fout); // 第二个参数是要传输多少数据 } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { colse(fin); colse(fout); } } @Override public String toString() { return "nioTransferCopy"; } }; File source = new File("F:\\test\\project.zip"); File target1 = new File("F:\\test\\p1.zip"); File target2 = new File("F:\\test\\p2.zip"); File target3 = new File("F:\\test\\p3.zip"); File target4 = new File("F:\\test\\p4.zip"); benchmark(noBufferStreamCopy, source, target1); benchmark(bufferedStreamCopy, source, target2); benchmark(nioBufferCopy, source, target3); benchmark(nioTransferCopy, source, target4); } }
5.实验结果
- 以复制大小为2.75MB的文件为例,一个字节一个字节地拷贝实在是慢的可怕。。
6.Selector简析
- 作用:不停地“询问”通道什么时候处于可操作状态,即监听多个通道的状态。
- select()返回的数值表示目前有几个Channel处于可操作状态。
- 目的是,跟Selector这一个对象互动,就可以得到Selector所监听的多个Channel对象状态的改变,由此实现进一步的业务逻辑。