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



我正在尝试在 Kotlin 中下载大文件 (<1GB(,因为我已经知道我正在使用 okhttp 并且几乎遵循了这个问题的答案。除了我使用 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 库。

从版本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.")
}

可以取消 BufferedInputStream。或者,由于它在Oracle的java中的默认缓冲区大小是8192,请使用更大的ByteArray,例如4096。

但是,最好是使用java.nio或尝试Files.copy:

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

这将删除大约 12 行代码。

另一种方法是发送带有标头的请求以压缩gzip压缩Accept-Encoding: gzip,因此传输花费的时间更少。在此处的响应中,当给出响应标头Content-Encoding: gzip时,可能会将is包装在new GZipInputStream(is)中。或者,如果可行,存储压缩的文件,并带有附加结尾.gz;mybiography.mdmybiography.md.gz.

最新更新