【问题标题】:How can I more efficently download large files over http?如何通过 http 更有效地下载大文件?
【发布时间】:2019-03-27 09:06:54
【问题描述】:

我正在尝试在 Kotlin 中下载大文件 (this question 的答案。除了我使用的是 Kotlin 而不是 java,所以语法略有不同。

val client = OkHttpClient()
val request = Request.Builder().url(urlString).build()
val response = client.newCall(request).execute()

val is = response.body().byteStream()

val input = BufferedInputStream(is)
val output = FileOutputStream(file)

val data = ByteArray(1024)
val total = 0L
val count : Int
do {
    count = input.read(data)
    total += count
    output.write(data, 0, count)
} while (count != -1)

output.flush()
output.close()
input.close()

它可以在不使用太多内存的情况下下载文件,但它似乎不必要地无效,因为它不断尝试写入更多数据而不知道是否有任何新数据到达。 在资源非常有限的虚拟机上运行它时,我自己的测试似乎也证实了这一点,因为它似乎使用更多的 CPU,但下载速度低于 python 中的类似脚本,并且使用wget

我想知道是否有一种方法可以让我在 x 字节可用或文件结尾时调用回调,这样我就不必不断尝试获取更多数据知道有没有。

编辑: 如果使用 okhttp 无法实现,我使用其他东西没有问题,只是它是我习惯的 http 库。

【问题讨论】:

  • 它确实知道更多的数据已经到达,因为count 等于接收到的数据量,并且它一直循环直到count-1。还是我误会了什么?
  • 在调用 input.read(data) 之前它并不知道,并且该函数可以返回 0 或其他非常小的数字。如果我准备好例如 1024 字节,我只想将新数据写入磁盘。
  • while ((count = input.read(data)) == 0) { Thread.sleep(50L); } (虽然没用过。)
  • 好吧,这至少会减少不必要的 CPU 周期,但也不能完全按照我的意愿去做,这会导致我的代码不会向磁盘写入“任何内容”,但它仍然会写入少量数据(如果 200 字节)到达磁盘。谢谢,如果没有其他建议,我会考虑它:)

标签: java kotlin okhttp3


【解决方案1】:

从版本 11 开始,Java 有一个内置的 HttpClient 实现

具有非阻塞背压的异步数据流

如果您希望代码仅在有数据需要处理时运行,这就是您所需要的。

如果您有能力升级到 Java 11,则可以使用 HttpResponse.BodyHandlers.ofFile 正文处理程序开箱即用地解决您的问题。您不必自己实现任何数据传输逻辑。

Kotlin 示例:

fun main(args: Array<String>) {    
    val client = HttpClient.newHttpClient()

    val request = HttpRequest.newBuilder()
            .uri(URI.create("https://www.google.com"))
            .GET()
            .build()

    println("Starting download...")
    client.send(request, HttpResponse.BodyHandlers.ofFile(Paths.get("google.html")))
    println("Done with download.")
}

【讨论】:

  • 谢谢,这看起来很有希望,我得看看 kotlin 是否在 java 11 上运行良好。
  • 您会发现 Kotlin 与 Java 11 互操作没有问题。reddit.com/r/Kotlin/comments/90cqno/any_news_on_java_11_support
  • 查看 Java 11 HttpClient 的文档,它看起来正是我想要的,我将在今天晚些时候实现它,然后用一个特定的代码示例更新你的答案,假设我没有点击意想不到的问题。非常感谢!
【解决方案2】:

可以取消 BufferedInputStream。或者因为它在 Oracle 的 java 中的默认缓冲区大小是 8192,所以使用更大的 ByteArray,比如 4096。

不过最好的办法是使用 java.nio 或尝试 Files.copy:

Files.copy(is, file.toPath());

这会删除大约 12 行代码。

另一种方法是发送带有一个header的请求以放气gzip压缩Accept-Encoding: gzip,因此传输时间更短。在此处的响应中,当给出响应标头Content-Encoding: gzip 时,可能将is 包装在new GZipInputStream(is) 中。或者,如果可行的话,存储压缩后的文件,加上结尾.gzmybiography.mdmybiography.md.gz

【讨论】:

  • 我尝试使用它提供的.available() 方法在 BufferedInputStream 中等待更多数据可用,但即使等待 30 秒后也没有更多数据到达(我假设 okhttp 没有在阅读之前下载更多数据?)但我会尝试Files.copy的东西:)
猜你喜欢
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
  • 2021-01-29
  • 1970-01-01
  • 2013-03-01
相关资源
最近更新 更多