本文将简要介绍
RandomAccessFile
这个类的使用,主要包括RandomAccessFile相关的一些概念,常见API的使用,如何利用RandomAccessFile实现一个断点续传的效果
RandomAccessFile是什么?
RandomAccessFile
是 Java 中用于对文件进行随机访问的类。与普通的输入输出流不同,RandomAccessFile
允许在文件中任意位置读写数据。
RandomAccessFile的作用有哪些?
RandomAccessFile
允许在文件中任意位置进行读写操作,可以自由地定位文件指针。RandomAccessFile
实现了 DataInput
和 DataOutput
接口,提供了方便的方法用于读取和写入基本数据类型。read(byte[] buffer)
和 write(byte[] buffer)
方法来读写字节数组。setLength(long newLength)
方法来调整文件的长度,将文件截断或扩展为指定的大小。常见的应用场景是使用 RandomAccessFile
实现断点续传
注意事项:RandomAccessFile
只能读写文件(也就是字节数组)类型的数据,并不能读写流类型的数据
RandomAccessFile的四种访问模式
访问模式 | 特点 |
---|---|
r (read,只读模式) |
能读文件,不能写文件、持久化 |
rw (read write,读写模式) |
能读、写文件,不能持久化 |
rws (read write sync,同步读写模式) |
能读、写文件,每次写操作都会持久化 |
rwd (read write data,同步写模式) |
能读、写文件,只有在调用close()、getFD().sync()、关闭程序时才进行持久化 |
r
”(只读模式):使用只读模式打开文件,只能对文件进行读取操作,无法修改文件内容。rw
”(读写模式):使用读写模式打开文件,允许对文件进行读取和写入操作,并且可以修改文件内容。rws
”(同步读写模式):使用同步读写模式打开文件,除了具有读写模式的功能外,还要求每次写入操作都要将数据同步刷新到底层存储介质(如硬盘)。在使用 rws
模式打开文件时,每次进行写入操作时,不仅会将数据写入到内存缓冲区,还会立即将数据刷新到底层存储介质(如硬盘)。这样可以保证数据的持久性,并且在发生系统崩溃或断电等异常情况时,数据不会丢失。由于每次写入操作都要进行磁盘刷新,所以相比于 rwd
模式,rws
模式的写入速度可能较慢。rwd
”(同步写模式):使用同步写模式打开文件,类似于同步读写模式,在使用 rwd
模式打开文件时,每次进行写入操作时,只有将数据写入到内存缓冲区,而不会立即刷新到底层存储介质。只有在调用 close()
方法、显式调用 getFD().sync()
方法或关闭程序时,才会将数据刷新到存储介质。相比于 rws
模式,rwd
模式的写入速度可能稍快,因为不需要每次都进行磁盘刷新。适用场景说明:
RandomAccessFile家族体系
java.io.DataOutput
:DataOutput
接口提供了写入基本数据类型的方法,用于将数据以二进制格式写入输出流。RandomAccessFile
类实现了 DataOutput
接口,因此可以使用该接口定义的方法写入数据。java.io.DataInput
:DataInput
接口定义了读取基本数据类型的方法,用于从输入流中以二进制格式读取数据。RandomAccessFile
类实现了 DataInput
接口,因此可以使用该接口定义的方法读取数据。java.io.Closeable
:Closeable
是一个可关闭的接口,表示实现了该接口的类具备关闭资源的能力。RandomAccessFile
类实现了 Closeable
接口,因此可以通过调用 close()
方法关闭文件。java.lang.AutoCloseable
:AutoCloseable
是一个自动关闭的接口,在 Java 7 中引入。它扩展了 Closeable
接口,并要求实现类必须提供一个细化的 close()
方法。RandomAccessFile
实现了 AutoCloseable
接口,所以可以使用 try-with-resources 语句来自动关闭文件。构造方法:
RandomAccessFile(String name, String mode)
:创建一个具有指定名称的RandomAccessFile对象,并根据指定的模式打开文件。模式可以是"r"(只读),“rw”(读写)等。读取方法:
int read()
:从文件中读取一个字节并返回该字节的整数值。
int read(byte[] b)
:从文件中读取一定数量的字节并存储到字节数组b中,并返回实际读取的字节数。
int read(byte[] b, int off, int len)
:从文件中读取最多len个字节,并将其存储到字节数组b中,偏移量为off,并返回实际读取的字节数。
int skipBytes(int n)
:跳过指定字节读(相对位置)
写入方法:
void write(int b)
:将一个字节写入文件。
void write(byte[] b)
:将字节数组b中的所有字节写入文件。
void write(byte[] b, int off, int len)
:将字节数组b中从偏移量off开始的len个字节写入文件。
文件操作方法:
boolean exists()
:判断文件是否存在。
void createNewFile()
:创建一个新文件。
boolean delete()
:删除文件。
boolean renameTo(File dest)
:将文件重命名为dest指定的文件名。
long getFilePointer()
:返回当前文件指针的位置。
void seek(long pos)
:设置文件指针的位置为pos。(绝对位置)
文件长度相关方法:
long length()
:返回文件的长度(以字节为单位)。
void setLength(long newLength)
:设置文件的长度为newLength。
关闭方法:
void close()
:关闭该RandomAccessFile对象,释放相关资源环境搭建
Step1:创建一个Maven工程
Step2:在src/main/resources
目录下准备一个 data.txt
文件,文件中的内容是
hello world!
示例一
演示
read
方法
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("./src/main/resources/data.txt", "r");
// 读取一个字节,此时已经读取了 h,data.txt 还剩 ello world!没有读取
raf.read();
byte[] bytes = new byte[1024];
// 将从data.txt中读取的数据转存到字节数组中
int len = raf.read(bytes);
// 由于在 raf.read(bytes) 之前执行过一个 raf.read() 方法了,所以此时字节数组中会直接跳过第一个字节的数组
// 由于UTF-8编码英文一个字母占一个字节(中文占3个字节)所以最终结果回漏掉 data.txt 中的首字母
System.out.println(new String(bytes, 0, len)); // ello world!
}
备注:data.txt 中一个空格也算一个字节
示例二
演示
skipBytes
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("./src/main/resources/data.txt", "r");
// 读取一个字节,此时已经读取了 h,data.txt 还剩 ello world!没有读取
raf.read();
// 相对当前读取位置(也就是 ello world!)再跳过两个字节 e 和 l,此时 data.txt 还剩 lo world! 没有读取
raf.skipBytes(2);
byte[] bytes = new byte[1024];
int len = raf.read(bytes);
System.out.println(new String(bytes, 0, len)); // lo world!
}
示例三
演示
seek
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("./src/main/resources/data.txt", "r");
// 读取一个字节,此时已经读取了 h,data.txt 还剩 ello world!没有读取
raf.read();
// 相对 data.txt 原始位置(也就是 hello world!)跳过2个字节,此时 data.txt 还剩 llo world! 没有读取
raf.seek(2);
byte[] bytes = new byte[1024];
int len = raf.read(bytes);
System.out.println(new String(bytes, 0, len)); // llo world!
}
示例四
演示
write
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("./src/main/resources/data.txt", "rw");
// 此时直接调用write方法,是从文件的第一个字节开始写, data.txt 变成了 ghplo world!
// 同时此时指针也会随着写操作来到了 l
raf.write("ghp".getBytes(StandardCharsets.UTF_8));
// raf.seek(0);
byte[] bytes = new byte[1024];
int len = raf.read(bytes);
// 由于之前的写操作,导致指针来到了 l,所以读操作从 l 开始读,所以最终读取的结果是 lo world!
// 并不会读取到之前写入的数据
System.out.println(new String(bytes, 0, len)); // lo world!
}
备注:想要写入之后能够读取到 data.txt 中完整的数据,可以在执行完写操作之后,执行seek(0)
将指针重置为初始位置,注意如果使用skipBytes(0)
是没有效果的,因为它是相对位置
示例五
比较 原始输入输出流单线程拷贝大文件 和 RandomAccessFile实现多线程拷贝大文件
package com.ghp.file.test;
import java.io.*;
import java.util.concurrent.CountDownLatch;
/**
* @author ghp
* @title
* @description
*/
public class Main {
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
// copyFileBySingleThread(); // 单线程拷贝 476MB 的视频耗时 6903ms
copyFileByMultiThread(); // 多线程拷贝 476MB 的视频耗时 3022ms
long endTime = System.currentTimeMillis();
System.out.println("文件拷贝耗时: " + (endTime - startTime) + "ms");
}
private static void copyFileBySingleThread() throws IOException {
File file = new File("./src/main/resources/data.mp4");
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream("./src/main/resources/data-bak1.mp4");
byte[] bytes = new byte[1024];
int len = -1;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
}
private static void copyFileByMultiThread() throws Exception {
File file = new File("./src/main/resources/data.mp4");
int threadNum = 5;
// 计算每个线程需要读取的字节大小
int part = (int) Math.ceil(file.length() / threadNum);
// 创建线程计数器对象,用于阻塞主线程
CountDownLatch latch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
final int k = i;
new Thread(() -> {
try {
RandomAccessFile fis = new RandomAccessFile(file, "r");
RandomAccessFile fos = new RandomAccessFile("./src/main/resources/data-bak.mp4", "rw");
// 设置读和写的位置
fis.seek(k * part);
fos.seek(k * part);
byte[] bytes = new byte[1024];
int sum = 0;
while (true) {
int len = fis.read(bytes);
if (len == -1){
// 文件已经读完了
break;
}
sum += len;
fos.write(bytes, 0, len);
if (sum >= part){
// 当前线程需要读取的字节已经读完了
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
// 子线程执行完毕,线程计数器减一
latch.countDown();
}
}).start();
}
// 阻塞主线程,只有线程计数器归0,主线程才会继续执行
latch.await();
}
}
注意点:
/**
* 断点续传
*
* @param src 源文件(需要拷贝的文件)
* @param target 目标文件(拷贝后的文件)
* @param threadNum 线程数
*/
private static void breakpointContinuation(File src, File target, int threadNum) throws Exception {
// 每一个线程平均需要读取的字节数
final int part = (int) Math.ceil(src.length() / threadNum);
// 创建应该HashMap,用于记录每一个线程已读取的位置
final Map<Integer, Integer> map = new ConcurrentHashMap<>();
// 读取日志文件中的数据
String[] logDatas = null;
String logName = target.getCanonicalPath() + ".log";
File logFile = new File(logName);
if (logFile.exists()) {
// 日志文件存在,则从上一次读取的位置开始读
try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
String data = reader.readLine();
logDatas = data.split(",");
} catch (IOException e) {
e.printStackTrace();
}
}
final String[] logData = logDatas;
CountDownLatch latch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
final int k = i;
new Thread(() -> {
try (RandomAccessFile in = new RandomAccessFile(src, "r");
RandomAccessFile out = new RandomAccessFile(target, "rw");
RandomAccessFile log = new RandomAccessFile(logName, "rw")) {
// 从指定位置读
int start = logData == null ? k * part : Integer.parseInt(logData[k]);
in.seek(start);
out.seek(start);
byte[] bytes = new byte[1024 * 2];
int sum = 0;
while (true) {
int len = in.read(bytes);
if (len == -1) {
// 文件所有字节已读完,结束读取
break;
}
sum += len;
// 记录当前线程已读取的位置
map.put(k, sum + start);
// 将读取到的数据、进行写入
out.write(bytes, 0, len);
// 将 map 中的数据持久化
log.seek(0);
StringJoiner joiner = new StringJoiner(",");
map.forEach((key, val) -> joiner.add(String.valueOf(val)));
log.write(joiner.toString().getBytes(StandardCharsets.UTF_8));
if (sum + (start) >= (1 + k) * part) {
// 当前线程读取的字节数量已经够了,结束读取
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
latch.await();
// 读取完成后、将日志文件删除即可
new File(logName).delete();
}
参考文章:
- RandomAccessFile详解_江南煮酒的博客-CSDN博客
- Java.io.RandomAccessFile 类 (w3schools.cn)
- RandomAccessFile 解决多线程下载及断点续传-腾讯云开发者社区-腾讯云 (tencent.com)# RandomAccessFile学习笔记
RandomAccessFile是什么?
RandomAccessFile
是 Java 中用于对文件进行随机访问的类。与普通的输入输出流不同,RandomAccessFile
允许在文件中任意位置读写数据。
RandomAccessFile的作用有哪些?
RandomAccessFile
允许在文件中任意位置进行读写操作,可以自由地定位文件指针。RandomAccessFile
实现了 DataInput
和 DataOutput
接口,提供了方便的方法用于读取和写入基本数据类型。read(byte[] buffer)
和 write(byte[] buffer)
方法来读写字节数组。setLength(long newLength)
方法来调整文件的长度,将文件截断或扩展为指定的大小。常见的应用场景是使用 RandomAccessFile
实现断点续传
注意事项:RandomAccessFile
只能读写文件(也就是字节数组)类型的数据,并不能读写流类型的数据
RandomAccessFile的四种访问模式
访问模式 | 特点 |
---|---|
r (read,只读模式) |
能读文件,不能写文件、持久化 |
rw (read write,读写模式) |
能读、写文件,不能持久化 |
rws (read write sync,同步读写模式) |
能读、写文件,每次写操作都会持久化 |
rwd (read write data,同步写模式) |
能读、写文件,只有在调用close()、getFD().sync()、关闭程序时才进行持久化 |
r
”(只读模式):使用只读模式打开文件,只能对文件进行读取操作,无法修改文件内容。rw
”(读写模式):使用读写模式打开文件,允许对文件进行读取和写入操作,并且可以修改文件内容。rws
”(同步读写模式):使用同步读写模式打开文件,除了具有读写模式的功能外,还要求每次写入操作都要将数据同步刷新到底层存储介质(如硬盘)。在使用 rws
模式打开文件时,每次进行写入操作时,不仅会将数据写入到内存缓冲区,还会立即将数据刷新到底层存储介质(如硬盘)。这样可以保证数据的持久性,并且在发生系统崩溃或断电等异常情况时,数据不会丢失。由于每次写入操作都要进行磁盘刷新,所以相比于 rwd
模式,rws
模式的写入速度可能较慢。rwd
”(同步写模式):使用同步写模式打开文件,类似于同步读写模式,在使用 rwd
模式打开文件时,每次进行写入操作时,只有将数据写入到内存缓冲区,而不会立即刷新到底层存储介质。只有在调用 close()
方法、显式调用 getFD().sync()
方法或关闭程序时,才会将数据刷新到存储介质。相比于 rws
模式,rwd
模式的写入速度可能稍快,因为不需要每次都进行磁盘刷新。适用场景说明:
RandomAccessFile家族体系
java.io.DataOutput
:DataOutput
接口提供了写入基本数据类型的方法,用于将数据以二进制格式写入输出流。RandomAccessFile
类实现了 DataOutput
接口,因此可以使用该接口定义的方法写入数据。java.io.DataInput
:DataInput
接口定义了读取基本数据类型的方法,用于从输入流中以二进制格式读取数据。RandomAccessFile
类实现了 DataInput
接口,因此可以使用该接口定义的方法读取数据。java.io.Closeable
:Closeable
是一个可关闭的接口,表示实现了该接口的类具备关闭资源的能力。RandomAccessFile
类实现了 Closeable
接口,因此可以通过调用 close()
方法关闭文件。java.lang.AutoCloseable
:AutoCloseable
是一个自动关闭的接口,在 Java 7 中引入。它扩展了 Closeable
接口,并要求实现类必须提供一个细化的 close()
方法。RandomAccessFile
实现了 AutoCloseable
接口,所以可以使用 try-with-resources 语句来自动关闭文件。构造方法:
RandomAccessFile(String name, String mode)
:创建一个具有指定名称的RandomAccessFile对象,并根据指定的模式打开文件。模式可以是"r"(只读),“rw”(读写)等。读取方法:
int read()
:从文件中读取一个字节并返回该字节的整数值。
int read(byte[] b)
:从文件中读取一定数量的字节并存储到字节数组b中,并返回实际读取的字节数。
int read(byte[] b, int off, int len)
:从文件中读取最多len个字节,并将其存储到字节数组b中,偏移量为off,并返回实际读取的字节数。
int skipBytes(int n)
:跳过指定字节读(相对位置)
写入方法:
void write(int b)
:将一个字节写入文件。
void write(byte[] b)
:将字节数组b中的所有字节写入文件。
void write(byte[] b, int off, int len)
:将字节数组b中从偏移量off开始的len个字节写入文件。
文件操作方法:
boolean exists()
:判断文件是否存在。
void createNewFile()
:创建一个新文件。
boolean delete()
:删除文件。
boolean renameTo(File dest)
:将文件重命名为dest指定的文件名。
long getFilePointer()
:返回当前文件指针的位置。
void seek(long pos)
:设置文件指针的位置为pos。(绝对位置)
文件长度相关方法:
long length()
:返回文件的长度(以字节为单位)。
void setLength(long newLength)
:设置文件的长度为newLength。
关闭方法:
void close()
:关闭该RandomAccessFile对象,释放相关资源环境搭建
Step1:创建一个Maven工程
Step2:在src/main/resources
目录下准备一个 data.txt
文件,文件中的内容是
hello world!
示例一
演示
read
方法
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("./src/main/resources/data.txt", "r");
// 读取一个字节,此时已经读取了 h,data.txt 还剩 ello world!没有读取
raf.read();
byte[] bytes = new byte[1024];
// 将从data.txt中读取的数据转存到字节数组中
int len = raf.read(bytes);
// 由于在 raf.read(bytes) 之前执行过一个 raf.read() 方法了,所以此时字节数组中会直接跳过第一个字节的数组
// 由于UTF-8编码英文一个字母占一个字节(中文占3个字节)所以最终结果回漏掉 data.txt 中的首字母
System.out.println(new String(bytes, 0, len)); // ello world!
}
备注:data.txt 中一个空格也算一个字节
示例二
演示
skipBytes
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("./src/main/resources/data.txt", "r");
// 读取一个字节,此时已经读取了 h,data.txt 还剩 ello world!没有读取
raf.read();
// 相对当前读取位置(也就是 ello world!)再跳过两个字节 e 和 l,此时 data.txt 还剩 lo world! 没有读取
raf.skipBytes(2);
byte[] bytes = new byte[1024];
int len = raf.read(bytes);
System.out.println(new String(bytes, 0, len)); // lo world!
}
示例三
演示
seek
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("./src/main/resources/data.txt", "r");
// 读取一个字节,此时已经读取了 h,data.txt 还剩 ello world!没有读取
raf.read();
// 相对 data.txt 原始位置(也就是 hello world!)跳过2个字节,此时 data.txt 还剩 llo world! 没有读取
raf.seek(2);
byte[] bytes = new byte[1024];
int len = raf.read(bytes);
System.out.println(new String(bytes, 0, len)); // llo world!
}
示例四
演示
write
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("./src/main/resources/data.txt", "rw");
// 此时直接调用write方法,是从文件的第一个字节开始写, data.txt 变成了 ghplo world!
// 同时此时指针也会随着写操作来到了 l
raf.write("ghp".getBytes(StandardCharsets.UTF_8));
// raf.seek(0);
byte[] bytes = new byte[1024];
int len = raf.read(bytes);
// 由于之前的写操作,导致指针来到了 l,所以读操作从 l 开始读,所以最终读取的结果是 lo world!
// 并不会读取到之前写入的数据
System.out.println(new String(bytes, 0, len)); // lo world!
}
备注:想要写入之后能够读取到 data.txt 中完整的数据,可以在执行完写操作之后,执行seek(0)
将指针重置为初始位置,注意如果使用skipBytes(0)
是没有效果的,因为它是相对位置
示例五
比较 原始输入输出流单线程拷贝大文件 和 RandomAccessFile实现多线程拷贝大文件
package com.ghp.file.test;
import java.io.*;
import java.util.concurrent.CountDownLatch;
/**
* @author ghp
* @title
* @description
*/
public class Main {
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
// copyFileBySingleThread(); // 单线程拷贝 476MB 的视频耗时 6903ms
copyFileByMultiThread(); // 多线程拷贝 476MB 的视频耗时 3022ms
long endTime = System.currentTimeMillis();
System.out.println("文件拷贝耗时: " + (endTime - startTime) + "ms");
}
private static void copyFileBySingleThread() throws IOException {
File file = new File("./src/main/resources/data.mp4");
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream("./src/main/resources/data-bak1.mp4");
byte[] bytes = new byte[1024];
int len = -1;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
}
private static void copyFileByMultiThread() throws Exception {
File file = new File("./src/main/resources/data.mp4");
int threadNum = 5;
// 计算每个线程需要读取的字节大小
int part = (int) Math.ceil(file.length() / threadNum);
// 创建线程计数器对象,用于阻塞主线程
CountDownLatch latch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
final int k = i;
new Thread(() -> {
try {
RandomAccessFile fis = new RandomAccessFile(file, "r");
RandomAccessFile fos = new RandomAccessFile("./src/main/resources/data-bak.mp4", "rw");
// 设置读和写的位置
fis.seek(k * part);
fos.seek(k * part);
byte[] bytes = new byte[1024];
int sum = 0;
while (true) {
int len = fis.read(bytes);
if (len == -1){
// 文件已经读完了
break;
}
sum += len;
fos.write(bytes, 0, len);
if (sum >= part){
// 当前线程需要读取的字节已经读完了
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
// 子线程执行完毕,线程计数器减一
latch.countDown();
}
}).start();
}
// 阻塞主线程,只有线程计数器归0,主线程才会继续执行
latch.await();
}
}
注意点:
/**
* 断点续传
*
* @param src 源文件(需要拷贝的文件)
* @param target 目标文件(拷贝后的文件)
* @param threadNum 线程数
*/
private static void breakpointContinuation(File src, File target, int threadNum) throws Exception {
// 每一个线程平均需要读取的字节数
final int part = (int) Math.ceil(src.length() / threadNum);
// 创建应该HashMap,用于记录每一个线程已读取的位置
final Map<Integer, Integer> map = new ConcurrentHashMap<>();
// 读取日志文件中的数据
String[] logDatas = null;
String logName = target.getCanonicalPath() + ".log";
File logFile = new File(logName);
if (logFile.exists()) {
// 日志文件存在,则从上一次读取的位置开始读
try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
String data = reader.readLine();
logDatas = data.split(",");
} catch (IOException e) {
e.printStackTrace();
}
}
final String[] logData = logDatas;
CountDownLatch latch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
final int k = i;
new Thread(() -> {
try (RandomAccessFile in = new RandomAccessFile(src, "r");
RandomAccessFile out = new RandomAccessFile(target, "rw");
RandomAccessFile log = new RandomAccessFile(logName, "rw")) {
// 从指定位置读
int start = logData == null ? k * part : Integer.parseInt(logData[k]);
in.seek(start);
out.seek(start);
byte[] bytes = new byte[1024 * 2];
int sum = 0;
while (true) {
int len = in.read(bytes);
if (len == -1) {
// 文件所有字节已读完,结束读取
break;
}
sum += len;
// 记录当前线程已读取的位置
map.put(k, sum + start);
// 将读取到的数据、进行写入
out.write(bytes, 0, len);
// 将 map 中的数据持久化
log.seek(0);
StringJoiner joiner = new StringJoiner(",");
map.forEach((key, val) -> joiner.add(String.valueOf(val)));
log.write(joiner.toString().getBytes(StandardCharsets.UTF_8));
if (sum + (start) >= (1 + k) * part) {
// 当前线程读取的字节数量已经够了,结束读取
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
latch.await();
// 读取完成后、将日志文件删除即可
new File(logName).delete();
}
参考文章:
- RandomAccessFile详解_江南煮酒的博客-CSDN博客
- Java.io.RandomAccessFile 类 (w3schools.cn)
- RandomAccessFile 解决多线程下载及断点续传-腾讯云开发者社区-腾讯云 (tencent.com)