java标准的I/O中,提供了基于流的I/O实现,即InputStream和OutputStream。这种基于流的实现以字节为单位处理数据。
NIO在java 1.4中被纳入到了JDK中,与旧式的的基于流的I/O相比,NIO是基于块的,以块为单位处理数据。NIO有两个重要的组件缓冲(Buffer)和通道(Channel)。
Buffer:缓冲是一块连续的内存块,是NIO读写数据的中转池。
Channel:通道主要用于向缓冲读取和写入数据,是访问缓冲的接口。
NIO是为了弥补IO操作的不足,NIO的一些新特性有:非阻塞I/O,缓冲,管道以及选择器。管道(Channel),缓冲(Buffer) ,选择器( Selector)是其主要特征。
Buffer和Channel:
在NIO的实现中,Buffer是一个抽象类。JDK为每一种java基本数据类型(除了Boolean)提供了缓冲区类型。
缓冲区类型 | 基本数据类型 |
---|---|
ByteBuffer | byte |
CharBuffer | char |
ShortBuffer | short |
IntBuffer | int |
LongBuffer | long |
FloatBuffer | float |
DoubleBuffer | double |
在NIO中,Buffer必须配合Channel使用。比如在读取一个channel的时候,需要先将数据写入到相应的Buffer,然后再Buffer中读取。
以一个读文件为例:
// 首先将文件打开,取得文件的Channel:
FileInputStream inputStream = new FileInputStream ( new File( "d:\\temp_buffer.txt"));
FileChannel fc = inputStream.getChannel();
// 要从文件Channel中读取数据,必须使用Buffer.
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
fc.read(byteBuffer);
// 此时,文件内容以及存在于Buffer中,准备读取ByteBuffer.
byteBuffer.flip();
Buffer的基本原理:
Buffer有三个重要的参数:位置(position)、limit(上限)、capacity(容量)。
参数 | 写模式 | 读模式 |
---|---|---|
位置(position) | 当前缓存区的位置,将从position的下一个位置写数据 | 当前缓冲区读取的位置,将从此位置后,读取数据 |
capacity(容量) | 缓存区的总容量上限 | 缓存区的总容量上限 |
limit(上限) | 通常情况下,和容量相同。小于等于容量 | 可读取的总容量,和上次写入的数据量相等 |
Buffer的创建:
Buffer的创建有两种方式:
1.静态allocate()从堆中分配缓冲区
2.从一个既有数组中创建缓冲区
// 从堆中分配
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从既有数组中创建
byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);
重置缓冲区:
Buffer提供了重置Buffer的函数。
public final Buffer flip()
函数flip()先将limit设置到position所在位置,然后将position置零,并清除标志位mark。通常在读写转换时使用。
NIO和传统的IO有什么区别呢?
1、IO是面向流的,NIO是面向块(缓冲区)的。
IO面向流的操作一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。,导致了数据的读取和写入效率不佳;
NIO面向块的操作在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多,同时数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。
通俗来说,NIO采取了“预读”的方式,当你读取某一部分数据时,他就会猜测你下一步可能会读取的数据而预先缓冲下来。
2、IO是阻塞的,NIO是非阻塞的。
对于传统的IO,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
而对于NIO,使用一个线程发送读取数据请求,没有得到响应之前,线程是空闲的,此时线程可以去执行别的任务,而不是像IO中那样只能等待响应完成。