一、NIO概述

对比BIO

BIO即blockIO:

  • ServerSocket.accept()

调用了这个函数后,就会一直处于阻塞状态,直到服务端接收了新的连接请求

  • InputStream.read(),OutputStream.write()

这个其实更严重,若用户长时间不输入信息则会造成长时间的阻塞

  • 无法在同一线程里处理多个StreamIO

即使在伪异步优化以后,也是多个线程处理多个流的IO

非阻塞式NIO

NIO即NonBlocking:

  • 使用channel代替Stream

流是单向的写入或者写出数据,而channel是有两种模式的,一种是类似于Stream的单向阻塞式操作,另一种则是非阻塞式的方法。

  • 使用Selector监控多条Channel

非阻塞的意义则是某一channel的去数据时,若数据没准备好即立刻返回状态并保持查询状态。

  • 可以在一个线程处理多个ChannelIO

多线程是很浪费资源的,而一个线程处理多个Channel则把现成的利用率最大化了。

二、Channel与Buffer

Buffer是干什么的?

在NIO中读写是要通过Buffer来完成的,在Channel写数据是要写在Buffer中的,读数据也是要在Buffer中读取的,因此Buffer也是双向的。
image.png

Buffer的写入数据:

step 1:

image.png
如图所示,图中由三个指针类的变量:postion、limit、capacity。
capacity:即整个Buffer最大的容量,及最多可写到的位置。
position:即当前位置
在最开始的时候,position指向最开始,从最开始的地方开始写入。
而limit暂时不做解释,他只是指在了capacity这个位置。

step 2:

image.png
在写入一定的数据后,poistion的位置发生了改变,这时候为了接下来的读取,调用flip()方法。

step 3:

image.png
调用flip函数后,position对到了最开始的位置,limit移动到了写入道德最远位置。

Buffer读取数据:

接上图,在position和limit之间则是写入的数据。

情况一:

step 1:

数据全部读完,position移动到了limit的位置。
image.png
这时候需要调用clear()函数将指针调整方便下一次写入数据。

step 2:

image.png
熟悉的样子,熟悉的配方,这里就不过多阐述了。注意:数据没有进行清除,只是移动了指针,在下一次写入数据时后进行覆盖。

情况二:

step 1:

数据没有全部读完,却要进行模式转换。

image.png
这时候则调用compact()函数。

step 2:

compat函数将未读数据拷贝到开始的位置,position会指向未读数据以下位置,在未读数据以前的数据则会进行覆盖。
image.png

三、Channel简析

Channel的基本操作

image.png
如图,进行双向的数据传输。

几个重要的Channel

Channel的子类还有很多,这里就不一一列出了,以后找时间扩展好了。
image.png
ServerSocketChannel和SocketChannel主要用于网络编程中的数据传输。
这里举例FileChannel的例子。

多方法实现文件拷贝

