前言:

FileChannel是一个连接到文件的通道,我们可以通过FileChannel来读写文件。在之前我们读写文件的方式中,主要是采用InputStream和OutputStream的流式方式来操作的。

本文会介绍下FileChannel的那些常用api。

1.FileChannel的基本结构

通过它的类结构图我们可以看到,FileChannel实现了对文件的读写操作,还被设置为可中断。下面来具体了解下其API。

2.FileChannel API

2.1 FileChannel的创建

File file = new File("D:\\test.txt");// 1.通过RandomAccessFile创建
RandomAccessFile raFile = new RandomAccessFile(file, "rwd");
FileChannel channel = raFile.getChannel();// 2.通过FileInputStream创建
FileInputStream fileInputStream = new FileInputStream(file);
FileChannel inputStreamChannel = fileInputStream.getChannel();// 3.通过FileOutputStream创建
FileOutputStream fileOutputStream = new FileOutputStream(file);
FileChannel outputStreamChannel = fileOutputStream.getChannel();

通过这三种方式创建的FileChannel有什么具体的区别呢?我们通过源码来比对下

// RandomAccessFile.getChannel()
channel = FileChannelImpl.open(fd, path, true, rw, this);// FileInputStream.getChannel()
channel = FileChannelImpl.open(fd, path, true, false, this);// FileOutputStream.getChannel()
channel = FileChannelImpl.open(fd, path, false, true, append, this);// FileChannelImpl构造方法
private FileChannelImpl(FileDescriptor var1, String var2, boolean var3, boolean var4, boolean var5, Object var6) {this.fd = var1;this.readable = var3;this.writable = var4;this.append = var5;this.parent = var6;this.path = var2;this.nd = new FileDispatcherImpl(var5);
}

通过FileChannelImpl的私有构造方法我们可以了解到var3参数对应的是是否可读,var4对应的是是否可写。

再结合FileInputStream.getChannel FileOutputStream.getChannel时传入FileChannelImplement的参数,可以得到以下结果:

获取方式 是否有文件读写权限
RandomAccessFile.getChannel 可读,是否可写根据传入mode来判断
FileInputStream.getChannel 可读,不可写
FileOutputStream.getChannel 可写,不可读

另:FileChannel还提供了一个open()的static方法,也可以通过该方式来获取,只不过这种方式不太常用,笔者不再详述。

2.2 RandomAccessFile的mode

RandomAccessFile的构造方法中有两个参数,分别对应file引用和mode(模式)。

mode具体有哪些值呢?我们直接看源码

public RandomAccessFile(File file, String mode)throws FileNotFoundException {String name = (file != null ? file.getPath() : null);int imode = -1;// read 只读模式if (mode.equals("r"))imode = O_RDONLY;// rw read and write 读写模式else if (mode.startsWith("rw")) {imode = O_RDWR;rw = true;if (mode.length() > 2) {// 还有s和d,分别对应于O_SYNC O_DSYNCif (mode.equals("rws"))imode |= O_SYNC;else if (mode.equals("rwd"))imode |= O_DSYNC;elseimode = -1;}}...fd = new FileDescriptor();fd.attach(this);path = name;open(name, imode);
}

O_SYNC O_DSYNC这两个分别代表什么呢?

