Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >java使用nio读写文件[通俗易懂]

java使用nio读写文件[通俗易懂]

作者头像
全栈程序员站长
发布于 2022-08-25 06:52:21
发布于 2022-08-25 06:52:21
1.2K00
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

IO原理

最近在研究 JAVA NIO 的相关知识,学习NIO,就不能提到IO的原理和事项,必经NIO是基于IO进化而来

IO涉及到的底层的概念大致如下:

  1. 缓冲区操作。2) 内核空间与用户空间。3) 虚拟内存。4) 分页技术

一,虚拟存储器 虚拟存储器是硬件异常(缺页异常)、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。

虚拟存储器的三大能力:

①将主存看成是一个存储在磁盘上的地址空间的高速缓存。 ②为每个进程提供了一个一致的地址空间。 ③保护每个进程的地址空间不被其他进程破坏。

虚拟内存的两大好处: ① 一个以上的虚拟地址可指向同一个物理内存地址。 ② 虚拟内存空间可大于实际可用的硬件内存。

二,用户空间与内核空间 设虚拟地址为32位,那么虚拟地址空间的范围为0~4G。操作系统将这4G分为二部分,将最高的1G字节(虚拟地址范围为:0xC0000000-0xFFFFFFFF)供内核使用,称为内核空间。而将较低的3G字节供各个进程使用,称为用户空间。 每个进程可以通过系统调用进入内核,因为内核是由所有的进程共享的。对于每一个具体的进程,它看到的都是4G大小的虚拟地址空间,即相当于每个进程都拥有一个4G大小的虚拟地址空间。

三,IO操作

一般IO缓冲区操作:

  1. 用户进程使用read()系统调用,要求其用户空间的缓冲区被填满。
  2. 内核向磁盘控制器硬件发命令,要求从磁盘读入数据。
  3. 磁盘控制器以DMA方式(数据不经过CPU)把数据复制到内核缓冲区。
  4. 内核将数据从内核缓冲区复制到用户进程发起read()调用时指定的用户缓冲区。

从上图可以看出:磁盘中的数据是先读取到内核的缓冲区中。然后再从内核的缓冲区复制到用户的缓冲区。为什么会这样呢?

因为用户空间的进程是不能直接硬件的(操作磁盘控制器)。磁盘是基于块存储的硬件设备,它一次操作固定大小的块,而用户请求请求的可能是任意大小的数据块。因此,将数据从磁盘传递到用户空间,由内核负责数据的分解、再组合。

内存映射IO:就是复用一个以上的虚拟地址可以指向同一个物理内存地址。将内核空间的缓冲区地址(内核地址空间)映射到物理内存地址区域,将用户空间的缓冲区地址(用户地址空间)也映射到相同的物理内存地址区域。从而数据不需要从内核缓冲区映射的物理内存地址移动到用户缓冲区映射的物理内存地址了。从链路上看,这样的方式明显比上述的IO操作方式要短了,节省出来的路程,就是NIO操作的优势所在

要求:①用户缓冲区与内核缓冲区必须使用相同的页大小对齐。 ②缓冲区的大小必须是磁盘控制器块大小(512字节磁盘扇区)的倍数—因为磁盘是基于块存储的硬件设备,一次只能操作固定大小的数据块。

用户缓冲区按页对齐,会提高IO的效率—这也是为什么在JAVA中new 一个字节数组时,指定的大小为2的倍数(4096)的原因吧。

四,JAVA中的IO,本质上是把数据移进或者移出缓冲区。

read()和write()系统调用完成的作用是:把内核缓冲区映射的物理内存空间中的数据 拷贝到 用户缓冲区映射的物理内存空间中。

因此,当使用内存映射IO时,可视为:用户进程直接把文件数据当作内存,也就不需要使用read()或write()系统调用了。

当发起一个read()系统调用时,根据待读取的数据的位置生成一个虚拟地址(用户进程使用的是虚拟地址),由MMU转换成物理地址,若内核中没有相应的数据,产生一个缺页请求,内核负责页面调入从而将数据从磁盘读取到内核缓冲区映射的物理内存中。对用户程序而言,这一切都是在不知不觉中进行。

总之,从根本上讲数据从磁盘装入内存是以页为单位通过分页技术装入内存的。

五,JAVA NIO中的直接缓存和非直接缓存

