java如何實現(xiàn)大文件加速下載

針對大文件下載,Java可以使用多線程實現(xiàn)加速下載倒淫,并且結合斷點續(xù)傳功能翔曲,可以提高下載的穩(wěn)定性和效率。具體實現(xiàn)步驟如下:

獲取文件總長度和已下載長度漫玄,如果已下載長度等于文件總長度茄蚯,則說明文件已下載完成,不需要進行下載睦优。

如果已下載長度小于文件總長度渗常,則需要進行多線程下載。根據(jù)文件總長度和線程數(shù)計算出每個線程需要下載的文件塊大小汗盘,然后每個線程分別下載對應的文件塊皱碘。

在HTTP請求中添加Range頭信息,指定下載的起始位置和結束位置隐孽,例如Range:bytes=0-499表示下載文件的前500個字節(jié)癌椿。每個線程下載時需要指定不同的Range范圍。

下載過程中菱阵,記錄已下載的字節(jié)數(shù)和文件塊的下載狀態(tài)踢俄,并在下載中斷時保存下載狀態(tài),以便下次繼續(xù)下載送粱。

下載完成后褪贵,將所有文件塊合并成完整的文件。

下面是一個簡單的Java實現(xiàn)多線程下載的示例代碼:


import java.io.*;
import java.net.*;
import java.util.concurrent.*;

public class DownloadManager {
    private static final int THREAD_POOL_SIZE = 5;
    private final URL url;
    private final String savePath;
    private final int bufferSize;
    private final int threadCount;
    private final ExecutorService executorService;

    public DownloadManager(String url, String savePath, int bufferSize, int threadCount) throws MalformedURLException {
        this.url = new URL(url);
        this.savePath = savePath;
        this.bufferSize = bufferSize;
        this.threadCount = threadCount;
        this.executorService = Executors.newFixedThreadPool(threadCount);
    }

    public void download() throws IOException, InterruptedException {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        int totalSize = conn.getContentLength();
        int blockSize = totalSize / threadCount;
        int downloadedSize = 0;
        boolean isCompleted = true;

        RandomAccessFile out = new RandomAccessFile(savePath, "rw");
        out.setLength(totalSize);
        out.close();

        for (int i = 0; i < threadCount; i++) {
            int start = i * blockSize;
            int end = (i == threadCount - 1) ? totalSize - 1 : (i + 1) * blockSize - 1;
            DownloadTask task = new DownloadTask(url, savePath, start, end, bufferSize);
            executorService.execute(task);
        }

        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

        for (int i = 0; i < threadCount; i++) {
            int start = i * blockSize;
            int end = (i == threadCount - 1) ? totalSize - 1 : (i + 1) * blockSize - 1;
            File file = new File(savePath + "." + i);
            if (!file.exists() || file.length() != end - start + 1) {
                isCompleted = false;
                break;
            }
        }

        if (isCompleted) {
            FileOutputStream outStream = new FileOutputStream(savePath);
            for (int i = 0; i < threadCount; i++) {
                FileInputStream inStream = new FileInputStream(savePath + "." + i);
                byte[] buffer = new byte[bufferSize];
                int len;
                while ((len = inStream.read(buffer)) != -1) {
                    outStream.write(buffer, 0, len);
                }
                inStream.close();
                File file = new File(savePath + "." + i);
                file.delete();
            }
            outStream.close();
        }
    }

    private class DownloadTask implements Runnable {
        private final URL url;
        private final String savePath;
        private final int start;
        private final int end;
        private final int bufferSize;

        public DownloadTask(URL url, String savePath, int start, int end, int bufferSize) {
            this.url = url;
            this.savePath = savePath;
            this.start = start;
            this.end = end;
            this.bufferSize = bufferSize;
        }

