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();
	}

}
你一定发现,在这里,并没有告诉Channel把多少内容读进Buffer。在每个Buffer中都有一套完整的内部计数系统来跟踪已经读了多少数据了,Buffer中还剩多少空间。关于Buffer的计数系统在随后的“Buffer内部原理”中讲解。


使用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();
	}

}
再次注意,我们没有必要告诉Channel总共要写多少数据,Buffer的内部计数系统会跟踪已经写了多少数据了,还剩多少空间可以使用。


本文介绍了Java NIO中三个核心概念中的两个,并且看了两个简单的示例,分别是使用NIO进行数据的读取和写入,下篇将会介绍Buffer内部实现。


参考:

Java NIO使用及原理分析 (一)

NIO 入门









发布了14 篇原创文章 · 获赞 21 · 访问量 4万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览