动态

详情 返回 返回

【Java多線程】斷點續傳 如何使用Java多線程下載網絡文件 - 动态 详情

如何使用Java多線程下載網絡文件,並實現斷點續傳

在現代網絡應用中,多線程下載是一種常見的技術,它可以顯著提高下載速度並提供更好的用户體驗。本篇文章將介紹如何使用Java實現多線程下載,並結合項目中的代碼作為示例進行講解。

1. 多線程下載的基本原理

多線程下載的基本思想是將一個文件分成多個部分,每個部分由一個線程獨立下載,最後將這些部分合併成完整的文件。這樣可以充分利用帶寬和計算資源,提高下載速度。
使用Http請求頭的Range字段可以實現文件的分段下載,服務器會根據Range字段返回指定範圍的文件內容。例如,請求頭Range: bytes=0-1023表示獲取文件的前1024字節。

斷點續傳是多線程下載的一個重要功能,它可以在下載中斷後繼續從中斷的地方繼續下載,避免重新下載整個文件。斷點續傳的實現方法是在下載過程中保存下載進度,例如保存已下載的字節數,以便在下次下載時繼續下載。

注意⚠️: 後續示例代碼為了方便閲讀,省略細節處理,只貼出核心代碼。完整代碼請參考文章最後給的項目地址。

2. 創建下載器類

首先,我們需要創建一個下載器類,用於管理下載任務。以下是項目中的Downloader類的基本框架:

public class Downloader {
    private String url;
    private String fileName;
    private int threadCount;
    private long fileSize;
    private List<DownloadThread> threads = new ArrayList<>();

    public Downloader(String url, String fileName, int threadCount) {
        this.url = url;
        this.fileName = fileName;
        this.threadCount = threadCount;
    }

    public void download() throws Exception {
        // 省略具體實現
    }

    // 其他方法
}

3. 獲取文件大小

在開始下載之前,需要獲取文件的大小,以便確定每個線程下載的範圍。可以使用HttpURLConnection來實現:

public void getFileSize() throws IOException {
    URL url = new URL(this.url);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("HEAD");
    this.fileSize = conn.getContentLengthLong();
    conn.disconnect();
}

4. 創建下載線程

接下來,我們需要創建下載線程,每個線程負責下載文件的一部分。以下是DownloadThread類的基本實現:

public class DownloadThread extends Thread {
    private String url;
    private String fileName;
    private long start;
    private long end;

    public DownloadThread(String url, String fileName, long start, long end) {
        this.url = url;
        this.fileName = fileName;
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        try {
            URL url = new URL(this.url);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 主要是這一行代碼,設置Range頭部信息,告訴服務器要獲取文件的哪一部分
            conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
            InputStream in = conn.getInputStream();
            RandomAccessFile raf = new RandomAccessFile(this.fileName, "rw");
            raf.seek(start);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }
            raf.close();
            in.close();
            conn.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5. 啓動下載線程

在Downloader類中,我們需要根據文件大小和線程數量來啓動多個下載線程:

public void download() throws Exception {
    getFileSize();
    long partSize = fileSize / threadCount;
    for (int i = 0; i < threadCount; i++) {
        long start = i * partSize;
        long end = (i == threadCount - 1) ? fileSize - 1 : (i + 1) * partSize - 1;
        DownloadThread thread = new DownloadThread(url, fileName, start, end);
        threads.add(thread);
        thread.start();
    }

    for (DownloadThread thread : threads) {
        thread.join();
    }
}

6. 斷點續傳

在文件下載過程中,斷點續傳功能非常重要。它可以在下載中斷後(例如暫停或網絡中斷)繼續從中斷的地方繼續下載,避免重新下載整個文件。以下是項目中實現斷點續傳的關鍵代碼片段。

1. 檢查文件是否已經下載

在開始下載之前,需要檢查目標文件是否已經存在。如果文件已經存在且下載未完成,則繼續下載:

private void checkFile(File target, File tempFile) throws StoppedException {
    if (target.exists()) {
        if (!tempFile.exists()) {
            System.out.println(target.getAbsoluteFile().getPath() + "文件已經存在,是否覆蓋 ? y/n ");
            var scanner = new Scanner(System.in);
            var s = scanner.next();
            if (!Objects.equals("y", s)) {
                throw new StoppedException();
            }
            return;
        }
        System.out.println(target.getAbsoluteFile().getPath() + "文件存在,下載未完成,繼續下載");
    }
}

2. 設置文件大小和文件名

獲取文件的大小和文件名,並設置目標文件的地址:

private void setTotalAndFileName(DownFileBO downFileBO) throws IOException {
    URL url = new URL(downFileBO.getUrl());
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.connect();
    int contentLength = conn.getContentLength();
    downFileBO.setTotalSize(contentLength);
    total = contentLength;

    String fileName = "";
    String contentDisposition = conn.getHeaderField("Content-Disposition");
    if (contentDisposition != null && contentDisposition.indexOf("=") != -1) {
        fileName = contentDisposition.split("=")[1];
    } else {
        fileName = url.getPath().substring(url.getPath().lastIndexOf("/") + 1);
    }
    downFileBO.setFileName(fileName);
    var targetFolder = FileUtil.getTargetFolder(downFileBO.getTargetLocalPath());
    downFileBO.setTargetLocalPath(targetFolder);

    conn.disconnect();
}

3. 等待下載完成並獲取下載結果

使用多線程下載文件,並等待所有線程完成下載。期間可以保存臨時文件以記錄下載進度:

private boolean waitDownAndGetResult(List<Future<Boolean>> future, DownFileBO downFileBO,
    RandomAccessFile tempRandomAccessFile) throws IOException, InterruptedException {
    while (true) {
        var finish = future.stream().allMatch(Future::isDone);
        if (finish) {
            break;
        }
        saveTempFile(tempRandomAccessFile, downFileBO);
        FileUtil.printLog(total, progressSize.get());
        Thread.sleep(500 * 1);
    }
    return future.stream().allMatch(futureItem -> {
        try {
            return futureItem.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    });
}

private void saveTempFile(RandomAccessFile tempRandomAccessFile, DownFileBO downFileBO)
    throws IOException {
    var jsonString = JSON.toJSONString(downFileBO);
    var length = jsonString.length();
    var tempStr = length + TEMP_LEN_FLAG + jsonString;
    tempRandomAccessFile.seek(0);
    tempRandomAccessFile.write(tempStr.getBytes());
}

結語
通過以上步驟,我們實現了一個簡單的Java多線程下載器。你可以根據實際需求進行擴展和優化,例如添加下載進度顯示、錯誤處理等功能。

希望這篇文章對你有所幫助!如果你覺得這個文章有用,幫忙點贊、收藏,謝謝!

需要源碼的可以去這裏clone 項目地址

user avatar u_17513518 头像 sofastack 头像 u_13529088 头像 AmbitionGarden 头像 u_16769727 头像 lenglingx 头像 chuanghongdengdeqingwa_eoxet2 头像 ahahan 头像 jkdataapi 头像 yizhidanshendetielian 头像 huangxunhui 头像 nianqingyouweidenangua 头像
点赞 39 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.