一、Channel(通道)介绍

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

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

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

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

javahd video javahd video channel_java 获得h通道

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

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

通道可以异步读写。

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

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

FileChannel: 用于文件的数据读写

DatagramChannel: 用于UDP的数据读写

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

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

类层次结构:

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

javahd video javahd video channel_数据_02

二、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 * |-- FIleChannel9 * |-- SocketChannel10 * |-- ServerSocketChannel11 * |-- DatagramChannel12 *13 * 3、获取通道14 * 1)Java 针对支持通道的类提供类 getChannel() 方法15 * 本地IO:16 * FileInputStream/FileOutputStream17 * RandomAccessFile18 *19 * 网络IO:20 * Socket21 * ServerSocket22 * DatagramSocket23 *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、字符集:Charset36 * 编码:字符串 -> 字节数组37 * 解码:字节数组 -> 字符串38 */
39 public classChannelTest {40
41
42 //1】利用通道完成文件的复制
43 @Test44 public voidtest1(){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 intlen;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 @Test4 public void test2() throwsIOException {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中

javahd video javahd video channel_tomcat_03

javahd video javahd video channel_javahd video_04

1 //3】通道之间数据传输(也是直接缓冲区方式)2 //异常需要使用try-catch块处理
3 @Test4 public void test3() throwsIOException {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中。如下图描述:

javahd video javahd video channel_数据_05

代码示例如下:

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。如下图描述:

javahd video javahd video channel_java 获得h通道_06

代码示例如下:

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能较好的处理动态消息。

完整示例演示如下:

javahd video javahd video channel_tomcat_03

javahd video javahd video channel_javahd video_04

1 //4】分散和聚集
2 @Test3 public voidtest4() {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 longlen;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