• 【Java】Java NIO 之 Channel 通道(三)


    一、Channel(通道)介绍

      通常来说NIO中的所有IO都是从 Channel(通道) 开始的。

    • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。

    • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

      数据读取和写入操作图示:

      

      Java NIO Channel通道和流非常相似,主要有以下几点区别:

    • 通道可以读也可以写,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO操作的时候需要分别创建一个输入流和一个输出流)。

    • 通道可以异步读写。

    • 通道总是基于缓冲区Buffer来读写。

      Java NIO中最重要的几个Channel的实现:

    • FileChannel: 用于文件的数据读写

    • DatagramChannel: 用于UDP的数据读写

    • SocketChannel: 用于TCP的数据读写,一般是客户端实现

    • ServerSocketChannel: 允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel,一般是服务器实现

      类层次结构:

      下面的UML图使用Idea生成的。

      

    二、FileChannel的使用

    2.1、基本用法

      示例一:使用FileChannel读取数据到Buffer(缓冲区)以及利用Buffer(缓冲区)写入数据到FileChannel:

      1 /**
      2  * 1、通道(Channel):
      3  *  用于源节点与目标节点的连接。在Java NIO中负责缓冲区数据的传输。
      4  *  Channel本身不存储数据,因此需要缓冲区进行传输
      5  *
      6  * 2、通道的主要实现类
      7  *  java.nio.channels.Channel 接口
      8  *      |-- FIleChannel
      9  *      |-- SocketChannel
     10  *      |-- ServerSocketChannel
     11  *      |-- DatagramChannel
     12  *
     13  * 3、获取通道
     14  *    1)Java 针对支持通道的类提供类 getChannel() 方法
     15  *      本地IO:
     16  *      FileInputStream/FileOutputStream
     17  *      RandomAccessFile
     18  *
     19  *      网络IO:
     20  *      Socket
     21  *      ServerSocket
     22  *      DatagramSocket
     23  *
     24  *    2)在JDK 1.7 中的 NIO.2 针对各个通道提供类静态方法 open()
     25  *    3)在JDK 1.7 中的 NIO.2 的Files 工具类的 newByteChannel()
     26  *
     27  * 4、通道之间的数据传输
     28  *      transferFrom()
     29  *      transferTo()
     30  *
     31  * 5、分散(Scatter)与聚集(Gather)
     32  * 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
     33  * 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
     34  *
     35  * 6、字符集:Charset
     36  * 编码:字符串 -> 字节数组
     37  * 解码:字节数组 -> 字符串
     38  */
     39 public class ChannelTest {
     40 
     41 
     42     // 1】利用通道完成文件的复制
     43     @Test
     44     public void test1(){
     45 
     46         FileInputStream fis = null;
     47         FileOutputStream fos = null;
     48 
     49         FileChannel inChannel = null;
     50         FileChannel outChannel = null;
     51         try {
     52             fis = new FileInputStream("tomcat.png");
     53             fos = new FileOutputStream("tomcat2.png");
     54 
     55             // 1、获取通道
     56             inChannel = fis.getChannel();
     57             outChannel = fos.getChannel();
     58 
     59             // 2、分配指定大小的缓存区
     60             ByteBuffer buf = ByteBuffer.allocate(1024);
     61 
     62             // 3、将通道中的数据存入缓冲区中
     63             int len;
     64             while ((len = inChannel.read(buf)) != -1) {
     65                 buf.flip(); // 切换成读取数据的模式
     66                 // 4、将缓冲区中的数据写入通道中
     67                 outChannel.write(buf);
     68                 buf.clear(); // 清空缓冲区
     69             }
     70         } catch (FileNotFoundException e) {
     71             e.printStackTrace();
     72         } catch (IOException e) {
     73             e.printStackTrace();
     74         } finally {
     75             // 5、关闭通道
     76             if(outChannel != null) {
     77                 try {
     78                     outChannel.close();
     79                 } catch (IOException e) {
     80                     e.printStackTrace();
     81                 }
     82             }
     83             if(inChannel != null) {
     84                 try {
     85                     inChannel.close();
     86                 } catch (IOException e) {
     87                     e.printStackTrace();
     88                 }
     89             }
     90             if(fos != null) {
     91                 try {
     92                     fos.close();
     93                 } catch (IOException e) {
     94                     e.printStackTrace();
     95                 }
     96             }
     97             if(fis != null) {
     98                 try {
     99                     fis.close();
    100                 } catch (IOException e) {
    101                     e.printStackTrace();
    102                 }
    103             }
    104         }
    105     }
    106  }

      示例二:使用直接缓冲区完成文件的复制(内存映射文件)

     1     // 2】使用直接缓冲区完成文件的复制(内存映射文件)
     2     // 异常需要使用try-catch块处理
     3     @Test
     4     public void test2() throws IOException {
     5         // READ 读文件
     6         FileChannel inChannel = FileChannel.open(Paths.get("tomcat.png"), StandardOpenOption.READ);
     7         // WRIT 写文件 CREATE_NEW 文件存在就报错,不存在就创建 CREATE 覆盖
     8         FileChannel outChannel = FileChannel.open(Paths.get("tomcat2.png"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
     9 
    10         // 内存映射文件,直接缓冲区内存在物理内存中
    11         MappedByteBuffer inMapappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
    12         MappedByteBuffer outMapappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
    13 
    14         // 直接对缓冲区进行数据的读写操作
    15         byte[] dst = new byte[1024];
    16         ByteBuffer buf;
    17         int len = 0;
    18         while (inMapappedBuf.hasRemaining()){
    19             len = dst.length > inMapappedBuf.remaining() ? inMapappedBuf.remaining() : dst.length;
    20 //            System.out.println(len);
    21             inMapappedBuf.get(dst, 0, len) ;
    22             outMapappedBuf.put(dst, 0, len);
    23         }
    24     }

    2.2、通道之间数据传输

      在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel传输到另外一个channel。

      transferFrom()

      FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中

     1     // 3】通道之间数据传输(也是直接缓冲区方式)
     2     // 异常需要使用try-catch块处理
     3     @Test
     4     public void test3() throws IOException {
     5         // READ 读文件
     6         FileChannel inChannel = FileChannel.open(Paths.get("tomcat.png"), StandardOpenOption.READ);
     7         // WRIT 写文件 CREATE_NEW 文件存在就报错,不存在就创建 CREATE 存在覆盖,不存在创建
     8         FileChannel outChannel = FileChannel.open(Paths.get("tomcat2.png"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
     9 
    10 //        inChannel.transferTo(0, inChannel.size(), outChannel);
    11         outChannel.transferFrom(inChannel, 0, inChannel.size());
    12 
    13         outChannel.close();
    14         inChannel.close();
    15     }
    View Code

      transferTo()

      transferTo()方法将数据从FileChannel传输到其他的channel中

    2.3、分散(scatter)与聚集(gather)

      Java NIO开始支持scatter/gather,scatter/gather用于描述从Channel(译者注:Channel在中文经常翻译为通道)中读取或者写入到Channel的操作。

      分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
      聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。

      scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。  

      Scattering Reads

      Scattering Reads是指数据从一个channel读取到多个buffer中。如下图描述:

      

      代码示例如下:

    1 ByteBuffer header = ByteBuffer.allocate(128);
    2 ByteBuffer body   = ByteBuffer.allocate(1024);
    3 
    4 ByteBuffer[] bufferArray = { header, body };
    5 
    6 channel.read(bufferArray);

      注意buffer首先被插入到数组,然后再将数组作为channel.read() 的输入参数。read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。

      Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。

      Gathering Writes

      Gathering Writes是指数据从多个buffer写入到同一个channel。如下图描述:

      

      代码示例如下:

    1 ByteBuffer header = ByteBuffer.allocate(128);
    2 ByteBuffer body   = ByteBuffer.allocate(1024);
    3 
    4 //write data into buffers
    5 
    6 ByteBuffer[] bufferArray = { header, body };
    7 
    8 channel.write(bufferArray);

      buffers数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。

      完整示例演示如下:

     1     // 4】分散和聚集
     2     @Test
     3     public void test4() {
     4         FileChannel inRafChannel = null;
     5         FileChannel outRafChannel = null;
     6         try {
     7             RandomAccessFile inRaf = new RandomAccessFile("hello.txt", "r");
     8             RandomAccessFile outRaf = new RandomAccessFile("hello2.txt", "rw");
     9 
    10             // 1、获取通道
    11             inRafChannel = inRaf.getChannel();
    12             outRafChannel = outRaf.getChannel();
    13 
    14             // 2、分配指定大小的缓冲区
    15             ByteBuffer buf1 = ByteBuffer.allocate(5);
    16             ByteBuffer buf2 = ByteBuffer.allocate(10);
    17 
    18             // 3、分散读取
    19             ByteBuffer[] bufs = {buf1, buf2};
    20             long len;
    21             while ((len = inRafChannel.read(bufs)) != -1){
    22                 for (ByteBuffer byteBuffer : bufs) {
    23                     // 切换读模式
    24                     byteBuffer.flip();
    25                     System.out.print(new String(byteBuffer.array(), 0, byteBuffer.limit()));
    26                 }
    27 
    28                 // 4、聚集写入
    29                 outRafChannel.write(bufs);
    30 
    31                 for (ByteBuffer byteBuffer : bufs) {
    32                     // 清除缓存
    33                     byteBuffer.clear();
    34                 }
    35             }
    36 
    37         } catch (FileNotFoundException e) {
    38             e.printStackTrace();
    39         } catch (IOException e) {
    40             e.printStackTrace();
    41         } finally {
    42             if (outRafChannel != null) {
    43                 try {
    44                     outRafChannel.close();
    45                 } catch (IOException e) {
    46                     e.printStackTrace();
    47                 }
    48             }
    49             if (inRafChannel != null) {
    50                 try {
    51                     inRafChannel.close();
    52                 } catch (IOException e) {
    53                     e.printStackTrace();
    54                 }
    55             }
    56         }
    57     }
    View Code
  • 相关阅读:
    C# 谈谈代码重构
    收藏.NET 技术社区
    步步为营 .NET三层架构解析 四、Model设计(四种设计方式)
    C# 谈谈abstract用法 和一个简单abstract factory
    步步为营 .NET三层架构解析 一、什么是三层架构
    用户登陆的验证码的制作
    控制部分字体的颜色
    回发或回调参数无效。在配置中使用 <pages enableEventValidation= "true "/> 或在页面中使用 <%@ Page EnableEventValidation= "true " %> 启用了事件验证
    实习记2
    sniffer攻击
  • 原文地址:https://www.cnblogs.com/h--d/p/14203489.html
Copyright © 2020-2023  润新知