NIO简介
Java NIO 与传统 IO 相比,具有同样的作用和目的都是进行文件的读写操作,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。
Java NIO 与 IO 的主要区别:
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(无) | 选择器(Selectors) |
Java NIO的核心在于通道(Channel)和缓冲区(Buffer),通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。在NIO中,数据总是在channel中读取到缓冲区,或者从缓冲区写入到channel。
简而言之,Channel 负责传输, Buffer 负责运输。
缓冲区(Buffer)
Buffer就像一个数组,在Java NIO 中负责数据的存取。可以保存多个相同类型的数据。根据数据类型不同(boolean 除外) ,有以下 Buffer 常用子类:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
这些缓冲区的管理方式几乎一致,都是通过allocate()获取缓冲区。
缓冲区存取数据的两个核心方法:
- put():存入数据到缓冲区中
- get():获取缓冲区中的数据
缓冲区中的四个核心属性
- capacity:容量。表示缓冲区中最大存储数据的容量,一旦声明不可改变。
- limit:界限。表示缓冲区中可以操作数据的大小。(表示limit后的数据不可进行读写。)
- position:位置。表示缓冲区中正在操作数据的位置。
规律: mark <= position <= limit <= capacity
其中,mark
表示标记,记录当前position的位置,可以通过reset()恢复到mark的位置。
-
分配一个指定大小的缓冲区
ByteBuffer buf = ByteBUffer.allocate(1024);
缓冲区中 position,capacity,limit的示意图如下:
-
缓冲区写数据
String str = "abcde"; ByteBuffer buf = ByteBUffer.allocate(1024); buf.put(str.getBytes());
-
切换到读取数据模式
buf.flip()
-
缓冲区
mark
的使用
mark表示标记,表示记录当前position的位置,可以通过reset()恢复到mark的位置String str = "abcde"; ByteBuffer buf = ByteBuffer.allocate(1024); buf.put(str.getBytes);//向缓冲区中写入数据 buf.flip();//切换到读数据模式 byte[] dst = new byte[buf.limit()]; //声明一个limit大小的数组 buf.get(dst,0,2); //从缓冲区读两个字节数据到声明的数组中 buf.mark();//标记一下此时position的位置,此时position=2 buf.get(dst,2,2); //再读取两个字节的数据,此时position=4 buf.reset();//恢复到mark的位置
缓冲区的直接缓冲区与非直接缓冲区:
非直接缓冲区,是通过allocate()方法分配的缓冲区,建立在JVM的内存中的的一种缓冲区。
应用程序跟物理内存之间要经过一系列复杂的读写拷贝操作。
直接缓冲区,通过allocateDirect()方法分配的一种缓冲区,将缓冲区建立在操作系统的物理内存中。在某种情况下可以提高效率。
直接缓冲区,开辟在物理内存。应用程序一有数据直接面对缓冲区,物理磁盘也是直接买对缓冲区, 省去了物理磁盘、应用程序的拷贝操作。
通道(Channel)
通道(Channel),在java.nio.channels包下。Channel表示IO源与目标打开的连接。Channel类似于传统的“流”。只不过Channel本身不能直接访问,Channel只能与Buffer进行交互。
Channel的主要实现类
java.nio.channels.Channel 接口:
- FileChannel
- SocketChannel
- ServerSocketChannel
- DatagramChannel
从上面的类名我们就可以看出FileChannel主要用于本地的文件传输,SocketChannel、ServerSocketChannel配合用于网络TCP传输,DatagramChannel主要用于网络UDP传输。
获取通道的方式
- Java 针对支持通道的类提供了 getChannel()方法
- 本地IO:
- FileInputStream / FileOutputStream
- RandomAccessFile
- 网络IO:
- Socket
- ServerSocket
- DatagramSocket
- 本地IO:
- JDK 1.7 中的 NIO.2 针对各个通道,提供了静态方法
open()
- JDK 1.7 中的 NIO.2 的 Files 工具类的
newByteChannel()
利用通道完成文件的复制(非直接缓冲区)
public void test() {
long start = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
//获取通道
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("resources/1.mkv");
fos = new FileOutputStream("resources/2.mkv");
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//将通道中的数据写入缓冲区中
while(inChannel.read(buf) != -1) {
buf.flip();//切换读取数据的模式
outChannel.write(buf);//将缓冲区中的数据写入通道中
buf.clear();//清空缓冲区
}
}catch(Exception e) {
e.printStackTrace();
}finally {
if(outChannel != null){
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inChannel != null){
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
利用静态方法open() ,实现文件复制(直接缓冲区)
FileChannel inChannel = FileChannel.open(Paths.get("resources/Lambda.png"),StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("resources/2.png"),StandardOpenOption.WRITE,
StandardOpenOption.READ,StandardOpenOption.CREATE);
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE,0,inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();