1. Pengenalan
Dalam tutorial ini, kita akan melihat beberapa kaedah yang boleh kita gunakan untuk memuat turun fail.
Kami akan merangkumi contoh mulai dari penggunaan asas Java IO hingga pakej NIO, dan beberapa perpustakaan umum seperti Async Http Client dan Apache Commons IO.
Akhirnya, kita akan membincangkan bagaimana kita dapat meneruskan muat turun sekiranya sambungan kita gagal sebelum keseluruhan fail dibaca.
2. Menggunakan Java IO
API paling asas yang dapat kita gunakan untuk memuat turun fail adalah Java IO. Kita boleh menggunakan kelas URL untuk membuka sambungan ke fail yang ingin kita muat turun. Untuk membaca fail dengan berkesan, kami akan menggunakan kaedah openStream () untuk mendapatkan InputStream:
BufferedInputStream in = new BufferedInputStream(new URL(FILE_URL).openStream())
Semasa membaca dari InputStream , disarankan untuk membungkusnya dalam BufferedInputStream untuk meningkatkan prestasinya.
Peningkatan prestasi datang dari penyanggaan. Semasa membaca satu bait pada satu masa menggunakan kaedah baca () , setiap panggilan kaedah menyiratkan panggilan sistem ke sistem fail yang mendasari. Apabila JVM memanggil panggilan sistem read () , konteks pelaksanaan program beralih dari mod pengguna ke mod kernel dan kembali.
Peralihan konteks ini mahal dari perspektif prestasi. Apabila kita membaca sebilangan besar bait, prestasi aplikasi akan menjadi buruk, kerana banyaknya pertukaran konteks yang terlibat.
Untuk menulis bait yang dibaca dari URL ke fail tempatan kami, kami akan menggunakan kaedah tulis () dari kelas FileOutputStream :
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 }
Semasa menggunakan BufferedInputStream, kaedah read () akan membaca sebanyak bait yang kita tetapkan untuk ukuran buffer. Dalam contoh kami, kami sudah melakukan ini dengan membaca blok 1024 bait pada satu masa, jadi BufferedInputStream tidak diperlukan.
Contoh di atas sangat verbose, tetapi untungnya, pada Java 7, kita mempunyai kelas Files yang mengandungi kaedah pembantu untuk mengendalikan operasi IO. Kita boleh menggunakan kaedah Files.copy () untuk membaca semua bait dari InputStream dan menyalinnya ke fail tempatan:
InputStream in = new URL(FILE_URL).openStream(); Files.copy(in, Paths.get(FILE_NAME), StandardCopyOption.REPLACE_EXISTING);
Kod kami berfungsi dengan baik tetapi boleh diperbaiki. Kelemahan utamanya adalah hakikat bahawa bait disangga dalam memori.
Nasib baik, Java menawarkan pakej NIO yang mempunyai kaedah untuk memindahkan bait secara langsung antara 2 Saluran tanpa penyangga.
Kami akan memperincikannya di bahagian seterusnya.
3. Menggunakan NIO
Pakej Java NIO menawarkan kemungkinan untuk mentransfer bait antara 2 Saluran tanpa memasukkannya ke dalam memori aplikasi.
Untuk membaca fail dari URL kami, kami akan membuat ReadableByteChannel baru dari aliran URL :
ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream());
Bait yang dibaca dari ReadableByteChannel akan dipindahkan ke FileChannel yang sepadan dengan fail yang akan dimuat turun:
FileOutputStream fileOutputStream = new FileOutputStream(FILE_NAME); FileChannel fileChannel = fileOutputStream.getChannel();
Kami akan menggunakan kaedah transferFrom () dari kelas ReadableByteChannel untuk memuat turun bait dari URL yang diberikan ke FileChannel kami :
fileOutputStream.getChannel() .transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
Kaedah transferTo () dan transferFrom () lebih cekap daripada sekadar membaca dari aliran menggunakan penyangga. Bergantung pada sistem operasi yang mendasari, data dapat dipindahkan secara langsung dari cache sistem fail ke file kami tanpa menyalin byte ke dalam memori aplikasi .
Pada sistem Linux dan UNIX, kaedah ini menggunakan teknik zero-copy yang mengurangkan jumlah pertukaran konteks antara mod kernel dan mod pengguna.
4. Menggunakan Perpustakaan
Kami telah melihat dalam contoh di atas bagaimana kami boleh memuat turun kandungan dari URL hanya dengan menggunakan fungsi inti Java. Kami juga dapat memanfaatkan fungsi perpustakaan yang ada untuk memudahkan kerja kami, ketika tweak prestasi tidak diperlukan.
Sebagai contoh, dalam senario dunia nyata, kami memerlukan kod muat turun kami agar tidak segerak.
Kita dapat membungkus semua logik menjadi Callable , atau kita boleh menggunakan perpustakaan yang ada untuk ini.
4.1. Pelanggan HTTP Async
AsyncHttpClient adalah perpustakaan yang popular untuk melaksanakan permintaan HTTP asinkron menggunakan rangka kerja Netty. Kita dapat menggunakannya untuk melaksanakan permintaan GET ke URL file dan mendapatkan konten file.
Pertama, kita perlu membuat klien HTTP:
AsyncHttpClient client = Dsl.asyncHttpClient();
Kandungan yang dimuat turun akan dimasukkan ke dalam FileOutputStream :
FileOutputStream stream = new FileOutputStream(FILE_NAME);
Seterusnya, kami membuat permintaan HTTP GET dan mendaftarkan pengendali AsyncCompletionHandler untuk memproses kandungan yang dimuat turun:
client.prepareGet(FILE_URL).execute(new AsyncCompletionHandler() { @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; } })
Perhatikan bahawa kita telah mengatasi kaedah onBodyPartReceived () . Pelaksanaan lalai mengumpulkan potongan HTTP yang diterima ke dalam ArrayList . Ini boleh menyebabkan penggunaan memori yang tinggi, atau pengecualian OutOfMemory ketika cuba memuat turun fail yang besar.
Daripada mengumpulkan setiap HttpResponseBodyPart ke dalam memori, kami menggunakan FileChannel untuk menulis bait ke fail tempatan kami secara langsung . Kami akan menggunakan kaedah getBodyByteBuffer () untuk mengakses kandungan bahagian badan melalui ByteBuffer .
ByteBuffers mempunyai kelebihan bahawa memori dialokasikan di luar timbunan JVM, sehingga tidak mempengaruhi memori aplikasi.
4.2. Apache Commons IO
Perpustakaan lain yang sangat digunakan untuk operasi IO ialah Apache Commons IO. Kita dapat melihat dari Javadoc bahawa ada kelas utiliti bernama FileUtils yang digunakan untuk tugas manipulasi fail umum.
Untuk memuat turun fail dari URL, kami boleh menggunakan satu-pelapik ini:
FileUtils.copyURLToFile( new URL(FILE_URL), new File(FILE_NAME), CONNECT_TIMEOUT, READ_TIMEOUT);
Dari sudut prestasi, kod ini sama dengan yang telah kita contohkan dalam bahagian 2.
Kod yang mendasari menggunakan konsep yang sama iaitu membaca dalam satu gelung beberapa bait dari InputStream dan menulisnya ke OutputStream .
Satu perbezaan adalah hakikat bahawa di sini kelas URLConnection digunakan untuk mengawal tamat sambungan sehingga muat turun tidak menyekat untuk sejumlah besar masa:
URLConnection connection = source.openConnection(); connection.setConnectTimeout(connectionTimeout); connection.setReadTimeout(readTimeout);
5. Muat turun semula
Memandangkan sambungan internet gagal dari semasa ke semasa, sangat berguna bagi kita untuk menyambung semula muat turun, dan bukannya memuat turun fail lagi dari byte zero.
Mari tulis semula contoh pertama dari sebelumnya, untuk menambahkan fungsi ini.
Perkara pertama yang harus kita ketahui ialah kita dapat membaca ukuran fail dari URL yang diberikan tanpa benar-benar memuat turunnya dengan menggunakan kaedah HTTP HEAD:
URL url = new URL(FILE_URL); HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); httpConnection.setRequestMethod("HEAD"); long removeFileSize = httpConnection.getContentLengthLong();
Sekarang kerana kami mempunyai ukuran keseluruhan kandungan fail, kami dapat memeriksa sama ada fail kami telah dimuat turun sebahagiannya. Sekiranya demikian, kami akan meneruskan muat turun dari bait terakhir yang dirakam pada cakera:
long existingFileSize = outputFile.length(); if (existingFileSize < fileLength) { httpFileConnection.setRequestProperty( "Range", "bytes=" + existingFileSize + "-" + fileLength ); }
Apa yang berlaku di sini ialah kita telah mengkonfigurasi URLConnection untuk meminta bait fail dalam julat tertentu . Julatnya akan bermula dari bait terakhir yang dimuat turun dan akan berakhir pada bait yang sesuai dengan ukuran fail jauh.
Cara lain yang biasa digunakan untuk menggunakan tajuk Range adalah dengan memuat turun fail dalam potongan dengan menetapkan julat bait yang berbeza. Sebagai contoh, untuk memuat turun fail 2 KB, kita boleh menggunakan julat 0 - 1024 dan 1024 - 2048.
Perbezaan halus lain dari kod di bahagian 2. adalah bahawa FileOutputStream dibuka dengan parameter append ke true :
OutputStream os = new FileOutputStream(FILE_NAME, true);
Setelah kami melakukan perubahan ini, selebihnya kodnya sama dengan yang kami lihat di bahagian 2.
6. Kesimpulannya
Kami telah melihat dalam artikel ini beberapa cara untuk memuat turun fail dari URL di Java.
Pelaksanaan yang paling umum adalah pelaksanaan di mana kita menyimpan bait ketika melakukan operasi membaca / menulis. Pelaksanaan ini selamat digunakan bahkan untuk fail besar kerana kita tidak memuat keseluruhan fail ke dalam memori.
Kami juga telah melihat bagaimana kami dapat melaksanakan muat turun sifar menggunakan Saluran Java NIO . Ini berguna kerana meminimumkan jumlah pertukaran konteks yang dilakukan semasa membaca dan menulis bait dan dengan menggunakan penyangga langsung, bait tidak dimuat ke dalam memori aplikasi.
Juga, kerana biasanya memuat turun fail dilakukan melalui HTTP, kami telah menunjukkan bagaimana kami dapat mencapainya menggunakan pustaka AsyncHttpClient.
Kod sumber untuk artikel tersebut terdapat di GitHub.