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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java难点重构-NIO
在学习本篇之前,首先你要对 IO 有一定的了解。当然不了解的话,也可以看得哈哈,我会说的很通俗易懂。
Petterp
2022/02/09
5540
Java难点重构-NIO
NIO前言:一、NIO与IO的区别二、通道和缓冲区三、NIO的网络通信总结:
所谓NIO,就是New IO的缩写。是从JDK 1.4开始引入的全新的IO API。NIO将以更高效的方式进行文件的读写操作,可完全代替传统的IO API使用。而且JDK 1.7对NIO又进行了更新,可以称作NIO 2.0。
贪挽懒月
2018/12/27
6.7K0
NIO
详细知识参考我有道云笔记 package com.shi.nio; import java.nio.ByteBuffer; /** * * @author shiye * 一、缓冲区(Buffer):在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据 * * 根据数据类型不同(boolean 除外),提供了相应类型的缓冲区: * ByteBuffer * CharBuffer * ShortBuffer * IntBuffer * LongBuf
用户5927264
2019/10/15
7360
NIO之通道Channel【FileChannel介绍】
  Channel是一个对象,作用是用于源节点和目标节点的连接,在java NIO中负责缓冲区数据的传递。Channel本身不存储数据,因此需要配合缓冲区进行传输。
用户4919348
2019/04/18
6350
NIO之通道Channel【FileChannel介绍】
java nio
文章目录 1. 缓冲区(Buffer) 1.1. 常用的方法 1.2. 核心属性 1.3. 直接缓冲区 1.4. 非直接缓冲区 2. 通道(Channel) 2.1. 获取通道 2.2. 实例 2.3. 通道之间指定进行数据传输 2.4. 分散读取 2.5. 聚集写入 2.6. NIO阻塞式 3. Selector(选择器) 3.1. SelectionKey 3.2. NIO非阻塞式 4. 参考文章 缓冲区(Buffer) 负责数据的存取,实际上就是一个数组,用于存储不同的数据 除了布尔类型之后,其他
爱撒谎的男孩
2019/12/31
1.1K0
NIO最全教程,看这一篇就够了
Java NIO(New IO 或 Non Blocking IO)是从 Java 1.4 版本开始引入的一个新的 IO API,可以替代标准的 Java IO API。NIO 支持面向缓冲区的、基于通道的 IO 操作。NIO 将以更加高效的方式进行文件的读写操作。
公众号 IT老哥
2020/09/16
6710
NIO最全教程,看这一篇就够了
Java持久化之 -- 傲娇的NIO
xxxBuffer buffer = xxxBuffer.allocate(最大容量);
房上的猫
2019/01/24
5230
Java持久化之 -- 傲娇的NIO
java:NIO读写文件的示例
版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net/10km/article/details/51822932
10km
2019/05/25
2.3K0
open()+直接缓冲区使用原理
通过非直接缓冲区读写数据,需要通过通道来传输缓冲区里的数据 import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class demo4 { public static void main(String[] args) {
用户7108768
2021/09/26
4170
【Java NIO】那NIO为什么速度快?
Java IO在工作中其实不常用到,更别提NIO了。但NIO却是高效操作I/O流的必备技能,如顶级开源项目Kafka、Netty、RocketMQ等都采用了NIO技术,NIO也是大多数面试官必考的体系知识。虽然骨头有点难啃,但还是要慢慢消耗知识、学以致用哈~
JavaSouth南哥
2024/04/16
2670
【Java NIO】那NIO为什么速度快?
JVM-直接内存
JVM 直接内存(Direct Memory)是 JVM 运行时使用的一种特殊内存区域,它是 JVM 堆外的一块内存空间。在 Java 中,我们使用java.nio 包和java.lang.System类中的arraycopy()方法等来操作直接内存。
程序员朱永胜
2023/11/11
5801
JVM-直接内存
(代码篇)从基础文件IO说起虚拟内存,内存文件映射,零拷贝
JAVA虚拟机内部便会调用OS底层的 read()系统调用完成操作,在调用 in.read()的时候就是从内核缓冲区直接返回数据了。
intsmaze-刘洋
2018/08/29
4740
(代码篇)从基础文件IO说起虚拟内存,内存文件映射,零拷贝
DirectByteBuffer_bytebuffer.get
ByteBuffer是NIO里用得最多的Buffer,它包含两个实现方式:HeapByteBuffer是基于Java堆的实现,而DirectByteBuffer则使用了unsafe的API进行了堆外的实现。这里只说HeapByteBuffer。
全栈程序员站长
2022/11/09
3560
DirectByteBuffer_bytebuffer.get
Java NIO之Java中的IO分类
前面两篇文章(Java NIO之理解I/O模型(一)、Java NIO之理解I/O模型(二))介绍了,IO的机制,以及几种IO模型的内容,还有涉及到的设计模式。这次要写一些更贴近实际一些的内容了,终于要说到了Java中的各种IO了。我也是边学边理解,有写的不对的地方,欢迎小伙伴们指出和补充。
纪莫
2019/10/28
5170
Java NIO之Java中的IO分类
Java - 从文件压缩聊一聊I/O一二事
可以看到read0() 一个调用本地方法与原生操作系统进行交互,从磁盘中读取数据。每读取一个字节的数据就调用一次本地方法与操作系统交互,一个63M的文档,转换成直接,那得交互多少次…那耗时…
小小工匠
2021/08/17
4310
NIO从入门到踹门
java.nio全称java non-blocking IO,是指JDK1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络(来源于百度百科)。
java技术爱好者
2020/09/22
9590
NIO从入门到踹门
Java的NIO入门
Java NIO是从Java 1.4版本开始引入的一个新的IO ,在传统的IO模型中,使用的是同步阻塞IO,也就是blocking IO。
半月无霜
2023/03/03
2720
Java的NIO入门
Java I/O流面试之道
南哥在国外 stackoverflow 看到13年前的这么一个问题:如何使用 Java 逐行读取大型文本文件。大家有什么思路吗?评论区一起讨论讨论。
JavaSouth南哥
2024/11/08
1500
Java I/O流面试之道
NIO~~
NIO 有三大核心部分:Channel( 通道) ,Buffer( 缓冲区), Selector( 选择器)
大忽悠爱学习
2022/05/06
9000
NIO~~
按照缓冲区的顺序,分散读取和非阻塞式网络通信区别
分散读取(Scattering Reads)是指从Channel 中读取的数据“分散”到多个Buffer 中。
用户7108768
2021/09/26
3000
相关推荐
Java难点重构-NIO
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文