Download en fil fra en URL i Java

1. Introduktion

I denne vejledning ser vi flere metoder, som vi kan bruge til at downloade en fil.

Vi dækker eksempler lige fra den grundlæggende brug af Java IO til NIO-pakken og nogle almindelige biblioteker som Async Http Client og Apache Commons IO.

Endelig vil vi tale om, hvordan vi kan genoptage en download, hvis vores forbindelse mislykkes, før hele filen læses.

2. Brug af Java IO

Den mest basale API, vi kan bruge til at downloade en fil, er Java IO. Vi kan bruge URL klasse for at åbne en forbindelse til den fil, vi vil downloade. For effektivt at læse filen bruger vi openStream () metode til at opnå en InputStream:

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

Når du læser fra en InputStream, anbefales det at pakke det ind i en BufferedInputStream for at øge ydeevnen.

Præstationsforøgelsen kommer fra buffering. Når du læser en byte ad gangen ved hjælp af Læs() metode, indebærer hver metodeopkald et systemopkald til det underliggende filsystem. Når JVM påberåber sig Læs() systemopkald skifter programudførelseskonteksten fra brugertilstand til kernetilstand og tilbage.

Denne kontekst switch er dyrt set fra et præstationsperspektiv. Når vi læser et stort antal bytes, vil applikationsydelsen være dårlig på grund af et stort antal involverede kontekstskiftere.

Til at skrive de byte, der er læst fra URL'en til vores lokale fil, bruger vi skrive() metode fra FileOutputStream klasse:

