即时通讯首页投稿(暂停使用,暂停投稿)今日看点

Java NIO 常用归纳

2017-02-16  本文已影响442人  androidjp

前言: 之前的文章《Java文件IO常用归纳》主要写了Java 标准IO要注意的细节和技巧,由于网上各种学习途径,所以并没有详细示例等。本文主要简单看看java的NIO库的用法,并做个小归纳,可以对比标准IO参考一下。

NIO概述


(一)背景

NIO(New IO),在Java 1.4引入的一个新的IO API。【可替代标准IO API】

(二)工作方式

NIO 与 标准IO 的区别


  1. IO面向Stream(流),NIO面向Buffer(缓存)
  1. IO流都是阻塞的,而NIO有非阻塞模式
  1. NIO独有选择器(Selector)

常用API总结


(一)核心接口与类关系图解与分析

IO与NIO 接口与类关系图.png

(二)常用API清单

(三)文件IO --- FileChannel

  1. 读文件
 public static byte[] readBytes(String fileName) {
      try {
          ///获取对应文件的FileChannel对象
          RandomAccessFile accessFile = new RandomAccessFile(fileName, "rw");
          FileChannel fileChannel = accessFile.getChannel();
          /// 创建一个缓冲区(大小为48byte)
          ByteBuffer byteBuffer = ByteBuffer.allocate(48);
          StringBuilder builder = new StringBuilder();

          int bytesRead = fileChannel.read(byteBuffer);
          while (bytesRead != -1) {
              System.out.println("Read " + bytesRead);
              ///翻转buffer
              byteBuffer.flip();
              ///每次读取完之后,输出缓存中的内容
              while (byteBuffer.hasRemaining()) {
                  System.out.println((char) byteBuffer.get());
                  builder.append((char) byteBuffer.get());
              }
              ///然后清空缓存区
              byteBuffer.clear();
              ///重新再读数据到缓存区中
              bytesRead = fileChannel.read(byteBuffer);
          }

          accessFile.close();
          return builder.toString().getBytes();
      } catch (IOException e) {
          e.printStackTrace();
          return null;
      }
  }
  1. 写入文件
  public static void writeBytes(String fileName, byte[] data) {
      try {
          RandomAccessFile accessFile = new RandomAccessFile(fileName, "rw");
          FileChannel channel = accessFile.getChannel();
          ByteBuffer buffer = ByteBuffer.allocate(48);
          buffer.put(data);
          channel.write(buffer);
      } catch (FileNotFoundException e) {
          e.printStackTrace();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
  1. 通道间内容传输
  /**
   * channel 间的传输
   *
   * @param sFileName 源文件
   * @param dFileName 目标文件
   */
  public static void channelToChannel(String sFileName, String dFileName) {
      try {
          RandomAccessFile sAccess = new RandomAccessFile(sFileName, "rw");
          RandomAccessFile dAccess = new RandomAccessFile(dFileName, "rw");
          FileChannel sChannel = sAccess.getChannel();
          FileChannel dChannel = dAccess.getChannel();

          long pos = 0;
          long sCount = sChannel.size();
          long dCount = dChannel.size();
//            dChannel.transferFrom(sChannel,pos,sCount);//dChannel 必须是FileChannel
          sChannel.transferTo(pos, dCount, dChannel);///sChannel 是FileChannel
      } catch (FileNotFoundException e) {
          e.printStackTrace();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

(四)TCP通信 --- SocketChannel

  1. 基本的C/S TCP通信

/**
* Client SocketChannel 写法:
*/
public static void client(String fileName) {
SocketChannel sc = null;
try {
// 创建一个SocketChannel 通道
////TODO: FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下
sc = SocketChannel.open();
///TODO:非阻塞IO状态下,socketChannel就可以异步地执行read()、write()、connect()方法了
sc.configureBlocking(false);
sc.connect(new InetSocketAddress("http://jianshu.com", 80));

        while (!sc.finishConnect()) {///保证在connect成功之前,可以做别的事情
            //做点别的事。。。。。
        }
        while((int len  = sc.read(xxx))==0){ ///保证NBIO下,read数据不会read空
             // 做别的事。。。
        }
        while((int len  = sc.write(xxx))==0){///保证NBIO下,write数据不会write空
            // 做别的事。。。
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (sc != null) {
                sc.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

/**
* 关于:ServerSocketChannel
*/
public static void serverSocketChannel() {
ServerSocketChannel serverSocketChannel = null;
try {
///打开
serverSocketChannel = ServerSocketChannel.open();
///连接并开始监听TCP 9999端口
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
///TODO:可设置非阻塞状态(需要检查accept到的socketChannel是否为null)
serverSocketChannel.configureBlocking(false);
while (true) {
SocketChannel socketChannel =
serverSocketChannel.accept();
//TODO: 非阻塞时需要考虑返回的socketChannel对象是否为null
if(socketChannel != null){
//do something with socketChannel...
}
//do something with socketChannel...
}

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (serverSocketChannel != null)
            try {
                serverSocketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
}
```
  1. 配合Selector,简化SocketChannel在非阻塞IO状态下的Null情况监测逻辑
/**
   * 关于 选择器 和 SocketChannel 的配合使用
   */
  public static void selectorAndSocketChannel(String fileName) {
      SocketChannel sc1 = null;
      SocketChannel sc2 = null;
      SocketChannel sc3 = null;
      try {
          // 创建几个SocketChannel 通道
          ////TODO: FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下
          sc1 = SocketChannel.open();
          sc2 = SocketChannel.open();
          sc3 = SocketChannel.open();
          ///TODO:非阻塞IO状态下,socketChannel就可以异步地执行read()、write()、connect()方法了
          sc1.configureBlocking(false);
          sc2.configureBlocking(false);
          sc3.configureBlocking(false);
          sc1.connect(new InetSocketAddress("http://jenkov.com", 80));
          sc2.connect(new InetSocketAddress("http://jenkov.com", 80));
          sc3.connect(new InetSocketAddress("http://jenkov.com", 80));

          // 创建Selector
          Selector selector = Selector.open();
          // 注册channels
          SelectionKey key1 = sc1.register(selector, SelectionKey.OP_READ);
          SelectionKey key2 = sc2.register(selector, SelectionKey.OP_READ);
          SelectionKey key3 = sc3.register(selector, SelectionKey.OP_READ);
          // 持续监控selector的四个事件(接受、连接、读、写)是否就绪
          while (true) {
              int readyChannels = selector.select();
              if (readyChannels == 0) continue;
              Set selectedKeys = selector.selectedKeys();
              Iterator keyIterator = selectedKeys.iterator();
              while (keyIterator.hasNext()) {
                  SelectionKey key = (SelectionKey) keyIterator.next();
                  if (key.isAcceptable()) {
                      // a connection was accepted by a ServerSocketChannel.
                      ///我的这个连接请求被服务端接受了
                  } else if (key.isConnectable()) {
                      // a connection was established with a remote server.
                      ///已经连接上
                  } else if (key.isReadable()) {
                      // a channel is ready for reading
                      ///可读数据
                  } else if (key.isWritable()) {
                      // a channel is ready for writing
                      ///可写数据
                  }
              }
              keyIterator.remove();
          }

      } catch (IOException e) {
          e.printStackTrace();
      } finally {
          try {
              if (sc1 != null) {
                  sc1.close();
              }
              if (sc2 != null) {
                  sc2.close();
              }
              if (sc3 != null) {
                  sc3.close();
              }
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  }

(五)UDP通信 --- DatagramChannel

  1. 收发UDP数据包 的简单示例
   /**
   * 关于:DatagramChannel
   * UDP 无连接网络协议
   * 发送和接收的是数据包
   */
  public static void datagramChannel() {
      DatagramChannel datagramChannel = null;
      try {
          ///打开
          datagramChannel = DatagramChannel.open();
          ///连接并开始监听UDP 9999端口
          datagramChannel.socket().bind(new InetSocketAddress(9999));
          // 接收数据包(receive()方法会将接收到的数据包内容复制到指定的Buffer. 如果Buffer容不下收到的数据,多出的数据将被丢弃。 )
          ByteBuffer buf = ByteBuffer.allocate(48);
          buf.clear();
          datagramChannel.receive(buf);
          // 发送数据 send()
          String sendMsg = "要发送的数据";
          ByteBuffer sendBuf = ByteBuffer.allocate(48);
          sendBuf.clear();
          sendBuf.put(sendMsg.getBytes());
          sendBuf.flip();
          datagramChannel.send(sendBuf,new InetSocketAddress("xxxxx",80));

          // TODO: 连接到特定的地址(锁住DatagramChannel ,让其只能从特定地址收发数据 因为UDP无连接,本身没有真正的连接产出)
          datagramChannel.connect(new InetSocketAddress("jenkov.com", 80));
          ///连接后,也可以使用Channal 的read()和write()方法,就像在用传统的通道一样。只是在数据传送方面没有任何保证

      } catch (IOException e) {
          e.printStackTrace();
      } finally {
          if (datagramChannel != null)
              try {
                  datagramChannel.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
      }
  }

(六)NIO管道(Pipe)

首先,什么是NIO管道,下图可以看出其内部结构和功能特点:

  1. 示例: 管道传输数据
/**
   * 关于NIO管道(Pipe)
   * 定义:2个线程之间的单向数据连接
   */
  public static void aboutPipe(){
      Pipe pipe=null;
      try {
          /// 打开管道
          pipe = Pipe.open();
          ///TODO: 一、 向管道写入数据
          /// 访问Pipe.sinkChannel,向Pipe写入数据
          /// 首先,获取Pipe.sinkChannel
          Pipe.SinkChannel sinkChannel = pipe.sink();
          /// 然后,调用write(),开始写入数据
          String newData = "New String to write to file..." + System.currentTimeMillis();
          ByteBuffer buf = ByteBuffer.allocate(48);
          buf.clear();
          buf.put(newData.getBytes());
          buf.flip();
          while(buf.hasRemaining()){
          sinkChannel.write(buf);
          }
          // TODO: 二、读取管道中的数据
          // 首先,获取Pipe.sourceChannel
          Pipe.SourceChannel sourceChannel = pipe.source();
          /// 读取数据到buffer
          ByteBuffer buf2 = ByteBuffer.allocate(48);
          int bytesRead = sourceChannel.read(buf2);
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

附录:java.nio包相关接口与类图一览


Buffer类图 java.nio.channels 相关接口关系图 java.nio.channels 相关类图 java.nio.charset 相关类图(这个包主要做不同编码格式的加解密等工作)

参考文章


上一篇 下一篇

猜你喜欢

热点阅读