如何更有效地通过 http 下载大文件?

How can I more efficently download large files over http?

我正在尝试在 Kotlin 中下载大文件 (<1GB),因为我已经知道我正在使用 okhttp 并且几乎只是使用了来自 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()

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

的原因

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

编辑: 如果用 okhttp 不可能,我使用其他东西没有问题,只是它是我习惯的 http 库。

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

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

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

这删除了大约 12 行代码。

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

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

asynchronous streams of data with non-blocking back pressure

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

如果您有能力升级到 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.")
}