通道有缓存区就像铁路与火车,铁路本身不具备运输能力,需要依靠火车的运作才具备运输能力,同样的,通道本身不具备数据传输能力,需要与缓冲区的配合,才能完成数据的传输。
通道有FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel这几类。
获取通道的方式:
1.通过getChannel()方法获取。
本地IO:FileOutPutStream,FileOutPutStream,RandomAccessFile
网络IO:Socket,ServerSocket ,DatagramSocket
FileChannel insChannel = ins.getChannel();
2.在JDK1.7以后,nio中针对各个IO提供静态方法open。
fileChannel = FileChannel.open(Path,StandardOpenOption.READ等)
3.JDK1.7以后,nio中的Files工具类提供newByteChannel方法。
缓冲区分为:直接缓冲区,非直接缓冲区
直接缓冲区创建在JVM上,通过allocate(int size)方法创建;
非直接缓冲区创建在物理机的内存中,通过allocateDirect(int size)创建;
直接缓冲区的性能比非直接缓冲区的性能要高,因为非直接缓冲区在应用程序与磁盘之间有个拷贝的过程,而直接缓冲区是通过内存文件映射的方式进行直接交互,没有拷贝过程,所以性能较好,但是会占据系统的内存。
下面的代码是使用缓冲区与通道的具体例子(均以文件复制为例):
1.非直接缓冲区
FileInputStream ins = new FileInputStream(filePath);
String newPath = filePath.substring(0,filePath.lastIndexOf(".")) +
"COPY" + filePath.substring(filePath.lastIndexOf("."));
FileOutputStream out = new FileOutputStream(newPath);
//1.获取通道
FileChannel insChannel = ins.getChannel();
FileChannel outChannel = out.getChannel();
//2.分配缓冲区ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取缓冲区数据
while(insChannel.read(buffer) != -1){
buffer.flip();//转换到写入模式。
outChannel.write(buffer);//将缓冲区写入到通道
buffer.clear();//清空缓冲区
}
System.out.println("copy完成...copy文件:" + newPath);
//关闭通道
insChannel.close();
outChannel.close();
ins.close();
out.close();
读取缓冲区数据时,如果read返回的-1则表示读到缓冲区末尾了。获取通道中的缓冲区后,通过flip()方法将写入模式转换为读取模式,flip方法源码如下,调用flip方法时,将limit(最多可读)设置为当前的option(缓冲区中当前写入的位置)的值(缓冲区写入模式中limit最大不超过capacity),并将option设置为0。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
2.直接缓冲区
// 获取通道,StandardOpenOption 枚举类,具体属性查看源码。
FileChannel inChannel = FileChannel.open(Paths.get(filePath), StandardOpenOption.READ);
String newPath = filePath.substring(0, filePath.lastIndexOf(".")) + "COPY"
+ filePath.substring(filePath.lastIndexOf("."));
// 给写入通道添加读权限、 写入权限和创建文件权限。
FileChannel outChannel = FileChannel.open(Paths.get(newPath), StandardOpenOption.WRITE,
StandardOpenOption.READ, StandardOpenOption.CREATE);
// 内存映射文件,只有ByteBuffer才有直接缓冲区操作。
// 内存映射文件只读模式。
MappedByteBuffer inMapBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
// 内存映射读写模式。
MappedByteBuffer outMapBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
// 直接对缓冲区进行数据读写操作 limit 属性表示当前缓冲区最多可读多少。
byte[] dst = new byte[inMapBuffer.limit()];
inMapBuffer.get(dst);
outMapBuffer.put(dst);
System.out.println("copy完成...copy文件:" + newPath);
inChannel.close();
outChannel.close();
1.创建通道fileChannel = FileChannel.open(Path,StandardOpenOption.READ等)
2.创建完通道后,通过inChannel.map(MapMode.READ_ONLY, 0, inChannel.size())返回的是 MappedByteBuffer 实际上就是内存映射文件
3.获得映射对象后,通过put,get等方法将byte数组写入到内存映射中去。inMapBuffer.get(byte[]);outMapBuffer.put(byte[]);等方法。
练习的时候有个小插曲,如果outChannel中没有设置read属性即StandardOpenOption.READ,而在创建MappedByteBuffer时MapMode选择的是READ_WRIT,运行会报异常。
Exception in thread "main" java.nio.channels.NonReadableChannelException
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:840)
at com.channel.TestChannel.copyFile2(TestChannel.java:35)
at com.channel.TestChannel.main(TestChannel.java:258)
还有注意StandardOpenOption中CREATE和CREATE_NEW,两则都表示当文件不存在时创建一个文件,两个的区别是:CREATE_NEW表示当文件存在时,则报错,抛出异常。
3.通道间的数据传输
//获取通道,StandardOpenOption 枚举类,具体属性查看源码。
FileChannel inChannel = FileChannel.open(Paths.get(filePath), StandardOpenOption.READ);
String newPath = filePath.substring(0,filePath.lastIndexOf(".")) +
"COPY" + filePath.substring(filePath.lastIndexOf("."));
//给写入通道添加读权限、 写入权限和创建文件权限。
FileChannel outChannel = FileChannel.open(Paths.get(newPath), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
inChannel.transferTo(0, inChannel.size(), outChannel);
// 与前面等价
// writeChannel.transferFrom(readerChannel, 0, readerChannel.size());
System.out.println("copy完成...copy文件:" + newPath);
inChannel.close();
outChannel.close();
通道间的数据传输本质也是直接缓冲区,可以通过transferTo或则transfreFrom方法,直接在两个通道间数据。
以上三种文件复制的方法,通过测试,通道间数据传输<直接缓冲区<普通IO<非直接缓冲区。都是复制一个500M大小的文件,测试结果跟本机也有关系。通道传输的的内存消耗比直接缓冲区的略少,普通IOCPU消耗较高,非直接缓冲区各项均衡。
分散读取与聚集写入:
分散读取: 将通道的数据按顺序分散到各个缓冲区
聚集写入: 将多个缓冲区的数据聚集到一个通道中。
FileChannel.reade(ByteByffer[])
FileChannel.write(ByteBuffer[])
原理就是创建缓冲区数组,由原来的操作单个缓冲区变为操作缓冲区数组。