java实现⾼效下载⽂件的⽅法
本⽂实例为⼤家分享了java实现下载⽂件的⽅法,供⼤家参考,具体内容如下
本⽂我们介绍⼏种⽅法下载⽂件。从基本JAVA IO 到 NIO包,也介绍第三⽅库的⼀些⽅法,如Async Http Client 和 Apache Commons IO.
最后我们还讨论在连接断开后如何恢复下载。
使⽤java IO
下载⽂件最基本的⽅法是java IO,使⽤URL类打开待下载⽂件的连接。为有效读取⽂件,我们使⽤openStream() ⽅法获取InputStream:
BufferedInputStream in = new BufferedInputStream(new URL(FILE_URL).openStream())
当从InputStream读取⽂件时,强烈建议使⽤BufferedInputStream去包装InputStream,⽤于提升性能。
使⽤缓存可以提升性能。read⽅法每次读⼀个字节,每次⽅法调⽤意味着系统调⽤底层的⽂件系统。当JVM调⽤read()⽅法时,程序执⾏上下⽂将从⽤户模式切换到内核模式并返回。
从性能的⾓度来看,这种上下⽂切换⾮常昂贵。当我们读取⼤量字节时,由于涉及⼤量上下⽂切换,应⽤程序性能将会很差。
为了读取URL的字节并写⾄本地⽂件,需要使⽤FileOutputStream 类的write⽅法:
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
}
使⽤BufferedInputStream,read⽅法按照我们设置缓冲器⼤⼩读取⽂件。⽰例中我们设置⼀次读取1024字节,所以BufferedInputStream 是必要的。
上述⽰例代码冗长,幸运的是在Java7中Files类包含处理IO操作的助⼿⽅法。可以使⽤py()⽅法从InputStream中读取所有字节,然后复制⾄本地⽂件:
InputStream in = new URL(FILE_URL).openStream();
上述代码可以正常⼯作,但缺点是字节被缓冲到内存中。Java为我们提供了NIO包,它有⽅法在两个通道之间直接传输字节,⽽⽆需缓冲。下⾯我们会详细讲解。
使⽤NIO
java NIO包提供了⽆缓冲情况下在两个通道之间直接传输字节的可能。
为了读来⾃URL的⽂件,需从URL流创建ReadableByteChannel :
ReadableByteChannel readableByteChannel = wChannel(url.openStream());
从ReadableByteChannel 读取字节将被传输⾄FileChannel:
FileOutputStream fileOutputStream = new FileOutputStream(FILE_NAME);
FileChannel fileChannel = Channel();
然后使⽤transferFrom⽅法,从ReadableByteChannel 类下载来⾃URL的字节传输到FileChannel:
.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
transferTo() 和 transferFrom() ⽅法⽐简单使⽤缓存从流中读更有效。依据不同的底层操作系统,数据可以直接从⽂件系统缓存传输到我们的⽂件,⽽不必将任何字节复制到应⽤程序内存中。
在Linux和UNIX系统上,这些⽅法使⽤零拷贝技术,减少了内核模式和⽤户模式之间的上下⽂切换次数。
使⽤第三⽅库
上⾯我们已经使⽤java 核⼼功能实现从URL下载⽂件。当⽆需调整性能是,我们也可以利⽤第三⽅库轻松实现。举例,在实际场景中,需要实现异步下载,我们可以封装逻辑⾄Callable,下⾯看已有库实现。
Async HTTP Client
Async HTTP Client是使⽤Netty框架执⾏异步HTTP请求的流⾏库。我们使⽤它对URL⽂件执⾏GET请求并获取其内容。⾸先需要创建HTTP client:
AsyncHttpClient client = Dsl.asyncHttpClient();
下⾯内容放到FileOutputStream:
FileOutputStream stream = new FileOutputStream(FILE_NAME);
接下来,创建HTTP GET请求并注册AsyncCompletionHandler 处理器去处理下载内容:
client.prepareGet(FILE_URL).execute(new AsyncCompletionHandler<FileOutputStream>() {
@Override
public State onBodyPartReceived(HttpResponseBodyPart bodyPart)
throws Exception {
return State.CONTINUE;
}
@Override
public FileOutputStream onCompleted(Response response)
throws Exception {
return stream;
}
})
上⾯我们覆盖了onBodyPartReceived() ⽅法。缺省实现是累加HTTP 接收块⾄ArrayList,这可能导致耗费⾼内存或下载⼤⽂件时OutOfMemory 异常。
代替累加每个HttpResponseBodyPart ⾄内存,我们使⽤FileChannel写字节⾄本地⽂件。getBodyByteBuffer()⽅法通过ByteBuffer访问bodyPart内容。ByteBuffers的优势是把内存分配到JVM堆之外,所以不会影响应⽤程序的内存。
Apache Commons IO
另⼀个⾼可⽤的IO操作库是Apache Commons IO。我们从其Javadoc看到FileUtils实⽤类,⽤于⼀般的⽂件操作任务。从URL 下载⽂件,仅需⼀⾏代码:
new URL(FILE_URL),
new File(FILE_NAME),
CONNECT_TIMEOUT,
READ_TIMEOUT);
从性能⾓度看,与前⾯JAVA IO⽰例相同。底层代码使⽤相同的概念,从InputStream读取⼀些字节并将它们写⼊OutputStream。不同之处在于,URLConnection类在这⾥⽤于控制连接超时,这样下载就不会阻塞很长时间: URLConnection connection = source.openConnection();
connection.setConnectTimeout(connectionTimeout);connect下载
connection.setReadTimeout(readTimeout);
恢复下载
考虑到internet连接的不确定性,失败时我们可以重新下载⽂件,但不是再次从字节0位置下载⽂件。
让我们重写前⾯的第⼀个⽰例,以添加这个功能。
我们⾸先要知道的是,我们可以使⽤HTTP HEAD⽅法从给定URL读取⽂件的⼤⼩,⽽⽆需实际下载它:
URL url = new URL(FILE_URL);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestMethod("HEAD");
long removeFileSize = ContentLengthLong();
现在我们有了⽂件内容的总⼤⼩,可以检查⽂件是否已下载了部分内容。如果是,我们将继续从磁盘上记录的最后⼀个字节开
始下载:
long existingFileSize = outputFile.length();
if (existingFileSize < fileLength) {
httpFileConnection.setRequestProperty(
"Range",
"bytes=" + existingFileSize + "-" + fileLength
);
}
上述代码我们配置了URLConnection以在⼀定范围内请求⽂件内容。范围将从最后下载的字节位置开始,并以远程⽂件⼤⼩的字节长度为结束。
使⽤范围HEAD标识的另⼀种常见⽅法是通过设置不同的字节范围以块形式下载⽂件。例如,要下载2KB⽂件,我们可以使⽤范围0 - 1024和1024 - 2048。
与前节代码稍微不同的是设置FileOutputStream ⽅法append参数为true:
OutputStream os = new FileOutputStream(FILE_NAME, true);
在我们做了这个更改之后,其余的代码与我们前⾯看到的代码⼀样。
总结
在本⽂中,我们已经看到Java中从URL下载⽂件的⼏种实现⽅式。
最常见的实现是在执⾏读/写操作时使⽤缓冲区。这个实现即使对于⼤⽂件也是安全的,因为我们没有将整个⽂件加载到内存中。
我们还了解了如何使⽤Java NIO通道实现零拷贝下载。这很有⽤,因为它最⼩化了在读取和写⼊字节时
执⾏的上下⽂切换的次数,并且通过使⽤直接缓冲区字节不会加载到应⽤程序内存中。另外,由于下载⽂件通常是通过HTTP完成的,我们也说明如何使⽤AsyncHttpClient库实现这⼀点。
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。