您的位置:首页 > 编程语言 > Java开发

【Java没基础】I/O学习笔记(二)-NIO

2018-01-16 18:07 190 查看
Java 在 1.4 版本之后引入了新的 I/O 类库 – java.nio.* 目的在于提高速度。而且,Java 的传统 IO 类库中的一部分内容也由 NIO 进行了重新实现,以便充分的利用这种速度提高,尽管我们有时并没有显示的使用 NIO 类库,我们也能从中受益。

NIO 速度的提升原因只要来自于所使用的结构更接近与系统执行I/O 的方式:通道和缓冲器。

在 NIO 中,唯一直接与通道交互的缓冲器是
byteBuffer
——存储未加工字节的缓冲器。而旧的I/O类库有三个类被修改,用以产生
FileChannel(文件通道)
,这三个被修改的类是
FileInputStream
FileOutputStream
以及用于即读又写的
RandomAccessFile
, 这些类是字节操纵流,与底层的 NIO 性质一致。
Reader
Writer
这种字符模式类不能用于产生通道,但是
java.nio.channels.Channels
类为我们提供了实用方法,用以在通道中产生 Reader 和 Writer.

ByteBuffer

使用 ByteBuffer 的方法之一 是使用 put 直接进行填充,填入一个或多个字节、或基本数据类型的值,也可以使用 warp() 方法将已存在的字节数组“包装”到 ByteBuffer 中,一旦如此做,就不再复制底层的数组,而是把它作为 “产生的ByteBuffer 的存储器”,我们称其为 数组支持的 ByteBuffer

对于只读访问,我们必须显示的使用静态的
allocate()
方法来分配 ByteBuffer。 NIO 的目标就是快速移动大量数据,因此 ByteBuffer 的大小就显得十分重要 —— 实际上我们需要通过实际运行程序来找到最佳尺寸(可以先设定为 1K)。 我们也可以达到更高的速度,不过我们需要使用
allocateDirect()
而不是
allocate()
,用以产生一个与操作系统具有更高耦合性的 “直接”缓冲器,但是这种分配的开支很大,并且具体实现也随着操作系统的不容而不同,因此我们需要再次运行程序来了解 直接缓冲 是否可以使我们获得更大的速度优势。

使用 put() 将值填充到 ByteBuffer 之后,就需要调用 read() 来告知 FileChannel 向 ByteBuffer 存储字节,一旦使用 read(),就必须调用缓冲器上的 flip(), 让它做好让别人读取字节的准备。 如果我们打算使用缓冲器进行进一步的 read() 操作,我们也必须要调用 clearI() 来为每个 read() 做好准备。

/**
* 使用 Channel 来进行数据拷贝
* @author Thinking In Java
*/
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
FileChannel in = new FileInputStream("a.txt").getChannel();
FileChannel out = new FileOutputStream("a_copy.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1) {
buffer.flip(); // 准备写
out.write(buffer);
buffer.clear(); // 准备读
}
}
}


从上面一段拷贝数据的代码可以看到,打开一个 FileChannel 用于读,打开另一个用于写。 ByteBuffer 被分配了空间,当 FileChannel.read() 返回 -1 时,表示我们已经达到了输入的末尾。

read() 操作后,数据已经输入到了缓冲器中,flip() 则是准备缓冲器,以便它的信息可以由 write() 提取。write() 操作之后,信息仍在缓存器中,接着 clear() 操作则对所有的内部指针重新安排,以便缓冲器在另一个 read() 操作旗舰能够做好接受数据的准备。

然而,上边的代码并不是最好的实现方式,特殊方法
transferTo()
transferFrom()
允许我们将一个通道和另一个通道相连:

// 上边后半部分代码可以改成
...
in.transferTo(0, in.size(), out);
// 或者为
out.transferFrom(in, 0, in.size()); //功能相同


数据转换

当我们需要从 Channel 中读取数据,为了输出文件的信息,我们需要每次从 ByteBuffer 中只读取一个字节的数据,然后将
byte
类型转换为
char
,这种方式看起来有些原始 —— 当我们查看 nio 中的 java.nio.CharBuffer 类时,会发现他有个
toString()
方法:返回一个包换缓冲器中所有字符的字符串。

ByteBuffer 中容纳的是普通的字节,为了将其转换为字符,我们要么在输入它们的时候对其进行编码(这样才能获取到有意义的输出);要么在其从缓冲器中输出的时候对其进行编码。可以使用
java.nio.charset.Charset
类来实现编码功能,这个类提供了把数据编码为多种字符集的数据的工具。

数据在进入缓冲器时的字符编码与从缓冲器输出时的编码相同时,才会出现想要的结果。

在编码中,可以使用
System.getProperty("file.encoding")
方法来发现默认的字符集,它会产生代表字符集名称的字符串,如:GB2312、GBK、utf-8 等等。把字符集名称字符串传递给
Charset.forName()
方法,会返回一个 Charset 对象,我们可以使用这个 Charset 对象来对缓冲器中输出的字符串进行解码。

在 ByteBuffer 中只能保存字节类型的数据,但是它具有可以从其所容纳的字节中产生各种不同基本类型的方法

向ByteBuffer插入基本数据类型的最简单的方法是:利用asCharBuffer()/asIntBuffer() 等方法获得该缓冲器上的视图,然后使用视图的 put() 方法。此方法适用于所有的基本数据类型,只有一个例外:使用 asShortBuffer() 的 put() 方法时,需要对数据进行强制类型转换。至于原因?想想基本类型定义以及相互转化~

4000
插入其他类型的值*.put(内容)获取其他类型的值
ByteBuffer.asCharBuffer()ByteBuffer.getChar()
ByteBuffer.asShortBuffer()ByteBuffer.getShort()
ByteBuffer.asIntBuffer()ByteBuffer.getInt()
ByteBuffer.asLongBuffer()ByteBuffer.getLong()
ByteBuffer.asFloatBuffer()ByteBuffer.getFloat()
ByteBuffer.asDoubleBuffer()ByteBuffer.getDouble()
以上这些,我们可以称之为视图缓冲器
view Buffer


view Buffer 可以让我们通过某个特定的基本类型的视图查看其底层的 ByteBuffer。而“ByteBuffer”依然是存储数据的地方(类似于适配器模式),“支持”着前面的视图。因此,对视图的任何修改都会映射成为对 ByteBuffer 中数据的修改。

当底层的 ByteBuffer 通过视图缓冲器填满了 其他基本类型的数据时,就可以直接写入到通道中了。

这里多说一句,我们可以自定义 ByteBuffer 的大小,当数据不够的时候,ByteBuffer 会自动用 0(zero) 来进行填充。

ByteBuffer bb = ByteBuffer.allocate(1024);
//其中,allocate()方法调用了 ByteBuffer 的构造函数


ByteBuffer 由一个被“包装”的 8 字节数组产生:

ByteBuffer bb = ByteBuffer.warp(new Byte[]{0,0,0,0,0,0,0,0})


然后通过各种不同的基本类型的视图缓冲器显示了出来,当从不同的缓冲器读取时,数据显示的方式也不同。(以 a 为例)

000000097bytes
achars
00097shorts
097ints
0.01.36E-43floats
97longs
4.8E-322doublies
这节先到这里,后面的继续整理
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java NIO 文件IO