直接缓存:不是分配于堆上的存储,位于JVM之外,它不受JAVA的GC管理,相当于内核缓冲区。非直接缓存:建立在JAVA堆上的缓存,受JVM管理,相当于用户缓冲区。

根据上面第三点,将直接缓存中的数据写入通道的速度要快于非直接缓存。因为,连接到通道的另一端是文件(磁盘,FileChannel)或者网络(Socket通道),这些都是某种形式上的硬件。那么,对于非直接缓存而言,数据从缓冲区传递到硬件,要经过内核缓冲区中转。而对于直接缓存而言,就不需要了,因为直接缓存已经直接映射到内核缓冲区了。

了解了上述的基本概念后,下面我们分别使用传统的IO方式和NIO方式实现一个文件拷贝的功能,简单对比一下

IO方式实现文件拷贝:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	//IO方法实现文件k拷贝
    private static void traditionalCopy(String sourcePath, String destPath) throws Exception { 
   
        File source = new File(sourcePath);
        File dest = new File(destPath);
        if (!dest.exists()) { 
   
            dest.createNewFile();
        }
        FileInputStream fis = new FileInputStream(source);
        FileOutputStream fos = new FileOutputStream(dest);
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = fis.read(buf)) != -1) { 
   
            fos.write(buf, 0, len);
        }
        fis.close();
        fos.close();
    }

然后我们来测试一下一个比较大的文件拷贝,看看性能如何

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws Exception{ 
   
        long start = System.currentTimeMillis();
        traditionalCopy("D:\\常用软件\\JDK1.8\\jdk-8u181-linux-x64.tar.gz", "D:\\常用软件\\JDK1.8\\IO.tar.gz");
        long end = System.currentTimeMillis();
        System.out.println("用时为:" + (end-start));
    }

180M的文件,这个速度也还不算太差,下面我们再尝试使用NIO的方式试一下,提供两种方式的拷贝,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void nioCpoy(String source, String target, int allocate) throws IOException{ 
   
        ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);
        FileInputStream inputStream = new FileInputStream(source);
        FileChannel inChannel = inputStream.getChannel();

        FileOutputStream outputStream = new FileOutputStream(target);
        FileChannel outChannel = outputStream.getChannel();

        int length = inChannel.read(byteBuffer);
        while(length != -1){ 
   
            byteBuffer.flip();//读取模式转换写入模式
            outChannel.write(byteBuffer);
            byteBuffer.clear(); //清空缓存,等待下次写入
            // 再次读取文本内容
            length = inChannel.read(byteBuffer);
        }
        outputStream.close();
        outChannel.close();
        inputStream.close();
        inChannel.close();
    }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void fileChannelCopy(String sfPath, String tfPath) { 
   

        File sf = new File(sfPath);
        File tf = new File(tfPath);
        FileInputStream fi = null;
        FileOutputStream fo = null;
        FileChannel in = null;
        FileChannel out = null;
        try{ 
   
            fi = new FileInputStream(sf);
            fo = new FileOutputStream(tf);
            in = fi.getChannel();//得到对应的文件通道
            out = fo.getChannel();//得到对应的文件通道
            in.transferTo(0, in.size(), out);//连接两个通道,并且从in通道读取,然后写入out通道
        }catch (Exception e){ 
   
            e.printStackTrace();
        }finally { 
   
            try{ 
   
                fi.close();
                in.close();
                fo.close();
                out.close();
            }catch (Exception e){ 
   
                e.printStackTrace();
            }
        }
    }

测试一下性能如何,运行一下下面的代码,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) { 
   
        long start = System.currentTimeMillis();
        String sPath = "D:\\常用软件\\JDK1.8\\jdk-8u181-linux-x64.tar.gz";
        String tPath = "D:\\常用软件\\JDK1.8\\NIO.tar.gz";
        fileChannelCopy(sPath,tPath);
        long end = System.currentTimeMillis();
        System.out.println("用时为:" + (end-start));
    }

这个效率通过简单的对比可以说明问题了,NIO在操作大文件读写时,性能优势就体现出来了,下面附上通过NIO操作文件读写的几个方法,后面做参考使用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** * NIO读写文件工具类 */
public class NIOFileUtil { 
   

    private String file;

    public String getFile() { 
   
        return file;
    }

    public void setFile(String file) { 
   
        this.file = file;
    }

    public NIOFileUtil(String file) throws IOException { 
   
        super();
        this.file = file;
    }

