Java代码实现多线程下载和断点续传

多线程下载原理

  • 客户端要下载一个文件, 首先请求服务器,服务器将这个文件传送给客户端,客户端保存到本地, 完成了一个下载的过程.
  • 多线程下载的思想是客户端开启多个线程同时下载,每个线程只负责下载文件的一部分, 当所有线程下载完成的时候,文件下载完毕. 
    • 并不是线程越多下载越快, 与网络环境有很大的关系
    • 在同等的网络环境下,多线程下载速度要高于单线程.
    • 多线程下载占用资源比单线程多,相当于用资源换取速度

java代码实现多线程下载

代码的思路:

  1. 首先要获取要下载文件的大小
  2. 在磁盘上使用RandomAccessFile 这个类在磁盘上创建一个大小一样的文件,将来将数据写入这个文件.
  3. 为每个线程分配下载任务. 内容包括线程现在文件的开始位置和结束位置.这里面有一点数学知识,代码中有备注.
  4. 启动下载线程
  5. 判断有没有保存上次下载的临时文件.
  6. 在启动线程下载的时候保存下载的位置信息
  7. 下载完毕后删除当前线程产生的临时文件
package com.yb.muchthreaddown;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * 多线程下载 和 断点续传
 * @author 杨斌.
 *
 */
public class MuchThreadDown {

//  private String path = "http://mpge.5nd.com/2016/2016-11-15/74847/1.mp3";    //下载路径
    private String path = "http://117.169.69.238/mp3.9ku.com/m4a/186947.m4a";
    private String targetFilePath="/";  //下载文件存放目录
    private int threadCount = 3;    //线程数量

    /**
     * 构造方法 
     * @param path 要下载文件的网络路径
     * @param targetFilePath 保存下载文件的目录
     * @param threadCount 开启的线程数量,默认为 3
     */
    public MuchThreadDown(String path, String targetFilePath, int threadCount) {
        this.path = path;
        this.targetFilePath = targetFilePath;
        this.threadCount = threadCount;
    }

    /**
     * 下载文件
     */
    public void download() throws Exception{
        //连接资源
        URL url = new URL(path);

        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(10000);

        int code = connection.getResponseCode();
        if(code == 200){
            //获取资源大小
            int connectionLength = connection.getContentLength();
            System.out.println(connectionLength);
            //在本地创建一个与资源同样大小的文件来占位
            RandomAccessFile randomAccessFile = new RandomAccessFile(new File(targetFilePath,getFileName(url)), "rw");
            randomAccessFile.setLength(connectionLength);
            /*
             * 将下载任务分配给每个线程
             */
            int blockSize = connectionLength/threadCount;//计算每个线程理论上下载的数量.
            for(int threadId = 0; threadId < threadCount; threadId++){//为每个线程分配任务
                int startIndex = threadId * blockSize; //线程开始下载的位置
                int endIndex = (threadId+1) * blockSize -1; //线程结束下载的位置
                if(threadId == (threadCount - 1)){  //如果是最后一个线程,将剩下的文件全部交给这个线程完成
                    endIndex = connectionLength - 1;
                }

                new DownloadThread(threadId, startIndex, endIndex).start();//开启线程下载

            }
//          randomAccessFile.close();
        }

    }

    //下载的线程
    private class DownloadThread extends Thread{

        private int threadId;
        private int startIndex;
        private int endIndex;

