JAVA實現大文件多線程下載,提速30倍!想學?我教你啊

2021-02-13 Java技術迷
前言

兄弟們看到這個標題可能會覺得是個標題黨,為了解決疑慮,我們先來看下最終的測試結果:

測試雲盤下載的文件 46M,自己本地最大下載速度 2M

1. 單線程下載,總耗時: 603s2. 多線程下載,50個線程,總耗時:13s

測試結果,「提速46倍」,我還是太謙虛了,只說提速30倍,此處我們覺得應該有掌聲(我聽不到,還是點讚實在)

源碼地址:https://gitee.com/silently9527/fast-download

喜歡請記得star哦

❞HTTP協議Range請求頭

Range主要是針對只需要獲取部分資源的範圍請求,通過指定Range即可告知伺服器資源的指定範圍。格式: Range: bytes=start-end

比如:獲取字節範圍 5001-10000

Range: bytes=5001-10000

也可以指定開始位置不指定結束位置,表示獲取開始位置之後的全部數據

Range: bytes=5001-

伺服器接收到帶有Range的請求,會在處理請求之後返回狀態碼為206 Partial Content的響應。

基於Range的特性,我們就可以實現文件的多線程下載,文件的斷點續傳

準備工作

本文我們使用的SpringMVC中的RestTemplate;由於雲盤的連結是Https,所以我們需要設置RestTemplate繞過證書驗證

pom.xml編寫RestTemplate的構造器,以及繞過https的證書驗證RestTemplateBuilder在下載的過程中,我們需要知道當前下載的速度是多少,所以需要定義一個顯示下載速度的接口DisplayDownloadSpeed

因為計算下載速度,我們需要知道每秒傳輸的字節數是多少,為了監控傳輸數據的過程,我們需要了解SpringMVC中的接口ResponseExtractor

ResponseExtractor

該接口只有一個方法,當客戶端和伺服器端連接建立之後,會調用這個方法,我們可以在這個方法中監控下載的速度。

DisplayDownloadSpeed接口的抽象實現 AbstractDisplayDownloadSpeedResponseExtractorAbstractDisplayDownloadSpeedResponseExtractor簡單的文件下載器

這裡使用的是restTemplate調用execute, 先文件獲取到字節數組, 再將字節數組直接寫到目標文件。

這裡我們需要注意的點是: 這種方式會將文件的字節數組全部放入內存中, 及其消耗資源;我們來看看如何實現。

創建ByteArrayResponseExtractor類繼承AbstractDisplayDownloadSpeedResponseExtractorByteArrayResponseExtractor調用restTemplate.execute執行下載,保存字節數據到文件中

執行一段時間之後,我們可以看到內存已經使用了800M左右,所以這種方式只能使用於小文件的下載,如果我們下載幾G的大文件,內存肯定是不夠用的。至於下載時間,因為文件太大也沒有等下載完成就結束了程序。

單線程大文件下載

上面的方式只能下載小的文件,那大文件的下載我們該用什麼方式呢?我們可以把流輸出到文件而不是內存中。接下來我們來實現我們大文件的下載。

創建FileResponseExtractor類繼承AbstractDisplayDownloadSpeedResponseExtractor,把流輸出到文件中文件下載器,先把流輸出到臨時下載文件(xxxxx.download),下載完成後在重命名文件

執行一段時間之後,我們再看看下內存的使用情況,發現這種方式內存消耗較少,效果比較理想,下載時間:199s

多線程文件下載

如果伺服器不限速的話,通常能夠把自己本地的帶寬給跑滿,那麼使用單線程下載就夠了,但是如果遇到伺服器限速,下載速度遠小於自己本地的帶寬,那麼可以考慮使用多線程下載。多線程我們使用CompletableFuture(可以參考之前的文章 《CompletableFuture讓你的代碼免受阻塞之苦》)。

實現多線程文件下載的基本流程:

首先我們通過Http協議的Head方法獲取到文件的總大小然後根據設置的線程數均分文件的大小,計算每個線程的下載的字節數據開始位置和結束位置開啟線程,設置HTTP請求頭Range信息,開始下載數據到臨時文件下載完成後把每個線程下載完成的臨時文件合併成一個文件

完成代碼如下:

從執行的結果上來看,因為開啟了30個線程同時在下載,內存的佔用要比單線程消耗的多,但是也在接受範圍內,下載時間:81s,速度提升2.5倍,這是因為idea的下載伺服器沒有限速,本次多線程速度的提升僅僅是在充分的壓榨本地的帶寬,所以提示的幅度不大。

單線程下載和對線程下載對比測試

因為雲盤對單個線程的下載速度做了限制,大概是在100kb,所以我們使用雲盤的下載連結,來測試多線程和單線程的下載速度。

測試雲盤中 46M 的文件的下載速度,自己本地最大下載速度 2M

注意:從瀏覽器中獲取的連結需要先使用URLDecode解碼,否則下載會失敗,並且雲盤文件的下載連結是有時效性的,過期後就不能在下載,需要重新生成下載連結

❞測試單線程下載文件

執行的結果可以看出,雲盤對單線程的下載限速真的是喪心病狂, 46M的文件下載需要耗時:600s

測試多線程下載文件

為了充分的壓榨網速,找出最合適的線程數,所以測試了不同線程數的下載速度

線程數下載總耗時1060s2030s3021s4015s5013s

從測試的結果上來看,對於自己的運行環境把線程數設置在30個左右比較合適

文件斷點續傳如何實現,歡迎在大家評論區說出自己的思路。

相關焦點

  • Java實現大文件下載,提速30倍!想學?我教你啊!
    img測試結果,提速46倍,我還是太謙虛了,只說提速30倍,此處我們覺得應該有掌聲(我聽不到,還是點讚實在)至於下載時間,因為文件太大也沒有等下載完成就結束了程序。單線程大文件下載上面的方式只能下載小的文件,那大文件的下載我們該用什麼方式呢?我們可以把流輸出到文件而不是內存中。接下來我們來實現我們大文件的下載。
  • JAVA面試題:你在項目中使用多線程的場景?
    但是實際上依然還有一些場景可以使用多線程來處理,這裡我列舉一下我在實際項目中用到的多線程。多線程處理後臺任務一般來說,我們需要在後臺處理的任務,通常會使用定時器來開啟後臺線程處理,比如有些數據表的狀態我需要定時去修改、我們搜尋引擎裡面的數據需要定時去採集、定時生成統計信息、定時清理上傳的垃圾文件等。
  • 實現多線程的標準操作,基於Runnable接口實現java多線程
    1 為什麼要用Runnable上一篇文章介紹了通過繼承Thread類,實現java多線程。但如果當我們創建的這個線程類還想繼承其他類的時候,這種方法就略顯局限了。這也是java單繼承的局限性。為了避免這種局限性,所以又提供了第二種多線程主體定義的形式:實現Runnable接口。
  • 新手編程:Java多線程中Thread與Runnable的區別
    Java多線程中Thread與Runnable的區別定義extends Thread子類繼承Thread具備多線程能力,可以實現多線程;啟動線程的方法:①創建子類對象 ②對象名.start();不建議使用:避免多線程OOP單繼承的局限性(OOP:Object Oriented Programming,面向對象的編程、類似的還有OOD
  • Java實現多線程的方式,啊四種
    小夥伴們都知道Java實現多線程有兩種方式:1、繼承Thread類,重寫run()方法;2、實現Runnable接口,實現run()方法實際上還有兩種方式:>3、實現Callable接口,實現call()方法;4、通過線程池創建線程接下來一個一個說明:1、繼承Thread類,重寫run()方法線程類:測試類:
  • Java面試題-多線程篇十三
    線程是作業系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。程式設計師可以通過它進行多處理器編程,你可以使用多線程對運算密集型任務提速。比如,如果一個線程完成一個任務要100毫秒,那麼用十個線程完成改任務只需10毫秒。122,線程和進程有什麼區別?
  • JAVA多線程 集合同步
    包裝器實現方式註:同步列表上的單個操作保證是原子操作,但如果要以一致的方式執行並發操作(multiple operations),則必須同步(synchronized)該操作。休眠時間結束後thread-1進入運行狀態去執行synchronizedArrayList的add(i)方法,但由於synchronizedArrayList已被synchronized限制(你必須清楚的是只有返回的迭代器是非線程安全外,synchronizedArrayList其餘的所有方法都是完全的線程安全的), 因此thread-1必須等待thread-2釋放synchronizedArrayList
  • 阿里面試官問我Java線程和作業系統線程什麼關係
    這個問題是安琪拉之前面試被問到的一個問題,正好順著上一篇文章介紹完線程調用時的用戶態和內核態的切換,後續把Java 並發的都一起講了。面試官:聽前一個面試官說你Java並發這塊掌握的不錯,我們深入的交流一下;我:  看了看面試官頭部稀疏的結締組織,已然覺得這場面試不簡單,不過好在事前把安琪拉的博客看了個遍,有所準備,我回答說:咳咳,掌握的還算可以。
  • 通俗易懂的告訴你「策略模式」在java多線程中的應用
    花10分鐘認真的閱讀一篇文章有時或許比敲60分鐘代碼還有效我們都知道java啟動多線程有兩種方式,一種是繼承Thread類,一種是實現Runnable接口,但是很多小夥伴可能不知道實現Runnable接口這種方式中運用了
  • 擼了個多線程斷點續傳下載器,我從中學習到了這些知識(附開源地址)
    感謝看客老爺點進來了,周末閒來無事,想起同事強哥的那句話:「你有沒有玩過斷點續傳?」 當時轉念一想,斷點續傳下載用的確實不少,具體細節嘛,真的沒有去思考過啊。這不,思考過後有了這篇文章。感謝強哥,讓我有了一篇可以水的文章,下面會用純 Java 無依賴實現一個簡單的多線程斷點續傳下載器。這篇文章到底有什麼內容呢?先簡單列舉一下,順便思考幾個問題。多線程斷點續傳會用到哪些知識呢?上面已經拋出了幾個問題,不妨思考一下。
  • Java多線程帶返回值得Callable接口
    Java多線程帶返回值的Callable接口在面試的時候,有時候是不是會遇到面試會問你,Java中實現多線程的方式有幾種?你知道嗎?你知道Java中有可以返回值的線程嗎?在具體的用法你知道嗎?如果兩個線程同時來調用同一個計算對象,計算對象的call方法會被調用幾次你知道嗎?
  • Java 線程面試題 Top 50
    線程是作業系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。程式設計師可以通過它進行多處理器編程,你可以使用多線程對運算密集型任務提速。比如,如果一個線程完成一個任務要100毫秒,那麼用十個線程完成改任務只需10毫秒。Java在語言層面對多線程提供了卓越的支持,它也是一個很好的賣點。2) 線程和進程有什麼區別?
  • 40個Java多線程問題總結
    單核CPU上所謂的」多線程」那是假的多線程,同一時間處理器只會處理一段邏輯,只不過線程之間切換得比較快,看著像多個線程」同時」運行罷了。多核CPU上的多線程才是真正的多線程,它能讓你的多段邏輯同時工作,多線程,可以真正發揮出多核CPU的優勢來,達到充分利用CPU的目的。
  • 教你學習:Python-100-Days-13 進程與線程
    教你學習: Python-100-Days-13 進程與線程本項目是參考項目內容,進行個人理解,和原有項目有出入,如想了解詳情,請自行百度去搜索項目今天我們使用的計算機早已進入多CPU或多核時代,而我們使用的作業系統都是支持「多任務」的作業系統,這使得我們可以同時運行多個程序,也可以將一個程序分解為若干個相對獨立的子任務
  • 面試前必看Java線程面試題
    什麼是線程?線程是作業系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。程式設計師可以通過它進行多處理器編程,你可以使用多線程對運算密集型任務提速。比如,如果一個線程完成一個任務要100毫秒,那麼用十個線程完成改任務只需10毫秒。Java在語言層面對多線程提供了卓越的支持,它也是一個很好的賣點。
  • Java多線程synchronized
    本篇主要介紹Java多線程中的同步,也就是如何在Java語言中寫出線程安全的程序,如何在Java語言中解決非線程安全的相關問題。
  • 大數據基礎:Java多線程入門
    在大數據開發學習當中,Java基礎是非常重要的一部分,打好了Java基礎,才能在後續的大數據框架技術學習階段,也能有所主力。而Java當中的一個重要知識點,就是多線程。今天的大數據基礎分享,我們就主要來講講Java多線程入門基礎。
  • 【堪稱經典】JAVA多線程和並發基礎面試問答
    需要注意的是,這並不會讓線程終止,一旦從休眠中喚醒線程,線程的狀態將會被改變為Runnable,並且根據線程調度,它將得到執行。8. 你對線程優先級的理解是什麼?每一個線程都是有優先級的,一般來說,高優先級的線程在運行時會具有優先權,但這依賴於線程調度的實現,這個實現是和作業系統相關的(OS dependent)。
  • Java面試題解析(事務+緩存+資料庫+多線程+JVM)
    額外的 io,內存和 cpu 的消耗,因為多取了不必要的列。用 SELECT * 需謹慎,因為一旦列的個數或順序更改,就有可能程序執行失敗。多線程Java實現多線程有幾種方式?這其實是很有用的一個特性,因為多線程相比單線程更難、更複雜的一個重要原因就是因為多線程充滿著未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候我們期望的數據是否已經賦值完畢?無法得知,我們能做的只是等待這條多線程的任務執行完畢而已。
  • 我是一個Java class
    "唉,果然沒有被裝載過, 你是個class 文件,當然要報文件開頭的那幾個數了, 就是Java 他爸James Gosling 在jdk 1.0時確定的那個數啊""奧, 我看看, 0xCAFEBABE""不錯, 是個java 類, 把你後邊的兩個數也報一下", 小個子繼續問"50 , 0" "看來版本不高啊, 是jdk 1.6編譯出來的啊