背景
JDK1.4的java.nio.*包中引入了新的JavaI/O类库,其目的在于 提高速度。实际上,旧的I/O包已经使用nio重新实现过。因此,即使不显式使用nio编写代码,也能从中受益。
I/O的应用场景分为文件I/O和网络I/O,在这里之研究前者。
组成部分
nio主要有三大组成部分:通道(Channel)、缓冲器(Buffer)、Selector(选择区)。
因为nio的数据传输结构更接近于操作系统执行I/O的方式:通道和缓冲器,而传统的I/O是面向字节流和字符流,所以nio的速度更快。
通道用于存放数据,缓冲器用于传输数据,选择区用于监听多个通道的事件(比如:连接打开,数据到达)。
通道和缓冲器
我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
唯一直接与通道交互的缓冲器是ByteBuffer,即可以存储未加工字节的缓冲器。它是一个很基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个选择集。用于以原始的字节形式或基本数据类型输出和读取数据。但是它不能直接读取或输出对象,即使是字符串对象也不行。虽然这种处理很低级,但是却更贴合操作系统的处理方式。
旧I/O类库中的FileInputStream、FileOutputStream、RandomAccessFile这三个类被修改了,用以产生FileChannel。这三个类都是字节操纵流,与nio性质一致。像Reader和Writer这种面向字符流的类就不能用于产生通道;但是java.nio.channels.Channels类中提供了方法,可以在通道中产生Reader和Writer。
读写实例
public class GetChannel {
private static final int BSIZE = 2014;
public static void main(String[] args) throws IOException {
/**
* 写数据,
* 若已有内容,原内容会被覆盖;
* 若不存在文件,则会创建新文件并写入新内容
*/
FileChannel fc = new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("some text".getBytes()));
fc.close();
/**
* 添加内容至文件末尾
*/
fc = new RandomAccessFile("data.txt", "rw").getChannel();
fc.position(fc.size()); // 移动到文件末尾
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
/**
* 读取文件
*/
fc = new FileInputStream("data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
while(buff.hasRemaining()){
System.out.print((char)buff.get());
}
}
}
channel:对于上述三种流类,getChannel()方法都可以获得一个FileChannel。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问ByteBuffer:将字节存放于ByteBuffer的方法之一是:使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。也可以使用wrap()方法将已存在的字节数组“包装”到ByteBuffer中,这样就把一个数组包装为ByteBuffer缓冲器,一旦完成包装,底层数据就可以通过缓冲区或者直接访问。我们称第二种为数组支持的ByteBufferposition:可以使用FileChannel的position方法来在文件中移动FileChannel。在上例中是将其移动到文件末尾,方便内容的增加。allocate:对于只读访问,我们要显式使用静态的allocate()方法来分配ByteBuffer,nio的目标就是快速移动大量数据,因此ByteBuffer的大小就显得尤为重要——事实上,这里使用的1K可能比我们通常要使用的小一点(必须通过实际运行程序来找到最佳尺寸)。使用allocateDirect替代allocate,以产生一个与操作系统有更高耦合性的“直接”缓冲器还有可能达到更高的速度。但是,这种分配会增加开支,并且具体的实现也随操作系统的不同而不同。read()和flip():一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备。如果打算使用缓冲器执行进一步的read()操作,我们也必须调用clear()来为每个read()做好准备。如下例:
public class ChannelCopy {
private static final int BSIZE = 2014;
public static void main(String[] args) throws IOException {
FileChannel in = new FileInputStream("src/main/resources/in.txt").getChannel();
FileChannel out = new FileOutputStream("src/main/resources/out.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1){
buffer.flip(); // 准备写
out.write(buffer);
buffer.clear(); // 准备读
}
/**
* 常用的是直接使用transferTo 或者 transferFrom
*/
// in.transferTo(0, in.size(), out);
// 或者out.transferFrom(in, 0, in.size());
}
}
这里有两个FileChannel,一个用于读,一个用于写。每次read()操作之后,就会将数据输入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲区中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接收数据的准备。
文件内容复制之类的操作,一般使用transferTo或者transferFrom方法
本文介绍了Java NIO(New IO)的基本概念及其在文件I/O操作中的应用。主要内容包括:nio的背景介绍,核心组成部分(通道、缓冲器和选择区),以及如何使用ByteBuffer进行文件的读写操作。此外,还提供了一个完整的文件内容复制示例,展示了如何利用FileChannel和ByteBuffer进行高效的数据传输。
——综述&spm=1001.2101.3001.5002&articleId=78924852&d=1&t=3&u=be514c33a8c04736a0dd4ac84b8b4d2e)

被折叠的 条评论
为什么被折叠?



