NIO之FileChannel
kafka顺序写盘用的就是FileChannel类实现
FileChannel优势:
-
多线程并发读写,并发性;
-
IO读写性能提高(OS负责),也可引做共享内存,减少IO操作,提升并发性;
-
应用crash,保证这部分内容还能写的进去文件。在我们调用channel.write(bytebuffer)之后,具体何时写入磁盘、bytebuffer中内容暂存于哪里(os cache)等相关一系列问题,就交由OS本身负责了
FileChannel
就是连接到文件的Channel
。使用FileChannel
,你可以读取文件数据,以及往文件里面写入数据。Java NIO的FileChannel
是使用标准Java IO读取文件的一种替代方案。
FileChannel
不能被设置非阻塞模式,它总是以阻塞模式运行。
Opening a FileChannel
在你使用FileChannel
前,你必须先打开它。你不能直接打开一个FileChannel
。你必须通过一个InputStream
、OutputStream
或者RandomAccessFile
来获得一个FileChannel
。以下是如何通过一个RandomAccessFile
来获得FileChannel
的示例:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
Reading Data from a FileChannel
为了从FileChannel
读取数据,你需要调用其read()
方法。如下:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
首先分配一个Buffer
,从FileChannel
读取的数据将被读入Buffer
。
然后调用FileChannel.read()
方法。这个方法将数据从FileChannel
读入Buffer
。read()
方法返回的int
代表着有多少数据被读入了Buffer
。如果返回-1,代表着已经读到文件结尾。
Writing Data to a FileChannel
要将数据写入FileChannel
,你需要调用带有一个Buffer
类型参数的FileChannel.write()
方法。如下:
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while (buf.hasRemaining()) {
channel.write(buf);
}
注意在while-循环里面调用的FileChannel.write()
方法。我们无法保证每次write()
方法向Channel
写了多少字节。因此我们重复调用write()
直至没有数据可写为止。
Closing a FileChannel
当时使用完FileChannel
后,你必须关闭它。如下:
channel.close();
FileChannel Position
当读取或写入FileChannel
时,需要在特定position
执行。你可以通过调用position()
方法来获得FileChannel
的当前position
。
你还可以通过调用position(long pos)
来设置FileChannel
的position
。
以下是两个例子:
long pos = channel.position();
channel.position(pos + 123);
如果你设置的position
超过了文件的大小,并且尝试从Channel
读取数据,则会返回-1代表文件结尾。
如果你设置的position
超过了文件的大小,并且尝试往Channel
写入数据,文件会自动扩张至能放下position
以及写入的数据。这个可能导致"file hole",即磁盘上的物理文件在写入的数据中存在漏洞(即中间有一段完全没有任何数据)。
FileChannel Size
FileChannel
的size()
方法返回这个Channel
连接的文件大小。如下:
long fileSize = channel.size();
FileChannel Truncate
通过调用FileChannel.truncate()
方法,你可以truncate一个文件。当你truncate一个文件,你会把其截断为指定的长度。如下:
channel.truncate(1024);
这个例子将文件截断为1024字节。
FileChannel Force
FileChannel.force()
方法会将Channel
里面还未写入的数据全部刷新到磁盘。操作系统可能会将数据缓存在内存里以提升性能,因此我们无法保证你写入Channel
的数据都被写到了磁盘,直到你调用force()
方法。
force()
方法有一个boolean
类型的参数,代表是否将文件元数据(比如权限等)也刷新到磁盘。
以下是刷新数据以及元数据到磁盘的例子:
channel.force(true);
概述
对于文件的复制,平时我们都是使用输入输出流进行操作,利用源文件创建出一个输入流,然后利用目标文件创建出一个输出流,最后将输入流的数据读取写入到输出流中。这样也是可以进行操作的。但是利用fileChannel是很有用的一个方式。它能直接连接输入输出流的文件通道,将数据直接写入到目标文件中去。而且效率更高。
FileChannel类
FileChannel是一个用读写,映射和操作一个文件的通道。出了读写操作之外,还有裁剪特定大小文件truncate(),强制在内存中的数据刷新到硬盘中去force(),对通道上锁lock()等功能。
他们的使用分别如下面代码:
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//读取1024字节内容到byteBuffer钟
fileChannelInput.read(byteBuffer);
解释:上面代码首先创建一个1024大小的缓冲对象,然后在输入通道中读取1024大小数据,放入到缓冲对象中。
byteBuffer.clear();
byteBuffer.put("需要写入的数据".getBytes());
//类似于flush()函数功能,将buffer里面的数据刷新出去
byteBuffer.flip();
//检查是否还有数据未写入
while (byteBuffer.hasRemaining()) fileChannelOutput.write(byteBuffer);
解释:上面的代码是将一段字符串写入到输出文件通道中,因为写入的时候并不保证能一次性写入到文件中,所以需要进行判断是否全部写入,如果没有需要再次调用写入函数操作
//获取文件通道位置
fileChannelInput.position();
fileChannelInput.size();
//截取内容
fileChannelInput.truncate(1024);
//强制刷新数据到硬盘
fileChannelInput.force(true);
解释:上面的代码是获取文件通道的位置和大小。truncate()方法是截取1024大小的数据,指定长度后面的部分将被删除。以及将数据强制刷新到硬盘中,因为系统会将数据先保存在内存中,不保证数据会立即写入到硬盘中,所以有这个需求,就可以直接强制数据写入内存中。
使用
说那么多可能没用,我们还是直接来看看分别使用两种方法进行文件复制的对比。
首先是普通的输入输出流进行复制文件:
/**
* 普通的文件复制方法
*
* @param fromFile 源文件
* @param toFile 目标文件
* @throws FileNotFoundException 未找到文件异常
*/
public void fileCopyNormal(File fromFile, File toFile) throws FileNotFoundException {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(fromFile));
outputStream = new BufferedOutputStream(new FileOutputStream(toFile));
byte[] bytes = new byte[1024];
int i;
//读取到输入流数据,然后写入到输出流中去,实现复制
while ((i = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null)
inputStream.close();
if (outputStream != null)
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
解释:在上面的代码中,传入源文件和目标文件两个参数,然后根据两个文件,分别出具输入输出流,然后将输入流的数据读取,并且写入输出流中,就完成了文件的复制操作。
下面再看一下利用fileChannel进行文件的复制操作。
/**
* 用filechannel进行文件复制
*
* @param fromFile 源文件
* @param toFile 目标文件
*/
public void fileCopyWithFileChannel(File fromFile, File toFile) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
FileChannel fileChannelInput = null;
FileChannel fileChannelOutput = null;
try {
fileInputStream = new FileInputStream(fromFile);
fileOutputStream = new FileOutputStream(toFile);
//得到fileInputStream的文件通道
fileChannelInput = fileInputStream.getChannel();
//得到fileOutputStream的文件通道
fileChannelOutput = fileOutputStream.getChannel();
//将fileChannelInput通道的数据,写入到fileChannelOutput通道
fileChannelInput.transferTo(0, fileChannelInput.size(), fileChannelOutput);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileInputStream != null)
fileInputStream.close();
if (fileChannelInput != null)
fileChannelInput.close();
if (fileOutputStream != null)
fileOutputStream.close();
if (fileChannelOutput != null)
fileChannelOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
解释:上面代码中,也是先分别创建了两个文件的输入输出流,然后在分别获取到两个文件的文件通道,然后将源文件的文件通道直接和目标文件的文件通道进行连接,直接将数据写入到目标文件中区。不需要进行分别的读取和写入操作了。
运行代码之后,复制一个文件,对比两种复制方法,发现利用filechannel使用的时间比普通的读取输入时间缩短了将近一半。尤其是在进行大文件复制的时候,filechannel显得更加有优势
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话