Last ned en fil fra en URL i Java

1. Introduksjon

I denne opplæringen ser vi flere metoder som vi kan bruke til å laste ned en fil.

Vi vil dekke eksempler fra alt fra grunnleggende bruk av Java IO til NIO-pakken, og noen vanlige biblioteker som Async Http Client og Apache Commons IO.

Til slutt vil vi snakke om hvordan vi kan gjenoppta en nedlasting hvis forbindelsen mislykkes før hele filen leses.

2. Bruke Java IO

Den mest grunnleggende APIen vi kan bruke til å laste ned en fil er Java IO. Vi kan bruke URL klasse for å åpne en forbindelse til filen vi vil laste ned. For å effektivt lese filen bruker vi openStream () metode for å oppnå en InputStream:

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

Når du leser fra en InputStream, anbefales det å pakke den inn i en BufferedInputStream for å øke ytelsen.

Ytelsesøkningen kommer fra buffering. Når du leser en byte om gangen ved hjelp av lese() metode, innebærer hver metodeanrop en systemanrop til det underliggende filsystemet. Når JVM påkaller lese() systemanrop, bytter programutførelseskonteksten fra brukermodus til kjernemodus og tilbake.

Denne kontekstbryteren er kostbar fra et ytelsesperspektiv. Når vi leser et stort antall byte, vil applikasjonsytelsen være dårlig på grunn av et stort antall kontekstbrytere involvert.

For å skrive byte som er lest fra URL til vår lokale fil, bruker vi skrive() metoden fra FileOutputStream klasse:

prøv (BufferedInputStream in = new BufferedInputStream (new URL (FILE_URL) .openStream ()); FileOutputStream fileOutputStream = new FileOutputStream (FILE_NAME)) {byte dataBuffer [] = ny byte [1024]; int bytesLes; mens ((bytesRead = in.read (dataBuffer, 0, 1024))! = -1) {fileOutputStream.write (dataBuffer, 0, bytesRead); }} fangst (IOException e) {// unntak for håndtering}

Når du bruker en BufferedInputStream, de lese() metoden vil lese så mange byte som vi angir for bufferstørrelsen. I vårt eksempel gjør vi dette allerede ved å lese blokker på 1024 byte av gangen, så BufferedInputStream er ikke nødvendig.

Eksemplet ovenfor er veldig ordentlig, men heldigvis har vi fra Java 7 Filer klasse som inneholder hjelpemetoder for håndtering av IO-operasjoner. Vi kan bruke Files.copy () metode for å lese alle byte 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);

Koden vår fungerer bra, men kan forbedres. Den viktigste ulempen er det faktum at byte er bufret i minnet.

Heldigvis tilbyr Java oss NIO-pakken som har metoder for å overføre byte direkte mellom 2 Kanaler uten buffering.

Vi vil gå inn på detaljer i neste avsnitt.

3. Bruke NIO

Java NIO-pakken gir muligheten til å overføre byte mellom 2 Kanaler uten å buffere dem inn i applikasjonsminnet.

For å lese filen fra URL-en vår, oppretter vi en ny ReadableByteChannel fra URL strøm:

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

Byte lest fra ReadableByteChannel vil bli overført til en FileChannel tilsvarer filen som skal lastes ned:

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

Vi bruker Overfør fra() metoden fra ReadableByteChannel klasse for å laste ned byte fra gitt URL til vår FileChannel:

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

De Overfør til() og Overfør fra() metodene er mer effektive enn å bare lese fra en strøm ved hjelp av en buffer. Avhengig av det underliggende operativsystemet, dataene kan overføres direkte fra filsystembufferen til filen vår uten å kopiere byte til applikasjonsminnet.

På Linux- og UNIX-systemer bruker disse metodene null-kopi teknikk som reduserer antall kontekstbytter mellom kjernemodus og brukermodus.

4. Bruke biblioteker

Vi har sett i eksemplene ovenfor hvordan vi kan laste ned innhold fra en URL bare ved hjelp av Java-kjernefunksjonaliteten. Vi kan også utnytte funksjonaliteten til eksisterende biblioteker for å lette arbeidet vårt når ytelsejusteringer ikke er nødvendige.

For eksempel, i et virkelig scenario, trenger vi nedlastingskoden for å være asynkron.

Vi kunne pakke all logikken inn i en Kan kalles, eller vi kan bruke et eksisterende bibliotek til dette.

4.1. Async HTTP-klient

