首页 > 其他分享 >多线程文件复制,断点继续复制

多线程文件复制,断点继续复制

时间:2023-10-06 11:35:43浏览次数:29  
标签:文件 System length 复制 线程 new import 多线程 断点

1、思路

多线程首先要对文件进行分割,这里使用每个子线程的任务大小固定的方法,根据文件大小分配不同数量的子线程。

要实现断点下载,必须要记录已经复制的位置,每次继续时从上次下载的结束位置继续复制,这里将已经复制的文件位置以long类型写入一个日志文件,继续下载时每个线程从对应的日志文件位置继续复制。

2、实现

子线程类:

实现runnable接口,重写run方法。

使用stopwatch计算每个线程的下载时间。

package com.cn.threadtest;

import org.apache.commons.lang3.time.StopWatch;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * IO流线程
 *
 * @author Tobieance
 * @date 2023-08-29 20:17
 */
public class DownloadThread implements Runnable {
    RandomAccessFile inputStream;
    RandomAccessFile outputStream;
    RandomAccessFile offsetStream;

    long start;
    long end;
    int length;
    File toFile = null;
    File fromFile = null;
    File logFile = null;
    boolean isStack = false;
    int index;
    byte[] bytes = new byte[1024];

    public DownloadThread(File fromFile, long start, long end, File toFile, int index, File logFile) {
        this.start = start;
        this.end = end;
        this.fromFile = fromFile;
        this.toFile = toFile;
        this.index = index;
        this.logFile = logFile;
    }