        @Override
        public void run() {
            try {
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setRequestProperty("Range", "bytes=" + start + "-" + end);

                InputStream in = conn.getInputStream();
                RandomAccessFile out = new RandomAccessFile(savePath + "." + Thread.currentThread().getId(), "rw");
                out.seek(start);

                byte[] buffer = new byte[bufferSize];
                int len;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }

                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在上面的代碼中,我們使用了ExecutorService線程池來管理多個下載線程脆丁。每個線程下載對應的文件塊世舰,并將下載狀態(tài)保存在單獨的文件中。在所有線程下載完成后槽卫,我們將所有文件塊合并成完整的文件跟压。

在使用時,可以創(chuàng)建DownloadManager對象歼培,并調用download()方法啟動下載震蒋。例如:


DownloadManager manager = new DownloadManager("http://example.com/largefile.zip", "C:/Downloads/largefile.zip", 1024, 5);
manager.download();

其中,第一個參數(shù)是要下載的文件URL躲庄,第二個參數(shù)是保存路徑查剖,第三個參數(shù)是緩沖區(qū)大小,第四個參數(shù)是線程數(shù)噪窘。



其他方法客供參考:


public void downLoadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 設置編碼格式
        response.setCharacterEncoding(UTF_8);
        //獲取文件路徑
        String fileName = request.getParameter("fileName");
        String path = OUTPUT_PATH;
        //參數(shù)校驗
        log.info(fileName, path);
        //完整路徑(路徑拼接待優(yōu)化-前端傳輸優(yōu)化-后端從新格式化  )
        String pathAll = path + File.separator + fileName;
        log.info("pathAll{}", pathAll);
        Optional<String> pathFlag = Optional.ofNullable(pathAll);
        File file = null;
        if (pathFlag.isPresent()) {
            //根據(jù)文件名笋庄,讀取file流
            file = new File(pathAll);
            log.info("文件路徑是{}", pathAll);
            if (!file.exists()) {
                log.warn("文件不存在");
                return;
            }
        } else {
            //請輸入文件名
            log.warn("請輸入文件名!");
            return;
        }
        try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
            //分片下載
            long fSize = file.length();//獲取長度
            response.setContentType("application/x-download");
            String file_Name = URLEncoder.encode(file.getName(), "UTF-8");
            //獲取下載文件名
            String newFileName = URLEncoder.encode(request.getParameter("sourceDept"), "UTF-8") + ".zip";
            response.addHeader("Content-Disposition", "attachment;filename=" + (StringUtils.isNotEmpty(newFileName) ? newFileName : fileName));
            //根據(jù)前端傳來的Range  判斷支不支持分片下載
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("fName", file_Name);
            //定義斷點
            long pos = 0, last = fSize - 1, sum = 0;
            //判斷前端需不需要分片下載
            if (null != request.getHeader("Range")) {
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                String numRange = request.getHeader("Range").replaceAll("bytes=", "");
                String[] strRange = numRange.split("-");
                if (strRange.length == 2) {
                    pos = Long.parseLong(strRange[0].trim());
                    last = Long.parseLong(strRange[1].trim());
                    //若結束字節(jié)超出文件大小 取文件大小
                    if (last > fSize - 1) {
                        last = fSize - 1;
                    }
                } else {
                    //若只給一個長度  開始位置一直到結束
                    pos = Long.parseLong(numRange.replaceAll("-", "").trim());
                }
            }
            long rangeLength = last - pos + 1;
            String contentRange = new StringBuffer("bytes").append(pos).append("-").append(last).append("/").append(fSize).toString();
            response.setHeader("Content-Range", contentRange);
            response.setHeader("Content-Length", String.valueOf(rangeLength));
            response.setHeader("Connection", "keep-alive");
            response.setHeader("Keep-Alive", "timeout=300, max=100");

            is.skip(pos);//跳過已讀的文件(重點倔监,跳過之前已經(jīng)讀過的文件)
            byte[] buffer = new byte[8192];  // 8KB buffer
            int length = 0;
            try (OutputStream os = new BufferedOutputStream(response.getOutputStream())) {
                while (sum < rangeLength) {
                    length = is.read(buffer, 0, (rangeLength - sum) <= buffer.length ? (int) (rangeLength - sum) : buffer.length);
                    sum = sum + length;
                    os.write(buffer, 0, length);
                }
            } // 這里的OutputStream將在這個塊結束時關閉
            log.info("下載完成");
        } catch (IOException ex) {
            // handle exception here
            log.error("下載失敗", ex);
        }
    }

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末直砂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子浩习,更是在濱河造成了極大的恐慌静暂,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谱秽,死亡現(xiàn)場離奇詭異洽蛀,居然都是意外死亡,警方通過查閱死者的電腦和手機弯院,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門辱士,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人听绳,你說我怎么就攤上這事颂碘。” “怎么了椅挣?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵头岔,是天一觀的道長。 經(jīng)常有香客問我鼠证,道長峡竣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任量九,我火速辦了婚禮适掰,結果婚禮上颂碧,老公的妹妹穿的比我還像新娘。我一直安慰自己类浪,他們只是感情好载城,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著费就,像睡著了一般诉瓦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上力细,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天睬澡,我揣著相機與錄音,去河邊找鬼眠蚂。 笑死煞聪,一個胖子當著我的面吹牛,可吹牛的內容都是我干的逝慧。 我是一名探鬼主播米绕,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼馋艺!你這毒婦竟也來了?” 一聲冷哼從身側響起迈套,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤捐祠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后桑李,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體踱蛀,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年贵白,在試婚紗的時候發(fā)現(xiàn)自己被綠了率拒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡禁荒,死狀恐怖猬膨,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情呛伴,我是刑警寧澤勃痴,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站热康,受9級特大地震影響沛申,放射性物質發(fā)生泄漏。R本人自食惡果不足惜姐军,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一铁材、第九天 我趴在偏房一處隱蔽的房頂上張望尖淘。 院中可真熱鬧,春花似錦著觉、人聲如沸村生。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梆造。三九已至,卻和暖如春葬毫,著一層夾襖步出監(jiān)牢的瞬間镇辉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工贴捡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忽肛,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓烂斋,卻偏偏與公主長得像屹逛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子汛骂,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內容