推荐关注公众号:锅外的大佬

每日推送国外技术好文,帮助每位开发者更优秀地成长

原文链接:https://www.baeldung.com/java-filechannel

作者:baeldung

译者:Leesen

1.概述

在这篇速学教程中,我们将研究Java NIO库中提供的FileChannel类,讨论如何使用FileChannelByteBuffer读写数据,探讨使用FileChannel以及其他文件操作特性的优点。

2.FileChannel的优点

FileChannel的优点包括:

  • 在文件特定位置进行读写操作
  • 将文件一部分直接加载到内存,这样效率更高
  • 以更快的速度将文件数据从一个通道传输到另一个通道
  • 锁定文件的某一部分来限制其他线程访问
  • 为了避免数据丢失,强制立即将更新写入文件并存储

3.FileChannel读操作

当我们读取一个大文件时,FileChannel标准I/O执行得更快。需要注意,虽然FileChannelJava NIO的一部分,但是FileChannel操作是阻塞的,并且没有非阻塞模式。

3.1.使用FileChannel读取文件

先了解如何使用FileChannel读取一个文件,该文件包含:

 Hello world

下面测试读取文件,并检查是否ok:

@Test
public void givenFile_whenReadWithFileChannelUsingRandomAccessFile_thenCorrect()
throws IOException {
try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
FileChannel channel = reader.getChannel();
ByteArrayOutputStream out = new ByteArrayOutputStream()) { int bufferSize = 1024;
if (bufferSize > channel.size()) {
bufferSize = (int) channel.size();
}
ByteBuffer buff = ByteBuffer.allocate(bufferSize); while (channel.read(buff) > 0) {
out.write(buff.array(), 0, buff.position());
buff.clear();
} String fileContent = new String(out.toByteArray(), StandardCharsets.UTF_8); assertEquals("Hello world", fileContent);
}
}

这里使用FileChannelRandomAccessFileByteBuffer从文件中读取字节。还应该注意,多个并发线程可以安全地使用FileChannel。但是,每次只允许一个线程执行涉及更新通道位置(channel position)或更改其文件大小的操作。这会阻止其他试图执行类似操作的线程,直到前一个操作完成。
但是,显式提供通道位置的操作可以并发运行且不会被阻塞。

3.2.打开FileChannel

为了使用FileChannel读取文件,我们必须打开它(Open FileChannel)。看看如何使用RandomAccessFile打开FileChannel:

RandomAccessFile reader = new RandomAccessFile(file, "r");
FileChannel channel = reader.getChannel();

模式“r”表示通道仅为“只读“,注意,关闭RandomAccessFile也将关闭与之关联的通道。
接下来,使用FileInputStream打开一个FileChannel来读取文件:

FileInputStream fin= new FileInputStream(file);
FileChannel channel = fin.getChannel();

同样的,关闭FileInputStream也会关闭与之相关的通道。

3.3.从FileChannel中读取数据

为了读取数据,我们可以使用只读模式。接下来看看如何读取字节序列,我们将使用ByteBuffer来保存数据:

ByteBuffer buff = ByteBuffer.allocate(1024);
int noOfBytesRead = channel.read(buff);
String fileContent = new String(buff.array(), StandardCharsets.UTF_8); assertEquals("Hello world", fileContent);

然后,我们将看到如何从文件某个位置开始读取一个字节序列:

ByteBuffer buff = ByteBuffer.allocate(1024);
int noOfBytesRead = channel.read(buff, 5);
String fileContent = new String(buff.array(), StandardCharsets.UTF_8);
assertEquals("world", fileContent);

我们应该注意:需要使用字符集(Charset)将字节数组解码为字符串
我们指定原始编码字节的字符集。没有它,我们可能会以断章取义的文字结束。特别是像UTF-8UTF-16这样的多字节编码可能无法解码文件的任意部分,因为一些多字节字符可能是不完整的。

4.FileChannel写操作

4.1.使用FileChannel写入文件

我们来探究下如何使用FileChannel写:

@Test
public void whenWriteWithFileChannelUsingRandomAccessFile_thenCorrect()
throws IOException {
String file = "src/test/resources/test_write_using_filechannel.txt";
try (RandomAccessFile writer = new RandomAccessFile(file, "rw");
FileChannel channel = writer.getChannel()){
ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff); // verify
RandomAccessFile reader = new RandomAccessFile(file, "r");
assertEquals("Hello world", reader.readLine());
reader.close();
}
}

4.2.打开FileChannel

要使用FileChannel写入文件,必须先打开它。使用RandomAccessFile打开一个FileChannel:

