我的编程空间,编程开发者的网络收藏夹
学习永远不晚

NIO深入理解FileChannel使用方法原理

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

NIO深入理解FileChannel使用方法原理

前言

前文我们已经了解了NIO的三大核心组件,本篇文章会详细介绍FileChannel中的使用方法和原理。

FileChannel

FileChannel是对一个文件读,写,映射,操作的Channel。FileChannel是线程安全的,可以被多个线程并发使用。同一个进程中的多个FileChannel看到的同一个文件的视图是相同的,由于底层操作系统执行的缓存和网络文件系统协议引起的延迟,不同进程中在同一时间看到的文件视图可能会不同。

FileChannel的类图如下,FileChannel是一个抽象类,它的具体实现是FileChannelImpl。

FileChannel的创建

获取FileChannel的方式有下面四种

  • FileChannel.open()

直接调用FileChannel的open()方法,传入Path即可获得FileChannel

// 直接传入Path默认是只读FileChannel
FileChannel fileChannel = FileChannel.open(Path.of("./tmp/linshifu.txt"));
// 和直接传入Path相比,支持传入OpenOption数组
FileChannel channel = FileChannel.open(Path.of("./tmp/linshifu.txt"), StandardOpenOption.WRITE);

OpenOption是一个空接口,我们可以传入StandardOpenOption枚举,StandardOpenOption有如下值

public enum StandardOpenOption implements OpenOption {
    // 可读Channel
    READ,
    // 可写Channel
    WRITE,
    // 如果Channel是可写(WRITE)的,缓冲中的数据会从文件末尾开始写,而不是从头开始写
    APPEND,
    // 如果Channel是可写(WRITE)的,文件的长度会被置为0
    TRUNCATE_EXISTING,
    // 如果文件不存在,则会创建一个新的文件,如果配置了CREATE,则CREATE_NEW会失效
    CREATE,
    // 创建换一个新的文件,如果文件已经存在,则会失败
    CREATE_NEW,
    // Channel关闭时删除
    DELETE_ON_CLOSE,
    // 稀疏文件
    SPARSE,
    // 要求对文件内容或元数据的每次更新都同步写入基础存储设备。
    SYNC,
    // 要求对文件内容的每次更新都同步写入基础存储设备。
    DSYNC;
}
  • FileInputStream.getChannel()

通过FileInputStream的getChannel()方法获取FileChannel

FileInputStream fileInputStream = new FileInputStream("./tmp/linshifu.txt");
FileChannel fileChannel = fileInputStream.getChannel();

FileInputStream创建的FileChannel不可写,只能读

  • FileOutputStream.getChannel()

通过FileOutputStream的getChannel()方法获取FileChannel

FileOutputStream fileInputStream = new FileOutputStream("./tmp/linshifu.txt");
FileChannel fileChannel = fileInputStream.getChannel();

FileOutputStream创建FileChannel不可读,只能写

  • RandomAccessFile.getChannel()

通过RandomAccessFile的getChannel()方法获取FileChannel

RandomAccessFile file = new RandomAccessFile("./tmp/linshifu.txt", "rw");
FileChannel fileChannel = file.getChannel();

RandomAccessFile中的模式

与OutputStream和InputStream不同的是创建RandomAccessFile需要传入模式,RandomAccessFile的模式也会影响到FileChannel,创建RandomAccessFile可以传入的模式有下面4种

  • r

只读模式,创建的RandomAccessFile只能读,如果使用只读的RandomAccessFile创建的FileChannel写数据会抛出NonWritableChannelException

  • rw

读写模式,创建的RandomAccessFile即可读,也可写

  • rws

rw一样,打开以进行读取和写入,并且还要求对文件内容或元数据的每次更新同步写入基础存储设备

  • rwd

rw一样,打开以进行读取和写入,并且还要求对文件内容的每次更新都同步写入底层存储设备

FileChannel操作文件

读文件操作

读文件的方法有如下三个

// 从position位置读取ByteBuffer.capacity个byte,Channel的position向后移动capacity个位置
public abstract int read(ByteBuffer dst) throws IOException;
// 从传入的position开始读取ByteBuffer.capacity个byte,Channel的positon位置不变
public abstract int read(ByteBuffer dst, long position) throws IOException;
// 按顺序读取文件到ByteBuffer数组中,会将数据读取到ByteBuffer数组的[offset,offset+length)的ByteBuf子数组中
public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;

我们准备一个

写一个demo

写文件操作

// 从position开始写入ByteBuffer中的数据
public abstract int write(ByteBuffer class="lazy" data-src) throws IOException;
// 从position开始将ByteBuffer数组的[offset,offset+length)的ByteBuf子数组中的数据写入文件
public abstract long write(ByteBuffer[] class="lazy" data-srcs, int offset, int length) throws IOException;
// 从position开始将ByteBuffer数组中的数据写入文件
public final long write(ByteBuffer[] class="lazy" data-srcs) throws IOException {
    return write(class="lazy" data-srcs, 0, class="lazy" data-srcs.length);
}

写一个Demo

对文件的更新强制输出到底层存储设备

