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

时间:2018-10-23 07:47:39

标签: java kotlin okhttp3

我正在尝试在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上运行它时,我自己的测试似乎也证实了这一点,因为与python中的可比脚本相比,它使用更多的CPU并获得了更低的下载速度,并且使用了wget

我想知道是否有一种方法可以给我一些回调,如果x字节可用或者它是文件的末尾,那么该回调将被调用,这样我就不必不断尝试以获取更多数据而无需知道是否有。

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

2 个答案:

答案 0 :(得分: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.")
}

答案 1 :(得分:0)

可以消除BufferedInputStream。或者由于Oracle Java中其默认缓冲区大小为8192,请使用更大的ByteArray,例如4096。

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

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

这将删除大约12行代码。

另一种方法是发送带有标头的请求以缩小 gzip 压缩Accept-Encoding: gzip,因此传输所需的时间更少。然后,在此响应中,可能会在给定响应标头is的情况下将new GZipInputStream(is)包装在Content-Encoding: gzip中。或者,如果可行,存储压缩后的文件以.gz结尾的文件; mybiography.mdmybiography.md.gz