• RandomAccessFile实现断点续传


    断点续传是指在文件传输过程中,当传输中断或失败时,能够恢复传输并继续从上次中断的位置继续传输。

    RandomAccessFile类

    RandomAccessFile是Java提供的一个用于文件读写的类,它可以对文件进行随机访问,即可以直接跳转到文件的任意位置进行读写操作。

    seek()方法将文件指针移动到指定位置。这样可以在文件中随机访问不同的位置。(绝对定位)
    skipBytes()方法用于将文件指针向后移动指定的字节数。这个方法可以用于跳过一定数量的字节,以便在文件中定位到需要读取或写入的位置。(相对定位)
    read()方法从当前指针处读取数据,也可以使用readFully()方法读取一定长度的数据。
    write()方法向文件中指定位置写入数据,也可以使用writeBytes()方法将字节数组写入文件。

    断点续传原理

    RandomAccessFile的seek()方法是断点续传的关键.我们将文件资源分成若干块,每个线程负责完成每块资源的传输。

    实现断点续传

    实现断点续传可以通过以下步骤来完成:

    1. 获取原始文件大小:在开始传输之前,获取原始文件的总大小(以字节为单位),可以通过File类的length()方法获取。
    2. 设置传输起始位置:根据需求,记录或设置已经传输完成的字节数,作为起始传输位置
    3. 创建 RandomAccessFile 对象:使用 RandomAccessFile 类来进行文件读写操作,并设置以读或写方式打开文件。
    4. 设置文件指针位置:使用 seek(long position) 方法将文件指针定位到起始传输位置。
    5. 开始传输:使用合适的传输方式 (如通过网络传输数据) 将文件内容传输到目标位置。
    6. 更新已传输的字节数:在传输过程中,记录已经成功传输的字节数,可通过文件指针的位置与起始传输位置的差值得出。
    7. 传输完成检查:在传输过程中,检查是否已经传输完整个文件,可以比较已传输的字节数与原始文件的总大小。
    8. 完成传输:完成传输后,关闭文件资源。

    实现代码

    1. import java.io.*;
    2. import java.nio.charset.StandardCharsets;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. import java.util.Map;
    6. import java.util.StringJoiner;
    7. import java.util.concurrent.ConcurrentHashMap;
    8. public class FileUtils {
    9. /**
    10. * 支持断点续传
    11. * @src 拷贝的原文件
    12. * @desc 拷贝的位置
    13. * @threadNum 开启的线程数
    14. */
    15. public static void transportFile(File src, File desc, int threadNum) throws Exception {
    16. // 每一个线程读取的大小
    17. Long part = (long)Math.ceil(src.length() / threadNum);
    18. // 存储多个线程、用于阻塞主线程
    19. List list = new ArrayList<>();
    20. // 定义一个基于多线程 的 hashmap 高并发map
    21. final Map map = new ConcurrentHashMap<>();
    22. // 读取 日志文件中的数据
    23. String[] $data = null ;
    24. String logName = desc.getCanonicalPath() + ".log";
    25. File fl = new File(logName);
    26. if (fl.exists()) {
    27. BufferedReader reader = new BufferedReader(new FileReader(fl));
    28. String data = reader.readLine();
    29. // 拆分 字符串
    30. $data = data.split(",");
    31. reader.close();
    32. }
    33. final String[] _data = $data ;
    34. for (int i = 0; i < threadNum; i++) {
    35. final int k = i ;
    36. Thread thread = new Thread(() -> {
    37. // 线程具体要做的事情
    38. RandomAccessFile log = null ;
    39. try {
    40. RandomAccessFile in = new RandomAccessFile(src, "r");
    41. RandomAccessFile out = new RandomAccessFile(desc, "rw");
    42. log = new RandomAccessFile(logName, "rw");
    43. // 从指定位置读
    44. in.seek(_data ==null ?k * part : Long.parseLong(_data[k]) );
    45. out.seek(_data ==null ?k * part : Long.parseLong(_data[k]) );
    46. byte[] bytes = new byte[1024 * 2];
    47. int len = -1, plen = 0;
    48. while (true) {
    49. len = in.read(bytes);
    50. if (len == -1) {
    51. break;
    52. }
    53. // 如果不等于 -1 , 则 累加求和
    54. plen += len;
    55. // 将读取的字节数,放入 到 map 中
    56. map.put(k, plen + (_data ==null ?k * part : Long.parseLong(_data[k])) );
    57. // 将读取到的数据、进行写入
    58. out.write(bytes, 0, len);
    59. // 将 map 中的数据进行写入文件中
    60. log.seek(0); // 直接覆盖全部文件
    61. StringJoiner joiner = new StringJoiner(",");
    62. map.forEach((key, val)-> joiner.add(String.valueOf(val)));
    63. log.write(joiner.toString().getBytes(StandardCharsets.UTF_8));
    64. if (plen + (_data ==null ? k * part : Long.parseLong(_data[k])) >= (k+1) * part ) {
    65. break;
    66. }
    67. }
    68. } catch (Exception e) {
    69. e.printStackTrace();
    70. }finally {
    71. try {
    72. if (log !=null) log.close();
    73. } catch (IOException e) {
    74. e.printStackTrace();
    75. }
    76. }
    77. });
    78. thread.start();
    79. // 把这5个线程保存到集合中
    80. list.add(thread);
    81. }
    82. for(Thread t : list) {
    83. t.join(); // 将线程加入,并阻塞主线程
    84. }
    85. // 读取完成后、将日志文件删除即可
    86. new File(logName).delete();
    87. }
    88. /**
    89. * 支持断点续传
    90. * @src 拷贝的原文件
    91. * @desc 拷贝的位置
    92. */
    93. public static void transportFile(File src, File desc) throws Exception {
    94. transportFile(src, desc, 5);
    95. }
    96. public static void transportFile(String src, String desc) throws Exception {
    97. transportFile(new File(src), new File(desc));
    98. }
    99. }

     测试

    需要上传的文件资源

     

     将iso资源文件上传到指定目录 

     在上传文件过程中中断程序,模拟文件资源因为网络原因中断了

    当前文件资源还未上传完全,并且有一个日志去标志文件上传进度 

     日志文件标志了每个线程进度

     重新执行程序,模拟重新上传文件

     

    剩余文件上传完毕,进度日志也被删除 

     

  • 相关阅读:
    41.说说Promise自身的静态方法
    新品速递|海泰边缘安全网关护航工控数据采集
    Java实现的基于欧式距离的聚类算法的Kmeans作业
    基于HTML+CSS+JavaScript的在线图书阅读网页设计
    一种用于Linux内核驱动开发的Vim环境配置
    webGL编程指南 第五章 MultiTexture.html
    总结1008
    sentinel环境搭建以及微服务接入
    Spring框架漏洞总结
    将C语言中的命名格式改为Java中的驼峰式命名
  • 原文地址:https://blog.csdn.net/qq_63431773/article/details/133468292