15.9.3 使用Channel
15.9.3 使用Channel
Channel与流对象的区别
Channel类似于传统的流对象,但与传统的流对象有两个主要区别。
Channel可以直接将指定文件的部分或全部直接映射成Buffer- 程序不能直接访问
Channel中的数据,包括读取、写入都不行,Channel只能与Buffer进行交互。也就是说:- 如果要从
Channel中取得数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据; - 如果要将程序中的数据写入
Channel,一样先让程序将数据放入Buffer中,程序再将Buffer里的数据写入Channe中。
- 如果要从
Channel分类
Java为Channel接口提供了DatagramChannel、FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、SelectableChannel、ServerSocketChannel、SocketChannel等实现类,本节主要介绍FileChannel的用法。
根据这些Channel的名字不难发现,新IO里的Channel是按功能来划分的,例如
Pipe.SinkChannel、Pipe.SourceChannel是用于支持线程之间通信的管道Channel;ServerSocketChannel、SocketChannel是用于支持**TCP网络通信**的Channel;DatagramChannel则是用于支持**UDP网络通信**的Channel
本书将会在第17章介绍网络通信编程的详细内容,如果需要掌握ServerSocketChannel,SocketChannel等Channel的用法,可以参考本书第17章。
如何创建Channel
所有的Channel都不应该通过构造器来直接创建,而是通过传统的节点InputStream、OutputStream的getChannel()方法来返回对应的Channel,不同的节点流获得的Channel不一样。例如:
FileInputStream、FileOutputStream的getChannel()方法返回的是FileChannel,PipedInputStream和PipedOutputStream的getChannel()方法返回的是Pipe.SinkChannel、Pipe.SourceChannel
Channel常用方法
Channel中最常用的三类方法是map()、read()和write(),其中
map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;read或write方法都有一系列重载形式,这些方法用于从Buffer中读取数据或向Buffer中写入数据。
FileChannel的map方法
map()方法的方法签名为:
1 | abstract MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) |
- 第一个参数执行映射时的模式,分别有只读、读写等模式
- 而第二个、第三个参数用于控制将
Channel的哪些数据映射成ByteBuffer
程序 FileChannel示例
下面程序示范了直接将FileChannel的全部数据映射成ByteBuffer的效果。
1 | import java.io.*; |
上面程序中分别使用FileInputStream、FileOutputStream来获取FileChannel,虽然FileChannel既可以读取也可以写入,
- 但**
FileInputStream获取的FileChannel只能读**, - 而**
FileOutputStream获取的FileChannel只能写**。
程序中①号代码:
1 | MappedByteBuffer buffer = |
直接将指定Channel中的全部数据映射成ByteBuffer.
然后程序中②号代码:
1 | outChannel.write(buffer); // ② |
直接将整个ByteBuffer的全部数据写入一个输出FileChannel中,这就完成了文件的复制.
程序后面部分为了能将FileChannelTest.java文件里的内容打印出来,使用了Charset类和CharsetDecoder类将ByteBuffer转换成CharBuffer关于Charset和CharsetDecoder下一节将会有更详细的介绍。
程序 RandomAccessFile获取的Channel
不仅InputStream、OutputStream包含了getChannel()方法,在RandomAccessFile中也包含了一个getChannel()方法,RandomAccessFile返回的FileChannel是只读的还是读写的,则取决于RandomAccessFile打开文件的模式。
例如,下面程序将会对a.txt文件的内容进行复制,追加在该文件后面。
1 | import java.io.*; |
上面程序中的代码1:
1 | randomChannel.position(f.length()); |
可以将Channel的记录指针移动到该Channel的最后,从而可以让程序将指定ByteBuffer的数据追加到该Channel的后面。每次运行上面程序,都会把a.txt文件的内容复制份,并将全部内容追加到该文件的后面。
像传统IO那样使用Channel和Buffer读写文件
如果读者习惯了传统IO的“用竹筒多次重复取水”的过程,或者担心Channel对应的文件过大,使用map()方法一次将所有的文件内容映射到内存中引起性能下降,也可以使用Channel和Buffer传统的“用竹筒多次重复取水”的方式。
如下程序所示。
1 | import java.io.*; |
上面代码虽然使用FileChannel和Buffer来读取文件,但处理方式和使用InputStream、byte[]来读取文件的方式几乎一样,都是采用“用竹筒多次重复取水”的方式。但因为Buffer提供了fip()和clear()两个方法,所以程序处理起来比较方便
类似传统方式使用NIO步骤
- 读取数据到
Buffer中 - 每次读取数据后调用
fip()方法将没有数据的区域“封印”起来,避免程序从Buffer中取出null值; - 从
Buffer中取出数据 - 数据取出后立即调用
clear()方法将Buffer的position设0,为下次读取数据做准备。