    /** * NIO读取文件 * @param allocate * @throws IOException */
    public void read(int allocate) throws IOException { 
   

        RandomAccessFile access = new RandomAccessFile(this.file, "r");

        //FileInputStream inputStream = new FileInputStream(this.file);
        FileChannel channel = access.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);

        CharBuffer charBuffer = CharBuffer.allocate(allocate);
        Charset charset = Charset.forName("GBK");
        CharsetDecoder decoder = charset.newDecoder();
        int length = channel.read(byteBuffer);
        while (length != -1) { 
   
            byteBuffer.flip();
            decoder.decode(byteBuffer, charBuffer, true);
            charBuffer.flip();
            System.out.println(charBuffer.toString());
            // 清空缓存
            byteBuffer.clear();
            charBuffer.clear();
            // 再次读取文本内容
            length = channel.read(byteBuffer);
        }
        channel.close();
        if (access != null) { 
   
            access.close();
        }
    }

    /** * NIO写文件 * @param context * @param allocate * @param chartName * @throws IOException */
    public void write(String context, int allocate, String chartName) throws IOException{ 
   
        // FileOutputStream outputStream = new FileOutputStream(this.file); //文件内容覆盖模式 --不推荐
        FileOutputStream outputStream = new FileOutputStream(this.file, true); //文件内容追加模式--推荐
        FileChannel channel = outputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);
        byteBuffer.put(context.getBytes(chartName));
        byteBuffer.flip();//读取模式转换为写入模式
        channel.write(byteBuffer);
        channel.close();
        if(outputStream != null){ 
   
            outputStream.close();
        }
    }

    /** * nio事实现文件拷贝 * @param source * @param target * @param allocate * @throws IOException */
    public static void nioCpoy(String source, String target, int allocate) throws IOException{ 
   
        ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);
        FileInputStream inputStream = new FileInputStream(source);
        FileChannel inChannel = inputStream.getChannel();

        FileOutputStream outputStream = new FileOutputStream(target);
        FileChannel outChannel = outputStream.getChannel();

        int length = inChannel.read(byteBuffer);
        while(length != -1){ 
   
            byteBuffer.flip();//读取模式转换写入模式
            outChannel.write(byteBuffer);
            byteBuffer.clear(); //清空缓存,等待下次写入
            // 再次读取文本内容
            length = inChannel.read(byteBuffer);
        }
        outputStream.close();
        outChannel.close();
        inputStream.close();
        inChannel.close();
    }

    //IO方法实现文件k拷贝
    private static void traditionalCopy(String sourcePath, String destPath) throws Exception { 
   
        File source = new File(sourcePath);
        File dest = new File(destPath);
        if (!dest.exists()) { 
   
            dest.createNewFile();
        }
        FileInputStream fis = new FileInputStream(source);
        FileOutputStream fos = new FileOutputStream(dest);
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = fis.read(buf)) != -1) { 
   
            fos.write(buf, 0, len);
        }
        fis.close();
        fos.close();
    }

    public static void main(String[] args) throws Exception{ 
   
        /*long start = System.currentTimeMillis(); traditionalCopy("D:\\常用软件\\JDK1.8\\jdk-8u181-linux-x64.tar.gz", "D:\\常用软件\\JDK1.8\\IO.tar.gz"); long end = System.currentTimeMillis(); System.out.println("用时为:" + (end-start));*/

        long start = System.currentTimeMillis();
        nioCpoy("D:\\常用软件\\JDK1.8\\jdk-8u181-linux-x64.tar.gz", "D:\\常用软件\\JDK1.8\\NIO.tar.gz",1024);
        long end = System.currentTimeMillis();
        System.out.println("用时为:" + (end-start));
    }

}

