Java NIO 教程(二)Channel

一. Channel概述

  1. 作用:Channel表示IO源与目标打开的连接。Channel类似于传统的"流",只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。

  2. 与传统IO流比较:

    1. 传统的IO流:CPU处理IO,性能损耗太大,读写单向的

    2. Channel:不需要向CPU申请权限,专门用于IO,读写可以双向,可以异步读写

  3. Channel的实现:

    1. FileChannel :从文件中读写数据。

    2. SocketChannel:能通过TCP读写网络中的数据。

    3. ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

    4. DatagramChannel: 能通过UDP读写网络中的数据。


二. FileChannel(*注:请先点击查看第三部分Buffer,再继续

  1. 对文件内容进行解析

    1. 步骤:

      1. 打开FileChannel

      2. 创建buffer对象

      3. 从FileChannel中读取数据

      4. 循环读取buffer

      5. 关闭file对象和FileChannel对象

    2. 实例:

      /**
      
       * 用filechannel进行文件内容解析
      
       */
      
      private static void fileChannelTest() {
      
          RandomAccessFile aFile = null;
      
          FileChannel inChannel=null;
      
          try {
      
              //1.打开FileChannel
      
              aFile = new RandomAccessFile("data/nio-data.txt", "rw");
      
              inChannel = aFile.getChannel();
      
              //2.创建buffer对象
      
              ByteBuffer buf = ByteBuffer.allocate(48);
      
              //3.从FileChannel读取数据:把channel中的数据读取到buffer中,bytesRead表示有多少字节读到了buffer中
      
              int bytesRead = inChannel.read(buf);
      
              //4.循环读取buffer
      
              while (bytesRead != -1) {
      
                  //4.1.将buffer从写切换成读
      
                  buf.flip();
      
                  while(buf.hasRemaining()){
      
                      //4.2.获取buf中的字节,转换成char
      
                      System.out.print((char) buf.get());
      
                  }
      
                  //4.3.清除buffer
      
                  buf.clear();
      
                  //4.4.再次从FileChannel读取数据
      
                  bytesRead = inChannel.read(buf);
      
              }
      
              //5.关闭file对象和channel对象
      
              aFile.close();
      
              inChannel.close();
      
          } catch (IOException e) {
      
              e.printStackTrace();
      
          }
      
      }
  2. 复制文件

    1. 步骤:

      1. 得到源文件通道

      2. 得到目标文件通道

      3. 设置position和count

      4. 将源文件通道数据写入目标文件通道

      5. 关闭FileChannel对象

    2. 实例:

      /**
      
      * 用filechannel进行文件复制
      
      */
      
      private static void fileCopyWithFileChannel(){
      
          try {
      
              RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
      
              //1.得到源文件通道
      
              FileChannel fromChannel = fromFile.getChannel();
      
      
      
              RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
      
              //2.得到目标文件通道
      
              FileChannel      toChannel = toFile.getChannel();
      
      
      
              //3.设置position和count
      
              long position = 0;
      
              long count = fromChannel.size();
      
      
      
              //4.将源文件通道的数据写入到目标文件通道
      
              toChannel.transferFrom(fromChannel, position, count);
      
              fromFile.close();
      
              toFile.close();
      
          }catch (IOException e){
      
              e.printStackTrace();
      
          }
      
      }
      
      
  3. 其他FileChannel方法

    • FileChannel的position方法

      1. 概述:有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取FileChannel的当前位置。也可以通过调用position(long pos)方法设置FileChannel的当前位置。

      2. 实例:

        //获取position
        
        long pos = channel.position();
        
        //设置position
        
        channel.position(pos +123);
    • FileChannel的size方法

      1. 概述:FileChannel实例的size()方法将返回该实例所关联文件的大小

      2. 实例:

        //获取实例所关联文件的大小
        
        long fileSize = channel.size();
    • FileChannel的truncate方法

      1. 概述:可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除

      2. 实例:

        //截取文件的前1024个字节。
        
        channel.truncate(1024);
    • FileChannel的force方法

      1. 概述:方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。

      2. 实例:

        //同时将文件数据和元数据强制写到磁盘上
        
        channel.force(true);

三. SocketChannel

  1. socket通信--客户端(阻塞模式下)

    1. 步骤

      1. 打开 SocketChannel

      2. 新建读,写buffer

      3. 写入 SocketChannel

      4. 读取 SocketChannel 

    2. 实现

      //“WebClient客户端”需要配合“WebServer服务端”测试
      
      public class WebClient {
      
          public static void main(String[] args) throws IOException {
      
              try {
      
                  //1.打开SocketChannel
      
                  SocketChannel socketChannel = SocketChannel.open();
      
                  socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));
      
      
      
                  //2.新建读,写buffer
      
                  ByteBuffer writeBuffer = ByteBuffer.allocate(32);
      
                  ByteBuffer readBuffer = ByteBuffer.allocate(32);
      
      
      
                  //3.1.向writeBuffer中放入值
      
                  writeBuffer.put("hello".getBytes());
      
                  //3.2.将buffer从写转换成读
      
                  writeBuffer.flip();
      
      
      
                  while (true) {
      
                      //3.3.使buffer的position置0
      
                      writeBuffer.rewind();
      
                      //3.4.将writeBuffer中的内容写入socketChannel
      
                      socketChannel.write(writeBuffer);
      
      
      
                      readBuffer.clear();
      
                      //4.读取socketChannel的内容到readBuffer
      
                      socketChannel.read(readBuffer);
      
                      System.out.println(new String(readBuffer.array()));
      
                  }
      
              } catch (IOException e) {
      
                  e.printStackTrace();
      
              }
      
          }
      
      }
      
      
  2. socket通信--客户端(非阻塞模式下)

    1. 步骤:

      1. 在阻塞模式的第1步添加“设置channel为非阻塞模式”

      2. 然后在连接的地方判断是否连接上

    2. 注意:

      1. 设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()

      2. connect():如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法

      3. write():非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。

      4. read():非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。

    3. 实现:

      //设置SocketChannel为非阻塞模式
      
      socketChannel.configureBlocking(false);
      
      socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));
      
      //判断连接是否完成
      
      while(! socketChannel.finishConnect() ){
      
          //如果没有完成连接进入这里等待
      
      }

