最近在写一个项目啊,要从Github上面搬文件下来。文件很大,那么肯定要搬出传统艺能多线程下载——提一嘴,Java中实现多线程下载其实不用那么麻烦,把InputStream集合到ArrayList里转换成平行流,在平行流里完成下载就好了(源码我放到最后)。
现在问题来了:文件很大,服务器用的是——
Transfer-Encoding: chunked
也就是说,服务器不会返回Content-Length。没有Content-Length,也就没有文件大小。没有文件大小,我就不能切片下发给每个线程去完成下载,那怎么办呢?

这时候,我注意到文档里面有这么一段:

什么意思呢?就是,你只要给服务器发送的请求头中有Range请求且服务器支持(大部分都支持,因为很多客户端需要多线程下载)时,服务器在响应头内就会回复一个content-range,其格式为:
content-range: bytes [切片开始位置]-[切片结束位置]/[文件总大小]
看见[文件总大小]了没?那个就是和你要的Content-Length等效的东西。
举个例子:
CMD里输入:
curl http://114514.cn/ -i -H "Range: bytes=0-1"
输出:
HTTP/1.1 206 Partial Content
...
Content-Length: 2
Content-Range: bytes 0-1/996
...
虽然这个有Content-Length,不过实验品嘛,没关系,权当看不见。
看到那个“996”了没?像不像社畜的你?这个就是总大小。但是可能你会说“不对啊,Content-Length是2,而Content-Range却说有996个bytes啊?”
这是因为,看见“/996”前面的“0-1”了嘛?你在请求的时候只请求了0 byte - 1 byte 这个切片,自然也只返回了第0个和第1个字节,加起来是两个。也就是说,Content-Length只会报告当前包有多少个字符,而Content-Range报告的总字节数是无视切割的,会返回总的字节数。
不放心?那就这样:
CMD里输入:
curl http://114514.cn/ -i -H "Range: bytes=0-"
输出:
HTTP/1.1 206 Partial Content
...
Content-Length: 996
Content-Range: bytes 0-995/996
...
为了节省服务器消耗,每次请求的时候,只用请求Range: bytes=0-0就行了。
然后用正则表达式把文件总大小抓出来,这样就可以下发给线程了——而且完全不用担心416捏。
然后这里是多线程下载的Java源码。
//如果是二进制文件请把BufferedReader换成BufferedInputStream
//把BufferedWriter换成BufferedOutputStream
//把StringBuilder换成byte[]
//前面准备:将每个切片的范围生成好,然后按照范围生成threadCount(见下)个HttpURLConnection。
int threadCount = ... //线程个数。
ArrayList<BufferedReader> ins = new ArrayList<>(threadCount);
ins.add(...); //将每个切片的HttpURLConnection的getInputStream转换成Reader加到ins里。
StringBuilder sb = new StringBuilder(threadCount);
//利用平行流完成多线程下载。
ins.parallelStream().forEach((in) -> {
String str;
try {
while ((str = in.readLine()) != null) {
sb.append(str);
}
} catch (IOException exc) {
throw new RuntimeException(exc);
}
});
BufferedWriter out = new BufferedWriter(new FileWriter(to));
out.write(sb.toString());
