RandomAccessFile学习笔记

文章目录

  • RandomAccessFile学习笔记
    • 前言
    • 1、RandomAccessFile基本介绍
      • 1.1 RandomAccessFile相关基本概念
      • 1.2 RandomAccessFile家族体系
    • 2、RandomAccessFile基本使用
      • 2.1 RandomAccessFile常用API介绍
      • 2.2 RandomAccessFile常用API演示
      • 2.3 RandomAccessFile实现断点续传
    • 1、RandomAccessFile基本介绍
      • 1.1 RandomAccessFile相关基本概念
      • 1.2 RandomAccessFile家族体系
    • 2、RandomAccessFile基本使用
      • 2.1 RandomAccessFile常用API介绍
      • 2.2 RandomAccessFile常用API演示
      • 2.3 RandomAccessFile实现断点续传

RandomAccessFile学习笔记

前言

本文将简要介绍RandomAccessFile这个类的使用,主要包括RandomAccessFile相关的一些概念,常见API的使用,如何利用RandomAccessFile实现一个断点续传的效果

1、RandomAccessFile基本介绍

1.1 RandomAccessFile相关基本概念

  • RandomAccessFile是什么

    RandomAccessFile 是 Java 中用于对文件进行随机访问的类。与普通的输入输出流不同,RandomAccessFile 允许在文件中任意位置读写数据。

  • RandomAccessFile的作用有哪些

    • 随机访问:与其他输入输出流不同,RandomAccessFile 允许在文件中任意位置进行读写操作,可以自由地定位文件指针。
    • 读写基本数据类型RandomAccessFile 实现了 DataInputDataOutput 接口,提供了方便的方法用于读取和写入基本数据类型。
    • 支持读写字节数组:除了读写基本数据类型外,还可以通过 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 模式的写入速度可能稍快,因为不需要每次都进行磁盘刷新。

    适用场景说明:

    1. 如果文件只需要具有读操作,就使用 r 模式;
    2. 如果文件既需要读又需要写,同时对持久化没有要求,就可以用 rw 模式;
    3. 如果文件既需要读又需要写,同时对持久化有要求严格或者是读多写少的情况,推荐使用 rws 模式
    4. 如果文件既需要读又需要写,同时对持久化有要求不是很严格 或者是 读少写多,推荐使用 rwd 模式

1.2 RandomAccessFile家族体系

  • RandomAccessFile家族体系

    image-20230826185124272

    image-20230826185142134

    • java.io.DataOutputDataOutput 接口提供了写入基本数据类型的方法,用于将数据以二进制格式写入输出流。RandomAccessFile 类实现了 DataOutput 接口,因此可以使用该接口定义的方法写入数据。
    • java.io.DataInputDataInput 接口定义了读取基本数据类型的方法,用于从输入流中以二进制格式读取数据。RandomAccessFile 类实现了 DataInput 接口,因此可以使用该接口定义的方法读取数据。
    • java.io.CloseableCloseable 是一个可关闭的接口,表示实现了该接口的类具备关闭资源的能力。RandomAccessFile 类实现了 Closeable 接口,因此可以通过调用 close() 方法关闭文件。
    • java.lang.AutoCloseableAutoCloseable 是一个自动关闭的接口,在 Java 7 中引入。它扩展了 Closeable 接口,并要求实现类必须提供一个细化的 close() 方法。RandomAccessFile 实现了 AutoCloseable 接口,所以可以使用 try-with-resources 语句来自动关闭文件。

2、RandomAccessFile基本使用

2.1 RandomAccessFile常用API介绍

  • 构造方法

    • 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对象,释放相关资源

2.2 RandomAccessFile常用API演示

环境搭建

  • 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!// 同时此时指针也会随着写操作来到了 lraf.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 的视频耗时 6903mscopyFileByMultiThread(); // 多线程拷贝 476MB 的视频耗时 3022mslong 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();}}

注意点

  1. 文件的切分必须是向上取整,否则回存在数据遗漏。向上取整能够保证即使多读了数据也会被覆盖避免数据遗漏,不会发生数据堆叠,而如果是向下取整就会导致数据遗漏
  2. 当前线程是否已经将自己那部分字节读取完毕的判断操作要在写操作之后,这样能够防止数据遗漏,判断操作放在写操作的后面,即使多写了数据,会被覆盖掉

2.3 RandomAccessFile实现断点续传