这种方式可以确保在系统崩缺时不会丢失数据,参数中的boolean表示刷盘时是否将文件元数据也同时写到磁盘上。

public abstract void force(boolean metaData) throws IOException;

通道之间数据传输

如果需要将FileChannel的数据快速传输到另一个Channel,可以使用transferTotransFrom

// 将字节从此通道的文件传输到给定的可写字节通道
public abstract long transferTo(long position, long count,WritableByteChannel target) throws IOException;
// 将字节从给定的可读字节通道传输到此通道的文件中
public abstract long transferFrom(ReadableByteChannel class="lazy" data-src,long position, long count) throws IOException;

通常情况下我们要将一个通道的数据传到另一个通道。例如,从一个文件读取数据通过socket通道进行发送。比如通过http协议读取服务器上的一个静态文件,要经过下面几个阶段

  • 将文件从硬盘拷贝到页缓冲区
  • 从页缓冲区读拷贝到用户缓冲区
  • 用户缓冲区的数据拷贝到socket内核缓冲区,最终再将socket内核缓冲区的数据拷贝到网卡中

当我们通过transferTo或者transferFrom在通道之间传输数据时,如果内核支持,则会使用零拷贝的方式传输数据

零拷贝技术可以避免将数据拷贝到用户空间中

MappedByteBuffer

MappedByteBuffer是NIO中应对的操作大文件的方式,它的读写性能极高,它是一种基于mmap的零拷贝方案,通常情况下可以映射出整个文件,如果文件比较大,也支持分段映射。这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。 一般来说,只有文件中实际读取或者写入的部分才会映射到内存中。

mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。mmap需要4次上下文切换,3次数据拷贝。

MappedByteBuffer使用的是虚拟内存,因此分配(map)的内存大小不受JVM的-Xmx参数限制。

MappedByteBuffer使用的方式也比较简单,首先我们准备一个文件,文件内容如下所示,文件大小34B

我们利用MappedByteBuffer写一段代码,将上面文件的第一个字符改成a,代码如下

public static void main(String[] args) throws Exception {
    RandomAccessFile file = new RandomAccessFile("./tmp/01.txt", "rw");
    MappedByteBuffer mbf = file.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1024);
    mbf.put(0, (byte) 'a');
    file.close();
}

执行完上面代码之后,这个文件的第一个字符确实被改成了a,但是在文字的末尾也多了很多奇怪的符号

再次查看文件,发现文件大小变为了1KB,我们在进行文件映射时应当注意文件的position和size不应当超出文件的范围,否则可能导致"文件空洞",磁盘上物理文件中写入数据间产生间隙。

以上就是NIO深入理解FileChannel的详细内容,更多关于NIO深入理解FileChannel的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

NIO深入理解FileChannel使用方法原理

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

NIO深入理解FileChannel使用方法原理

这篇文章主要为大家介绍了NIO深入理解FileChannel的源码示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-19

深入理解Oracle DECODE函数的使用方法

Oracle数据库中的DECODE函数是一种非常常用的条件表达式函数,它的作用是根据不同的条件返回不同的值。本文将深入探讨Oracle DECODE函数的使用方法,并提供具体的代码示例以帮助读者更好地理解。一、DECODE函数的基本语法
深入理解Oracle DECODE函数的使用方法
2024-03-08

深入了解Vue3中props的原理与使用

props指父组件往子组件中传入参数,这篇文章主要为大家介绍了vue3中props的原理与使用,文中的示例代码讲解详细,感兴趣的可以了解一下
2023-05-19

Thread.sleep(0)的写法原理深入解析

这篇文章主要为大家介绍了Thread.sleep(0)的写法原理深入解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-27

深入了解Java中finalize方法的作用和底层原理

这篇文章主要为大家详细介绍了Java中finalize方法的作用和底层原理,文中的示例代码讲解详细,具有一定的学习价值,需要的可以参考一下
2022-12-29

深入理解Python中的super()方法

前言 python的类分别有新式类和经典类,都支持多继承。在类的继承中,如果你想要重写父类的方法而不是覆盖的父类方法,这个时候我们可以使用super()方法来实现 python语言与C++有相似的类继承,在类定义时,python中会自定义第
2022-06-04

Java NIO多路复用的方法以及Linux epoll实现原理详解

这篇文章主要介绍“Java NIO多路复用的方法以及Linux epoll实现原理详解”,在日常操作中,相信很多人在Java NIO多路复用的方法以及Linux epoll实现原理详解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作
2023-06-02

深入理解Vue响应式原理及其实现方式

Vue的响应式原理是Vue最核心的特性之一,也是Vue能够为开发者提供高效便捷的开发体验的重要原因之一,这篇文章主要介绍了响应式的原理及其实现方式,需要详细了解可以参考下文
2023-05-19

Vueslot插槽作用与原理深入讲解

插槽slot可以说在一个Vue项目里面处处都有它的身影,比如我们使用一些UI组件库的时候,我们通常可以使用插槽来自定义我们的内容,这篇文章主要介绍了Vue3中slot插槽使用方式,需要的朋友可以参考下
2023-01-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录