        public DownloadThread(int threadId, int startIndex, int endIndex) {
            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        @Override
        public void run() {
            System.out.println("线程"+ threadId + "开始下载");
            try {
                //分段请求网络连接,分段将文件保存到本地.
                URL url = new URL(path);

                //加载下载位置的文件
                File downThreadFile = new File(targetFilePath,"downThread_" + threadId+".dt");
                RandomAccessFile downThreadStream = null;
                if(downThreadFile.exists()){//如果文件存在
                    downThreadStream = new RandomAccessFile(downThreadFile,"rwd");
                    String startIndex_str = downThreadStream.readLine();
                    if(null==startIndex_str||"".equals(startIndex_str)){  //网友 imonHu 2017/5/22  
                        this.startIndex=startIndex;  
                    }else{  
                        this.startIndex = Integer.parseInt(startIndex_str)-1;//设置下载起点  
                    }
                }else{
                    downThreadStream = new RandomAccessFile(downThreadFile,"rwd");
                }

                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(10000);

                //设置分段下载的头信息。  Range:做分段数据请求用的。格式: Range bytes=0-1024  或者 bytes:0-1024
                connection.setRequestProperty("Range", "bytes="+ startIndex + "-" + endIndex);

                System.out.println("线程_"+threadId + "的下载起点是 " + startIndex + "  下载终点是: " + endIndex);

                if(connection.getResponseCode() == 206){//200:请求全部资源成功, 206代表部分资源请求成功
                    InputStream inputStream = connection.getInputStream();//获取流
                    RandomAccessFile randomAccessFile = new RandomAccessFile(
                            new File(targetFilePath,getFileName(url)), "rw");//获取前面已创建的文件.
                    randomAccessFile.seek(startIndex);//文件写入的开始位置.


                    /*
                     * 将网络流中的文件写入本地
                     */
                    byte[] buffer = new byte[1024];
                    int length = -1;
                    int total = 0;//记录本次下载文件的大小
                    while((length = inputStream.read(buffer)) > 0){
                        randomAccessFile.write(buffer, 0, length);
                        total += length;
                        /*
                         * 将当前现在到的位置保存到文件中
                         */
                        downThreadStream.seek(0);
                        downThreadStream.write((startIndex + total + "").getBytes("UTF-8"));
                    }

                    downThreadStream.close();
                    inputStream.close();
                    randomAccessFile.close();                   
                    cleanTemp(downThreadFile);//删除临时文件
                    System.out.println("线程"+ threadId + "下载完毕");
                }else{
                    System.out.println("响应码是" +connection.getResponseCode() + ". 服务器不支持多线程下载");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    //删除线程产生的临时文件
    private synchronized void cleanTemp(File file){
        file.delete();
    }

    //获取下载文件的名称
    private String getFileName(URL url){
        String filename = url.getFile();
        return filename.substring(filename.lastIndexOf("/")+1);
    }

    public static void main(String[] args) {
        try {
            new MuchThreadDown(null, null, 3).download();

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

 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JAVA多线程实现断点续传的思路如下: 1. 将大文件均分成几块,每个线程负责处理一块数据的读取和写入。 2. 每次写入数据时,更新记录的日志文件,记录已经传输的字节数或块数。 3. 当断网或暂停后重新开始传输时,根据日志文件的信息,可以接着读取和写入数据,而不需要从头开始传输。 以下是一个JAVA多线程实现断点续传的示例代码: ```java import java.io.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FileTransfer { private static final int THREAD_COUNT = 4; // 线程数量 private static final String LOG_FILE = "transfer.log"; // 日志文件名 private static final String SOURCE_FILE = "source.txt"; // 源文件名 private static final String TARGET_FILE = "target.txt"; // 目标文件名 public static void main(String[] args) { // 读取日志文件,获取已传输的字节数 long transferredBytes = readLogFile(); // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); // 计算每个线程需要处理的字节数 long blockSize = new File(SOURCE_FILE).length() / THREAD_COUNT; // 创建并执行线程 for (int i = 0; i < THREAD_COUNT; i++) { long start = i * blockSize + transferredBytes; long end = (i + 1) * blockSize - 1; if (i == THREAD_COUNT - 1) { end = new File(SOURCE_FILE).length() - 1; } executorService.execute(new TransferThread(i, start, end)); } // 关闭线程池 executorService.shutdown(); } private static long readLogFile() { long transferredBytes = 0; try { File logFile = new File(LOG_FILE); if (logFile.exists()) { BufferedReader reader = new BufferedReader(new FileReader(logFile)); String line; while ((line = reader.readLine()) != null) { transferredBytes += Long.parseLong(line); } reader.close(); } } catch (IOException e) { e.printStackTrace(); } return transferredBytes; } private static void writeLogFile(long transferredBytes) { try { BufferedWriter writer = new BufferedWriter(new FileWriter(LOG_FILE, true)); writer.write(String.valueOf(transferredBytes)); writer.newLine(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } private static class TransferThread implements Runnable { private int threadId; private long start; private long end; public TransferThread(int threadId, long start, long end) { this.threadId = threadId; this.start = start; this.end = end; } @Override public void run() { try { RandomAccessFile sourceFile = new RandomAccessFile(SOURCE_FILE, "r"); RandomAccessFile targetFile = new RandomAccessFile(TARGET_FILE, "rw"); sourceFile.seek(start); targetFile.seek(start); byte[] buffer = new byte[1024]; int bytesRead; long transferredBytes = 0; while ((bytesRead = sourceFile.read(buffer)) != -1 && sourceFile.getFilePointer() <= end) { targetFile.write(buffer, 0, bytesRead); transferredBytes += bytesRead; } writeLogFile(transferredBytes); sourceFile.close(); targetFile.close(); } catch (IOException e) { e.printStackTrace(); } } } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值