了解了缓冲区后,下来需要了解真正传输数据的通道Channel,Channel是做什么用的?再来简单回顾一下,通道顾名思义就是传递的介质,Channel就类似于传统IO中的流,是直接对接操作系统的,当我们处理好缓冲区后,就可以通过缓冲区对通道进行数据的输入输出,完成整个NIO的过程。
Java为Channel接口提供了DatagtamChannel、FileChannel、Pipe.SinkChannel、ServerSocketChannel、SocketChannel等实现类,这些通道是根据功能来划分的,例如Pipe.SinkChannel和Pipe.SourceChannel是用于支持线程之间通信的管道,ServerSocketChannel,SocketChannel是用于支持TCP网络通信的,而DatagramChannel则是用于支持UDP网络通信的通道。
NIO使用通道进行数据读写有两种方式:
1.通过内存映射
2.通过传统IO模式,通过多次读写
通过内存映射是将数据映射到内存空间中,这样的方式明显效率是很高的,如果Channel对应的文件过大,使用映射一次将所有的文件内容映射到内存中会引起性能的下降,就可以使用第二种方式,使用传统的IO方式分多次进行
比较几种读写方式,效率从高到低是:
NIO内存映射 > NIO多次读写 > 传统IO使用缓存 > 使用普通IO
下面的两个例子都用FileChannel来说明
内存映射
Channel提供了map()方法来完成内存映射的过程,该过程会返回一个MappedByteBuffer,这个类是ByteBuffer的子类,也是一个缓存。
我们可以直接通过操作缓存来进行通道数据的交换
public class ChannelTest {
public static void main(String[] args) {
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
//获得通道
inChannel = new FileInputStream("e:\\txt\\beauty.jpeg").getChannel();
//为什么不是OutputStream???
outChannel = new RandomAccessFile("e:\\beauty.jpeg","rw").getChannel();
long size = inChannel.size();
//映射为缓存
MappedByteBuffer inBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
MappedByteBuffer outBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
for (int i = 0; i < size; i++) {
outBuffer.put(inBuffer.get(i));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭通道
try {
if(inChannel!=null) {
inChannel.close();
}
if(outChannel!=null) {
outChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这个demo实现一个图片的复制,这块用到的通道是FileChannel。
观察demo,会有一个为什么读入文件通道是FileInputStream,但是写入为什么用的是RandomAccessFile?
这是因为FileChannel和SocketChannel不同,他是一个单向的通道,虽然有write方法,但是如果调用write就会抛出一个NonWritableChannelException 异常,这是因为他们都实现了ByteChannel,ByteChannel同时有read和write方法。如果用FileOutputStream映射的模式确没有WRITE_NOLY,所以只能用RandomAccessFile这种可以读,也可以写的流来获得通道,这样只用其中写的功能就可以完成了。
多次读写
public class ChannelTest2 {
public static void main(String[] args) {
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
inChannel = new FileInputStream("e:\\beauty.jpeg").getChannel();
outChannel = new FileOutputStream("e:\\beauty2.jpeg").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while((inChannel.read(buffer))!=-1) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(inChannel!=null) {
inChannel.close();
}
if(outChannel!=null) {
outChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这种多次读写的方式和普通IO流非常相似,以前是一个缓冲数组,现在是一个缓冲区,都是分多次读入,然后再写到指定流中。