四. ServerSocketChannel

  1. socket通信--服务端(建议非阻塞模式)

    1. 步骤:

      1. 打开 SocketChannel

      2. 设置 serverSocketChannel非阻塞

      3. 新建读,写buffer

      4. 监听连接进来的 SocketChannel (通过 serverSocketChannel获取 socketChannel)

      5. 写入 SocketChannel

      6. 读取 SocketChannel 

    2. 注意:非阻塞模式下,获取socketChannel后一定要判空,因为accpet会立即返回一个socketChannel(第4条位置)

    3. 实现:

      
      public class WebServer {
      
          public static void main(String[] args) {
      
              try {
      
                  //1.打开ServerSocketChannel
      
                  ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
      
                  serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8000));
      
                  //2.设置非阻塞
      
                  serverSocketChannel.configureBlocking(false);
      
      
      
                  //3.新建读,写buffer
      
                  ByteBuffer writeBuffer = ByteBuffer.allocate(32);
      
                  ByteBuffer readBuffer = ByteBuffer.allocate(32);
      
      
      
                  //3.1.向writeBuffer中放入值
      
                  writeBuffer.put("hello".getBytes());
      
                  //3.2.将buffer从写转换成读
      
                  writeBuffer.flip();
      
      
      
                  while(true){
      
                      //4.监听进入的SocketChannel(获取SocketChannel)
      
                      SocketChannel socketChannel =
      
                              serverSocketChannel.accept();
      
                      if (socketChannel!=null){
      
                          //5.1.使buffer的position置0
      
                          writeBuffer.rewind();
      
                          //5.2.将writeBuffer中的内容写入socketChannel
      
                          socketChannel.write(writeBuffer);
      
      
      
                          readBuffer.clear();
      
                          //6.读取socketChannel的内容到readBuffer
      
                          socketChannel.read(readBuffer);
      
                          System.out.println(new String(readBuffer.array()));
      
                      }
      
                  }
      
              } catch (IOException e) {
      
                  e.printStackTrace();
      
              }
      
          }
      
      }
      
       
      
      

