【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 中,唯一直接与通道交互的缓冲器是
对于只读访问,我们必须显示的使用静态的
使用 put() 将值填充到 ByteBuffer 之后,就需要调用 read() 来告知 FileChannel 向 ByteBuffer 存储字节,一旦使用 read(),就必须调用缓冲器上的 flip(), 让它做好让别人读取字节的准备。 如果我们打算使用缓冲器进行进一步的 read() 操作,我们也必须要调用 clearI() 来为每个 read() 做好准备。
从上面一段拷贝数据的代码可以看到,打开一个 FileChannel 用于读,打开另一个用于写。 ByteBuffer 被分配了空间,当 FileChannel.read() 返回 -1 时,表示我们已经达到了输入的末尾。
read() 操作后,数据已经输入到了缓冲器中,flip() 则是准备缓冲器,以便它的信息可以由 write() 提取。write() 操作之后,信息仍在缓存器中,接着 clear() 操作则对所有的内部指针重新安排,以便缓冲器在另一个 read() 操作旗舰能够做好接受数据的准备。
然而,上边的代码并不是最好的实现方式,特殊方法
数据转换
当我们需要从 Channel 中读取数据,为了输出文件的信息,我们需要每次从 ByteBuffer 中只读取一个字节的数据,然后将
ByteBuffer 中容纳的是普通的字节,为了将其转换为字符,我们要么在输入它们的时候对其进行编码(这样才能获取到有意义的输出);要么在其从缓冲器中输出的时候对其进行编码。可以使用
数据在进入缓冲器时的字符编码与从缓冲器输出时的编码相同时,才会出现想要的结果。
在编码中,可以使用
在 ByteBuffer 中只能保存字节类型的数据,但是它具有可以从其所容纳的字节中产生各种不同基本类型的方法
向ByteBuffer插入基本数据类型的最简单的方法是:利用asCharBuffer()/asIntBuffer() 等方法获得该缓冲器上的视图,然后使用视图的 put() 方法。此方法适用于所有的基本数据类型,只有一个例外:使用 asShortBuffer() 的 put() 方法时,需要对数据进行强制类型转换。至于原因?想想基本类型定义以及相互转化~
以上这些,我们可以称之为视图缓冲器
view Buffer 可以让我们通过某个特定的基本类型的视图查看其底层的 ByteBuffer。而“ByteBuffer”依然是存储数据的地方(类似于适配器模式),“支持”着前面的视图。因此,对视图的任何修改都会映射成为对 ByteBuffer 中数据的修改。
当底层的 ByteBuffer 通过视图缓冲器填满了 其他基本类型的数据时,就可以直接写入到通道中了。
这里多说一句,我们可以自定义 ByteBuffer 的大小,当数据不够的时候,ByteBuffer 会自动用 0(zero) 来进行填充。
ByteBuffer 由一个被“包装”的 8 字节数组产生:
然后通过各种不同的基本类型的视图缓冲器显示了出来,当从不同的缓冲器读取时,数据显示的方式也不同。(以 a 为例)
这节先到这里,后面的继续整理
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() 方法时,需要对数据进行强制类型转换。至于原因?想想基本类型定义以及相互转化~
插入其他类型的值*.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 为例)
0 | 0 | 0 | 0 | 0 | 0 | 0 | 97 | bytes |
a | chars | |||||||
0 | 0 | 0 | 97 | shorts | ||||
0 | 97 | ints | ||||||
0.0 | 1.36E-43 | floats | ||||||
97 | longs | |||||||
4.8E-322 | doublies |
相关文章推荐
- Apache Mina学习笔记:Java NIO基础概念
- 整理JAVA学习笔记 JAVA基础需要掌握重点
- Java学习笔记(七、网络编程基础)
- Java学习笔记(一、Java语言基础)
- Java基础学习笔记
- 很不错的JAVA学习笔记-Java基础-Java-编程开发
- java学习笔记,关于java的一些基础知识,适用于初学者,第一节
- [转]CoreJava学习笔记_Java语法基础
- 传智博客学习笔记4--JAVA编程基础1
- 传智博客学习笔记16--JAVA SCRIPT HTML语言基础
- 做java程序员有一段时间了,但是感觉基础的东西了解的很少很少,我从现在开始从新开始。。。。。。。。。。。学习笔记(连载)给有需要的人
- Java NIO 学习笔记 - ByteBuffer (早期笔记)
- Java学习笔记(八、数据库编程基础)
- java面试基础题,学习笔记!
- Java2核心技术第七版的学习笔记(三) Fundamental Programming Structures in Java(Java语言的基础)(二)
- Java NIO 学习笔记 selector 行为机制分析(select操作 cancel操作)
- 分布计算环境学习笔记5——Java Enterprise Edtion基础
- Java 基础学习笔记(持续更新中)
- java NIO非阻塞式IO网络编程学习笔记(一)
- java基础学习笔记原始类型变量赋值与非原始变量赋值