Fájl letöltése Java-beli URL-ről

1. Bemutatkozás

Ebben az oktatóanyagban számos módszert fogunk látni, amelyekkel fájlokat tölthetünk le.

Bemutatunk példákat a Java IO alapvető használatától a NIO csomagig, és néhány általános könyvtárat, például az Async Http klienst és az Apache Commons IO-t.

Végül beszélünk arról, hogyan folytathatjuk a letöltést, ha a kapcsolat meghiúsul, mielőtt a teljes fájlt elolvassák.

2. Java IO használata

A legalapvetőbb API, amelyet egy fájl letöltésére használhatunk, a Java IO. Használhatjuk a URL osztály megnyitja a kapcsolatot a letölteni kívánt fájlhoz. A fájl hatékony olvasásához a openStream () módszer egy InputStream:

BufferedInputStream in = új BufferedInputStream (új URL (FILE_URL) .openStream ())

Amikor olvas egy InputStream, ajánlott becsomagolni a BufferedInputStream a teljesítmény növelése érdekében.

A teljesítménynövekedés a pufferelésből származik. Amikor egy bájtot olvas be egyszerre a olvas() metódus, minden metódushívás egy rendszerhívást jelent az alapul szolgáló fájlrendszer felé. Amikor a JVM a olvas() rendszerhívás esetén a program végrehajtási kontextusa felhasználói módról kernelmódra és vissza kapcsol.

Ez a kontextuskapcsoló teljesítmény szempontból drága. Amikor nagyszámú bájtot olvasunk, az alkalmazás teljesítménye gyenge lesz, a nagyszámú összefüggésváltó miatt.

Az URL-ről beolvasott bájtok helyi fájlunkba történő írásához a ír() módszer a FileOutputStream osztály:

