多线程断点下载原理

建议读者在看之前先独立思考一番,以下为个人实现方案

思路图

编码实现

JavaSE:

public class BreakpointDownloader {

    private static final int THREAD_COUNT = 3;

    private static final String LOCAL_SAVE_FILENAME = "setup.exe";

    private static volatile int runningThreadCount = THREAD_COUNT;

    public static void main(String[] args) {
        String remoteFilePath = "http://www.voidtools.com/Everything-1.4.1.895.x86-Setup.exe";
        try {
            // 1. 获取当前服务器上文件的大小
            URL url = new URL(remoteFilePath);
            HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
            httpConn.setConnectTimeout(10000);
            httpConn.setRequestMethod("GET");
            if (httpConn.getResponseCode() != 200) {
                System.out.println("服务器返回的状态码是 :" + httpConn.getResponseCode());
                return;
            }
            int remoteFileLength = httpConn.getContentLength();
            System.out.println("下载的文件大小是:" + remoteFileLength);
            // 2. 创建一个和服务器大小一致的本地文件 用来存放下载的文件
            // 随机访问文件写入
            // 其中第二个参数 中的mode rwd 和 rws 区别是:rws 要求对文件的内容或元数据的每个更新都同步写入到底层存储设备 ,元数据是指 文件的一些摘要、作者、日期等信息,我们暂用不到
            // bt毁硬盘,因为会实时更新到存储设备,如果不指定rwd 或者rws 默认会先写入缓存区 这样就不保证断电等一些硬件设备异常数据不能及时保存到硬盘中
            RandomAccessFile storeFileRaf = new RandomAccessFile(LOCAL_SAVE_FILENAME, "rwd");
            storeFileRaf.setLength(remoteFileLength);
            storeFileRaf.close();

            // 3. 确定每一个线程下载的block
            int averageBlockLen = remoteFileLength / THREAD_COUNT;
            // 4. 确定每一个线程下载的区间
            for (int i = 1; i <= THREAD_COUNT; i++) {
                int startIndex = (i - 1) * averageBlockLen;
                int endIndex = i * averageBlockLen - 1;
                if (i == THREAD_COUNT) {
                    // 最后一个线程特殊一点,剩余的全部交付给他执行下载
                    endIndex = remoteFileLength;
                }
                System.out.println("线程" + i + "首次分配的 指针分别是:" + startIndex + "-------->" + endIndex);
                // 5. 开启线程 下载文件
                new DownloadFileThread(startIndex, endIndex, remoteFilePath, i + "").start();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private static class DownloadFileThread extends Thread {

        private int startIndex;
        private int endIndex;
        private String path;
        private String threadName;

        public DownloadFileThread(int startIndex, int endIndex, String path, String threadName) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.path = path;
            this.threadName = threadName;
        }

        @Override
        public void run() {
            URL url = null;
            try {
                url = new URL(path);
                HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
                httpConn.setConnectTimeout(10000);
                httpConn.setRequestMethod("GET");

                File recordTmpFile = new File(threadName + ".txt");
                if (recordTmpFile.exists() && recordTmpFile.length() > 0) {
                    // 说明上次下载到某一个位置就停止了,本次下载从下次的位置开始进行
                    FileInputStream fis = new FileInputStream(recordTmpFile);
                    byte[] buffer = new byte[1024];
                    int read = fis.read(buffer); // 读取到buffer了
                    String res = new String(buffer, 0, read);
                    startIndex = Integer.parseInt(res);
                    fis.close();
                }

                System.out.println("线程" + threadName + "真实下载位置: statIndex:" + startIndex + "------->" + "endIndex = " + endIndex);

                // 确定当前这个线程下载指定文件block ,非常重要
                httpConn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
                System.out.println("指定线程获取server return code = " + httpConn.getResponseCode());
                // 从服务器上获取全部资源返回 200, 部分资源返回206
                if (httpConn.getResponseCode() / 200 != 1) {
                    return;
                }
                InputStream inputStream = httpConn.getInputStream();// 如果不指定Range , 这个获取的输入流肯定是整个文件的输入流
                // 输入流写入到指定位置
                RandomAccessFile targetSaveRaf = new RandomAccessFile(LOCAL_SAVE_FILENAME, "rwd");
                targetSaveRaf.seek(startIndex);
                byte[] buffer = new byte[1024];
                int readLen;
                int totalLen = 0;
                File recordFile = new File(threadName + ".txt");
                if (!recordFile.exists()) {
                    recordFile.createNewFile();
                }
                RandomAccessFile recordProgressRaf = null;
                while ((readLen = inputStream.read(buffer)) != -1) {
                    targetSaveRaf.write(buffer, 0, readLen);
                    totalLen += readLen;
                    // 每一次实例化一个 raf, 每一次都覆盖上一次的值,保证实时更新到本地文件中
                    recordProgressRaf = new RandomAccessFile(recordFile, "rwd");
                    recordProgressRaf.write(String.valueOf(startIndex + totalLen).getBytes());
                    recordProgressRaf.close();
                }
                inputStream.close();
                targetSaveRaf.close();
                System.out.println("线程" + threadName + "下载完毕!!!");
                runningThreadCount--;
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("线程" + threadName + "下载失败: " + e.getMessage());
            } finally {
                if (runningThreadCount == 0) {
                    // 所有线程都下载完毕且成功了 删除进度记忆文件
                    for (int i = 1; i <= THREAD_COUNT; i++) {
                        File file = new File(i + ".txt");
                        boolean delete = file.delete();
                        if (delete) {
                            System.out.println("记录文件 " + i + ".txt 删除成功");
                        } else {
                            System.out.println("记录文件 " + i + ".txt 删除失败");
                        }
                    }
                }
            }
        }
    }

}
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值