笔者直接从网络上摘抄出来一段解释(来自https://zhuanlan.zhihu.com/p/104994838)

由于内存比磁盘读写速度快了好几个数量级,为了弥补磁盘IO性能低,Linux内核引入了页面高速缓存(PageCache)。我们通过Linux系统调用(open--->write)写文件时,内核会先将数据从用户态缓冲区拷贝到PageCache便直接返回成功,然后由内核按照一定的策略把脏页Flush到磁盘上,我们称之为write back。write写入的数据是在内存的PageCache中的,一旦内核发生Crash或者机器Down掉,就会发生数据丢失,对于分布式存储来说,数据的可靠性是至关重要的,所以我们需要在write结束后,调用fsync或者fdatasync将数据持久化到磁盘上。
write back减少了磁盘的写入次数,但却降低了文件磁盘数据的更新速度,会有丢失更新数据的风险。为了保证磁盘文件数据和PageCache数据的一致性,Linux提供了sync、fsync、msync、fdatasync、sync_file_range5个函数。
open函数的O_SYNC和O_DSYNC参数有着和fsync及fdatasync类似的含义:使每次write都会阻塞到磁盘IO完成。O_SYNC:使每次write操作阻塞等待磁盘IO完成,文件数据和文件属性都更新。
O_DSYNC:使每次write操作阻塞等待磁盘IO完成,但是如果该写操作并不影响读取刚写入的数据,则不需等待文件属性被更新。O_DSYNC和O_SYNC标志有微妙的区别:
文件以O_DSYNC标志打开时,仅当文件属性需要更新以反映文件数据变化(例如,更新文件大小以反映文件中包含了更多数据)时,标志才影响文件属性。在重写其现有的部分内容时,文件时间属性不会同步更新。
文件以O_SYNC标志打开时,数据和属性总是同步更新。对于该文件的每一次write都将在write返回前更新文件时间,这与是否改写现有字节或追加文件无关。相对于fsync/fdatasync,这样的设置不够灵活,应该很少使用。实际上:Linux对O_SYNC、O_DSYNC做了相同处理,没有满足POSIX的要求,而是都实现了fdatasync的语义。

正是由于内存和磁盘之间的读写速度差异,所以才有了write方法只是将数据写入pageCache的优化做法,同时操作系统也提供了O_SYNC和O_DSYNC来保证数据刷入磁盘。

2.3 write写相关方法

// 1.将单个ByteBuffer写入FileChannel
public abstract int write(ByteBuffer src) throws IOException;// 2.写入批量ByteBuffer,offset即ByteBuffer的offset
public abstract long write(ByteBuffer[] srcs, int offset, int length)throws IOException;// 3.同2,offset为0
public final long write(ByteBuffer[] srcs) throws IOException {return write(srcs, 0, srcs.length);
}

标准写入方式:

File file = new File("D:\\test.txt");// 1.通过RandomAccessFile创建
RandomAccessFile raFile = new RandomAccessFile(file, "rwd");
FileChannel channel = raFile.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(100);String text = "When grace is lost from life, come with a burst of song";
byteBuffer.put(text.getBytes());byteBuffer.flip();
// 写入数据
while (byteBuffer.hasRemaining()) {channel.write(byteBuffer);
}

注意:write方法是在while循环中做的,因为无法保证一次write方法向FileChannel中写入多少字节

2.4 read读相关方法

// 1.将文件内容读取到单个ByteBuffer
public abstract int read(ByteBuffer dst) throws IOException;// 2.将文件内容读取到ByteBuffer[]中,ByteBuffer的offset为指定值
public abstract long read(ByteBuffer[] dsts, int offset, int length)throws IOException;// 3.同2
public final long read(ByteBuffer[] dsts) throws IOException {return read(dsts, 0, dsts.length);
}

标准读取方式:

File file = new File("D:\\test.txt");// 1.通过RandomAccessFile创建
RandomAccessFile raFile = new RandomAccessFile(file, "rwd");
FileChannel channel = raFile.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(100);
// 真正读取到readCount个字节
int readCount = channel.read(byteBuffer);byteBuffer.flip();
byte[] array = byteBuffer.array();
// 将读取到的内容写入到String
String s = new String(array);
// 结果就是刚才2.3 write方法中写入的值
System.out.println(s);

2.5 force方法

public abstract void force(boolean metaData) throws IOException;

之前2.2说过,write方法写入文件可能只是写入了PageCache,如果此时系统崩溃,那么只存在于PageCache而没有刷入磁盘的数据就有可能丢失。使用force方法,我们就可以强制将文件内容和元数据信息(参数boolean metaData就是用来决定是否将元数据也写入磁盘)写入磁盘。该方法对一些关键性的操作,比如事务操作,就是非常关键的,使用force方法可以保证数据的完整性和可靠恢复。

2.6 lock相关方法

// 1.从file的position位置开始,锁定长度为size,锁定类别共享锁(true)或独占锁(false)
public abstract FileLock lock(long position, long size, boolean shared)throws IOException;// 2.同1,基本独占全文件
public final FileLock lock() throws IOException {return lock(0L, Long.MAX_VALUE, false);
}// 3.同1,尝试进行文件锁定
public abstract FileLock tryLock(long position, long size, boolean shared)throws IOException;// 4.同2,尝试进行文件锁定
public final FileLock tryLock() throws IOException {return tryLock(0L, Long.MAX_VALUE, false);
}

首先,我们需要明白的是:锁定针对的是文件本身,而不是Channel或者线程

FileLock可以是共享的,也可以是独占的。

锁的实现很大程度上依赖于本地的操作系统实现。当操作系统不支持共享锁时,则会主动升级共享锁为独占锁。

// 通过两个进程来测试下FileLock
FileLock lock = null;
try {File file = new File("D:\\test.txt");// 1.通过RandomAccessFile创建RandomAccessFile raFile = new RandomAccessFile(file, "rwd");FileChannel channel = raFile.getChannel();// 主动设置独占锁或共享锁lock = channel.lock(0, Integer.MAX_VALUE, true);System.out.println(lock);
} catch (FileNotFoundException e) {e.printStackTrace();
} catch (IOException e) {e.printStackTrace();
} finally {try {// 需要主动releaselock.release();} catch (IOException e) {e.printStackTrace();}
}
笔者基于使用Windows机器测试结果:支持两个进程对同一文件的共享锁;不支持两个进程对同一文件的独占锁(一个独占一个共享也不可以

总结:

本文主要介绍了下FileChannel的常用API。基于FileChannel,我们可以实现对文件的读写操作。

FileChannel还有些比较高级的API,比如map()、transferTo()、transferFrom()等,我们在下篇博客中继续介绍。

参考:

https://zhuanlan.zhihu.com/p/104994838

NIO源码解析-FileChannel相关推荐

  1. NIO源码解析:FileChannel基本使用

    文章目录 1. 简介 2. 写入文件 2.1 写入流程 2.2. 写入Demo 2.3 解析 3. 读取文件 3.1 读取流程 3.2 读取Demo 3.3 解析 4. 复制文件 4.1 流程 4.2 ...

  2. NIO源码解析:IntBuffer基本使用

    文章目录 简介 公共属性 初始化 添加数据 读取数据 简介 IntBuffer 是 Int 的缓冲区类型 类的关系图如下: Buffer:抽象类,一切Buffer相关的基类 IntBuffer:抽象类 ...

  3. 拆轮子-RxDownload2源码解析(三)

    本文为博主原创文章,未经允许不得转载 造轮子者:Season_zlc 轮子用法请戳作者链接 ↑ 前言 本文主要讲述 RxDownload2 的多线程断点下载技术. 断点下载技术前提 服务器必须支持按 ...

  4. mysql 网络io_分布式 | DBLE 网络模块源码解析(一):网络 IO 基础知识

    作者:路路 热爱技术.乐于分享的技术人,目前主要从事数据库相关技术的研究. 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源. 前言 对于计算机学科来说 ...

  5. Tomcat源码解析五:Tomcat请求处理过程

    前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求-> ...

  6. netty依赖_Netty系列之源码解析(一)

    接下来的时间灯塔君持续更新Netty系列一共九篇 当前:Netty 源码解析(一)开始 Netty 源码解析(二): Netty 的 Channel Netty 源码解析(三): Netty 的 Fu ...

  7. Netty 源码解析系列-服务端启动流程解析

    netty源码解析系列 Netty 源码解析系列-服务端启动流程解析 Netty 源码解析系列-客户端连接接入及读I/O解析 五分钟就能看懂pipeline模型 -Netty 源码解析 1.服务端启动 ...

  8. Hotspot 对象引用Reference和Finalizer 源码解析

    目录 一.Reference 1.SoftReference / WeakReference / PhantomReference 2.定义 3.ReferenceHandler 4.Cleaner ...

  9. Zookeeper源码解析 -- 本地事务日志持久化之FileTxnLog

    序言 在各个分布式组件中,持久化数据到本地的思想并不少见,为的是能保存内存中的数据,以及重启后能够重载上次内存状态的值.那么如何行之有效的进行,内存数据持久化到磁盘,怎么样的落盘策略合适,怎么设计持久 ...

  10. Dubbo 实现原理与源码解析系列 —— 精品合集

    摘要: 原创出处 http://www.iocoder.cn/Dubbo/good-collection/ 「芋道源码」欢迎转载,保留摘要,谢谢! 1.[芋艿]精尽 Dubbo 原理与源码专栏 2.[ ...

最新文章

  1. SVN commit,update用法
  2. swiper 定义放多少张图片,小程序swiper轮播图,自定义样式,两种方法:原生方法和bindchange方法;将点点改为数字(当前第几张 /总共几张);点击点点跳转当前图片...
  3. 角距离恒星_恒星问卷调查的10倍机器学习生产率
  4. [js] 写一个方法,实时验证input输入的值是否满足金额如:3.56(最多只有两位小数且只能数字和小数点)的格式,其它特殊字符禁止输入
  5. orbslam2初始化流程
  6. ppt矩形里面的图片怎么放大缩小_如何在PPT中插入大量图片而又保持其美感?
  7. Eddy‘s picture
  8. Xcode 和 Mac 的一些快捷键
  9. 启动tomcat闪退如何获取报错信息
  10. 中触媒科创板上市:市值74亿 为李进与刘颐静夫妻店
  11. rar和unrar压缩解压
  12. python大数相乘
  13. Python Tkinter - WiFi WL Test 上位机 (自动搜索Uart、执行exe/Bat)
  14. 数据库设计(二)——数据库设计原则
  15. TransactionSynchronizationManager用法和含义
  16. (已更新)王者荣耀改名神器助手微信小程序源码下载
  17. java 网络编程(二) tcp传输实现客户端和服务端进行信息交流
  18. 简单C++程序——掷骰子
  19. 什么时候建立数据库,怎么建立数据库?
  20. java中workbook_java workbook 类

热门文章

  1. linux centos ppp限速,Centos 中限制网络带宽速度
  2. matlab数据导出wps,怎么把金山WPS表格的数据导入MATLAB/
  3. android studio scala插件,在Android Studio中使用Scala和Java
  4. 牛客网暑期ACM多校训练营(第三场) J.Distance to Work 计算几何
  5. springMVC实现图片打包下载
  6. 每天一个linux命令(33):atq命令
  7. 这篇文章不错,仔细读读,码农晋升为技术管理者后,痛并快乐着的纠结内心...
  8. [译]eBay Elasticsearch性能调优实践
  9. windows slim read/write lock 原理剖析
  10. marshmallow——自定义类型