Java在1.5之后提供了新的IO通信框架,NIO和普通IO的区别是NIO是基于channel和Buffer来进行操作的,这和传统的IO是有一些区别的,传统的IO是基于管道流的方式进行数据传输,而NIO的数据首先需要添加到buffer中,之后通过channel来进行传输。
Buffer缓冲区
首先要学会Buffer缓冲区的基本用法,不同的数据类型都是自己的缓冲区,但是在NIO中比较通用的是ByteBuffer,通过allocate和allocateDirect来创建缓冲区,第一种缓冲区是在堆中创建,第二种缓冲区会在操作系统的内存中创建,第二种缓冲区占用的是系统的内存资源,创建和销毁都需要一定的开销,在使用channel的时候能够提高一定的效率。这里先介绍Buffer的用法
//创建了一个8个字节的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(8);
缓冲区中有四个比较有用的属性
- capacity:缓冲区的大小,此时是8个字节
- limit:缓冲区的目前的可存储量,如果不做任何操作,该值用来表示缓冲区当前的存储位置,如果存储之后超过这个值就不能再存储。
- position:即将被读写的缓冲区的位置的索引。
- mark:一个游标对象,使用mark()方法可以将mark设置到position的位置,使用reset()方法可以将position设置为mark的值。
下面通过代码来了解缓冲区
//此时capacity是8,limit是8,position是0
System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());
没有任何数据的时候capactiy是8,说明是缓冲区的总大小,limit是8,表示可以存储到8个字节的位置,position表示缓冲区当前可以直接写入的索引,下面写一些数据看看
//写入hello
buffer.put("hello".getBytes());
//此时capacity是8,limit是8,position是5
System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());
//还存在3个元素的数量
System.out.println(buffer.remaining());
//存储空间不够,报错
buffer.put("this".getBytes());
添加了数据之后limit的值不变,position的位置调整了,使用remaining可以获取还能存储的数量。下面我们来看limit的作用,limit主要是在读取的时候有用,要读取信息,可以将position设置到0,之后通过get方法来读取。nio中提供了flip()方法来反转缓冲区,反转之后limit会指向当前缓冲区的最大值。
buffer.flip();
//此时capacity是8,limit是5,position是0
System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());
byte[] buf = new byte[buffer.limit()];
buffer.get(buf,0,buffer.limit());
System.out.println(new String(buf));
//读完之后position的值和limit一样
System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());
//再写入一个值,由于已经到了limit的位置,就不能再写入了,写入就会报错
buffer.put((byte)('A'));
此时通过clear()可以重置position为0,而且设置limit为capacity。
buffer.clear();
//capactiy为8,limit为8,position为0
System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());
//可以读取值,说明clear并不会清空缓冲区
System.out.println((char)buffer.get());
buffer.clear();//清空
最后看看mark和reset,当在某个位置是调用mark()方法,会将mark设置为当前的position,此时可以通过reset回复到原来的mark的位置
buffer.put("abc".getBytes());
buffer.mark();//设置mark的标记,此时mark这个变量为position
buffer.put("yes".getBytes());//在abc之后加入yes
buffer.reset();//此时position会设置到mark
System.out.println((char)buffer.get());////读取的值是y
Channel的讲解
NIO中是通过Channel来传输buffer的数据,操作和IO类似,但多了将Buffer添加到Channel的步骤
public class TestChannel {
public static void main(String[] args) {
FileChannel fc = null;
try {
//创建文件管道
fc = FileChannel.open(Paths.get("d:/test/01.txt"), StandardOpenOption.READ);
//创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
byte[] buf = new byte[1024];
//只要能够从缓冲区中读取数据
while((len=fc.read(buffer))>0) {
//重置缓冲区
buffer.flip();
//从缓冲区读取到字节数组中
buffer.get(buf,0,len);
//输出字节数组的值
System.out.println(new String(buf,0,len));
//重置缓冲区
buffer.clear();
}
fc.read(buffer);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fc!=null) fc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
以上程序实现了通过一个文件管道读数据,此时没有输入和输出概念,如果希望把一个文件读到另外一个文件中,需要再创建一个通道来写文件。
public class TestChannel02 {
public static void main(String[] args) {
FileChannel fin = null;
FileChannel fout = null;
ByteBuffer buf = null;
try {
//创建两个通道,一个读数据,一个写数据
fin = FileChannel.open(Paths.get("d:/test/01.jpg"), StandardOpenOption.READ);
//设置文件如果不存在就创建,并且可以进行写操作
fout = FileChannel.open(Paths.get("d:/test/02.jpg"),StandardOpenOption.CREATE,StandardOpenOption.WRITE);
//创建缓冲区来作为数据的中转
buf = ByteBuffer.allocate(1024);
while((fin.read(buf))>0) {
buf.flip();
fout.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fin!=null) fin.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fout!=null) fout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在NIO中提供了transferTo方法来快速将两个通道进行转换
public class TestChannel03 {
public static void main(String[] args) {
FileChannel fin = null;
FileChannel fout = null;
try {
fin = FileChannel.open(Paths.get("d:/test/01.jpg"), StandardOpenOption.READ);
fout = FileChannel.open(Paths.get("d:/test/03.jpg"), StandardOpenOption.CREATE_NEW,StandardOpenOption.WRITE);
//通过transferTo可以将两个通道对接
fin.transferTo(0, fin.size(), fout);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fin!=null) fin.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fout!=null) fout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
以上就是NIO的基本操作,难度不大,NIO目前比较的优势在于网络通信中,下一部分将会讲解基于网络通信的NIO操作。