NIO 的使用
Posted 猪八戒1.0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NIO 的使用相关的知识,希望对你有一定的参考价值。
知识点
FileChannel
ByteBuffer
一.介绍
由于传统的 IO 流在实际运行时效率较低,所以 JDK 4 版本中提出了 NIO 技术,带来了可观的性能提升。
NIO :Non-Blocking IO,指的是非阻塞的 IO 。
传统的流式 IO ,又可以称为 BIO (Blocking IO) 阻塞 IO。
🤔 如何理解阻塞与非阻塞呢?
比如私家车和出租车,私家车买回来后只能是车主一家可以使用,不用的时候停在小区地库或是停在公共停车场,其他不相干的人是不能做这辆车的,这样私家车处于阻塞状态,车门锁上其他人是不能进入的;而出租车整日都是在路上穿行,有客人它就载客,到达目的客人下车,恢复空车状态后继续揽客,这样出租车便处于非阻塞状态,有客人时便可打开车门进行乘坐。显然阻塞状态存在资源极大浪费,而非阻塞状态可以充分发挥物尽其用的原则。
所以 NIO 的提出在文件的传输过程中极大的提高了传输的性能。 😀
之前的实验中,我们学习了 IO 流,应该知道分为输入流和输出流,输入与输出都有相对应的类进行处理,如,InputStream 类只能读文件,OutputStream 类只能写文件,两者是不能互换的,它们各司其职。
如果现在打开一个文件,想要一会写一会读,操作起来非常麻烦,读的时候需要 InputStream 的全套操作,写的时候需要 OutputStream 的全套操作,这样就忙坏了输入和输出流了。 😲
因此,我们需要引入 NIO 提供的 文件通道,文件通道中的数据可以双向流动,也就是说流进来便是读操作,流出去便是写操作,这样文件的读写操作可以在文件通道中进行,大大节省了系统资源的开销。
二.文件通道 FileChannel
Java 中提供的文件通道 FileChannel 类,存放在 java.nio.channels 包中。
我们可以查看 Java API ,发现 FileChannel 是一个抽象类。
有两种方式可以获取 FileChannel 实例。
👉 方式一:通过文件字节输入输出流中的 getChannel() 方法来获取。
FileChannel inputchannel = new FileInputStream(文件名).getChannel();
FileChannel outputchannel = new FileOutputStream(文件名).getChannel();
👉方式二:通过随机访问文件工具 RandomAccessFile 类中的 getChannel() 方法来获取。
// 获取可读的文件通道
FileChannel inputChannel = new RandomAccessFile(文件名, "r").getChannel();
// 获取可写的文件通道
FileChannel outputChannel = new RandomAccessFile(文件名, "rw").getChannel();
文件通道实例获取后,便可以通过类中的方法进行数据交互了。
FileChannel 类常用的方法,如下表:
方法名 说明
isOpen 判断文件通道是否打开。
size 获取文件通道中的文件大小。
truncate 把文件大小截取到指定长度。
read 把文件通道中的数据读到字节缓存。
write 往文件通道写入字节缓存中的数据。
force 强制写入,类似于缓冲流中的 flush 方法。
close 关闭文件通道。
文件通道中的读写操作,主要是通过 read() 和 write() 方法进行处理,而文件通道中的读写操作需要有存储空间,这个存储空间就是字节缓存。
三.字节缓存 ByteBuffer
文件通道的读写操作,在通道内需要有内部的存储空间进行存储操作,它是缓存 Buffer 类提供的。
Buffer 类存放在 Java API 的 java.nio 包中。
Buffer 类也是一个抽象类,主要缓存空间的使用,是采用它的子类来进行操作。
Buffer 缓存提供了:字节缓存、字符缓存、双精度缓存、单精度缓存、整型缓存、长整型缓存、短整型缓存,这些子类同样也都是抽象类。
👉 Buffer 类中提供常用方法有 3 个,如下:
clear() :清空缓冲区的数据。
flip() :把缓冲区从写模式切换到读模式。读取数据前,需要先调用 flip 方法。
rewind() :让缓冲区的指针回到开头,重新在读一遍。
ByteBuffer 字节缓存,它是一种特殊的存储空间,在操作中可以被多次读写。
👉 ByteBuffer 提供了几个构建方法:
静态方法 说明
ByteBuffer.wrap(byte[] array) 根据输入的字节数组生成对应的缓存对象。
ByteBuffer.wrap(byte[] array, int offset, int length) 根据输入的字节数组开始和长度生成对应的缓存对象。
ByteBuffer.allocate(int capacity) 根据输入的容量分配指定大小的新缓存。它将有一个后备数组,其数组偏移量将为零。
ByteBuffer.allocateDirect(int capacity) 根据输入的容量分配指定大小的新缓存。
获取 ByteBuffer 对象实例后,也可以通过 get() 和 put() 方法存储数据。
接下来,我们通过代码来感受一下,如何使用 FileChannel 和 ByteBuffer 进行文件读写操作。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 文件通道的使用
* @author 小桃子
*/
public class TestChannel
/**
* 通过文件通道写入数据
* @param filename 文件名
* @throws IOException IO异常
*/
public void writeChannel(String filename) throws IOException
// 创建文件输出流对象
FileOutputStream fos = new FileOutputStream(filename);
// 通过文件输出流对象获取文件通道对象
FileChannel channel = fos.getChannel();
String str = "明月几时有?把酒问青天。不知天上宫阙,今夕是何年。\\n我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。\\n转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?\\n人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。";
// 生成字符串对应的字节缓存对象
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
// 往文件通道写入字节缓存
channel.write(buffer);
// 资源释放
fos.close();
channel.close();
/**
* 通过文件通道读取文件
* @param filename 文件名
* @throws IOException IO异常
*/
public void readChannel(String filename) throws IOException
// 创建文件输入流对象
FileInputStream fis = new FileInputStream(filename);
// 通过文件输入流对象获取文件通道对象
FileChannel channel = fis.getChannel();
// 获取文件通道中文件大小
int size = (int) channel.size();
// 根据文件大小创建新的字节缓存
ByteBuffer buffer = ByteBuffer.allocateDirect(size);
// 把文件通道中的数据读到字节缓存
channel.read(buffer);
// 把缓冲区切换到读模式,必须调用 flip 方法
buffer.flip();
// 创建与文件大小相同长度的字节数组
byte[] bytes = new byte[size];
// 把字节缓存中的数据读取到字节数组
buffer.get(bytes);
// 把字节数组转换成字符串
String content = new String(bytes);
// 打印输出字符串信息
System.out.println(content);
// 资源释放
fis.close();
channel.close();
public static void main(String[] args)
TestChannel tc = new TestChannel();
try
tc.writeChannel("myfile.txt");
tc.readChannel("myfile.txt");
catch (IOException e)
e.printStackTrace();
四.文件通道的性能优势
文件通道的性能优势主要体现在文件复制,而且越大的文件越能体现它的优势。 😀
一般来说,文件传输最好采用字节流,使用字节流可以传输任何类型文件,不会被破坏任何格式,但是以字节为单位进行传输效率非常的低。
在这样的情况下,至少你应该想到采用缓冲流 BufferedInputStream 和 BufferedOutputStream 进行文件复制。
而今,大数据信息时代,高效操作时王道。所以采用文件通道进行文件复制,可以让效率完全体现。
⭐ 注意:当传输文件不大时,也无需使用文件通道,就好比杀鸡用牛刀,也是一种资源浪费。
/**
* 通过文件通道完成文件复制操作
* @param srcfile 源文件
* @param destfile 目标文件
* @return 是否完成复制操作
*/
public boolean copyFileChannel(String srcfile,String destfile)
boolean result = false;
// 声明文件输入流
FileInputStream input = null;
// 声明输入文件通道
FileChannel inputChannel = null;
// 声明文件输出流
FileOutputStream output = null;
// 声明输出文件通道
FileChannel outputChannel = null;
try
// 创建文件输入流对象
input = new FileInputStream(srcfile);
// 获取输入文件通道对象
inputChannel = input.getChannel();
// 创建文件输出流对象
output = new FileOutputStream(destfile);
// 获取输出文件通道对象
outputChannel = output.getChannel();
// 根据读取文件的大小创建字节缓存对象
ByteBuffer buffer = ByteBuffer.allocate((int)inputChannel.size());
// 通过输入文件通道读取源文件,指定放入buffer字节缓存中
inputChannel.read(buffer);
// 将读取的数据存储到字节缓存中
buffer.flip();
// 将字节缓存中的数据写入输出文件通道指定目标文件中
outputChannel.write(buffer);
// 完成复制后返回 true,有任何异常发生都返回 false
result = true;
catch (FileNotFoundException e)
// 找不到 srcfile 会捕获的异常
e.printStackTrace();
catch (IOException e)
// 读取和写入数据的过程中出现的流异常
e.printStackTrace();
finally
// 资源释放
try
// 输出文件通道关闭
if(outputChannel!= null)
outputChannel.close();
// 输出流关闭
if(output != null)
output.close();
catch (IOException e)
e.printStackTrace();
try
// 输入文件通道关闭
if(inputChannel != null)
inputChannel.close();
// 输入流关闭
if(input != null)
input.close();
catch (IOException e)
e.printStackTrace();
return result;
五.区别
我们强调一下传统 IO 流和文件通道复制文件的区别。
字节缓存是通道内部的存储空间,因此使用文件通道复制文件,无须动用系统内存,也无须使用应用内存,只需将磁盘上的文件内容读到通道中的字节缓存中,再将字节缓存中的数据写入磁盘上的新文件即可。
显然,文件通道复制文件性能优于传统 IO 。
以上是关于NIO 的使用的主要内容,如果未能解决你的问题,请参考以下文章