prøv (BufferedInputStream i = ny BufferedInputStream (ny URL (FILE_URL) .openStream ()); FileOutputStream-filOutputStream = ny FileOutputStream (FILE_NAME)) {byte dataBuffer [] = ny byte [1024]; int bytesLæs; mens ((bytesRead = in.read (dataBuffer, 0, 1024))! = -1) {fileOutputStream.write (dataBuffer, 0, bytesRead); }} fangst (IOException e) {// håndtag undtagelse}

Når du bruger en BufferedInputStream, det Læs() metode vil læse så mange byte, som vi indstiller til bufferstørrelsen. I vores eksempel gør vi det allerede ved at læse blokke på 1024 byte ad gangen, så BufferedInputStream er ikke nødvendigt.

Eksemplet ovenfor er meget detaljeret, men heldigvis har vi fra Java 7 Filer klasse, der indeholder hjælpemetoder til håndtering af IO-operationer. Vi kan bruge Files.copy () metode til at læse alle bytes fra en InputStream og kopier dem til en lokal fil:

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

Vores kode fungerer godt, men kan forbedres. Dens største ulempe er, at byte er bufret i hukommelsen.

Heldigvis tilbyder Java os NIO-pakken, der har metoder til at overføre bytes direkte mellem 2 Kanaler uden buffering.

Vi går i detaljer i det næste afsnit.

3. Brug af NIO

Java NIO-pakken giver mulighed for at overføre bytes mellem 2 Kanaler uden at buffere dem i applikationshukommelsen.

For at læse filen fra vores URL opretter vi en ny ReadableByteChannel fra URL strøm:

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

Bytes læst fra ReadableByteChannel vil blive overført til en FileChannel svarende til den fil, der downloades:

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

Vi bruger transferFrom () metode fra ReadableByteChannel klasse for at downloade bytes fra den givne URL til vores FileChannel:

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

Det Overfør til() og transferFrom () metoder er mere effektive end blot at læse fra en stream ved hjælp af en buffer. Afhængigt af det underliggende operativsystem, dataene kan overføres direkte fra filsystemets cache til vores fil uden at kopiere nogen bytes til applikationshukommelsen.

På Linux- og UNIX-systemer bruger disse metoder nul-kopi teknik, der reducerer antallet af kontekstskifter mellem kernetilstand og brugertilstand.

4. Brug af biblioteker

Vi har set i eksemplerne ovenfor, hvordan vi kan downloade indhold fra en URL bare ved hjælp af Java-kernefunktionaliteten. Vi kan også udnytte funktionaliteten i eksisterende biblioteker for at lette vores arbejde, når performance tweaks ikke er nødvendige.

For eksempel i et virkeligt scenarie ville vi have brug for vores downloadkode for at være asynkron.

Vi kunne pakke al logikken ind i en Kan kaldes, eller vi kunne bruge et eksisterende bibliotek til dette.

4.1. Async HTTP-klient

AsyncHttpClient er et populært bibliotek til udførelse af asynkrone HTTP-anmodninger ved hjælp af Netty-rammen. Vi kan bruge den til at udføre en GET-anmodning til filens URL og få filindholdet.

Først skal vi oprette en HTTP-klient:

AsyncHttpClient-klient = Dsl.asyncHttpClient ();

Det downloadede indhold placeres i en FileOutputStream:

FileOutputStream stream = ny FileOutputStream (FILE_NAME);

Dernæst opretter vi en HTTP GET-anmodning og registrerer en AsyncCompletionHandler handler til at behandle det downloadede indhold:

client.prepareGet (FILE_URL) .execute (ny AsyncCompletionHandler () {@Override public State onBodyPartReceived (HttpResponseBodyPart bodyPart) kaster undtagelse {stream.getChannel (). skriv (bodyPart.getBodyByteBuffer ()); FileOutputStream onCompleted (svarrespons) kaster undtagelse {returstrøm;}})

Bemærk, at vi har tilsidesat onBodyPartReceived () metode. Standardimplementeringen akkumulerer de modtagne HTTP-klumper til en ArrayList. Dette kan føre til højt hukommelsesforbrug eller et Ikke mere hukommelse undtagelse, når du prøver at downloade en stor fil.

I stedet for at akkumulere hver HttpResponseBodyPart i hukommelsen, vi bruger en FileChannel at skrive byte til vores lokale fil direkte. Vi bruger getBodyByteBuffer () metode til at få adgang til kropsdelens indhold gennem en ByteBuffer.

ByteBuffers har den fordel, at hukommelsen allokeres uden for JVM-bunken, så det påvirker ikke applikationshukommelsen.

4.2. Apache Commons IO

Et andet meget anvendt bibliotek til IO-drift er Apache Commons IO. Vi kan se fra Javadoc, at der er en hjælpeklasse, der hedder FileUtils der bruges til generelle filmanipuleringsopgaver.

For at downloade en fil fra en URL kan vi bruge denne one-liner:

FileUtils.copyURLToFile (ny URL (FILE_URL), ny fil (FILE_NAME), CONNECT_TIMEOUT, READ_TIMEOUT);

Set ud fra et præstationssynspunkt er denne kode den samme som den, vi har eksemplificeret i afsnit 2.

Den underliggende kode bruger de samme begreber at læse i en sløjfe nogle byte fra en InputStream og skrive dem til en OutputStream.

En forskel er det faktum, at her URL-forbindelse klasse bruges til at kontrollere forbindelsestimeouts, så download ikke blokerer i en lang periode:

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

5. Genoptagelig download

I betragtning af internetforbindelser mislykkes fra tid til anden, er det nyttigt for os at kunne genoptage en download i stedet for at downloade filen igen fra byte nul.

Lad os omskrive det første eksempel fra tidligere for at tilføje denne funktionalitet.

Den første ting, vi skal vide, er det Vi kan læse størrelsen på en fil fra en given URL uden faktisk at downloade den ved hjælp af HTTP HEAD-metoden:

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

Nu hvor vi har den samlede indholdsstørrelse på filen, kan vi kontrollere, om vores fil er delvist downloadet. I så fald genoptager vi downloadet fra den sidst byte, der er optaget på disken:

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

Hvad der sker her er det vi har konfigureret URL-forbindelse for at anmode om filbyte i et bestemt interval. Området starter fra den sidst downloadede byte og slutter med den byte, der svarer til størrelsen på den eksterne fil.

En anden almindelig måde at bruge Rækkevidde header er til download af en fil i klumper ved at indstille forskellige byteområder. For eksempel for at downloade 2 KB-fil kan vi bruge området 0 - 1024 og 1024 - 2048.

En anden subtil forskel fra koden i afsnit 2. er, at FileOutputStream åbnes med Tilføj parameter indstillet til sand:

OutputStream os = ny FileOutputStream (FILE_NAME, sand);

Efter at vi har foretaget denne ændring, er resten af ​​koden identisk med den, vi har set i afsnit 2.

6. Konklusion

Vi har i denne artikel set flere måder, hvorpå vi kan downloade en fil fra en URL i Java.

Den mest almindelige implementering er den, hvor vi buffer byte, når vi udfører læse / skrive-operationer. Denne implementering er sikker at bruge selv til store filer, fordi vi ikke indlæser hele filen i hukommelsen.

Vi har også set, hvordan vi kan implementere en nul-kopi-download ved hjælp af Java NIO Kanaler. Dette er nyttigt, fordi det minimerede antallet af kontekstskiftere, der er udført, når du læser og skriver bytes, og ved at bruge direkte buffere indlæses ikke bytes i applikationshukommelsen.

Fordi download af en fil normalt sker via HTTP, har vi også vist, hvordan vi kan opnå dette ved hjælp af AsyncHttpClient-biblioteket.

Kildekoden til artiklen er tilgængelig på GitHub.