AsyncHttpClient er et populært bibliotek for å utføre asynkrone HTTP-forespørsler ved hjelp av Netty-rammeverket. Vi kan bruke den til å utføre en GET-forespørsel til filens URL og få filinnholdet.

Først må vi opprette en HTTP-klient:

AsyncHttpClient-klient = Dsl.asyncHttpClient ();

Det nedlastede innholdet blir plassert i en FileOutputStream:

FileOutputStream stream = ny FileOutputStream (FILE_NAME);

Deretter oppretter vi en HTTP GET-forespørsel og registrerer en AsyncCompletionHandler behandler for å behandle det nedlastede innholdet:

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

Legg merke til at vi har overstyrt onBodyPartReceived () metode. Standardimplementeringen akkumulerer HTTP-biter mottatt i en ArrayList. Dette kan føre til høyt minneforbruk, eller et Tomt for minne unntak når du prøver å laste ned en stor fil.

I stedet for å samle hver HttpResponseBodyPart inn i minnet, vi bruker en FileChannel for å skrive byte til den lokale filen direkte. Vi bruker getBodyByteBuffer () metode for å få tilgang til kroppsdelens innhold gjennom en ByteBuffer.

ByteBuffers ha fordelen at minnet tildeles utenfor JVM-bunken, så det påvirker ikke applikasjonsminnet.

4.2. Apache Commons IO

Et annet høyt brukt bibliotek for IO-drift er Apache Commons IO. Vi kan se fra Javadoc at det er en verktøyklasse som heter FileUtils som brukes til generelle filmanipulasjonsoppgaver.

For å laste ned en fil fra en URL, kan vi bruke denne linjen:

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

Fra et ytelsesmessig synspunkt er denne koden den samme som den vi har eksemplifisert i avsnitt 2.

Den underliggende koden bruker de samme konseptene for å lese i en løkke noen byte fra en InputStream og skrive dem til en OutputStream.

En forskjell er det faktum at her URLtilkobling klasse brukes til å kontrollere tidsavbrudd for tilkobling, slik at nedlastingen ikke blokkeres i lang tid:

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

5. Fornybar nedlasting

Med tanke på at internettforbindelser mislykkes fra tid til annen, er det nyttig for oss å kunne gjenoppta en nedlasting, i stedet for å laste ned filen igjen fra byte null.

La oss skrive det første eksemplet fra tidligere for å legge til denne funksjonaliteten.

Det første vi burde vite er at Vi kan lese størrelsen på en fil fra en gitt URL uten å laste den ned ved hjelp av HTTP HEAD-metoden:

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

Nå som vi har den totale innholdsstørrelsen på filen, kan vi sjekke om filen vår er delvis lastet ned. I så fall fortsetter vi nedlastingen fra den siste byten som ble registrert på disken:

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

Det som skjer her er at vi har konfigurert URLtilkobling for å be om filbyte i et bestemt område. Området starter fra den siste nedlastede byten og slutter med byten som tilsvarer størrelsen på den eksterne filen.

En annen vanlig måte å bruke Område header er for nedlasting av en fil i biter ved å angi forskjellige byteområder. For eksempel, for å laste ned 2 KB-fil, kan vi bruke området 0 - 1024 og 1024 - 2048.

En annen subtil forskjell fra koden i avsnitt 2. er at FileOutputStream åpnes med legge til parameter satt til sann:

OutputStream os = ny FileOutputStream (FILE_NAME, sant);

Etter at vi har gjort denne endringen, er resten av koden identisk med den vi har sett i avsnitt 2.

6. Konklusjon

Vi har i denne artikkelen sett flere måter vi kan laste ned en fil fra en URL i Java.

Den vanligste implementeringen er den der vi bufrer byte når vi utfører lese / skrive-operasjonene. Denne implementeringen er trygg å bruke selv for store filer fordi vi ikke laster hele filen i minnet.

Vi har også sett hvordan vi kan implementere en nullkopi nedlasting ved hjelp av Java NIO Kanaler. Dette er nyttig fordi det minimerte antallet kontekstbrytere som er gjort når du leser og skriver byte, og ved å bruke direkte buffere, blir ikke bytene lastet inn i applikasjonsminnet.

Også fordi nedlasting av en fil vanligvis gjøres via HTTP, har vi vist hvordan vi kan oppnå dette ved hjelp av AsyncHttpClient-biblioteket.

Kildekoden for artikkelen er tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found