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)文章
SpringBoot集成Hadoop對HDFS的文件操作方法
這篇文章主要介紹了SpringBoot集成Hadoop對HDFS的文件操作方法,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-07-07Springboot2 session設(shè)置超時時間無效的解決
這篇文章主要介紹了Springboot2 session設(shè)置超時時間無效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Java 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最新評論