RandomAccessFile writer = new RandomAccessFile(file, "rw");
FileChannel channel = writer.getChannel();

模式“rw”表示通道为“读写”。
使用FileOutputStream打开FileChannel:

FileOutputStream fout = new FileOutputStream(file);
FileChannel channel = fout.getChannel();

4.3.FileChannel写入数据

使用FileChannel写数据,可以使用其中的某个写方法。
我们来看下如何写一个字节序列,使用ByteBuffer来存储数据:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8));
channel.write(buff);

接下来,我们将看到如何从文件某个位置开始写一个字节序列:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8));
channel.write(buff, 5);

5.当前位置

FileChannel允许我们获得和改变读或写的位置(position)。获得当前的位置:

long originalPosition = channel.position();

设置位置:

channel.position(5);
assertEquals(originalPosition + 5, channel.position());

6.获取文件大小

使用FileChannel.size方法获取文件大小(以字节为单位):

@Test
public void whenGetFileSize_thenCorrect()
throws IOException {
RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
FileChannel channel = reader.getChannel(); // the original file size is 11 bytes.
assertEquals(11, channel.size()); channel.close();
reader.close();
}

7.截断文件

使用FileChannel.truncate方法将文件截断为给定的大小(以字节为单位):

@Test
public void whenTruncateFile_thenCorrect() throws IOException {
String input = "this is a test input"; FileOutputStream fout = new FileOutputStream("src/test/resources/test_truncate.txt");
FileChannel channel = fout.getChannel(); ByteBuffer buff = ByteBuffer.wrap(input.getBytes());
channel.write(buff);
buff.flip(); channel = channel.truncate(5);
assertEquals(5, channel.size()); fout.close();
channel.close();
}

8.强制更新

由于性能原因,操作系统可能缓存文件更改,如果系统崩溃,数据可能会丢失。要强制文件内容和元数据不断写入磁盘,我们可以使用force方法:

channel.force(true);

仅当文件存储在本地设备上时,才能保证该方法有效。

9.将文件部分加载到内存

使用FileChannel.map方法将文件的部分加载到内存中。使用FileChannel.MapMode.READ_ONLY以只读模式打开文件:

@Test
public void givenFile_whenReadAFileSectionIntoMemoryWithFileChannel_thenCorrect() throws IOException {
try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r");
FileChannel channel = reader.getChannel();
ByteArrayOutputStream out = new ByteArrayOutputStream()) { MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 6, 5); if(buff.hasRemaining()) {
byte[] data = new byte[buff.remaining()];
buff.get(data);
assertEquals("world", new String(data, StandardCharsets.UTF_8));
}
}
}

类似地,可以使用FileChannel.MapMode.READ_WRITE以读写模式打开文件。还可以使用FileChannel.MapMode.PRIVATE模式,该模式下,更改不应用于原始文件。

10.锁定文件部分

来看下如何锁定文件某一部分,使用FileChannel.tryLock方法阻止对文件某一部分进行高并发访问。

@Test
public void givenFile_whenWriteAFileUsingLockAFileSectionWithFileChannel_thenCorrect() throws IOException {
try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "rw");
FileChannel channel = reader.getChannel();
FileLock fileLock = channel.tryLock(6, 5, Boolean.FALSE )){ //do other operations... assertNotNull(fileLock);
}
}

tryLock方法尝试获取文件部分(file section)上的锁。如果请求的文件部分已被另一个线程阻塞,它将抛出一个OverlappingFileLockException异常。此方法还接受Boolean参数来请求共享锁或独占锁。
我们应该注意到,有些操作系统可能不允许共享锁,默认情况下是独占锁。

11.FileChannel关闭

最后,当使用FileChannel时,必须关闭它。在示例中,我们使用了try-with-resources
如果有必要,我们可以直接使用FileChannel.close方法:

channel.close();

12.总结

在本教程中,我们了解了如何使用FileChannel读取和写入文件。此外,我们还研究了如何读取和更改文件大小及其当前读/写位置,并研究了如何在并发应用程序或数据关键应用程序中使用FileChannel
与往常一样,示例的源代码可以在GitHub上找到。