shutdown方法:

  1. private static void shutdown(Closeable... closeable) {
  2. for (Closeable shut : closeable) {
  3. try {
  4. if (shut != null) {
  5. shut.close();
  6. }
  7. } catch (IOException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. }

没有Buffer的Stream

  1. FileCopyRunner noBufferStreamCopy;
  2. noBufferStreamCopy = new FileCopyRunner() {
  3. @Override
  4. public void copyFile(File source, File target) {
  5. InputStream input = null;
  6. OutputStream output = null;
  7. try {
  8. input = new FileInputStream(source);
  9. output = new FileOutputStream(target);
  10. int result=0;
  11. while ((result = input.read()) != -1) {
  12. output.write(result);
  13. }
  14. } catch (FileNotFoundException e) {
  15. e.printStackTrace();
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } finally {
  19. shutdown(input, output);
  20. }
  21. }
  22. @Override
  23. public String toString() {
  24. return "noBufferStreamCopy";
  25. }
  26. };

有Buffer的Stream

  1. FileCopyRunner bufferedStreamCopy;
  2. bufferedStreamCopy = new FileCopyRunner() {
  3. @Override
  4. public void copyFile(File source, File target) {
  5. BufferedInputStream input = null;
  6. BufferedOutputStream output = null;
  7. try {
  8. input = new BufferedInputStream(new FileInputStream(source));
  9. output = new BufferedOutputStream(new FileOutputStream(target));
  10. byte[] buffer = new byte[1024];
  11. int result= 0;
  12. while ((result = input.read(buffer)) != -1) {
  13. output.write(buffer, 0, result);
  14. }
  15. } catch (FileNotFoundException e) {
  16. e.printStackTrace();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. } finally {
  20. shutdown(input, output);
  21. }
  22. }
  23. @Override
  24. public String toString() {
  25. return "bufferedStreamCopy";
  26. }
  27. };

NIO的ChannelCopy

  1. FileCopyRunner nioBufferCopy;
  2. nioBufferCopy = new FileCopyRunner() {
  3. @Override
  4. public void copyFile(File source, File target) {
  5. FileChannel input = null;
  6. FileChannel output = null;
  7. try {
  8. input = new FileInputStream(source).getChannel();
  9. output = new FileOutputStream(target).getChannel();
  10. ByteBuffer buffer = ByteBuffer.allocate(1024);
  11. while ((input.read(buffer)) != -1) {
  12. buffer.flip();
  13. while (buffer.hasRemaining()) {
  14. output.write(buffer);
  15. }
  16. buffer.clear();
  17. }
  18. } catch (FileNotFoundException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. } finally {
  23. shutdown(input, output);
  24. }
  25. }
  26. @Override
  27. public String toString() {
  28. return "nioBufferCopy";
  29. }
  30. };

NIO的TransferCopy

  1. FileCopyRunner nioTransfertCopy;
  2. nioTransfertCopy = new FileCopyRunner() {
  3. @Override
  4. public void copyFile(File source, File target) {
  5. FileChannel input = null;
  6. FileChannel output = null;
  7. try {
  8. input = new FileInputStream(source).getChannel();
  9. output = new FileOutputStream(target).getChannel();
  10. long size = 0L;
  11. long sumsize = input.size();
  12. while (sumsize != size) {
  13. size = input.transferTo(0,sumsize,output);
  14. }
  15. } catch (FileNotFoundException e) {
  16. e.printStackTrace();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. } finally {
  20. shutdown(input, output);
  21. }
  22. }
  23. @Override
  24. public String toString() {
  25. return "nioTransfertCopy";
  26. }
  27. };

运行情况测试

打包复制类:

  1. private static void benchmark(FileCopyRunner test, File source, File targe) {
  2. long elapsed = 0L;
  3. for (int i = 0; i < ROUNDS; i++) {
  4. long startTime = System.currentTimeMillis();
  5. test.copyFile(source, targe);
  6. elapsed += System.currentTimeMillis() - startTime;
  7. targe.delete();
  8. }
  9. System.out.println(test + ": " + elapsed / ROUNDS);
  10. }

小文件实现代码:

  1. System.out.println("--------smallDemo----noBufferStreamCopy----------");
  2. benchmark(noBufferStreamCopy, smallFile, smallFileCopy);
  3. System.out.println("--------smallDemo----bufferedStreamCopy----------");
  4. benchmark(bufferedStreamCopy, smallFile, smallFileCopy);
  5. System.out.println("--------smallDemo----nioBufferCopy----------");
  6. benchmark(nioBufferCopy, smallFile, smallFileCopy);
  7. System.out.println("--------smallDemo----nioTransferCopy----------");
  8. benchmark(nioTransferCopy, smallFile, smallFileCopy);

OutPut:
image.png

大文件实现代码:

OutPut:
image.png
大文件暂不做处理了,类似,而且耗时久。

结论:

  1. 缓冲区对于IO的帮助是很大的
  2. NIO相比传统IO差距并不大,不过相对来说NIO稍微好一点。(JDK1.4时推出的NIO,相比传统IO性能极佳,新版本的IO基层也用了NIO的方法,所以性能也不会太差)。

    四、Selector简析

    Selector与Channel

    可以选择通道进行非阻塞式的数据传输,但是通道是否可操作却需要不停的询问,因此需要用Selector监听Channel。
    在使用之前,要将Channel注册到Selector中,形成下图状态。
    image.png

    Channel的状态变化

    CONNECT:与服务器建立连接
    ACCEPT:服务器端接受了请求
    READ:有可读取信息
    WRITE:可写入状态
    无状态:没有任何状态。
    无论哪一种Channel都会处于一种状态

    在Selector上注册Channel

    inserstOps():注册的状态
    readyOps():显示可操作的状态
    channel():返回注册的channel对象
    selector():所注册的是哪个selector对象
    attachment():附加对象

    使用Selector选择Channel

    没有开启通道:

    image.png

    开启了一个通道:

    image.png
    注意:处理完Channel需要手动重置Channel为等待状态

    开启了两个通道:

image.png