NIO的使用
一)、什么叫NIO?
定义:是一套新的Java I/O标准, 在java1.4中被纳入JDK中。
二)、NIO的实现方法
NIO是基于块的, 以块为基本单位处理数据。
标准的I/O是基于流实现的,以字节为单位处理数据。
三)、NIO的特性
1).为所有的原始类型特供Buffer支持
ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
2).字符集编码解码解决方案,使用java.nio.Charset
- .增加通道(Channel)对象,做为新的原始的I/O抽象
4).支持锁和内存映射文件的文件访问接口
5).提供了基于Selector的异步网络I/O
四)、NIO的两个重要组件
Buffer: 缓冲, 是一块连续的内存块,是NIO中读写数据的中转地。
Channel: 通道, 表示缓冲数据的源头或目的地。
Buffer和Channel的关系:
Channel作为数据的源头:从Channel中写数据到Buffer
Channel ---------> Buffer
Channel作为数据的目的地:从Buffer中写出数据到Channel
Channel <--------- Buffer
五)、NIO的Buffer类族和Channel
Buffer: 是一个抽象类,JDK为每一种Java原生类型都创建了一个Buffer.
注: 除了ByteBuffer外,其它每一种Buffer都具有完全一样的操作。
原因:ByteBuffer多用于绝大多数数标准I/O操作的接口。
Channel: 是一个双向通道,既可读也可写。
注:应用程序中不能直接对Channel进行读写操作,在读取Channel时,需要先将数据读入到相对应的Buffer中,然后在Buffer中进行读取。
使用Buffer读取文件:
public class Nio_Buffer_Channel {
public static void main(String[] args) throws IOException {
//获取一个输入流对象
FileInputStream fin = new FileInputStream("d:/a.txt");
//获取输入流对象的通道,作为数据的源头
FileChannel fileChannel = fin.getChannel();
//创建一个Buffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
//从通道中读取数据到Buffer中
fileChannel.read(buffer);
//关闭通道
fileChannel.close();
buffer.flip();
}
}
使用Buffer完成文件的复制:
public class Nio_Buffer_Copy {
public static void main(String[] args) throws IOException {
//输出流对象
FileOutputStream fout = new FileOutputStream("d:/c.txt");
//输入流对象
FileInputStream fin = new FileInputStream("d:/a.txt");
//输出流的通道,数据的目的地
FileChannel writeChannel = fout.getChannel();
//输入流的通道,数据的源头
FileChannel readChannel = fin.getChannel();
//Buffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(true){
buffer.clear();
//返回读取数据的大小
int len = readChannel.read(buffer);
if(len == -1){
break;
}
buffer.flip();
writeChannel.write(buffer);
}
}
}
结果:
0
11
0
11
0
六)、深入学习Buffer
主要属性:
//标志位
private int mark = -1;
//写模式:当前缓冲区的位置,从Position的下一个位置写数据
//读模式:当前缓冲区的读位置,将从此位置后,读取数据
private int position = 0;
//写模式: 缓冲区的实际上限,总是小于等于容量,通常等于容量
//读模式: 代表可读取的总容量,和上次写入的数据量相等
private int limit;
//写模式: 缓冲区的总容量上限
//读模式: 缓冲区的总容量上限
private int capacity;
对Buffer进行claer()和flip()操作时lmint和position的变化
public class Filp_Clear {
public static void main(String[] args) {
//创建具有15个字节的Buffer对象
ByteBuffer buffer = ByteBuffer.allocate(15);
System.out.println("存入元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
//向Buffer中存入数据
for(int i = 0 ; i < 10 ; i++){
buffer.put((byte)i);
}
//存入元素后position和limit的变化
System.out.println("存入元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
buffer.flip();
//flip后position和limit的变化
System.out.println("flip后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
for(int i = 0 ; i < 5 ; i++){
System.out.print(buffer.get()+" ");
}
System.out.println();
//读取Buffer元素后position和limit的变化
System.out.println("读取Buffer元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
buffer.rewind();
System.out.println("rewind==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
buffer.flip();
//第二次flip后position和limit的变化
System.out.println("第二次flip后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
buffer.clear();
//clear后position和limit的变化
System.out.println("clear后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
}
}
运行结果:
存入元素后position和limit的变化==>position:0 limit:15 capacity:15
存入元素后position和limit的变化==>position:10 limit:15 capacity:15
flip后position和limit的变化==>position:0 limit:10 capacity:15
0 1 2 3 4
读取Buffer元素后position和limit的变化==>position:5 limit:10 capacity:15
rewind==>position:0 limit:10 capacity:15
第二次flip后position和limit的变化==>position:0 limit:0 capacity:15
clear后position和limit的变化==>position:0 limit:15 capacity:15
结果分析:
1).当第一次创建Buffer对象时
position = 0, capacity = limit = Buffer数组容量大小
2).往Buffer添加数据
position = 数组所占数据的大小,capacity = limit = Buffer数组容量大小
3).buffer.flip()操作
position = 0, limit = 数组中所占元素的大小,即原position值, capacity = Buffer数组容量大小
4).buffer.get()获取元素
position = 获取元素的个数,limit = 数组中所占元素的大小,capacity = Buffer数组容量大小
5).再次buffer.flip()
position = 获取元素的个数,limit = position值, capacity = Buffer数组容量大小
注: 当执行flip操作,limit值总是等于上一次的position值
6).buffer.clear
position = 0, capacity = limit = Buffer数组容量大小
总结:
i. put(): position = 数组元素个数 , limit 和 capacity 不变
ii. flip(): position = 0, limit = 原position值, capacity 不变
iii. rewind(): position = 0, limit 和 capacity 不变
iiii. claer(): 回到初始状态,position = 0, capacity = limit = Buffer数组容量大小。
七)、Buffer的相关操作
1)、Buffer的创建:
1.从堆中分配
//从堆中分配
ByteBuffer buffer = ByteBuffer.allocation(1024);
//ByteBuffer类
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
//HeapByteBuffer类 extend ByteBuffer
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);
/*
hb = new byte[cap];
offset = 0;
*/
}
//super(-1, 0, lim, cap, new byte[cap], 0)
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
2.从既有数组中创建
byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(arry);
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}
public static ByteBuffer wrap(byte[] array,
int offset, int length)
{
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}
HeapByteBuffer(byte[] buf, int off, int len) { // package-private
super(-1, off, off + len, buf.length, buf, 0);
/*
hb = buf;
offset = 0;
*/
}
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
2)、重置和清空缓存区
1.rewind(): 将position置为0,并清除标志位(mark)mark = -1
作用:提取Buffer的有效数据。
2.clear(): 将position重置为0,同时,将limit设置为capacity大小,清除标志位 (mark), msrk = -1
作用:为重写Buffer做准备。
3.flip(): 将limit设置到position所在位置,将position重置为0,并清除标志位mark = -1
作用:读写转换时使用。
注:这里的重置是指重置Buffer的各标志位,并不是清空Buffer的内容。
3)、读/写缓冲区
//返回当前position的数据,position后移一位
public byte get();
//读取Buffer的数据到dst,并恰当的移动position
public ByteBuffer get(byte[] dst);
//读取给定index上的数据,不改变position的位置
public byte get(int index);
//在当前位置写入给定数据,position后移一位
public ByteBuffer put(byte b);
//将当前数据写入index位置,position位置不变
public ByteBuffer put(int index, byte b);
//将给定的数据src写入到Buffer中,并恰当的移动position
public final ByteBuffer put(byte[] src);
4)、标志缓冲区
标志(mark)缓冲区:类似于书签,可以随时记录当前位置,在任意时刻,可以回到这个位置。
操作mark的方法:
public final Buffer mark()
public final Buffer reset()
1.设置mark为当前position值
buffer.mark();
public final Buffer mark() {
//将mark值置为当前的position值
mark = position;
return this;
}
2.获取mark值,将当前的position置为mark值
buffer.reset();
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
测试:
public class MarkTest {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0 ; i < 10 ; i++){
buffer.put(((byte)i));
}
//读写转换
buffer.flip();
for(int i = 0 ; i < buffer.limit() ; i++){
System.out.print(buffer.get());
if(i == 4){
//mark = 5,当 i = 4 时,get后,position后移的一位,所以mark = 5
buffer.mark();
System.out.print("mark = "+buffer.mark());
}
}
System.out.println();
System.out.println("原position值 = "+buffer.position());
//重置position为mark值
buffer.reset();
System.out.println("reset后position值 = "+buffer.position());
while(buffer.hasRemaining()){
System.out.print(buffer.get());
}
}
}
结果:
01234mark = java.nio.HeapByteBuffer[pos=5 lim=10 cap=15]56789
原position值 = 10
reset后position值 = 5
56789
5)、复制缓冲区
复制缓冲区:以原缓冲区为基础,生成一个完全一样的新缓冲区。
新缓冲区的特点:
1.新生成的缓冲区和原缓冲区共享相同的内存数据。
2.缓冲区的任意一方的数据改动都是相互可见的。
3.新生成的缓冲区和原缓冲区独立维护各自的position、limit、mark。
作用:增加了程序的灵活性,为多方同时处理数据提供了可能。
生成复制缓冲区的方法:
public ByteBuffer duplicate()
测试:
public class Duplicate_Buffer {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0 ; i < 10 ; i++){
buffer.put((byte)i);
}
//复制当前缓冲区
ByteBuffer copyBuffer = buffer.duplicate();
System.out.println("复制后的缓冲区");
System.out.println(buffer);
System.out.println(copyBuffer);
//重置copyBuffer
copyBuffer.flip();
//重置copyBuffer后缓冲区变化
System.out.println("重置copyBuffer后的缓冲区");
System.out.println(buffer);
System.out.println(copyBuffer);
//向copyBuffer缓冲区中存入数据
copyBuffer.put((byte)100);
//buffer在相同的位置数据也发生了变化
System.out.println("buffer.get(0) = "+buffer.get(0));
System.out.println("copyBuffer.get(0) = "+ copyBuffer.get(0));
}
}
结果:
复制后的缓冲区
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
重置copyBuffer后的缓冲区
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=0 lim=10 cap=15]
buffer.get(0) = 100
copyBuffer.get(0) = 100
结论:在对副本copyBuffer进行put操作后,原Buffer相同所在位置的数据也同样发
生了变化。
6)、缓冲区分片
定义:在现有的缓冲区中,创建新的子缓冲区。
特点:子缓冲区和父缓冲区共享数据。
创建子缓冲区的方法:slice()
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0; i < 10; i++){
buffer.put((byte)i);
}
buffer.position(2);
buffer.limit(6);
//生成2-6的缓冲区
ByteBuffer subBuffer = buffer.slice();
public class Slice_Buffer {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0; i < 10; i++){
buffer.put((byte)i);
}
buffer.position(2);
buffer.limit(6);
//生成2-6的缓冲区
ByteBuffer subBuffer = buffer.slice();
System.out.println(subBuffer);
System.out.println(subBuffer.get(0));
System.out.println(subBuffer.get(3));
}
}
结果:
java.nio.HeapByteBuffer[pos=0 lim=4 cap=4]
2
5
子缓冲区发生了变化,父缓冲区也随即发生变化:
public class Slice_Buffer {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0; i < 10; i++){
buffer.put((byte)i);
}
buffer.position(2);
buffer.limit(6);
//生成2-6的缓冲区
ByteBuffer subBuffer = buffer.slice();
System.out.println(subBuffer);
System.out.println(subBuffer.get(0));
System.out.println(subBuffer.get(3));
for(int i = 0; i < subBuffer.capacity(); i++){
byte bb = subBuffer.get(i);
bb*= 10;
subBuffer.put(bb);
}
//重置父缓冲区,若不重置,输出20、30、40、50
buffer.position(0);
buffer.limit(buffer.capacity());
while(buffer.hasRemaining()){
System.out.print(buffer.get()+" ");
}
System.out.println();
}
}
7)、只读缓冲区
特点:只读,只读缓冲区和原始缓冲区共享内存块,原始缓冲区的修改,只读缓冲区也是可见的。
创建只读缓冲区的方法:
asRreadOnlyBuffer()
public class AsReadOnlyBuffer_Test {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0; i < 10; i++){
buffer.put((byte)i);
}
//创建只读缓冲区
ByteBuffer readBuffer = buffer.asReadOnlyBuffer();
System.out.println(buffer);
System.out.println(readBuffer);
readBuffer.flip();
while(readBuffer.hasRemaining()){
System.out.print(readBuffer.get()+" ");
}
System.out.println();
//检测对原始缓冲区进行修改,只读缓冲区可见
buffer.put(2,(byte)20);
readBuffer.flip();
while(readBuffer.hasRemaining()){
System.out.print(readBuffer.get()+" ");
}
}
}
结果:
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBufferR[pos=10 lim=15 cap=15]
0 1 2 3 4 5 6 7 8 9
0 1 20 3 4 5 6 7 8 9
8)、文件映射到内存
使用文件映射到内存读取文件中的数据使用RandomAccessFile对象读取
RamdomAcessFile: 随机访问存取对象。
特点: 专门处理文件的类 ,支持"随机访问"方式。
随机:指可以跳转到文件的任意位置处读写数据 。
由FileChannel.map()将文件映射到内存:
//将文件的前1024个字节映射到内存
MappedByteBuffer mbb = channel.map(FileChannel.MapMode.Read_WRITE, 0, 1024)
public class MappedFile {
public static void main(String[] args) throws IOException {
//随机访问文件类
RandomAccessFile fin = new RandomAccessFile("d:/a.txt","rw");
FileChannel channel = fin.getChannel();
MappedByteBuffer buffer =
//将文件的前1024个字节映射到内存
channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
while(buffer.hasRemaining()){
System.out.print((char)buffer.get());
}
}
}
结果:
hhhhhhhhhhh
结论:使用文件映射内存的方式,将文本文件通过FileChannel映射到内存中,返回一个Buffer对象,从内存中读取文件的内容,通过修改Buffer,将实际数据写到对应的磁盘中。
9)、处理结构化数据
处理结构化数据的方法:
1.散射:将数据读入到一组Buffer中,即将数据读到Buffer[]中。
由scatteringByteBuffer接口提供操作方法:
public long read(Buffer[] dsts) throws IOException;
public long read(Buffer[] dsts, int offset, int length) throws IOException;
注:通过散射读取数据,通道依次填充每个缓冲区。
2.聚集:将数据写入到一组Buffer中,即将数据写入到Buffer[]中。
由GatheringByteChannel接口提供操作方法:
public long write(Buffer[] src) throws IOException;
public long write(Buffer[] src, int offset, int length) throws IOException;
散射/聚集的使用:
对于一个固定格式的文件的读写,在已知文件具体结构情况下,可以构建若干个符合文件结构的Buffer,Buffer的大小恰好符合各段结构的大小。
Jdk提供了各种通道,使用散射和聚集读写结构化数据:
例:DatagramChannel、 FileChannel、SocketChannel
注:这3个通道都实现了ScatteringByteChannel和GatheringByteChannel.
通过聚集创建文本文件,格式为 书名作者:
public class GatheringBuffer {
public static void main(String[] args) throws IOException {
//通过聚集创建文本文件,格式为 书名作者
ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("Utf-8"));
ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
int booklen = bookBuf.capacity();
int autlen = autBuf.capacity();
ByteBuffer buffer[] = new ByteBuffer[]{bookBuf ,autBuf};
File file = new File("d:/gather.txt");
//文件不存在时,创建一个新文件
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fou = new FileOutputStream(file);
FileChannel channel = fou.getChannel();
//聚集写文件
channel.write(buffer);
channel.close();
}
}
通过散射读取固定格式的文本文件:
前提:知道文件对应的格式长度,精确的构造Buffer
public class GatheringBuffer {
public static void main(String[] args) throws IOException {
//通过聚集创建文本文件,格式为 书名作者
ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("utf-8"));
ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
int booklen = bookBuf.capacity();
int autlen = autBuf.capacity();
ByteBuffer[] buffer = new ByteBuffer[]{bookBuf ,autBuf};
File file = new File("d:/gather.txt");
//文件不存在时,创建一个新文件
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fou = new FileOutputStream(file);
FileChannel channel = fou.getChannel();
//聚集写文件
channel.write(buffer);
channel.close();
//通过散射读取文件,格式:书名作者
//构造精确的Buffer
ByteBuffer scatter_bookBuffer = ByteBuffer.allocate(booklen);
ByteBuffer scatter_autBuffer = ByteBuffer.allocate(autlen);
ByteBuffer[] scatter_Buffer = new ByteBuffer[]{scatter_bookBuffer, scatter_autBuffer};
FileInputStream fin = new FileInputStream("d:/gather.txt");
FileChannel fileChannel = fin.getChannel();
fileChannel.read(scatter_Buffer);
String bookName = new String(scatter_Buffer[0].array(),"utf-8");
String authorName = new String(scatter_Buffer[1].array(),"utf-8");
System.out.println(bookName+authorName);
}
}
结果:
java性能优化技巧葛一鸣
八)、MappedByteBuffer性能评估
性能比较:
传统的基于流的I/O操作 < NIO的ByteBuffer < NIO的MappedByteBuffer(将文件映射到内存)。
九)、直接内存访问
DirectBuffer: Buffer提供的直接访问系统物理内存的类。
普通的ByteBuffer: 在堆上分配内存,其最大内存,受最大堆限制。
DirectBuffer: 直接分配在物理内存中,并不占用堆空间。
创建DirectBuffer对象:
ByteBuffer.allocateDirect()
DirectBuffer和ByteBuffer的区别:
1.DirectBuffer的内存访问速度比ByteBuffer的快
2.创建和销毁DirectBuffer的花费远比ByteBuffer的高
DirectBuffer的使用:
在需要频繁的创建和销毁Buffer时,不宜使用DirectBuffer, 但如果能将DirectBuffer进行复用,那么,在读写的情况下,可以大幅度的改善系统的性能。