为了提高Java I/O的速度和效率,从JDK1.4开始引入了java.nio.*包,即java new I/O(NIO)。
事实上,为了利用java nio的速度和效率优势,原来的java I/O包中相关的类已经使用java nio重新实现,因此在编程中即使没有显式地使用java nio的代码,使用传统java I/O还是利用了nio的速度和效率优势。Java nio的速度提高主要在:文件I/O和网络I/O两个方面。
Java nio的速度提升主要因为使用了类似于操作系统本身I/O的数据结构:I/O通道(Channel)和缓冲区。
1.Channel通道:
通道表示实体,如硬件设备、文件、网络套接字或可以执行的一个或多个不同的I/O操作(如读取或写入)的程序组件的开发的链接,用于I/O操作的链接。
通道可处于打开或关闭状态。创建通道时它处于打开状态,一旦将其关闭,则保持关闭状态。一旦关闭了某个通道,试图对其调用I/O操作都会抛出ClosedChannelException异常,通过调用通道的isOpen()方法可以探测通道是否处于打开状态。一般情况下通道对于多线程的访问是安全的。
2.ByteBuffer字节缓冲区:
字节缓冲区是nio中唯一直接和通道channel打交道的缓冲区。字节缓冲区可以存放原始字节,也可以存放java的8中基本类型数据,但是不能存放引用类型数据,String也是不能存放的。正是由于这种底层的存放类型似的字节缓冲区可以更加高效和绝大部分操作系统的I/O进行映射。
字节缓冲区通过allocation()方法创建,此方法为该缓冲区的内容分配空间,或者通过wrapping方法将现有的字节数组包装到缓冲区中来创建。
字节缓冲区的常用操作:
(1).读写单个字节的绝对和相对get和put方法:
a. 绝对方法:
get(int index):读取指定索引处的字节。
put(int index, byte b):将字节写入指定索引处。
b.相对方法:
get():读取此缓冲区当前位置的字节,然后该位置递增。
put(byte b):将字节写入此缓冲区的当前位置,然后该位置递增。
(2).相对批量get方法:
ByteBuffer get(byte[] dst):将此缓冲区的字节传输到给定的目标数组中。
(3).相对批量put方法:
ByteBuffer put(byte[] src):将给定的源byte数组的所有内容传输到此缓冲区中。
(4).读写其他基本类型值:
getChar(), putChar(char value), getChare(int index), putChar(int index, char value).
getInt(), putInt(int value), getInt(int index), putInt(int index, int value)等等读写基本类型值得相对和绝对方法。
注意:基本类型值得相对和绝对读写方法,根据java基本类型数据底层字节数进行缓冲区移动。
(5).创建视图缓冲区:
为了访问同类二进制数据,允许将字节缓冲区视为包含它们基本类型值的缓冲区,视图缓冲区只是其内容受该字节缓冲区支持的另一种缓冲区。字节缓冲区和视图缓冲区内容更改是相互可见的。这两种缓冲区的位置、限制和标记值都是独立的。创建方法如下:
asCharBuffer(), asDoubleBuffer(), asFloatBuffer(), asIntBuffer(), asLongBuffer(), asReadOnlyBuffer(), asShortBuffer()。
与具体类型的get和put方法系列相比,视图缓冲区优势如下:
a视图缓冲区不是根据字节进行索引,而是根据其特定类型的值得大小进行索引。
b.视图缓冲区提供了相对批量get和put方法,这些方法可以在缓冲区和数组或相同类型的其他缓冲区直接传输连续序列的值。
c.视图缓冲区可能更搞效,因为当且仅当其支持的字节缓冲区为直接缓冲区时它才是直接缓冲区。
(6).缓冲区其他操作:
a.ByteBuffer compact():压缩缓冲区,从缓冲区写入数据之后调用,以防写入不完整。
b.ByteBuffer duplicate():创建共享此缓冲区内容的新的字节缓冲区,新缓冲区中的内容为此缓冲区的内容,此缓冲区和新缓冲区内容更改是相互可见的。
c.ByteBuffer slice():创建新的字节缓冲区,其内容是此缓冲区内容的共享子序列。
3.直接与非直接缓冲区:
字节缓冲区要么是直接的,要么是非直接的。
如果字节缓冲区是直接的,则JVM会尽最大努力直接在此缓冲区上执行本机的I/O操作,即在每次调用底层操作系统的一个本机I/O之前(或之后),JVM都会尽量避免将缓冲区的内容复制到中间缓冲区中(或尽量避免从中间缓冲区复制内容)。直接字节缓冲区可以通过调用allocateDirect()方法来创建,此方法返回的缓冲区进行分配和取消分配所需的成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此它对应用程序的内存需求量造成的影响可能并不明显,所以建议将直接缓冲区主要分配给那些容易受底层操作系统的本机I/O操作影响的大型的、持久的缓冲区
可以通过调用isDirect()来判断字节缓冲区是直接的还是非直接的。
4.文件通道:
Java I/O的FileInputStream,FileOutputStream和RandomAccessFile可以产生文件通道,例子如下:
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class FileChannel{
//分配字节缓冲区时指定其大小
private static final int BSIZE = 1024;
public static void main(String[] args) throw Exception{
//获取FileOutputStram的文件通道
FileChannel fc = new FileOutputStream(“data.txt”).getChannel();
//向字节缓冲区中写入字节数组
fc.write(ByteBuffer.wrap(“Some text”.getBytes()));
fc.close();
//以读写方式获取随机访问文件的文件通道
fc = new RandomAccessFile(“data.txt”, “rw”).getChannel();
//定位到字节缓冲区当前内容之后
fc.position(fc.size());
//向字节缓冲区中追加内容
fc.write(ByteBuffer.wrap(“Some more”.getBytes()));
fc.close();
//获取FileInputStream的文件通道
fc = new FileInputStream(“data.txt”).getChannel();
//分配字节缓冲区
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
//将字节数组从文件通道读入到字节缓冲区中
fc.read(buff);
//放置缓冲区,为缓冲区写出或相对获取做准备
buff.flip();
//判断缓冲区是是否还有元素
while(buff.hasRemaining()){
//获取字节缓冲区字节的相对方法
System.out.println((char)buff.get());
}
}
}
输出结果:
Some text Some more
传统java I/O中FileInputStream, FileOutputStream和RandomAccessFile三个类可以产生文件通道。
注意:Java new I/O的目标是提高I/O速度,快速移动大批量的数据,因此,字节缓冲区的大小非常重要,例子中的1K大小不一定是合适的,应用程序需要在生产环境中测试确定合适的缓冲区大小。
5.文件复制:
文件通道和字节缓冲区不但可以实现数据从文件读入和读出,还可以实现文件的复制,例子如下:
import java.nio.*;
import java.nio.channels/*;
import java.io.*
public class FileCopy{
//字节缓冲区大小
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception{
//获取文件输入通道
FileChannel in = new FileInputStream(“FileCopy.java”).getChannel();
//获取文件输出通道
FileChannel out = new FileOutputStream(“FileOut.txt”).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
While(in.read(buffer) != -1){
//重置缓冲区,准备写出
buffer.flip();
//将字节缓冲区内容写出到文件输出通道
out.write(buffer);
//清除字节缓冲区
buffer.clear();
}
}
}
6.文件通道传输:
文件通道的transferFrom(ReadableByteChannel src, long position, long count)方法将字节从给定的可读字节通道传输到此通道的文件中,transferTo(long position, long count, WritableByteChannel target)方法将此通道的文件传输到给定的可写字节通道。例子如下:
import java.nio.channels.*;
import java.io.*
public class ChannelTransfer{
public static void main(String[] args) throws Exception{
FileChannel in = new FileInputStream(“ChannelTransfer.java”).getChannel();
FileChannel out = new FileOutputStream(“out.txt”).getChannel();
//将输入文件通道传输到输出文件通道
in.transferTo(0, in.size(); out);
//从输入文件通道传输到输出文件通道
out.transferFrom(in, 0, in.size());
}
}