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,为下次读取数据做准备。