java实现高效下载文件的方法

 更新时间:2021年08月26日 10:42:55   作者:梦想画家  
这篇文章主要为大家详细介绍了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操作的助手方法。可以使用File.copy()方法从InputStream中读取所有字节,然后复制至本地文件:

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

上述代码可以正常工作,但缺点是字节被缓冲到内存中。Java为我们提供了NIO包,它有方法在两个通道之间直接传输字节,而无需缓冲。下面我们会详细讲解。

使用NIO

java NIO包提供了无缓冲情况下在两个通道之间直接传输字节的可能。

为了读来自URL的文件,需从URL流创建ReadableByteChannel :

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

从ReadableByteChannel 读取字节将被传输至FileChannel:

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

然后使用transferFrom方法,从ReadableByteChannel 类下载来自URL的字节传输到FileChannel:

fileOutputStream.getChannel()
  .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 {
        stream.getChannel().write(bodyPart.getBodyByteBuffer());
        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下载文件,仅需一行代码:

FileUtils.copyURLToFile(
  new URL(FILE_URL), 
  new File(FILE_NAME), 
  CONNECT_TIMEOUT, 
  READ_TIMEOUT);

从性能角度看,与前面JAVA IO示例相同。底层代码使用相同的概念,从InputStream读取一些字节并将它们写入OutputStream。不同之处在于,URLConnection类在这里用于控制连接超时,这样下载就不会阻塞很长时间:

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

恢复下载

考虑到internet连接的不确定性,失败时我们可以重新下载文件,但不是再次从字节0位置下载文件。
让我们重写前面的第一个示例,以添加这个功能。

我们首先要知道的是,我们可以使用HTTP HEAD方法从给定URL读取文件的大小,而无需实际下载它:

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

现在我们有了文件内容的总大小,可以检查文件是否已下载了部分内容。如果是,我们将继续从磁盘上记录的最后一个字节开始下载:

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库实现这一点。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Java语言中&&与& ||与|的区别是什么

    Java语言中&&与& ||与|的区别是什么

    这篇文章主要介绍了Java语言中&&与& ||与|的区别是什么的相关资料,需要的朋友可以参考下
    2017-04-04
  • SpringBoot集成Hadoop对HDFS的文件操作方法

    SpringBoot集成Hadoop对HDFS的文件操作方法

    这篇文章主要介绍了SpringBoot集成Hadoop对HDFS的文件操作方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-07-07
  • Springboot2 session设置超时时间无效的解决

    Springboot2 session设置超时时间无效的解决

    这篇文章主要介绍了Springboot2 session设置超时时间无效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • mybatis中嵌套查询的使用解读

    mybatis中嵌套查询的使用解读

    这篇文章主要介绍了mybatis中嵌套查询的使用解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • springBoot接入阿里云oss的实现步骤

    springBoot接入阿里云oss的实现步骤

    这篇文章主要介绍了springBoot接入阿里云oss的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • 如何从Java接口的角度切入静态工厂模式

    如何从Java接口的角度切入静态工厂模式

    静态工厂模式是一种改进的获取实例的方法。通常我们会使用new关键字调用类的构造方法来创建一个对象。静态工厂可以根据用户传入的参数来动态地实例化对象,避免一次性实例化所有对象所带来的性能浪费,同时也降低了耦合性。
    2021-06-06
  • 一篇文章带你入门Java数据类型

    一篇文章带你入门Java数据类型

    下面小编就为大家带来一篇Java的基本数据类型)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-08-08
  • Java中Boolean和boolean的区别详析

    Java中Boolean和boolean的区别详析

    boolean是基本数据类型Boolean是它的封装类,和其他类一样,有属性有方法,可以new,下面这篇文章主要给大家介绍了关于Java中Boolean和boolean区别的相关资料,需要的朋友可以参考下
    2022-07-07
  • 详解OAuth2 Token 一定要放在请求头中吗

    详解OAuth2 Token 一定要放在请求头中吗

    这篇文章主要介绍了详解OAuth2 Token 一定要放在请求头中吗,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • Java Listener监听器使用规范详细介绍

    Java Listener监听器使用规范详细介绍

    监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变
    2023-01-01

最新评论