NIO在读写文件上体现出来的性能优势得益于其自身的结构设计,最重要的还是本文开头所讲的关于操作链路上的结构优化设计,掌握这一点原理基本就理解了NIO的实质,本篇到这里就结束了,最后感谢观看!

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/142696.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
PDF怎么添加水印?怎么给PDF文件添加图片水印
PDF怎么添加水印?水印分为文字水印与文件水印,这个文件水印也可以是图片水印,给文件添加上水印一方面是为了安全,另一方面是告诉大家这个文件出自哪里,水印的使用还是很方便的,那么怎么给PDF文件添加图片水印呢?PDF难编辑是出了名的,要进行添加水印的操作不是难为人嘛!别慌嘛,小编这里有一个实用的方法,不知道会不会对你有帮助,先看看吧!
用户5843321
2019/10/28
3.8K0
PDF怎么添加水印?怎么给PDF文件添加图片水印
怎么在PDF文件上标注?原来如此简单
不管是在学习中还是在办公中,都会接触到PDF文件,而当你在浏览或者是对PDF编辑修改时,遇到重点的内容,就需要做一个标注,这样在看的时候,就能够一眼看出哪些是重点,那么怎么在PDF文件上标注?今天就来给大家分享一个超级好用的方法吧,让你分分钟就能搞定。
高效办公
2019/06/17
2.8K0
怎么在PDF文件上标注?原来如此简单
PDF文档怎么加密?工作再忙也要学学这个方法
在工作中难免会遇到一些比较重要的PDF文件,而为了保护这些文件的安全,不能被别人随意打开浏览,像平时在工作中比较重要的数据汇报,工作汇报等这些都是比较重要的,需要对PDF文档加密,那么PDF文档怎么加密?今天就来给大家介绍超级好用的简单易学的方法,即使工作再忙也要学学这个方法。
用户2154429
2019/03/20
7760
PDF文档怎么加密?工作再忙也要学学这个方法
PDF如何设置全屏动画?这个技巧分享给你
PDF如何设置全屏动画?很多人都用过PDF文件,但是对于PDF这个格式的文件还是有很多无奈的地方,比如:怎么在PDF文件中进行内容的编辑?怎么给它设置全屏动画?等等一系列的问题。
用户5843321
2019/10/14
1.3K0
PDF如何设置全屏动画?这个技巧分享给你
PDF新建空白页的方法有哪些?怎么新建PDF文件
大家知道哪些PDF新建空白页的方法?问到这个问题大家都是不约而同的眉头紧锁,小编理解的可能是大家都不知道怎么新建PDF空白页吧。
用户5843321
2019/10/24
4K0
PDF新建空白页的方法有哪些?怎么新建PDF文件
PDF文件怎么制作?这两个方法绝对好用
PDF文件怎么制作?我们都知道相对于其他文件类型更具特殊性,PDF文件格式可以将文字、字型、格式、颜色及独立于设备和分辨率的图形图像等封装在一个文件中。该格式文件还可以包含超文本链接、声音和动态影像等电子信息,支持特长文件,集成度和安全可靠性都较高,而且需要在PDF相关的软件里才能打开。所以PDF文件怎么制作呢?相信大家都一定很好奇,那么就来看看我介绍的方法吧!
高效办公
2019/07/25
11.1K0
PDF文件怎么制作?这两个方法绝对好用
PDF怎么添加空白页?如何给PDF文件添加页面
PDF怎么添加空白页?PDF文件大家接触的应该不少,办公时发送文件都会选择PDF格式的,利于传输。
用户5843321
2019/10/12
2.3K0
PDF怎么添加空白页?如何给PDF文件添加页面
PDF怎么编辑内容,两大简单方法教你轻松搞定
在工作中要说用到最多的文件格式那当然是PDF格式是最多的了,因其本身的安全性比较高,所以很多在办公中的人都很喜欢用,可是很多人也会比较头疼,尤其是当PDF文件中内容出现错误需要修改的时候,想要修改PDF文件里面的内容是很难的,那么PDF怎么编辑内容?今天就来给大家介绍两大简单方法教你轻松搞定,一起来看看具体的操作方法吧。
高效办公
2019/03/12
7000
PDF怎么编辑内容,两大简单方法教你轻松搞定
PDF怎么转Word?教你三种转换方法
我们有时候需要将PDF文件转换成Word文件,这样可以方便我们查阅和编辑文件内容,也可以方便我们分享文件内容。那么PDF怎么免费Word呢?大家可以尝试下面这三种转换方法,都很好哦,肯定能帮到你的。
高效办公
2019/10/12
3.4K1
PDF怎么转Word?教你三种转换方法
PDF怎么加背景颜色?如何给PDF文件添加背景
PDF怎么加背景颜色?经常办公的朋友肯定会接触到很多的PDF文件吧,小编每天上班都快被PDF文件淹没了,每天查看那么多的文件也会有视觉疲劳的,想要给PDF文件添加一个有朝气一点的背景,你们知道怎么做嘛?小编这里有一个比较实用的方法,分享给大家,看看你会不会用得到!
用户5843321
2019/10/16
3.6K0
PDF怎么加背景颜色?如何给PDF文件添加背景
如何给PDF添加数字签名?教你一招轻松破解
当我们在处理PDF文档的时候,一些比较重要的文件需要在上面签名,这就是比较麻烦的事情,因为PDF文件本身就是很难进行编辑和修改的,更别提是要给PDF添加数字签名了,那么如何给PDF添加数字签名?今天呢就给大家分享一个超级好用的方法,让你分分钟就可以搞定哦。
高效办公
2019/05/28
3K0
如何给PDF添加数字签名?教你一招轻松破解
如何设置PDF全屏动画?PDF怎么全屏放映
如何给PDF文件设置全屏动画?顾名思义全屏动画及时文件在全屏的状态下进行页面的切换或者展示一些其他的特效,这样的文件其实能够有效的吸引读者的眼球。
用户5843321
2019/10/15
2.2K0
如何设置PDF全屏动画?PDF怎么全屏放映
如何创建PDF格式文件,这个方法教你快速创建
很多人接触到的PDF文件,很多都是从网上下载来的,而这些大都是转换来的,因为PDF本身就是比较安全,兼容性比较好,不论是在阅读还是在传输的时候都是比较便捷的,在办公中用到的还是比较多的,但是PDF文件很难进行修改,想要重新创建一个PDF进行编辑该怎么办呢?如何创建PDF格式文件,这是很多人比较关心的问题,今天来给大家分享一个超级好用的方法哦,然给你快速完成创建。
高效办公
2019/05/21
1.6K0
如何创建PDF格式文件,这个方法教你快速创建
如何交换PDF页面?PDF文件的页面位置怎么交换
收到读者大大的回复,提到PDF文件交换页面,也不知道要干嘛用,但是既然读者大大提到了,肯定是在某个时刻需要这个操作,如何交换PDF页面?PDF文件的页面位置怎么交换,小编这期决定出个教程,不喜勿喷,不
用户6477319
2019/10/31
2.3K0
如何交换PDF页面?PDF文件的页面位置怎么交换
Word文档怎么排版?这些小技巧你千万不能错过
我们常用Word编辑文档,有时候我们也需要对文档进行排版。我们可以对Word进行哪些简单有效的排版呢?大家可能没有注意到Word中的这些排版功能哦。到底是哪些功能呢?大家可以跟随我来一起看看。以后可以帮到你哦。
高效办公
2019/10/30
1.3K0
Word文档怎么排版?这些小技巧你千万不能错过
ABBYY FineReader PDF15下载安装技巧
刚刚,老板给我一堆扫描文件(图片和pdf文件),拿不到源文件,让我把客户发的扫描文件搞成word文档,密密麻麻,这些文件100多页,这要手工敲能把手敲费。
用户7442547
2023/03/31
2.2K0
ONLYOFFICE桌面编辑器8.1版:个性化编辑和功能强化的全面升级
现在,ONLYOFFICE 套件的在线版和桌面版都具有功能齐全的 PDF 编辑器,能够以不同方式创建、注释和编辑 PDF 文件。从 8.1 版本开始,ONLYOFFICE PDF 编辑器能够执行以下操作:
用户11029103
2024/07/01
1920
ONLYOFFICE桌面编辑器8.1版:个性化编辑和功能强化的全面升级
如何利用迅捷画图绘制工作流程图
迅捷画图可以绘制流程图,思维导图并且能制作的很精美出来,那怎样利用迅捷画图绘制工作流程图呢?下面是小编辑总结的操作方法,可以参考步骤进行操作使用。
啦啦啦
2018/11/19
1.3K0
如何利用迅捷画图绘制工作流程图
ABBYY FineReader,专业OCR识别,超强PDF编辑软件
刚刚,老板给我一堆扫描文件(图片和pdf文件),拿不到源文件,让我把客户发的扫描文件搞成word文档,密密麻麻,这些文件100多页,这要手工敲能把手敲费。
Eleven
2023/04/21
4.3K0
ABBYY FineReader,专业OCR识别,超强PDF编辑软件
PDF Plus for Mac(PDF处理工具)
PDF Plus Mac版是Mac平台上的一款PDF文档处理工具,功能强大,只需三个简单的步骤即可帮助您合并,拆分,加水印和裁切PDF文档。
皮西歪
2023/04/13
2.1K0
PDF Plus for Mac(PDF处理工具)
推荐阅读
相关推荐
PDF怎么添加水印?怎么给PDF文件添加图片水印
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验