Java NIO入门学习(一)
本文为NIO入门学习的第一篇,将会介绍NIO中几个重要的概念。
I/O即输入输出,指的是计算机和外界的接口,或者是单个程序同计算机其他部分的接口。 在Java1.4之前的I/O系统中,提供的都是面向流的I/O系统,系统每次处理一个字节,输入流(input stream)生产一个字节,输出流(output stream)消费一个字节。这种工作模式下,非常容易给流数据创建过滤器(filters),而且也很容易将多个过滤器串起来,每个过滤器针对流过自己的字节做相应处理。另一方面,在这种工作模式下面向流的IO通常很慢。而在Java 1.4中推出了NIO(New I/O),这是一个面向块的I/O系统,系统以块为单位处理数据,每个操作都会生产或者消费一“块”数据,以块为单位处理数据会比以字节(流)为单位处理数据快很多。但是面向块的IO系统同时也损失了一些优雅而简单的操作方式。
在NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。
缓冲区Buffer
Buffer本质上说是一个容器对象。任何发送到Channel的数据都必须先放进Buffer,类似的,任何从Channel中读出的数据都先读进Buffer。
Buffer就是一个装载数据的容器对象,数据从Buffer中读出,或者把数据写入Buffer中。在NIO中添加了Buffer对象,这是NIO和老IO最重要的区别。在面向流的I/O中,你可以把数据直接写入Stream对象,或者直接把数据从Stream中读出来,而不需要任何容器。
Buffer本质上就是一个数组(array)。通常它是一个字节数组,或者其他种类的可用数组。但是Buffer除了是一个数组之外,它还提供了结构化的访问数据的方法,并且还用来跟踪系统的读/写过程。
在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer。对于Java中的基本数据类型,基本上都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:
下面是一个简单的使用IntBuffer的例子:
- import java.nio.IntBuffer;
- public class TestIntBuffer {
- public static void main(String[] args) {
- // 分配新的int缓冲区,参数为缓冲区容量
- // 新缓冲区的当前位置position将为零,其界限(限制位置)limit将为其容量。
- // 它将具有一个底层实现数组,其数组偏移量将为零。
- IntBuffer buffer = IntBuffer.allocate(8);
- for (int i = 0; i < buffer.capacity(); ++i) {
- int j = 2 * (i + 1);
- // 将给定整数写入buffer的当前位置
- buffer.put(j);
- }
- // 重设buffer,将limit设置为position,position设置为0
- buffer.flip();
- // 查看在position和limit之间是否有元素
- while (buffer.hasRemaining()) {
- // 读取buffer当前位置的整数
- int j = buffer.get();
- System.out.print(j + " ");
- }
- }
- }
运行后可以看到:
在后面我们还会继续分析Buffer对象,以及它的几个重要的属性。
通道Channel
Channel模拟了老IO包中的流的概念。所有去任何地方(或者来自任何地方)的数据都必须通过Channel对象。可以从Channel中读取数据,也可以从Channel中写入数据。NIO与老IO相比而言,Channel就如同Stream。
所有被NIO处理的数据都必须通过Buffer对象。不能直接将任何字节写入Channel,而是必须先将数据写入Buffer。同样的,也不能直接从Channel中读取任何字节,必须先通过Channel将数据读入Buffer,然后再从Buffer中获取数据。
Channel与Stream的区别在于:Channel是双向的,而Stream只能是单向的(Stream必须是InputStream或者OutputStream的一个子类,即要么是输入流,要么是输出流,不能即输入又输出)。Channel在被打开之后,即可以读,也可以写,或者同时进行读写操作。 因为Channel是双向的,因此它比Stream更好的反应了底层操作系统IO的实质。特别是在Linux系统中,底层操作系统都是双向的。
在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:
使用NIO读取数据
在第一个练习中,我们首先从文件中读取一些数据。如果使用老的IO,只需要简单的创建FileInputStream,然后从中读取数据。在NIO中,事情变得有些不同了,首先需要从FileInputStream中获取Channel对象,然后使用这个Channel去读文件。
在NIO系统中任何时刻执行一个读操作时,都要从Buffer中读,但不是直接从Channel中读。由于所有的数据都需要通过Buffer承载,所以需首先从Channel中把数据读进Buffer。
因此,从文件中读数据一共有三步:
1. 从FileInputStream中获取Channel
2. 创建Buffer
3. 从Channel中把数据读入Buffer
下面是一个简单的使用NIO从文件中读取数据的例子:
- import java.io.FileInputStream;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- public class TestChannelRead {
- public static void main(String[] args) throws Exception {
- FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");
- // 获取通道
- FileChannel fileChannel = fileInputStream.getChannel();
- // 创建缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- // 读取数据到缓冲区
- fileChannel.read(buffer);
- // 重设buffer,将limit设置为position,position设置为0
- buffer.flip();
- // 查看在position和limit之间是否有元素
- while (buffer.hasRemaining()) {
- // 读取buffer当前位置的整数
- byte b = buffer.get();
- System.out.print((char) b);
- }
- fileInputStream.close();
- }
- }
使用NIO写入数据
使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入Channel,而是先将数据写入Buffer,可以分为下面三个步骤:
1. 从FileOutputStream获取Channel
2. 创建Buffer并且把数据放到Buffer中
3. 将Buffer中的数据写入Channel
下面是一个简单的使用NIO向文件中写入数据的例子:
- import java.io.FileOutputStream;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- public class TestChannelWrite {
- private static byte message[] = { 83, 111, 109, 101, 32, 98, 121, 116, 101,
- 115, 46 };
- public static void main(String[] args) throws Exception {
- FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");
- // 获取通道
- FileChannel fileChannel = fileOutputStream.getChannel();
- // 创建缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- // 数据存入缓冲区
- for (int i = 0; i < message.length; ++i) {
- buffer.put(message[i]);
- }
- // 重设buffer,将limit设置为position,position设置为0
- buffer.flip();
- // 将buffer中的数据写入
- fileChannel.write(buffer);
- fileOutputStream.close();
- }
- }
本文介绍了Java NIO中三个核心概念中的两个,并且看了两个简单的示例,分别是使用NIO进行数据的读取和写入,下篇将会介绍Buffer内部实现。
本文为NIO入门学习的第一篇,将会介绍NIO中几个重要的概念。
I/O即输入输出,指的是计算机和外界的接口,或者是单个程序同计算机其他部分的接口。 在Java1.4之前的I/O系统中,提供的都是面向流的I/O系统,系统每次处理一个字节,输入流(input stream)生产一个字节,输出流(output stream)消费一个字节。这种工作模式下,非常容易给流数据创建过滤器(filters),而且也很容易将多个过滤器串起来,每个过滤器针对流过自己的字节做相应处理。另一方面,在这种工作模式下面向流的IO通常很慢。而在Java 1.4中推出了NIO(New I/O),这是一个面向块的I/O系统,系统以块为单位处理数据,每个操作都会生产或者消费一“块”数据,以块为单位处理数据会比以字节(流)为单位处理数据快很多。但是面向块的IO系统同时也损失了一些优雅而简单的操作方式。
在NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。
缓冲区Buffer
Buffer本质上说是一个容器对象。任何发送到Channel的数据都必须先放进Buffer,类似的,任何从Channel中读出的数据都先读进Buffer。
Buffer就是一个装载数据的容器对象,数据从Buffer中读出,或者把数据写入Buffer中。在NIO中添加了Buffer对象,这是NIO和老IO最重要的区别。在面向流的I/O中,你可以把数据直接写入Stream对象,或者直接把数据从Stream中读出来,而不需要任何容器。
Buffer本质上就是一个数组(array)。通常它是一个字节数组,或者其他种类的可用数组。但是Buffer除了是一个数组之外,它还提供了结构化的访问数据的方法,并且还用来跟踪系统的读/写过程。
在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer。对于Java中的基本数据类型,基本上都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:
下面是一个简单的使用IntBuffer的例子:
- import java.nio.IntBuffer;
- public class TestIntBuffer {
- public static void main(String[] args) {
- // 分配新的int缓冲区,参数为缓冲区容量
- // 新缓冲区的当前位置position将为零,其界限(限制位置)limit将为其容量。
- // 它将具有一个底层实现数组,其数组偏移量将为零。
- IntBuffer buffer = IntBuffer.allocate(8);
- for (int i = 0; i < buffer.capacity(); ++i) {
- int j = 2 * (i + 1);
- // 将给定整数写入buffer的当前位置
- buffer.put(j);
- }
- // 重设buffer,将limit设置为position,position设置为0
- buffer.flip();
- // 查看在position和limit之间是否有元素
- while (buffer.hasRemaining()) {
- // 读取buffer当前位置的整数
- int j = buffer.get();
- System.out.print(j + " ");
- }
- }
- }
运行后可以看到:
在后面我们还会继续分析Buffer对象,以及它的几个重要的属性。
通道Channel
Channel模拟了老IO包中的流的概念。所有去任何地方(或者来自任何地方)的数据都必须通过Channel对象。可以从Channel中读取数据,也可以从Channel中写入数据。NIO与老IO相比而言,Channel就如同Stream。
所有被NIO处理的数据都必须通过Buffer对象。不能直接将任何字节写入Channel,而是必须先将数据写入Buffer。同样的,也不能直接从Channel中读取任何字节,必须先通过Channel将数据读入Buffer,然后再从Buffer中获取数据。
Channel与Stream的区别在于:Channel是双向的,而Stream只能是单向的(Stream必须是InputStream或者OutputStream的一个子类,即要么是输入流,要么是输出流,不能即输入又输出)。Channel在被打开之后,即可以读,也可以写,或者同时进行读写操作。 因为Channel是双向的,因此它比Stream更好的反应了底层操作系统IO的实质。特别是在Linux系统中,底层操作系统都是双向的。
在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:
使用NIO读取数据
在第一个练习中,我们首先从文件中读取一些数据。如果使用老的IO,只需要简单的创建FileInputStream,然后从中读取数据。在NIO中,事情变得有些不同了,首先需要从FileInputStream中获取Channel对象,然后使用这个Channel去读文件。
在NIO系统中任何时刻执行一个读操作时,都要从Buffer中读,但不是直接从Channel中读。由于所有的数据都需要通过Buffer承载,所以需首先从Channel中把数据读进Buffer。
因此,从文件中读数据一共有三步:
1. 从FileInputStream中获取Channel
2. 创建Buffer
3. 从Channel中把数据读入Buffer
下面是一个简单的使用NIO从文件中读取数据的例子:
- import java.io.FileInputStream;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- public class TestChannelRead {
- public static void main(String[] args) throws Exception {
- FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");
- // 获取通道
- FileChannel fileChannel = fileInputStream.getChannel();
- // 创建缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- // 读取数据到缓冲区
- fileChannel.read(buffer);
- // 重设buffer,将limit设置为position,position设置为0
- buffer.flip();
- // 查看在position和limit之间是否有元素
- while (buffer.hasRemaining()) {
- // 读取buffer当前位置的整数
- byte b = buffer.get();
- System.out.print((char) b);
- }
- fileInputStream.close();
- }
- }
使用NIO写入数据
使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入Channel,而是先将数据写入Buffer,可以分为下面三个步骤:
1. 从FileOutputStream获取Channel
2. 创建Buffer并且把数据放到Buffer中
3. 将Buffer中的数据写入Channel
下面是一个简单的使用NIO向文件中写入数据的例子:
- import java.io.FileOutputStream;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- public class TestChannelWrite {
- private static byte message[] = { 83, 111, 109, 101, 32, 98, 121, 116, 101,
- 115, 46 };
- public static void main(String[] args) throws Exception {
- FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");
- // 获取通道
- FileChannel fileChannel = fileOutputStream.getChannel();
- // 创建缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- // 数据存入缓冲区
- for (int i = 0; i < message.length; ++i) {
- buffer.put(message[i]);
- }
- // 重设buffer,将limit设置为position,position设置为0
- buffer.flip();
- // 将buffer中的数据写入
- fileChannel.write(buffer);
- fileOutputStream.close();
- }
- }
本文介绍了Java NIO中三个核心概念中的两个,并且看了两个简单的示例,分别是使用NIO进行数据的读取和写入,下篇将会介绍Buffer内部实现。