一、NIO概述
java.nio全称java non-blockingIO,是指JDK1.4开始提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(即NewIO)。新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写,新增了满足NIO的功能。
NIO和BIO有着相同的目的和作用,但是它们的实现方式完全不同,BIO 以流的方式处理数据,而NIO以块的方式处理数据,块I/O的效率比流I/O高很多。另外,NIO是非阻塞式的,这一点跟BIO也很不相同,使用它可以提供非阻塞式的高伸缩性网络。NIO主要有三大核心部分: Channel(通道), Buffer(缓冲区), Selector(选择器)。传统的BIO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此使用单个线程就可以监听多个数据通道。
二、通道和缓冲区
2.1 概述
通道和缓冲区是NIO中的核心对象,几乎在每一个 I/O 操作中都要使用它们。
通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。一个Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。
2.2 什么是缓冲区
Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,可以将数据直接写入或者将数据直接读到 Stream 对象中。
在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
2.3 缓冲区类型
最常用的缓冲区类型是 ByteBuffer。一个 ByteBuffer 可以在其底层字节数组上进行 get/set 操作(即字节的获取和设置)。
对于每一种基本 Java 类型都有一种缓冲区类型:
ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
每一个 Buffer 类都是 Buffer 接口的一个实例。 除了 ByteBuffer,每一个 Buffer 类都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数标准 I/O 操作都使用 ByteBuffer,所以它具有所有共享的缓冲区操作以及一些特有的操作。
ByteBuffer类(二进制数据)该类的主要方法如下:
- public abstract ByteBuffer
put(byte[] b)
; 存储字节数据到缓冲区 - public abstract byte[]
get()
; 从缓冲区获得字节数据 - public final byte[]
array()
; 把缓冲区数据转换成字节数组 - public static ByteBuffer
allocate(int capacity)
; 设置缓冲区的初始容量 - public static ByteBuffer
wrap(byte[] array)
; 把一个现成的数组放到缓冲区中使用 - public final Buffer
flip()
; 翻转缓冲区,重置位置到初始位置
2.4 什么是通道
Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。
所有数据都通过 Buffer 对象来处理。一半不会将字节直接写入通道中,相反,是将数据写入包含一个或者多个字节的缓冲区。同样,不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
2.5 通道类型
通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。
因为它们是双向的,所以通道可以比流更好地反映底层操作系统的真实情况。特别是在 UNIX 模型中,底层操作系统通道是双向的。
2.6 常用的Channel类
FileChannel、DatagramChannel、ServerSocketChannel 和SocketChannel.
FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据读写,ServerSocketChannel 和SocketChannel用于TCP的数据读写。
这里我们先说说FileChannel类,该类主要用来对本地文件进行IO操作,主要方法如下所示:
- public int
read(ByteBuffer dst)
,读取数据并放到缓冲区中 - public int
write(ByteBuffe[src)
,把缓冲区的数据写到通道中 - public long
transferFrom(ReadableByteChannel src, long position, long count)
,从目标通道中复制数据 - public long
transferTo(long position, long count, WritableByteChannel target)
,把数据从当前通道复制给目标通道
三、NIO的文件IO操作
3.1 文件写入
@Test
public void test1() throws Exception {
//1.创建输出流
FileOutputStream fos = new FileOutputStream("test.txt");
//2.从流中得到一个通道
FileChannel fc = fos.getChannel();
//3.提供一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//4.往缓冲区中存入数据
String str = "hello,nio";
buffer.put(str.getBytes());
//5.翻转缓冲区 需要把指针复位,不然是写入的hello,nio后面的内容
buffer.flip();
//6.把缓冲区写到通道中
fc.write(buffer);
//7.关闭
fos.close();
}
3.1 文件读取
@Test
public void test2() throws Exception{
File file = new File("test.txt");
//1.创建输入流
FileInputStream fis = new FileInputStream(file);
//2.得到一个通道
FileChannel fc = fis.getChannel();
//3.准备一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate((int) file.length());
//4.从通道里读取数据并存到缓冲区中
fc.read(buffer);
System.out.println(new String(buffer.array()));
//5.关闭
fis.close();
}
3.3 文件复制
@Test
public void test3() throws Exception{
//1.创建两个流,一个读一个写
FileInputStream fis = new FileInputStream("test.txt");
FileOutputStream fos = new FileOutputStream(" /Users/pengweiwei/Downloads/test.txt");
//2.准备两个通道
FileChannel sourceChannel = fis.getChannel();
FileChannel destChannel = fos.getChannel();
//3.复制
destChannel.transferFrom(sourceChannel,0,sourceChannel.size());
//4.关闭流
fis.close();
fos.close();
}