próbáld meg (BufferedInputStream in = új BufferedInputStream (új URL (FILE_URL) .openStream ()); FileOutputStream fileOutputStream = új FileOutputStream (FILE_NAME)) {byte dataBuffer [] = új byte [1024]; int bytesRead; while ((bytesRead = in.read (dataBuffer, 0, 1024))! = -1) {fileOutputStream.write (dataBuffer, 0, bytesRead); }} catch (IOException e) {// kivétel kezelése}

Ha a BufferedInputStream, a olvas() A metódus annyi bájtot fog olvasni, amennyit beállítottunk a puffer méretéhez. Példánkban ezt már úgy csináljuk, hogy egyszerre 1024 bájtos blokkokat olvasunk el, tehát BufferedInputStream nem szükséges.

A fenti példa nagyon bőbeszédű, de szerencsére a Java 7-től kezdve megvan a Fájlok osztály, amely segítő módszereket tartalmaz az IO műveletek kezelésére. Használhatjuk a Files.copy () módszer az összes bájt leolvasására egy InputStream és másolja át őket egy helyi fájlba:

InputStream in = új URL (FILE_URL) .openStream (); Files.copy (itt: Paths.get (FILE_NAME), StandardCopyOption.REPLACE_EXISTING);

Kódunk jól működik, de javítható. Legfőbb hátránya, hogy a bájtokat a memóriába pufferolják.

Szerencsére a Java felajánlja nekünk azt a NIO csomagot, amelynek módszerei vannak a bájtok közvetlen átvitelére 2 között Csatornák pufferelés nélkül.

A következő szakaszban részletezzük.

3. A NIO használata

A Java NIO csomag lehetőséget kínál bájtok átvitelére 2 között Csatornák pufferelés nélkül az alkalmazás memóriájába.

Ahhoz, hogy elolvassa a fájlt az URL-jünkről, létrehozunk egy újat ReadableByteChannel tól URL folyam:

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

A bájtok a ReadableByteChannel átkerül a FileChannel a letöltendő fájlnak megfelelő:

FileOutputStream fileOutputStream = új FileOutputStream (FILE_NAME); FileChannel fileChannel = fileOutputStream.getChannel ();

Használjuk a transferFrom () módszer a ReadableByteChannel osztályban töltse le a bájtokat a megadott URL-ről a mi weboldalunkra FileChannel:

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

A transferTo () és transferFrom () a módszerek hatékonyabbak, mint egyszerűen egy adatfolyamból puffer segítségével olvasni. Az alapul szolgáló operációs rendszertől függően az adatok a fájlrendszer gyorsítótárából közvetlenül átkerülhetnek a fájlunkba anélkül, hogy bájtokat másolnának az alkalmazás memóriájába.

Linux és UNIX rendszereken ezek a módszerek a nulla példány technika, amely csökkenti a rendszermag mód és a felhasználói mód közötti kapcsolatok számát.

4. Könyvtárak használata

A fenti példákban láthattuk, hogyan tölthetünk le tartalmat egy URL-ről, csupán a Java alapvető funkcióinak használatával. Kihasználhatjuk a meglévő könyvtárak funkcionalitását is a munkánk megkönnyítése érdekében, amikor a teljesítményre nincs szükség.

Például valós helyzetben szükségünk lenne arra, hogy a letöltési kódunk aszinkron legyen.

Az egész logikát a-ba csomagolhatnánk Hívható, vagy használhatnánk ehhez egy meglévő könyvtárat.

4.1. Async HTTP kliens

Az AsyncHttpClient egy népszerű könyvtár aszinkron HTTP kérések futtatásához a Netty keretrendszer segítségével. Használhatjuk a fájl URL-jére vonatkozó GET kérés végrehajtására és a fájl tartalmának megszerzésére.

Először létre kell hoznunk egy HTTP klienst:

AsyncHttpClient kliens = Dsl.asyncHttpClient ();

A letöltött tartalmat a FileOutputStream:

FileOutputStream stream = új FileOutputStream (FILE_NAME);

Ezután létrehozunk egy HTTP GET kérést és regisztrálunk egy AsyncCompletionHandler kezelő a letöltött tartalom feldolgozásához:

client.prepareGet (FILE_URL) .execute (new AsyncCompletionHandler () {@Orride public state onBodyPartReceived (HttpResponseBodyPart bodyPart) dobja a Kivételt: {stream.getChannel (). A FileOutputStream onCompleted (válaszválasz) kiveti a {visszatérés;}} kivételt)

Figyelje meg, hogy felülbíráltuk a onBodyPartReceived () módszer. Az alapértelmezett megvalósítás a kapott HTTP-darabokat egy Tömb lista. Ez magas memóriafelhasználást vagy an Elfogyott a memória kivétel egy nagy fájl letöltésekor.

Ahelyett, hogy mindegyiket felhalmoznák HttpResponseBodyPart az emlékezetbe, használjuk a FileChannel hogy a bájtokat közvetlenül a helyi fájlunkba írjuk. Használjuk a getBodyByteBuffer () módszer a testrész tartalmának a ByteBuffer.

ByteBuffers előnye, hogy a memória a JVM halomon kívül van lefoglalva, így ez nem befolyásolja az alkalmazások memóriáját.

4.2. Apache Commons IO

Az IO működéséhez egy másik nagyon használt könyvtár az Apache Commons IO. A Javadoc-ból láthatjuk, hogy van egy segédprogram-osztály FileUtils amelyet általános fájlkezelési feladatokhoz használnak.

Fájl letöltéséhez egy URL-ből ezt az egyvonalas vonalat használhatjuk:

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

Teljesítmény szempontjából ez a kód megegyezik azzal, amelyet a 2. szakaszban bemutattunk.

Az alapul szolgáló kód ugyanazokat a fogalmakat használja, hogy egy ciklusban olvasson néhány bájtot egy InputStream és megírja őket egy OutputStream.

Az egyik különbség az a tény, hogy itt a URLConnection osztály a kapcsolat időkorlátjainak vezérlésére szolgál, hogy a letöltés ne akadályozzon nagy ideig:

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

5. Folytatható letöltés

Figyelembe véve az internetkapcsolatok időnkénti meghibásodását, hasznos, hogy folytathassuk a letöltést, ahelyett, hogy a fájlt újra letöltenénk a nulla bájtról.

Írjuk át a korábbi első példát ennek a funkciónak a hozzáadásához.

Az első dolog, amit tudnunk kell, az kiolvashatjuk a fájl méretét egy adott URL-ről anélkül, hogy azt ténylegesen letöltenénk a HTTP HEAD módszer segítségével:

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

Most, hogy megvan a fájl teljes tartalmi mérete, ellenőrizhetjük, hogy a fájlunk részben letöltődött-e. Ha igen, folytatjuk a letöltést a lemezre rögzített utolsó bájtról:

long existingFileSize = outputFile.length (); if (existingFileSize <fileLength) {httpFileConnection.setRequestProperty ("Tartomány", "bájt =" + existingFileSize + "-" + fájlhossz); }

Ami itt történik, az az konfiguráltuk a URLConnection hogy kérje a fájl bájtjait egy meghatározott tartományban. A tartomány az utoljára letöltött bájttól kezdődik, és a távoli fájl méretének megfelelő bájtnál ér véget.

A. Használatának másik gyakori módja Hatótávolság fejléc egy fájl darabokra való letöltésére szolgál, különböző bájttartományok beállításával. Például 2 KB-os fájl letöltéséhez használhatjuk a 0-1024 és 1024-2048 tartományokat.

Egy másik finom különbség a 2. szakasz kódjától az, hogy a FileOutputStream megnyílik a mellékel paraméter értéke true:

OutputStream os = new FileOutputStream (FILE_NAME, true);

Miután elvégeztük ezt a változtatást, a kód többi része megegyezik a 2. szakaszban láthatóval.

6. Következtetés

Ebben a cikkben többféleképpen láthattunk fájlokat letölteni egy Java URL-ből.

A leggyakoribb megvalósítás az, amelyben az olvasási / írási műveletek végrehajtása során puffereljük a bájtokat. Ez az implementáció biztonságos még nagy fájlok esetén is, mert nem töltjük be a teljes fájlt a memóriába.

Láttuk azt is, hogy miként valósíthatjuk meg a nulla példányszámú letöltést a Java NIO használatával Csatornák. Ez azért hasznos, mert minimálisra csökkentette a bájtok olvasásakor és írásakor végzett kapcsolók számát, és közvetlen pufferek használatával a bájtok nem töltődnek be az alkalmazás memóriájába.

Továbbá, mivel általában egy fájl letöltése HTTP-n keresztül történik, megmutattuk, hogyan érhetjük el ezt az AsyncHttpClient könyvtár segítségével.

A cikk forráskódja elérhető a GitHubon.