Preuzmite datoteku s URL-a na Javi

1. Uvod

U ovom uputstvu vidjet ćemo nekoliko metoda koje možemo koristiti za preuzimanje datoteke.

Obuhvatit ćemo primjere od osnovne upotrebe Java IO do NIO paketa i neke uobičajene knjižnice poput Async Http Client i Apache Commons IO.

Na kraju ćemo razgovarati o tome kako možemo nastaviti s preuzimanjem ako naša veza zakaže prije nego što se pročita čitava datoteka.

2. Korištenje Java IO-a

Najosnovniji API koji možemo koristiti za preuzimanje datoteke je Java IO. Možemo koristiti URL razreda otvoriti vezu s datotekom koju želimo preuzeti. Da bismo učinkovito pročitali datoteku, koristit ćemo openStream () metoda za dobivanje Ulazni tok:

BufferedInputStream u = new BufferedInputStream (novi URL (FILE_URL) .openStream ())

Pri čitanju iz InputStream, preporuča se zamotati u BufferedInputStream za povećanje performansi.

Povećanje performansi dolazi od međuspremnika. Kada čitate po jedan bajt, koristeći čitati() metoda, svaki poziv metode podrazumijeva sistemski poziv osnovnom datotečnom sustavu. Kada JVM pozove čitati() sistemskog poziva, kontekst izvršenja programa prebacuje se iz korisničkog u modus jezgre i natrag.

Ova promjena konteksta skupa je iz perspektive izvedbe. Kada čitamo velik broj bajtova, izvedba aplikacije bit će loša zbog velikog broja uključenih preklopnika konteksta.

Za upisivanje bajtova pročitanih iz URL-a u našu lokalnu datoteku koristit ćemo pisati() metoda iz FileOutputStream razred:

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

Kada koristite BufferedInputStream, the čitati() metoda će pročitati onoliko bajtova koliko smo postavili za veličinu međuspremnika. U našem primjeru to već činimo čitajući blokove od 1024 bajta odjednom, dakle BufferedInputStream nije potrebno.

Gornji je primjer vrlo opširan, ali srećom, od Jave 7 imamo Datoteke klasa koja sadrži pomoćne metode za rukovanje IO operacijama. Možemo koristiti Files.copy () metoda za čitanje svih bajtova iz InputStream i kopirajte ih u lokalnu datoteku:

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

Naš kod dobro funkcionira, ali se može poboljšati. Glavni mu je nedostatak činjenica da se bajtovi baferiraju u memoriju.

Srećom, Java nam nudi NIO paket koji ima metode za prijenos bajtova izravno između 2 Kanali bez međuspremnika.

U detalje ćemo ući u sljedećem odjeljku.

3. Korištenje NIO-a

Java NIO paket nudi mogućnost prijenosa bajtova između 2 Kanali bez umetanja u memoriju aplikacije.

Da bismo datoteku pročitali s našeg URL-a, stvorit ćemo novu ReadableByteChannel od URL tok:

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

Bajtovi pročitani iz ReadableByteChannel bit će prebačen u FileChannel odgovara datoteci koja će se preuzeti:

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

Koristit ćemo transferFrom () metoda iz ReadableByteChannel klase za preuzimanje bajtova s ​​datog URL-a na naš FileChannel:

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

The transferTo () i transferFrom () metode učinkovitije su od jednostavnog čitanja iz struje pomoću međuspremnika. Ovisno o osnovnom operacijskom sustavu, podaci se mogu prenijeti izravno iz predmemorije datotečnog sustava u našu datoteku bez kopiranja bilo kakvih bajtova u memoriju aplikacije.

Na Linuxu i UNIX sustavima ove metode koriste nula-kopija tehnika koja smanjuje broj prebacivanja konteksta između načina jezgre i korisničkog načina.

4. Korištenje knjižnica

U gornjim primjerima vidjeli smo kako možemo preuzeti sadržaj s URL-a samo pomoću osnovne funkcije Java. Također možemo iskoristiti funkcionalnost postojećih knjižnica kako bismo olakšali naš rad, kada nisu potrebne prilagodbe performansi.

Na primjer, u stvarnom scenariju trebat će nam da kod za preuzimanje bude asinkron.

Mogli bismo svu logiku umotati u Pozivnoili bismo za to mogli upotrijebiti postojeću knjižnicu.

4.1. Async HTTP klijent

AsyncHttpClient je popularna knjižnica za izvršavanje asinkronih HTTP zahtjeva pomoću Netty okvira. Pomoću njega možemo izvršiti GET zahtjev za URL datoteke i dobiti sadržaj datoteke.

Prvo, moramo stvoriti HTTP klijent:

AsyncHttpClient klijent = Dsl.asyncHttpClient ();

Preuzeti sadržaj bit će smješten u FileOutputStream:

FileOutputStream stream = novi FileOutputStream (FILE_NAME);

Zatim stvorimo HTTP GET zahtjev i registriramo AsyncCompletionHandler rukovatelj za obradu preuzetog sadržaja:

client.prepareGet (FILE_URL) .execute (new AsyncCompletionHandler () {@ Override public state onBodyPartReceived (HttpResponseBodyPart bodyPart) baca izuzetak {stream.getChannel (). write (bodyPart.getBodyByteBuffer @); FileOutputStream onCompleted (odgovor odgovora) baca izuzetak {return stream;}})

Primijetite da smo nadjačali onBodyPartReceived () metoda. Zadana implementacija akumulira HTTP dijelove primljene u ArrayList. To bi moglo dovesti do velike potrošnje memorije ili Bez memorije iznimka pri pokušaju preuzimanja velike datoteke.

Umjesto da se akumulira svaki HttpResponseBodyPart u sjećanje, koristimo a FileChannel za izravno upisivanje bajtova u našu lokalnu datoteku. Koristit ćemo getBodyByteBuffer () metoda za pristup sadržaju dijelova tijela putem a ByteBuffer.

ByteBuffers imaju prednost što je memorija dodijeljena izvan JVM hrpe, tako da to ne utječe na memoriju aplikacija.

4.2. Apache Commons IO

Još jedna vrlo korištena knjižnica za IO rad je Apache Commons IO. Iz Javadoca možemo vidjeti da postoji klasa korisnih programa s imenom FileUtils koja se koristi za opće zadatke manipulacije datotekama.

Da bismo preuzeli datoteku s URL-a, možemo se poslužiti ovom jednoslojnom linijom:

FileUtils.copyURLToFile (novi URL (FILE_URL), nova datoteka (FILE_NAME), CONNECT_TIMEOUT, READ_TIMEOUT);

Sa stajališta izvedbe, ovaj je kod isti kao onaj koji smo prikazali u odjeljku 2.

Temeljni kôd koristi iste koncepte čitanja u petlji nekih bajtova iz InputStream i zapisujući ih na Izlazni tok.

Jedna je razlika činjenica da ovdje URLConnection klasa koristi se za kontrolu vremenskih ograničenja veze kako se preuzimanje ne bi blokiralo tijekom velikog broja vremena:

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

5. Obnovljivo preuzimanje

S obzirom na to da internetske veze s vremena na vrijeme ne uspiju, korisno je da možemo nastaviti s preuzimanjem, umjesto da datoteku ponovo preuzmemo iz nule bajta.

Prepišimo prvi primjer iz ranije, da dodamo ovu funkcionalnost.

Prvo što bismo trebali znati je to možemo pročitati veličinu datoteke s određenog URL-a, a da je zapravo ne preuzmemo metodom HTTP HEAD:

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

Sad kad imamo ukupnu veličinu sadržaja datoteke, možemo provjeriti je li naša datoteka djelomično preuzeta. Ako je tako, nastavit ćemo preuzimanje sa zadnjeg bajta zabilježenog na disku:

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

Ono što se ovdje događa je to konfigurirali smo URLConnection zatražiti bajtove datoteke u određenom rasponu. Raspon će započeti od zadnjeg preuzetog bajta i završit će u bajtu koji odgovara veličini udaljene datoteke.

Još jedan uobičajeni način korištenja Domet header je za preuzimanje datoteke u komadima postavljanjem različitih raspona bajtova. Na primjer, za preuzimanje datoteke od 2 KB možemo koristiti raspon 0 - 1024 i 1024 - 2048.

Druga suptilna razlika od koda u odjeljku 2. je ta što FileOutputStream je otvoren s dodati parametar postavljen na true:

OutputStream os = novi FileOutputStream (FILE_NAME, točno);

Nakon što smo izvršili ovu promjenu, ostatak koda identičan je onome koji smo vidjeli u odjeljku 2.

6. Zaključak

U ovom smo članku vidjeli nekoliko načina na koje možemo preuzeti datoteku s URL-a u Javi.

Najčešća implementacija je ona u kojoj baferiramo bajtove prilikom izvođenja operacija čitanja / pisanja. Ovu je implementaciju sigurno koristiti čak i za velike datoteke jer cijelu datoteku ne učitavamo u memoriju.

Također smo vidjeli kako možemo implementirati preuzimanje bez kopije pomoću Java NIO Kanali. To je korisno jer je minimiziralo broj prebacivanja konteksta tijekom čitanja i pisanja bajtova, a pomoću izravnih međuspremnika bajtovi se ne učitavaju u memoriju aplikacije.

Također, jer se obično preuzimanje datoteke vrši putem HTTP-a, pokazali smo kako to možemo postići pomoću biblioteke AsyncHttpClient.

Izvorni kôd članka dostupan je na GitHubu.