五. DatagramChannel

  1. 概述:DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。

  2. 第一种方式发送UDP包

    1. 步骤:

      1. 打开DatagramChannel(在这里绑定端口的时候,参数只需要写端口,因为只是指定哪个端口可以接收)

      2. 新建读,写buffer

      3. 发送给指定的主机和端口

      4. 接收内容到readBuffer

    2. 实现:

      public class WebClient {
      
          public static void main(String[] args) throws IOException {
      
              try {
      
                  //1.打开DatagramChannel
      
                  DatagramChannel datagramChannel = DatagramChannel.open();
      
                  //1.1.指定端口用来接收数据包
      
                  datagramChannel.socket().bind(new InetSocketAddress(9999));
      
      
      
                  //2.新建读,写buffer
      
                  ByteBuffer writeBuffer = ByteBuffer.allocate(32);
      
                  ByteBuffer readBuffer = ByteBuffer.allocate(32);
      
      
      
                  //3.1.向writeBuffer中放入值
      
                  writeBuffer.put("hello".getBytes());
      
                  //3.2.将buffer从写转换成读
      
                  writeBuffer.flip();
      
      
      
                  while (true) {
      
                      //3.3.使buffer的position置0
      
                      writeBuffer.rewind();
      
                      //3.4.将writeBuffer中的内容发送给指定主机和端口
      
                      datagramChannel.send(writeBuffer,new InetSocketAddress("127.0.0.1", 8000));
      
      
      
                      readBuffer.clear();
      
                      //4.接收内容到readBuffer
      
                      datagramChannel.receive(readBuffer);
      
                      System.out.println(new String(readBuffer.array()));
      
                  }
      
              } catch (IOException e) {
      
                  e.printStackTrace();
      
              }
      
          }
      
      }
      
       
      
      
  3. 第二种方式发送UDP包

    1. 通常方式连接并操作(与SocketChannel相同)数据传送方面没有保障

    2. 注意:这种方式是通过指定特定主机的连接后才可以使用,但是并不是真的连接,只是锁住DatagramChannel

    3. 实现:

      public class WebClient {
      
          public static void main(String[] args) throws IOException {
      
              try {
      
                  //1.打开DatagramChannel
      
                  DatagramChannel datagramChannel = DatagramChannel.open();
      
                  datagramChannel.connect(new InetSocketAddress("127.0.0.1", 8000));
      
      
      
                  //2.新建读,写buffer
      
                  ByteBuffer writeBuffer = ByteBuffer.allocate(32);
      
                  ByteBuffer readBuffer = ByteBuffer.allocate(32);
      
      
      
                  //3.1.向writeBuffer中放入值
      
                  writeBuffer.put("hello".getBytes());
      
                  //3.2.将buffer从写转换成读
      
                  writeBuffer.flip();
      
      
      
                  while (true) {
      
                      //3.3.使buffer的position置0
      
                      writeBuffer.rewind();
      
                      //3.4.将writeBuffer中的内容写入socketChannel
      
                      datagramChannel.write(writeBuffer);
      
      
      
                      readBuffer.clear();
      
                      //4.读取DatagramChannel的内容到readBuffer
      
                      datagramChannel.read(readBuffer);
      
                      System.out.println(new String(readBuffer.array()));
      
                  }
      
              } catch (IOException e) {
      
                  e.printStackTrace();
      
              }
      
          }
      
      }
      
      

猜你喜欢

转载自blog.csdn.net/qq919694688/article/details/81671339