    @Override
    public void run() {
        try {
            inputStream = new RandomAccessFile(fromFile, "rw");
            outputStream = new RandomAccessFile(toFile, "rw");
            offsetStream = new RandomAccessFile(logFile, "rw");
            //从记录文件中偏移量 index * 8 的位置开始,记录的是该线程的偏移量
            offsetStream.seek(index * 8L);
            //获取该线程到达的偏移量
            long offset = offsetStream.readLong();
            //偏移量为0,即为第一次复制,初始化偏移量为默认偏移量
            if (offset == 0) {
                offset = start;
            } else {
                System.out.println("线程" + (index + 1) + "继续下载\n");
            }
            // 要传输的数据长度
            long sum = end - offset + 1;
            System.out.println("线程" + (index + 1) +
                    "\t开始: " + (offset / (1024 * 1024)) +
                    "MB\t结束:" + (end / (1024 * 1024)) +
                    "MB\t写入大小:" + (end - offset + 1) / (1024 * 1024) + "MB\n");
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            //读写流偏移
            inputStream.seek(offset);
            outputStream.seek(offset);
            //偏移量
            long pointer;
            while (sum > 0) {
                if (sum >= 1024) {
                    inputStream.read(bytes);
                    length = 1024;
                    sum -= 1024;
                } else {
                    inputStream.read(bytes, 0, (int) sum);
                    length = (int) sum;
                    sum = 0;
                }
                //写入文件
                outputStream.write(bytes, 0, length);
                //获取复制文件输出流的偏移量
                pointer = outputStream.getFilePointer();
                //把记录文件流的偏移量设置回起点
                offsetStream.seek(index * 8L);
                //写入新的偏移量
                offsetStream.writeLong(pointer);
                //判断偏移量是否已经超过应该到达的偏移量
                if (pointer >= end) {
                    break;
                }
            }
            outputStream.close();
            offsetStream.close();
            stopWatch.stop();
            System.err.println("线程" + (index + 1) + "\t任务结束\t\t耗时:" + stopWatch.getTime() + "ms\n");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
        } finally {
            // 关流
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
                if (offsetStream != null) {
                    offsetStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

**具体实现类:

文件过大时会生成过多子线程,故使用线程池限制正常工作线程和最大工作线程。

将每个线程需要完成的任务信息,如源文件,目标文件,起始文件位置,结束文件位置,日志文件

存入节点中,使用节点的列表来创建多个线程。**

package com.cn.threadtest;

import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * IO流测试
 *
 * @author 87036
 * @date 2023/08/29
 */
public class Download {
    /**
     * 单个分片大小 500MB
     */
    public static final int PART_SIZE = 1024 * 1024 * 500;
    /**
     * 目标文件路径
     */
    private static String toPathName = null;
    /**
     * 长度
     */
    private static long length;
    /**
     * 源文件
     */
    private static File fromFile = null;
    /**
     * 目标文件
     */
    private static File toFile = null;
    /**
     * 日志文件
     */
    private static File logFile = null;
    /**
     * 节点列表
     */
    private static ArrayList<Node> arrayList = null;
    /**
     * 实例
     */
    private static Download INSTANCE = null;

    /**
     * 访问内部实例
     *
     * @return {@link Download}
     */
    public static Download getDownloadInstance(String fileName) {
        if(INSTANCE==null){
            INSTANCE=new Download(fileName);
            return INSTANCE;
        }
        else {
            return INSTANCE;
        }
    }

    /**
     * 私有构造方法
     *
     * @param fileName 文件名称
     */
    private Download(String fileName) {
        fromFile = new File(fileName);
        //获取文件长度
        length = fromFile.length();
        System.out.println("下载大文件\t文件大小:" + (length/(1024*1024))+"MB");
        //节点列表
        arrayList = getNodeList(length);
        if (fromFile.exists()) {
            toPathName = fromFile.getParent() + "copy\\" + fromFile.getName();
            System.out.println("目标文件目录:\t" + toPathName);
        }
        //实例化目标文件
        toFile = new File(toPathName);
        //日志文件
        logFile = new File("F:\\log.txt");
        System.out.println("初始化除日志文件外各属性\n");
    }

    public void downloadSingleThread(){
        //ThreadPoolExecutor创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                //正常工作的线程个数
                5,
                //最大可以工作的线程个数
                8,
                //线程释放时间
                60,
                //线程释放时间单位
                TimeUnit.SECONDS,
                //超过3个线程等待则出发最大线程
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());

            threadPool.execute(
                    new DownloadThread(fromFile,
                            0,
                            length-1,
                            toFile,
                            0,
                            logFile));
        threadPool.shutdown();
    }
    public void downloadMultipleThread() {
        //ThreadPoolExecutor创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                //正常工作的线程个数
                5,
                //最大可以工作的线程个数
                8,
                //线程释放时间
                60,
                //线程释放时间单位
                TimeUnit.SECONDS,
                //超过3个线程等待则出发最大线程
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i < arrayList.size(); i++) {
            threadPool.execute(
                    new DownloadThread(fromFile,
                            arrayList.get(i).start,
                            arrayList.get(i).end,
                            toFile,
                            i,
                            logFile));
        }
        threadPool.shutdown();
    }

    public void init(){
        //初始化日志文件
        System.out.println("初始化日志文件\n");
        try {
            RandomAccessFile raf = new RandomAccessFile(logFile, "rw");
            raf.setLength(0);
            for (int i = 0; i < arrayList.size(); i++) {
                raf.seek(i * 8L);
                raf.writeLong(0);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取每个线程的开始和结束节点列表
     *
     * @param length 长度
     * @return {@link ArrayList}
     */
    private static ArrayList getNodeList(long length) {
        ArrayList<Node> arrayList = new ArrayList<>();
        long split = length / PART_SIZE;
        long start = 0;
        if(split==0){
            System.err.println("小于最小分块,单线程处理");
            arrayList.add(new Node(start, length - 1));
            return arrayList;
        }
        for (int i = 0; i <= split; i++) {
            if (i != split ) {
                arrayList.add(new Node(start, start + PART_SIZE - 1));
                start += PART_SIZE;
            }else {
                arrayList.add(new Node(start, length - 1));
            }
        }
        return arrayList;
    }

    /**
     * 节点对象,包含开始和结束
     *
     * @author Tobieance
     * @date 2023/08/29
     */
    private static class Node {
        long start;
        long end;

        public Node(long start, long end) {
            this.start = start;
            this.end = end;
        }
    }
}

测试类:

package com.cn.threadtest;


import lombok.val;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Scanner;

/**
 * @author Tobieance
 * @date 2023-08-29 19:09
 */
public class DownloadTest {
    public static void  main(String[] args) {
        // 文件路径
        String fileName="D:\\TestBig.zip";
        val list=new LinkedList<DownloadThread>();
        val arrlist=new ArrayList<DownloadThread>();
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入n\nn等于0多线程下载\t等于1单线程下载");
        int n=scanner.nextInt();
        //n等于0多线程下载  等于1单线程下载
        if(n==0) {
            System.out.println("多线程");
            System.out.println("请输入m\nm等于0第一次下载\t等于1继续下载");
            int m= scanner.nextInt();
            if(m==0){
                //初始化日志文件
                Download.getDownloadInstance(fileName).init();
                Download.getDownloadInstance(fileName).downloadMultipleThread();
            }else if(m==1){
                Download.getDownloadInstance(fileName).downloadMultipleThread();
            }
        }else if(n==1){
            System.out.println("单线程");
            System.out.println("请输入m\nm等于0第一次下载\t等于1继续下载");
            int m= scanner.nextInt();
            if(m==0){
                //初始化日志文件
                Download.getDownloadInstance(fileName).init();
                Download.getDownloadInstance(fileName).downloadSingleThread();
            }else if(m==1){
                Download.getDownloadInstance(fileName).downloadSingleThread();
            }
        }
    }
}

3、运行截图:

image
image

标签:文件,System,length,复制,线程,new,import,多线程,断点
From: https://www.cnblogs.com/tobieance/p/17744361.html

相关文章

  • Ubuntu 中 vim 无法把内容复制到外部程序的解决方案
    检查vim是否把内容复制到剪贴板中这一功能$vim--version|grepclipboard情况大概有这么2种:情况1+clipboard:支持系统剪贴板,只需要在visual可视模式下选中要复制的内容之后按y键即可复制到剪贴板,然后到外部程序中粘贴即可情况2-clipboard:不支持系统剪贴板如何解决......
  • Debian12 vim中鼠标不能复制解决办法
    前奏rambo@debian:~$cat/etc/issueDebianGNU/Linux12\n\l解决#没有该文件则新建rambo@debian:~$sudovim/etc/vim/vimrcletskip_defaults_vim=1ifhas('mouse')setmouse-=aendif#保存并退出,一切都将恢复如果不想更改全局配置,应将这些更改放......
  • Lua断点调试 - 类似gdb的调试体验
    平时在做一个C++/Lua的项目,C++代码可以用gdb调试,但是Lua代码的调试却一直是个困扰人的难题。根据网上搜索的结果,无外乎都是用vscode插件调试,或者用socket之类的设施进行远程调试,个人都觉得太麻烦了,最好有个类似gdb那种直接在命令行中进行调试。不过经过我在网上的搜索,终于还是找......
  • 学习多进程多线程
    两个单词:Process进程、Thread线程线程的三种创建方式:1、继承Thread类   写一个子类去继承然后重写run()方法2、实现Runnable接口3、实现Callable接口  这个一般工作三到五年后才经常用到 1、创建一个线程对象,然后调用start()方法可以交替进行 要是用run()方......
  • redis主从复制基础上搭建哨兵模式
    假如156和157是不同的两台服务器两台redis主从复制基础上搭建哨兵模式如下156redis.confmasterauth123456bind0.0.0.0requirement123456daemonizeyessentinel.confsentinelauth-passmymaster123456sentinelmonitormymaster10.190.107.15663792157......
  • java多线程中的 锁(暂时记录)
    P150-lock----锁----那一节publicclassThreadExtendextendsThread{  staticintticket=0;  staticLocklock=newReentrantLock();  publicvoidrun(){    while(true){      lock.lock();      if(ticket......
  • java---多线程[(重点)上]
    15.1概念以前写的程序都是单线程,main方法程序称为主线程,主线程的结束所有的子线程都会跟着结束。多线程就代表着一个程序可以去做多件事情。线程:一个程序去做多件事情,每件事情由一个线程去完成。进程:一个进程由多个线程组成,一个进程至少有一个线程。一个进程就是一个应用程序多线......
  • java断点下载文件(整合多线程)
    技术介绍:断点下载指的是在文件下载过程中,如果下载中断或失败,比如下载到一半的时候停电了、断网了、不小心退出下载界面了等等,下一次进入下载页面可以从中断或失败的位置继续下载,而无需重新开始下载整个文件。 (注意:本文通过本地文件的拷贝来模拟文件传输的断点过程) 核心想法......
  • java本地文件多线程拷贝
    简单介绍:本地文件多线程拷贝是指通过多个线程同时进行文件复制操作。传统的文件复制操作往往是串行进行的,当需要复制单个大文件时,复制速度往往会比较慢。而采用多线程进行文件拷贝可以提高效率。通过同时创建多个线程,每个线程负责复制不同的文件或者不同的文件片段,可以充分利用计......
  • redis7源码分析:redis 多线程模型解析
    多线程模式中,在main函数中会执行InitServerLastvoidInitServerLast(){bioInit();//关键一步,这里启动了多条线程,用于执行命令,redis起名为IO线程initThreadedIO();set_jemalloc_bg_thread(server.jemalloc_bg_thread);server.initial_memory_usage=......