Java 输入与输出之 NIO.2【AIO】【内存映射文件】【自动资源管理】探索之【四】

一,自动资源管理

Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件等系统资源。这个特性又被称为自动资源管理(Automatic Resource Management, ARM), 该特性以 try 语句的扩展版为基础。自动资源管理主要用于,当不再需要文件(或其他资源)时,可以防止无意中忘记释放它们,从而防止内存泄漏的发生。

自动资源管理基于 try 语句的扩展形式,又称为"带资源的 try 语句"。
带资源的 try 语句的语法格式:

try(需要关闭的资源声明){

//可能发生异常的语句

}catch(异常类型 变量名){

//异常的处理语句

}finally{

//一定执行的语句

}

当try 代码块结束时,自动释放资源。因此不需要显式调用 close() 方法。该形式也称为"带资源的 try 语句" 。

注意:

①try 语句中声明的资源被隐式声明为 final ,资源的作用局限于带资源的 try 语句

②可以在一条 try 语句中声明和管理多个资源,每个资源以";" 隔开即可。

③需要关闭的资源,必须实现了 AutoCloseable 接口或其自接口 Closeable

这是一个使用FileChannel进行文件复制的应用实例。

在Java 7以前,没有自动管理资源的try语句时,这个文件复制功能需要这样来实现:

