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

  • 28
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 当您在下载Apache Maven二进制文件时,可能会遇到下载速度缓慢的问题。这可能是由于多种因素引起的。以下是一些可能导致下载速度缓慢的原因: 1. 网络连接不稳定或速度缓慢。这可能是由于您的互联网服务提供商(ISP)速度低于您的期望值,或者您正在使用高峰时间下载文件。 2. 您可能使用的是远程服务器。如果您与远程服务器之间的距离较远,您的下载速度可能会明显降低。 3. 下载源服务器可能正在经历网络流量峰值。这可能是由于下载了许多文件而导致的,或者是文件大小增加而导致的。 如果您的Apache Maven下载速度非常缓慢,请尝试以下方法: 1. 重启您的路由器或调整您在家中使用的网络连接。这可能会解决网络连接不稳定的问题。 2. 尝试从不同的下载源服务器下载Apache Maven。您可以在Apache Maven网站上找到可用的下载来源,并尝试从其他来源下载文件。 3. 增加您的Internet连接速度。您可以与您的互联网服务提供商联系,以升级您的计划,从而提高您的下载速度。 4. 考虑使用下载管理器。下载管理器可通过分段文件下载来提高下载速度,以及自动恢复中断的下载。 总之,如果您的Apache Maven下载速度缓慢,请尝试上述方法。此外,注意不要从不受信任的来源或下载速度过慢的来源下载文件。 ### 回答2: 可能的原因是您的网络连接较慢或者访问的服务器负载较高。您可以尝试使用其他下载源或者使用下载工具来加速下载。如果您使用的是Maven项目,还可以在项目的pom.xml文件中配置镜像仓库或者使用本地仓库来提高下载速度。 另外,为了避免网络连接问题,您也可以选择在国内的开源镜像站点下载,比如阿里云、华为云或者清华大学开源镜像站等。这些镜像站点拥有大量开源软件的镜像,下载速度较快稳定。具体的在Maven中如何配置使用镜像仓库的方法,请参考官方文档或者其他在线教程。 ### 回答3: Apache Maven是一个专业的软件构建工具,广泛应用于java项目当中。它提供了许多功能,比如依赖管理、版本管理和构建管理等等。不过使用Apache Maven时,有些用户可能会遇到下载速度很慢的问题。 造成此问题可能的原因有很多,有可能是网络问题,也有可能是服务器负载问题。如果是网络问题,建议尝试切换网络,或者等待一段时间后再次尝试。如果是服务器负载问题,则需要联系相关人员进行维护或者切换到其他可用的镜像站点。 此外,用户还可以尝试通过设置镜像仓库的方式解决下载速度慢的问题。镜像仓库提供了与中央仓库相同的软件包,但下载速度会更快。可以在maven的配置文件pom.xml或者settings.xml中添加镜像仓库,并将其设置为优先使用的仓库,以提高下载速度。 总之,下载速度慢是Apache Maven使用过程中常见的问题。只要我们通过一些有效的措施来解决,就可以更好地利用 Apache Maven 的各种功能,提高开发效率和质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值