NIO(New I/O)
- NIO采用内存映射文件的方式来处理输入输出,它将文件或文件一段区域映射到内存中,这样就可以像访问内存一样来访问文件
- 在标准I/O中,使用的是字节流和字符流,NIO使用的是通道(Channel)和缓冲区(Buffer)。
- 数据总是从通道读入缓冲区,或从缓冲区写入通道
NIO主要有三大核心:
1. Buffer:可以看作是一个容器,其本质是一个数组缓冲区,读入或写出到Channel中的所有对象都会先放在Buffer中
2. Channel:是对传统的输入输出的模拟,在NIO中,所有的数据都需要通过通道流的形式传输
3. Selector(选择器):用于监听多个通道的事件(例如:连接打开、数据到达等),主要用于多线程处理
Buffer(缓冲器)
Java NIO中的Buffer用于和NIO中的Channel进行交互,交互时数据会从Channel读取到Buffer,或从Buffer写入到Channel中,如图:
从结构上说,Buffer类似一个数组,它可以保存多个类型相同的数据。从类型上说,Buffer是一个抽象类,其子类有ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer和ShortBuffer,最常用的是ByteBuffer和CharBuffer
Buffer的子类没有构造方法,因此不能通过构造方法来创建对象。创建Buffer对象,通常通过子类中的static XxxBuffer allocate(int capaction)方法来实现,例如创建一个容量为6的CharBuffer对象的语句:
CharBuffer charBuffer = CharBuffer.allocate(6);
Buffer中的三个概念需要了解:
- capacity(容量):缓冲区的容量表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量值不能为负数,也不能改变
- limit(界限):表示Buffer容器中不可读取的区域的第一个索引,即位于Buffer容器中索引为0到limit之间的区域都可以进行读取操作。缓冲区的limit值不能为负,也从不大于其容量
- position(位置):用于指定下一个可以被读取的缓冲区位置索引。新创建的Buffer对象,position的默认值为0,每进行一次读取或写入操作,position的值都会自动向后移动一步。
Buffer类中,定义很多方法
int capacity() //获取缓冲区大小
int position() //获取Buffer中position的值
Buffer position(int newPosition) //设置Buffer的position,并返回位置被修改之后的Buffer对象
int limit() //获取Buffer的limit的位置
Buffer limit(int newLimit) //设置limit的值,并返回一个新的limit缓冲区对象
Buffer mark() //设置Buffer的标记mark,只能在0到position之间做标记
Buffer reset() //将此缓冲区的位置重置为先前标记的位置
Buffer clear() //清除缓冲区,将position设置为0,limit设置为capacity
Buffer flip() //反转缓冲区,先将limit设置为当前position位置,然后再将position位置设置为0
Buffer rewind() //倒带缓冲区,将position位置设置为0,并取消设置的标记
int remaining() //获取当前位置和界限之间的元素个数
boolean hasRemaining() //判断当前位置position和limit之间是否还有元素
代码如下:
package com.Put;
import java.nio.CharBuffer;
public class IO {
public static void main(String[] args) {
//创建CharBuffer对象,并指定缓冲区的容量大小为6
CharBuffer charBuffer = CharBuffer.allocate(6);
System.out.println("容量:"+charBuffer.capacity());
System.out.println("界限值:"+charBuffer.limit());
System.out.println("初始值"+charBuffer.position());
//向CharBuffer对象中放入3个元素
charBuffer.put("x");
charBuffer.put("y");
charBuffer.put("z");
System.out.println("加入元素后的界限值:"+charBuffer.limit());
System.out.println("加入元素后的位置:"+charBuffer.position());
//执行flip()方法
charBuffer.flip();
System.out.println("执行flip()方法后的界限值:"+charBuffer.limit());
System.out.println("执行flip()方法后的位置:"+charBuffer.position());
//取出第一个元素
System.out.println("取出第一个元素为:"+charBuffer.get());
System.out.println("取出后的界限值:"+charBuffer.limit());
System.out.println("取出后的位置:"+charBuffer.position());
//执行clear()方法
charBuffer.clear();
System.out.println("执行clear()后的界限值:"+charBuffer.limit());
System.out.println("执行clear()后的位置:"+charBuffer.position());
//取出第一个元素
System.out.println("取出第一个元素为:"+charBuffer.get(0));
System.out.println("取出后的界限值:"+charBuffer.limit());
System.out.println("取出后的位置:"+charBuffer.position());
}
}
运行结果:
容量:6
界限值:6
初始值0
加入元素后的界限值:6
加入元素后的位置:3
执行flip()方法后的界限值:3
执行flip()方法后的位置:0
取出第一个元素为:x
取出后的界限值:3
取出后的位置:1
执行clear()后的界限值:6
执行clear()后的位置:0
取出第一个元素为:x
取出后的界限值:6
取出后的位置:0
Channel(通道)
- Channel是一个接口对象,它类似与传统的流对象,但与传统的流对象右有些不同,如下:
- Channel可以异步执行I/O读写操作
- Channel的读写操作是双向的,既可以从Channel中读取数据,又可以写数据到Channel,而流的读写通常是都是单向的
- Channel可以直接将指定的文件的部分或者全部映射成Buffer
- Channel只能与Buffer进行交互,程序不能直接读写Channel中的数据
Channel接口的实现类很多,主要讲解FileChannel的使用。Channel对象通过传统的I/O的getChannel()方法来获取对应的Channel,不同的流获取的Channel是不同的。如FileInputStream和FileOutputStream获取的是FileChannel,同时还可以使用RandomAccseeFile获取该对象。
FileChannel常用的方法:
通过FileChannel类的transferTo(long position,long count,WritableByteChannel target)方法实现了整个文件的拷贝,第一个参数表示所需转移文件的起始位置,这里表示从0开始;第二个参数表示要传输的最大字节数,这里通过size()方法获取了文件的字节数;第三个参数表示目标通道,即要传输到的位置。最后文件拷贝完成后,关闭所有资源
package com.Put;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
public class IO {
public static void main(String[] args) throws Exception {
//创建RandomAccessFile对象,并指定源文件
RandomAccessFile infile = new RandomAccessFile("E:\\Idea\\JavaSE\\重新学习\\resource\\985.png","rw");
//获取读取文件FileChannel通道
FileChannel inchannel = infile.getChannel();
//创建RandomAccessFile对象,并指定目标文件
RandomAccessFile outfile = new RandomAccessFile("E:\\Idea\\JavaSE\\重新学习\\target\\211.png","rw");
//获取复制目标文件FileChannel通道
FileChannel outchannel = outfile.getChannel();
//使用TransferTo进行整体复制
long transferTo = inchannel.transferTo(0, inchannel.size(), outchannel);
if (transferTo>0){
System.out.println("复制成功");
}
infile.close();
outfile.close();
inchannel.close();
outchannel.close();
}
}