H__D  

一、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

 

posted on 2020-12-28 20:32  H__D  阅读(112)  评论(0编辑  收藏