/*** 断点续传** @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学习笔记

1、RandomAccessFile基本介绍

1.1 RandomAccessFile相关基本概念

  • RandomAccessFile是什么

    RandomAccessFile 是 Java 中用于对文件进行随机访问的类。与普通的输入输出流不同,RandomAccessFile 允许在文件中任意位置读写数据。

  • RandomAccessFile的作用有哪些

    • 随机访问:与其他输入输出流不同,RandomAccessFile 允许在文件中任意位置进行读写操作,可以自由地定位文件指针。
    • 读写基本数据类型RandomAccessFile 实现了 DataInputDataOutput 接口,提供了方便的方法用于读取和写入基本数据类型。
    • 支持读写字节数组:除了读写基本数据类型外,还可以通过 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 模式的写入速度可能稍快,因为不需要每次都进行磁盘刷新。

    适用场景说明:

    1. 如果文件只需要具有读操作,就使用 r 模式;
    2. 如果文件既需要读又需要写,同时对持久化没有要求,就可以用 rw 模式;
    3. 如果文件既需要读又需要写,同时对持久化有要求严格或者是读多写少的情况,推荐使用 rws 模式
    4. 如果文件既需要读又需要写,同时对持久化有要求不是很严格 或者是 读少写多,推荐使用 rwd 模式

1.2 RandomAccessFile家族体系

  • RandomAccessFile家族体系

    image-20230826185124272

    image-20230826185142134

    • java.io.DataOutputDataOutput 接口提供了写入基本数据类型的方法,用于将数据以二进制格式写入输出流。RandomAccessFile 类实现了 DataOutput 接口,因此可以使用该接口定义的方法写入数据。
    • java.io.DataInputDataInput 接口定义了读取基本数据类型的方法,用于从输入流中以二进制格式读取数据。RandomAccessFile 类实现了 DataInput 接口,因此可以使用该接口定义的方法读取数据。
    • java.io.CloseableCloseable 是一个可关闭的接口,表示实现了该接口的类具备关闭资源的能力。RandomAccessFile 类实现了 Closeable 接口,因此可以通过调用 close() 方法关闭文件。
    • java.lang.AutoCloseableAutoCloseable 是一个自动关闭的接口,在 Java 7 中引入。它扩展了 Closeable 接口,并要求实现类必须提供一个细化的 close() 方法。RandomAccessFile 实现了 AutoCloseable 接口,所以可以使用 try-with-resources 语句来自动关闭文件。

2、RandomAccessFile基本使用

2.1 RandomAccessFile常用API介绍

  • 构造方法

    • 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对象,释放相关资源

2.2 RandomAccessFile常用API演示

环境搭建

  • 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!// 同时此时指针也会随着写操作来到了 lraf.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 的视频耗时 6903mscopyFileByMultiThread(); // 多线程拷贝 476MB 的视频耗时 3022mslong 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();}}

注意点

  1. 文件的切分必须是向上取整,否则回存在数据遗漏。向上取整能够保证即使多读了数据也会被覆盖避免数据遗漏,不会发生数据堆叠,而如果是向下取整就会导致数据遗漏
  2. 当前线程是否已经将自己那部分字节读取完毕的判断操作要在写操作之后,这样能够防止数据遗漏,判断操作放在写操作的后面,即使多写了数据,会被覆盖掉

2.3 RandomAccessFile实现断点续传

/*** 断点续传** @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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171720.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言--三目运算符

一.介绍⭐ <表达式1>&#xff1f;<表达式2>&#xff1a;<表达式3> 它的含义是&#xff1a;如果表达式1的值为真&#xff08;非零&#xff09;&#xff0c;则整个表达式的值为表达式2的值&#xff1b;否则&#xff0c;整个表达式的值为表达式3的值。 三目运算…

Python——常见内置模块

Python 模块&#xff08;Modules&#xff09;1、概念模块函数类变量2、分类3、模块导入的方法&#xff1a;五种4、使用import 导入模块5、使用from……import部分导入6、使用as关键字为导入模块或功能命名别名7、模块的搜索目录8、自定义模块 常见内置模块一、math模块二、rand…

【详解二叉树】

&#x1f320;作者&#xff1a;TheMythWS. &#x1f387;座右铭&#xff1a;不走心的努力都是在敷衍自己&#xff0c;让自己所做的选择&#xff0c;熠熠发光。 目录 树形结构 概念 树的示意图 树的基本术语 树的表示 树的应用 二叉树(重点) 二叉树的定义 二叉树的五…

交换技术-电路交换-报文交换-分组交换

交换技术是指主机之间、通信设备之间或主机与通信设备之间为交换信息所采用的数据格式和交换装置的方式。按交换技术可分为&#xff1a;电路交换、报文交换和分组交换。 电路交换 交换(switching)&#xff0c;就是按照某种方式动态地分配传输线路的资源。 电路交换是在源结点…

解决Vscode使用git提交卡住的问题

使用Vscode的git提交代码经常会很慢/卡住。 先点击左下角&#xff0c;进入设置 找到git的配置(建议直接搜索)&#xff0c;把use Editor As commit input的勾选去掉即可解决。

【批量修改文件名,并去掉括号】

操作 一、 批量修改文件名操作二、去除括号 一、 批量修改文件名操作 在浏览器等下载很多图片后&#xff0c;命名顺序乱七八糟&#xff0c;想要将图片进行重新命名&#xff0c;从数字1开始 首先&#xff0c;全选文件夹中的图片 右键&#xff0c;重明明&#xff0c;选择一张图…

【c++文件】

C是一种面向对象的编程语言&#xff0c;它广泛应用于各个领域&#xff0c;如游戏开发、嵌入式系统、操作系统等。在C编程中&#xff0c;文件操作是一项非常重要的技能。本文将介绍C文件操作的基本知识以及一些有趣的应用&#xff0c;带领大家一起探索C文件操作的魅力。 一、C文…

jQuery_08 each函数的使用

each函数的使用 可以循环数组&#xff0c;json&#xff0c;dom对象数组 1.$.each(要循环的内容,function(index,element){处理函数}) 要循环的内容可以是数组&#xff0c;json对象&#xff0c;dom数组 function&#xff1a;循环的处理函数 每个成员都会执行这个函数一次 index&…

kafka,RabbitMQ,RocketMQ,他们之间的区别,架构,如何保证消息的不丢失,保证不重复消费,保证消息的有序性

文章目录 Kafka、RabbitMQ、RocketMQ 之间的区别是什么&#xff1f;性能数据可靠性服务可用性功能 RabbitMQ如何保证消息不丢失&#xff1f;Kafka 的架构说一下&#xff1f;Kafka 怎么保证消息是有序的&#xff1f;Kafka 怎么解决重复消费&#xff1f;Kafka 怎么保证消息不丢失…

最新Midjourney绘画提示词Prompt教程无需魔法

最新Midjourney绘画提示词Prompt教程无需魔法使用 一、AI绘画工具 SparkAi【无需魔法使用】&#xff1a; SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&#xff01;本系统使用NestjsVueTypes…

C#,《小白学程序》第二十课:大数的加法(BigInteger Add)

大数的&#xff08;加减乘除&#xff09;四则运算、阶乘运算。 乘法计算包括小学生算法、Karatsuba和Toom-Cook3算法。 重复了部分 19 课的代码。 1 文本格式 using System; using System.Linq; using System.Text; using System.Collections.Generic; /// <summary>…

如何在Ubuntu系统上安装MongoDB

简单介绍 MongoDB是由C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。在高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。MongoDB旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB将数据存储为一个文档&#xff0c;数…

Centos Bind安装与排错

1.配置Centos系统静态IP vi/etc/sysconfig/network-scripts/ifcfg-ens33BOOTPROTOstaticIPADDR192.168.1.100NETMASK255.255.255.0GATEWAY192.168.1.1DNS18.8.8.8:wqsudo systemctl restart network.service 2.安装BIND&#xff08;需要服务器连接互联网&#xff0c;如果服务…

肾合胶囊 | 冬不养肾春易病,若出现了这六大表现,小心是肾虚!

冬季作为一年中最寒冷的季节&#xff0c;自然万物皆静谧闭藏&#xff0c;而肾具有潜藏、封藏、闭藏精气的特点&#xff0c;是封藏之本&#xff0c;肾的脏腑特性与冬季相通应&#xff0c;所以在冬季更应该重视养肾。 而现在正值初冬&#xff0c;正是开始养肾的最佳时间。此时培…

VBA技术资料MF86:将PPT文件批量另存为PDF文件

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

单例模式与多线程

目录 前言 正文 1.立即加载/饿汉模式 2.延迟加载/懒汉模式 1.延迟加载/懒汉模式解析 2.延迟加载/懒汉模式的缺点 3.延迟加载/懒汉模式的解决方案 &#xff08;1&#xff09;声明 synchronized 关键字 &#xff08;2&#xff09;尝试同步代码块 &#xff08;3&am…

图形编辑器开发:缩放和旋转控制点

大家好&#xff0c;我是前端西瓜哥。好久没写图形编辑器开发的文章了。 今天来讲讲控制点。它是图形编辑器的不可缺少的基础功能。 控制点是吸附在图形上的一些小矩形和圆形点击区域&#xff0c;在控制点上拖拽鼠标&#xff0c;能够实时对被选中进行属性的更新。 比如使用旋…

数据库基础教程之数据库的创建(一)

双击打开Navicat&#xff0c;点击&#xff1a;文件-》新建连接-》PostgreSQL 在下图新建连接中输入各参数&#xff0c;然后点击&#xff1a;连接测试&#xff0c;连接成功后再点击确定。 点击新建数据库 数据库设置如下&#xff1a;

[pyqt5]pyqt5设置窗口背景图片后上面所有图片都会变成和背景图片一样

pyqt5的控件所有都是集成widget&#xff0c;窗体设置背景图片后控件背景也会跟着改变&#xff0c;此时有2个办法。第一个办法显然我们可以换成其他方式设置窗口背景图片&#xff0c;而不是使用styleSheet样式表&#xff0c;网上有很多其他方法。还有个办法就是仍然用styleSheet…

如何申请永久免费的SSL证书

首先&#xff0c;让我们了解什么是SSL证书。 SSL&#xff08;Secure Socket Layer&#xff09;证书是一种数字证书&#xff0c;它提供了一种在互联网上安全地传输数据的方法。 这是一个必须的安全工具&#xff0c;可以加密您的网站和客户之间的所有信息。为了保护用户数据和确保…