FileChannel指南的更多相关文章

  1. Flume FileChannel优化(扩展)实践指南

    本文系微博运维数据平台(DIP)在Flume方面的优化扩展经验总结,在使用Flume FileChannel的场景下将吞吐率由10M/s~20M/s提升至80M/s~90M/s,分为四个部分进行介绍: ...

  2. Flume NG Getting Started(Flume NG 新手入门指南)

    Flume NG Getting Started(Flume NG 新手入门指南)翻译 新手入门 Flume NG是什么? 有什么改变? 获得Flume NG 从源码构建 配置 flume-ng全局选 ...

  3. Netty权威指南

    Netty权威指南(异步非阻塞通信领域的经典之作,国内首本深入剖析Netty的著作,全面系统讲解原理.实战和源码,带你完美进阶Netty工程师.) 李林锋 著   ISBN 978-7-121-233 ...

  4. 【Flume NG用户指南】(1)设置

    作者:周邦涛(Timen) Email:zhoubangtao@gmail.com 转载请注明出处:  http://blog.csdn.net/zhoubangtao/article/details ...

  5. 【Flume NG用户指南】(2)构造

    作者:周邦涛(Timen) Email:zhoubangtao@gmail.com 转载请注明出处:  http://blog.csdn.net/zhoubangtao/article/details ...

  6. 【翻译】Flume 1.8.0 User Guide(用户指南) Processors

    翻译自官网flume1.8用户指南,原文地址:Flume 1.8.0 User Guide 篇幅限制,分为以下5篇: [翻译]Flume 1.8.0 User Guide(用户指南) [翻译]Flum ...

  7. 【翻译】Flume 1.8.0 User Guide(用户指南) Channel

    翻译自官网flume1.8用户指南,原文地址:Flume 1.8.0 User Guide 篇幅限制,分为以下5篇: [翻译]Flume 1.8.0 User Guide(用户指南) [翻译]Flum ...

  8. 【翻译】Flume 1.8.0 User Guide(用户指南)

    翻译自官网flume1.8用户指南,原文地址:Flume 1.8.0 User Guide 篇幅限制,分为以下5篇: [翻译]Flume 1.8.0 User Guide(用户指南) [翻译]Flum ...

  9. 《Netty权威指南》

    <Netty权威指南> 基本信息 作者: 李林锋 出版社:电子工业出版社 ISBN:9787121233432 上架时间:2014-5-29 出版日期:2014 年6月 开本:16开 页码 ...

随机推荐

  1. Applied Nonparametric Statistics-lec10

    Ref:https://onlinecourses.science.psu.edu/stat464/print/book/export/html/14 估计CDF The Empirical CDF ...

  2. Linux学习-什么是例行性工作排程

    那么 Linux 的例行性工作是如何进行排程的呢?所谓的排程就是将这些工作安排执行的流程之意! 咱们的 Linux 排程就是透过 crontab 与 at 这两个东西! Linux 工作排程的种类: ...

  3. #ifndef、#def、#endif说明

    你所遇到的这几个宏是为了进行条件编译.一般情况下,源程序中所有的行都参加编译.但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一 部分内容指定编译的条件,这就是“条件编译”.有时,希望 ...

  4. CodeForces 392C Yet Another Number Sequence 矩阵快速幂

    题意: \(F_n\)为斐波那契数列,\(F_1=1,F_2=2\). 给定一个\(k\),定义数列\(A_i=F_i \cdot i^k\). 求\(A_1+A_2+ \cdots + A_n\). ...

  5. 如何从Maven中央存储库下载?

    根据 Apache Maven说明: 下载时由项目的 pom.xml 文件的依赖来决定,目前不在本地存储库触发(当中央存储库包含了一个更新).默认情况下,Maven将从中央存储库下载. 在Maven中 ...

  6. loj2005 「SDOI2017」相关分析

    鬼畜线段树--Orz Capella #include <iostream> #include <cstdio> using namespace std; int n, m, ...

  7. mongodb的基本操作数据更新

    先启动服务器 查看数据库 选择数据库 删除数据库 插入信息 查看插入的表名 查看信息 修改表数据 修改指定信息,其他信息不改变 可以使不存在的命令进行修改并保存 修改多条数据 删除数据 删除表 查看集 ...

  8. HDU-2234 无题I

    为每个状态定义两个函数S和H,分别表示当前状态到列一致和行一致的目标状态的最少操作次数. 然后有了估价函数F=Min(S,H)就可以IDA*了. #include <cstdio> #in ...

  9. spring分布式事务控制

    应用场景问题描述解决方法多数据源配置单元测试第一种方法:最大努力一次提交模式第二种方法:最大努力一次提交模式 但使用ChainedTransactionManagerChainedTransactio ...

  10. [APIO2015] 雅加达的摩天楼 (分块,最短路)

    题目链接 Solution 分块+\(Dijkstra\). 难点在于建边,很明显 \(O(n^2)\) 建边会挂一堆 . 那么考虑一下, \(n^2\) 建边多余的是哪些东西 \(???\) 很显然 ...