cpp 复制代码
    public static void copyFileOld(String file,String path){
    	FileChannel in = null;
    	FileChannel out = null;
    	try {
        	in = FileChannel.open(Paths.get(file), READ);
        	out = FileChannel.open(Paths.get(path), WRITE, CREATE);
    		//从in(Channel)将信息流转到out(Channel)
            in.transferTo(0, in.size(), out);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				in.close();
				out.close();
			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
    }

下面是一个使用FileChannel进行文件复制的应用实例,这个例程很好的说明了FileChannel就是一个双向的输入和输出流,在本例的文件拷贝中甚至不需要缓冲区Buffer。本例程同时演示了"带资源的 try 语句" 的用法。

JDK7 以后,有了"带资源的 try 语句",我们可以写得很简明。

cpp 复制代码
	//自动资源管理:自动关闭实现 AutoCloseable 接口的资源
    public static void copyFile(String file,String path){
        try(FileChannel in = FileChannel.open(Paths.get(file), READ);
            FileChannel out = FileChannel.open(Paths.get(path), WRITE, CREATE)){
        	//从in(Channel)将信息流转到out(Channel)
            in.transferTo(0, in.size(), out);
        }catch(IOException e){
            e.printStackTrace();
        }
    }

完整源代码

这里是完整的测试例程源代码:

cpp 复制代码
package nio;
/***
 * @author QiuGen
 * @description  文件复制例程CopyFileNIO
 * 演示"带资源的 try 语句"用法和FileChannel的文件操作。
 * @date 2024/8/30
 * ***/
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import static java.nio.file.StandardOpenOption.*; 
public class CopyFileNIO {
	//以前没有自动关闭功能时的写法
    public static void copyFileOld(String file,String path){
    	FileChannel in = null;
    	FileChannel out = null;
    	try {
        	in = FileChannel.open(Paths.get(file), READ);
        	out = FileChannel.open(Paths.get(path), WRITE, CREATE);
    		//从in(Channel)将信息流转到out(Channel)
            in.transferTo(0, in.size(), out);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				in.close();
				out.close();
			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
    }

	//自动资源管理:自动关闭实现 AutoCloseable 接口的资源
    public static void copyFile(String file,String path){
        try(FileChannel in = FileChannel.open(Paths.get(file), READ);
            FileChannel out = FileChannel.open(Paths.get(path), WRITE, CREATE)){
        	//从in(Channel)将信息流转到out(Channel)
            in.transferTo(0, in.size(), out);
        }catch(IOException e){
            e.printStackTrace();
        }
    }

	public static void main(String[] args) {
    	String fileA = "D:/Temp/image/仿射变换.jpg";
    	String fileB = "D:/Temp/image/仿射变换2.jpg";
    	CopyFileNIO.copyFile(fileA, fileB);
	}
}

二、内存映射文件

文件操作的四大方法:

前提:计算机内存的访问速度比磁盘高几个数量级,但是在Java语言中基本的IO操作都是直接调用native方法获得驱动和磁盘进行交互的,IO速度限制在磁盘速度上。

由此,就有了缓存的思想,将磁盘内容预先缓存在内存上,这样当需要IO操作读写数据信息的时候,IO速度基本就是以内存的访问速度为主,例如:缓冲型的输入/输出BufferedInput/OutputStream等。

而我们知道大多数操作系统都可以利用虚拟内存来实现将一个文件或者文件的一部分映射到内存中,这样文件就可以当作是内存文件一样快速进行访问,我们可以把它当作一种"内存文件"使用。

内存映射文件:内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件,此时就可以假定整个文件都放在内存中,而且可以完全把它当成非常大的内存文件来访问(随机访问)。
当处理的大文件时,缓冲输入/输出方式和内存映射文件方式将有很大的优势。

处理大文件时,内存映射文件这个方式尤其在随机访问文件时能大幅提高性能,所以可用来替代RandomAccessFile。

当然,对于中小尺寸文件的顺序读入则没有必要使用内存映射以避免占用本就有限的I/O资源,这时应当使用带缓冲的输入流。

Java NIO(New IO)是在JDK 1.4版本中引入的,它提供了一种基于缓冲区的IO操作方式,旨在提高IO操作的效率和性能。MappedByteBuffer是Java NIO中的一个重要类,它允许Java程序直接从内存访问文件,通过将整个文件或文件的一部分映射到内存中,操作系统负责处理页面请求和写入文件,从而实现了非常快速的IO操作。这种内存映射文件的方式不仅提供了高性能的文件读写能力,还支持多个进程同时访问,实现低时延的共享内存操作。因此,MappedByteBuffer的引入,使得Java应用程序在处理大文件或需要高性能IO操作的场景下有了更多的选择和灵活性。

MappedByteBuffer的一个能力就是它可以让我们读写那些因为太大而不能放进内存中的文件。有了它,我们就可以假定整个文件都放在内存中(实际上,大文件放在内存和虚拟内存中),基本上都可以将它当作一个特别大的数组来访问,这样极大的简化了对于大文件的修改等操作。

MappedByteBuffer底层使用的技术是内存映射。
说明

对大多数操作系统来说,做内存文件映射都是一个昂贵的操作。所以MappedByteBuffer适用于对大文件的读写。对于小文件直接用普通的读写就好了。

java.nio包使得内存映射变得更加简单。

map过程

FileChannel提供了map方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。

1、首先,从文件中获得一个通道(channel)。通道是相当于双向的输入输出流,是对IO操作的一种抽象,它使我们可以进行内存映射,实现内存映射文件。

2、然后,通过调用FileChannel类的map方法进行内存映射,map方法从这个通道中获得一个MappedByteBuffer对象(ByteBuffer的子类),作为缓冲区,来存储文件数据信息。

FileChannelr的map方法可把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件从position开始的size大小的区域映射为内存映像文件,mode指出了 可访问该内存映像文件的方式。
FileChannel中的几个变量

MapMode mode:内存映像文件访问的方式,共三种:

MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。

MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。

MapMode.PRIVATE:私用,可读可写,但是修改的内容不会写入文件,只是缓冲区自身的改变,这种改变不会写回到文件中。

未完符续

Java输入输出系列的相关博客链接:
Java 输入与输出之 NIO.2【AIO】【Path、Paths、Files】【walkFileTree接口】探索之【三】

参考资料:
JAVA NIO学习四:Path&Paths&Files 学习
Java NIO 内存映射文件

相关推荐
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭13 分钟前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫30 分钟前
泛型(2)
java
超爱吃士力架34 分钟前
邀请逻辑
java·linux·后端
南宫生39 分钟前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石1 小时前
12/21java基础
java
李小白661 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp1 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶2 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
n北斗2 小时前
常用类晨考day15
java
骇客野人2 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言