一、NIO介绍
1、在软件系统中,由于I/O的速度要比内存速度慢,因此,I/O读写在很多场合都会成为系统的瓶颈。提升I/O速度,对提升系统整体性能有着很大的好处。
在java标准的I/O中,提供了基于流的I/O实现,及InputStream和outputStream.这种基于流的实现以 字节为单位处理数据,并且非常容易建立各种过滤器。
NIO是NEW I/O的简称,与旧式的基于流的I/O方法相对,表示新的一套java i/o标准。Java1.4加入到JDK中,有以下特性:
- 为所有的原始类型提供(Buffer)缓存支持;
- 使用Java.nio.charset.Charset作为字符集编码解码解决方案;
- 增加通道(Channel)对象,作为新的原始I/O抽象;
- 支持锁和内存映射文件的文件访问接口;
- 提供了基于Selector的异步网络I/O。
与流式的I/O不同,NIO是基于块(Block)的,他以块为基本单位处理数据,在NIO中最为重要的两个组件是缓存Buffer和通道Channel。缓存是一块连续的内存块,是NIO读写数据的中转地。通道表示缓冲数据的源头或者目的地,他用于缓冲读取或者写入数据,是访问缓冲的接口,如下图展示子通道和缓冲的关系。
2、NIO的Buffer类族和Channel
在NIO的实现中,Buffer是一个抽象类。JDK为每一种Java原生类型都创建了一个Buffer,
除了ByteBuffer外,其他每一种Buffer都具有完全一样的操作,唯一的区别仅仅在于他们所对应的数据类型。因为ByteBuffer多用于绝大多数标准I/O操作的接口,因此他有一些特殊的方法。
在NIO中和Buffer配合使用的还有Channel,Channel是一个双向通道,即可读,也可写,有点类似Stream,但Stream是单向的,应用程序中不能直接对Channel进行读写操作,而必须通过Buffer来进行。比如,在读一个Channel的时候,需要先将数据读入到相应的Buffer,然后再Buffer中进行读取。
public static void niocopeFile(String resource, String destination) throws Exception{
FileInputStream fis=new FileInputStream(resource);
FileOutputStream fos=new FileOutputStream(destination);
FileChannel readChannel = fis.getChannel();
FileChannel writerChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len;
while(true){
//为读入数据到buffer做准备
buffer.clear();
//讀入数据 ,,内容已存入buffer中
len = readChannel.read(buffer);
if(len==-1){
//讀取完畢
break;
}
//从 写状态 转为 读状态,flip()方法主要是在“读写切换时”调用
buffer.flip();
writerChannel.write(buffer);
}
readChannel.close();
writerChannel.close();
System.out.println("复制完");
}
3、Buffer的基本原理
Buffer中有三个重要的参数:位置(position) 、容量(capactiy)和上限(limit),三者的含义如下:
为了更好理解Buffer的工作模式,实现以下实例:
public static void demo1(){
ByteBuffer b=ByteBuffer.allocate(20);//15个字节大小的缓冲区
System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
for(int i=0;i<10;i++){
b.put((byte)i); //存入10个字节数据
}
System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
//重置position
b.flip();
System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
for (int i = 0; i < 5; i++) {
System.out.print(b.get()+" ");
}
System.out.println();
System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
b.flip(); //切到写
System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
}
输出如下:
position=0 limit=20 capactiy= 20
position=10 limit=20 capactiy= 20
position=0 limit=10 capactiy= 20
0 1 2 3 4
position=5 limit=10 capactiy= 20
position=0 limit=5 capactiy= 20
Buffer的三个重置和清空方法:实际上就是对缓冲区的参数进行操作
//比如用在复制该buffer的有效数据到另一个数组里面
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
标志缓冲区:mark()方法
public static void demo2(){
ByteBuffer b=ByteBuffer.allocate(20);
for (int i = 0; i < 15; i++) {
b.put((byte)i);
}
b.flip();//准备读
for (int i = 0; i < 15; i++) {
//在位置10的地方做mark
if(i==10){
b.mark();
System.out.print(" set mark ");
}
System.out.print(b.get()+" ");
}
//回到mark的位置,读取后续数据
b.reset();
System.out.println("\nreset to mark");
while(b.hasRemaining()){
System.out.print(b.get()+" ");
}
}
输出:
0 1 2 3 4 5 6 7 8 9 set mark 10 11 12 13 14
reset to mark
10 11 12 13 14
复制缓冲区 duplicate() 方法:可以为多方同时处理数据
/*
* 复制缓冲区:
* 1、主缓冲区、副本缓冲区,都是拿同一个地方的数据
* 2、各自维护自己的position、limit、mark
*/
public static void demo3(){
ByteBuffer b=ByteBuffer.allocate(20);
for (int i = 0; i < 15; i++) {
b.put((byte)i);
}
ByteBuffer copyB = b.duplicate();
System.out.println("执行b.duplicate()后,");
System.out.println(b);
System.out.println(copyB);
copyB.flip();
System.out.println("执行copyB.flip()后,");
System.out.println(b);
System.out.println(copyB);
System.out.println("想副本缓冲区插入一个数据:(byte)100");
copyB.put((byte)100);
System.out.println("获取主缓冲区跟副本缓冲区第一个数据");
System.out.println("b.get(0):"+b.get(0));
System.out.println("copyB.get(0):"+copyB.get(0));
}
缓冲区分片:slice()方法,
* 1、获取主缓冲去中的一个片段,
* 2、设置position、limit为范围,跟复制缓冲区一个概念,存储的数据本质是一样的。
* b.position(3);
* b.limit(10);
* b.slice();
只读缓冲区asReadOnlyBuffer() 方法
* 创建只读缓冲区,只能读取数据,写入数据会报错。
文件映射到内存 MappedByteBuffer
/*
* NIO提供一种将文件映射到内存的方法进行I/O操作,他可以比常规的基于流的I/O快很多,FileChannal.map()方法实现
*/
public static void demo5() throws Exception{
RandomAccessFile raf=new RandomAccessFile("d:\\student.txt","rw");
FileChannel fc=raf.getChannel();
//MappedByteBuffer 是ByteBuffer的子类
//通过FileChannel将文件映射到内存中。
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, raf.length());
while(mbb.hasRemaining()){
System.out.print(mbb.get()+" ");
}
//通过修改Buffer,通过映射,将实际数据写到对应 的磁盘中。
mbb.put(0,(byte)98);
raf.close();
}
处理结构化数据:
/*
* 处理结构化数据
* 1、散射:将数据读入一组Buffer中
* 2、聚集:将数据写入一组Buffer中
* 简单来说就是将将数据处理成Buffer数组
*/
public static void demo6() throws Exception{
/*
* 聚集
*/
ByteBuffer b1=ByteBuffer.wrap("java程序性能优化".getBytes());
ByteBuffer b2=ByteBuffer.wrap(" 葛一鸣".getBytes());
int booklen=b1.limit();
int autlen=b2.limit();
FileOutputStream fos=new FileOutputStream("d:\\student.txt");
ByteBuffer[] bs={b1,b2};
FileChannel channel = fos.getChannel();
channel.write(bs);
fos.close();
/*
* 散射
*/
ByteBuffer b3=ByteBuffer.allocate(booklen);
ByteBuffer b4=ByteBuffer.allocate(autlen);
ByteBuffer[] bs2=new ByteBuffer[]{b3,b4};
FileInputStream fis=new FileInputStream("d:\\student.txt");
FileChannel channel2 = fis.getChannel();
channel2.read(bs2);
String bookname=new String(bs2[0].array(),"utf-8");
String atuname=new String(bs2[1].array(),"utf-8");
System.out.println(bookname+atuname);
fis.close();
}
控制台输出跟文件内容展示: