欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java實現(xiàn)高效下載文件的方法

 更新時間:2021年08月26日 10:42:55   作者:夢想畫家  
這篇文章主要為大家詳細介紹了java實現(xiàn)高效下載文件的幾種方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

本文實例為大家分享了java實現(xiàn)下載文件的方法,供大家參考,具體內(nèi)容如下

本文我們介紹幾種方法下載文件。從基本JAVA IO 到 NIO包,也介紹第三方庫的一些方法,如Async Http Client 和 Apache Commons IO.
最后我們還討論在連接斷開后如何恢復下載。

使用java IO

下載文件最基本的方法是java IO,使用URL類打開待下載文件的連接。為有效讀取文件,我們使用openStream() 方法獲取 InputStream:

BufferedInputStream in = new BufferedInputStream(new URL(FILE_URL).openStream())

當從InputStream讀取文件時,強烈建議使用BufferedInputStream去包裝InputStream,用于提升性能。
使用緩存可以提升性能。read方法每次讀一個字節(jié),每次方法調(diào)用意味著系統(tǒng)調(diào)用底層的文件系統(tǒng)。當JVM調(diào)用read()方法時,程序執(zhí)行上下文將從用戶模式切換到內(nèi)核模式并返回。

從性能的角度來看,這種上下文切換非常昂貴。當我們讀取大量字節(jié)時,由于涉及大量上下文切換,應(yīng)用程序性能將會很差。

為了讀取URL的字節(jié)并寫至本地文件,需要使用FileOutputStream 類的write方法:

try (BufferedInputStream in = new BufferedInputStream(new URL(FILE_URL).openStream());
  FileOutputStream fileOutputStream = new FileOutputStream(FILE_NAME)) {
    byte dataBuffer[] = new byte[1024];
    int bytesRead;
    while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
        fileOutputStream.write(dataBuffer, 0, bytesRead);
    }
} catch (IOException e) {
    // handle exception
}

使用BufferedInputStream,read方法按照我們設(shè)置緩沖器大小讀取文件。示例中我們設(shè)置一次讀取1024字節(jié),所以BufferedInputStream 是必要的。

上述示例代碼冗長,幸運的是在Java7中Files類包含處理IO操作的助手方法??梢允褂肍ile.copy()方法從InputStream中讀取所有字節(jié),然后復制至本地文件:

InputStream in = new URL(FILE_URL).openStream();
Files.copy(in, Paths.get(FILE_NAME), StandardCopyOption.REPLACE_EXISTING);

上述代碼可以正常工作,但缺點是字節(jié)被緩沖到內(nèi)存中。Java為我們提供了NIO包,它有方法在兩個通道之間直接傳輸字節(jié),而無需緩沖。下面我們會詳細講解。

使用NIO

java NIO包提供了無緩沖情況下在兩個通道之間直接傳輸字節(jié)的可能。

為了讀來自URL的文件,需從URL流創(chuàng)建ReadableByteChannel :

ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream());

從ReadableByteChannel 讀取字節(jié)將被傳輸至FileChannel:

FileOutputStream fileOutputStream = new FileOutputStream(FILE_NAME);
FileChannel fileChannel = fileOutputStream.getChannel();

然后使用transferFrom方法,從ReadableByteChannel 類下載來自URL的字節(jié)傳輸?shù)紽ileChannel:

fileOutputStream.getChannel()
  .transferFrom(readableByteChannel, 0, Long.MAX_VALUE);

transferTo() 和 transferFrom() 方法比簡單使用緩存從流中讀更有效。依據(jù)不同的底層操作系統(tǒng),數(shù)據(jù)可以直接從文件系統(tǒng)緩存?zhèn)鬏數(shù)轿覀兊奈募槐貙⑷魏巫止?jié)復制到應(yīng)用程序內(nèi)存中。

在Linux和UNIX系統(tǒng)上,這些方法使用零拷貝技術(shù),減少了內(nèi)核模式和用戶模式之間的上下文切換次數(shù)。

使用第三方庫

上面我們已經(jīng)使用java 核心功能實現(xiàn)從URL下載文件。當無需調(diào)整性能是,我們也可以利用第三方庫輕松實現(xiàn)。舉例,在實際場景中,需要實現(xiàn)異步下載,我們可以封裝邏輯至Callable,下面看已有庫實現(xiàn)。

Async HTTP Client

Async HTTP Client是使用Netty框架執(zhí)行異步HTTP請求的流行庫。我們使用它對URL文件執(zhí)行GET請求并獲取其內(nèi)容。首先需要創(chuàng)建HTTP client:

AsyncHttpClient client = Dsl.asyncHttpClient();

下面內(nèi)容放到FileOutputStream:

FileOutputStream stream = new FileOutputStream(FILE_NAME);

接下來,創(chuàng)建HTTP GET請求并注冊AsyncCompletionHandler 處理器去處理下載內(nèi)容:

client.prepareGet(FILE_URL).execute(new AsyncCompletionHandler<FileOutputStream>() {

    @Override
    public State onBodyPartReceived(HttpResponseBodyPart bodyPart) 
      throws Exception {
        stream.getChannel().write(bodyPart.getBodyByteBuffer());
        return State.CONTINUE;
    }

    @Override
    public FileOutputStream onCompleted(Response response) 
      throws Exception {
        return stream;
    }
})

上面我們覆蓋了onBodyPartReceived() 方法。缺省實現(xiàn)是累加HTTP 接收塊至ArrayList,這可能導致耗費高內(nèi)存或下載大文件時OutOfMemory 異常。

代替累加每個HttpResponseBodyPart 至內(nèi)存,我們使用FileChannel寫字節(jié)至本地文件。getBodyByteBuffer()方法通過ByteBuffer訪問bodyPart內(nèi)容。ByteBuffers的優(yōu)勢是把內(nèi)存分配到JVM堆之外,所以不會影響應(yīng)用程序的內(nèi)存。

Apache Commons IO

另一個高可用的IO操作庫是Apache Commons IO。我們從其Javadoc看到FileUtils實用類,用于一般的文件操作任務(wù)。從URL下載文件,僅需一行代碼:

FileUtils.copyURLToFile(
  new URL(FILE_URL), 
  new File(FILE_NAME), 
  CONNECT_TIMEOUT, 
  READ_TIMEOUT);

從性能角度看,與前面JAVA IO示例相同。底層代碼使用相同的概念,從InputStream讀取一些字節(jié)并將它們寫入OutputStream。不同之處在于,URLConnection類在這里用于控制連接超時,這樣下載就不會阻塞很長時間:

URLConnection connection = source.openConnection();
connection.setConnectTimeout(connectionTimeout);
connection.setReadTimeout(readTimeout);

恢復下載

考慮到internet連接的不確定性,失敗時我們可以重新下載文件,但不是再次從字節(jié)0位置下載文件。
讓我們重寫前面的第一個示例,以添加這個功能。

我們首先要知道的是,我們可以使用HTTP HEAD方法從給定URL讀取文件的大小,而無需實際下載它:

URL url = new URL(FILE_URL);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestMethod("HEAD");
long removeFileSize = httpConnection.getContentLengthLong();

現(xiàn)在我們有了文件內(nèi)容的總大小,可以檢查文件是否已下載了部分內(nèi)容。如果是,我們將繼續(xù)從磁盤上記錄的最后一個字節(jié)開始下載:

long existingFileSize = outputFile.length();
if (existingFileSize < fileLength) {
    httpFileConnection.setRequestProperty(
      "Range", 
      "bytes=" + existingFileSize + "-" + fileLength
    );
}

上述代碼我們配置了URLConnection以在一定范圍內(nèi)請求文件內(nèi)容。范圍將從最后下載的字節(jié)位置開始,并以遠程文件大小的字節(jié)長度為結(jié)束。

使用范圍HEAD標識的另一種常見方法是通過設(shè)置不同的字節(jié)范圍以塊形式下載文件。例如,要下載2KB文件,我們可以使用范圍0 - 1024和1024 - 2048。

與前節(jié)代碼稍微不同的是設(shè)置FileOutputStream 方法append參數(shù)為true:

OutputStream os = new FileOutputStream(FILE_NAME, true);

在我們做了這個更改之后,其余的代碼與我們前面看到的代碼一樣。

總結(jié)

在本文中,我們已經(jīng)看到Java中從URL下載文件的幾種實現(xiàn)方式。

最常見的實現(xiàn)是在執(zhí)行讀/寫操作時使用緩沖區(qū)。這個實現(xiàn)即使對于大文件也是安全的,因為我們沒有將整個文件加載到內(nèi)存中。

我們還了解了如何使用Java NIO通道實現(xiàn)零拷貝下載。這很有用,因為它最小化了在讀取和寫入字節(jié)時執(zhí)行的上下文切換的次數(shù),并且通過使用直接緩沖區(qū)字節(jié)不會加載到應(yīng)用程序內(nèi)存中。另外,由于下載文件通常是通過HTTP完成的,我們也說明如何使用AsyncHttpClient庫實現(xiàn)這一點。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java語言中&&與& ||與|的區(qū)別是什么

    Java語言中&&與& ||與|的區(qū)別是什么

    這篇文章主要介紹了Java語言中&&與& ||與|的區(qū)別是什么的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • SpringBoot集成Hadoop對HDFS的文件操作方法

    SpringBoot集成Hadoop對HDFS的文件操作方法

    這篇文章主要介紹了SpringBoot集成Hadoop對HDFS的文件操作方法,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2024-07-07
  • Springboot2 session設(shè)置超時時間無效的解決

    Springboot2 session設(shè)置超時時間無效的解決

    這篇文章主要介紹了Springboot2 session設(shè)置超時時間無效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • mybatis中嵌套查詢的使用解讀

    mybatis中嵌套查詢的使用解讀

    這篇文章主要介紹了mybatis中嵌套查詢的使用解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • springBoot接入阿里云oss的實現(xiàn)步驟

    springBoot接入阿里云oss的實現(xiàn)步驟

    這篇文章主要介紹了springBoot接入阿里云oss的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • 如何從Java接口的角度切入靜態(tài)工廠模式

    如何從Java接口的角度切入靜態(tài)工廠模式

    靜態(tài)工廠模式是一種改進的獲取實例的方法。通常我們會使用new關(guān)鍵字調(diào)用類的構(gòu)造方法來創(chuàng)建一個對象。靜態(tài)工廠可以根據(jù)用戶傳入的參數(shù)來動態(tài)地實例化對象,避免一次性實例化所有對象所帶來的性能浪費,同時也降低了耦合性。
    2021-06-06
  • 一篇文章帶你入門Java數(shù)據(jù)類型

    一篇文章帶你入門Java數(shù)據(jù)類型

    下面小編就為大家?guī)硪黄狫ava的基本數(shù)據(jù)類型)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2021-08-08
  • Java中Boolean和boolean的區(qū)別詳析

    Java中Boolean和boolean的區(qū)別詳析

    boolean是基本數(shù)據(jù)類型Boolean是它的封裝類,和其他類一樣,有屬性有方法,可以new,下面這篇文章主要給大家介紹了關(guān)于Java中Boolean和boolean區(qū)別的相關(guān)資料,需要的朋友可以參考下
    2022-07-07
  • 詳解OAuth2 Token 一定要放在請求頭中嗎

    詳解OAuth2 Token 一定要放在請求頭中嗎

    這篇文章主要介紹了詳解OAuth2 Token 一定要放在請求頭中嗎,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07
  • Java Listener監(jiān)聽器使用規(guī)范詳細介紹

    Java Listener監(jiān)聽器使用規(guī)范詳細介紹

    監(jiān)聽器是一個專門用于對其他對象身上發(fā)生的事件或狀態(tài)改變進行監(jiān)聽和相應(yīng)處理的對象,當被監(jiān)視的對象發(fā)生情況時,立即采取相應(yīng)的行動。監(jiān)聽器其實就是一個實現(xiàn)特定接口的普通java程序,這個程序?qū)iT用于監(jiān)聽另一個java對象的方法調(diào)用或?qū)傩愿淖?/div> 2023-01-01

最新評論