最近我在一个需要比以前更多的IO交互的项目上工作,我觉得我想要查看常规库(特别是Commons IO)并解决更深入的IO问题 .
作为一项学术测试,我决定实现一个基本的,多线程的HTTP下载器 . 这个想法很简单:提供一个下载URL,代码将下载该文件 . 为了提高下载速度,将文件分块并同时下载每个块(使用HTTP Range: bytes=x-x
标头)以尽可能多地使用带宽 .
我有一个工作原型,但你可能已经猜到了,它并不完全理想 . 目前我手动启动3个“下载程序”线程,每个线程下载文件的1/3 . 这些线程使用通用的同步“文件编写器”实例来实际将文件写入磁盘 . 完成所有线程后,“文件编写器”完成,任何打开的流都关闭 . 一些代码片段可以给你一个想法:
线程启动:
ExecutorService downloadExecutor = Executors.newFixedThreadPool(3);
...
downloadExecutor.execute(new Downloader(fileWriter, download, start1, end1));
downloadExecutor.execute(new Downloader(fileWriter, download, start2, end2));
downloadExecutor.execute(new Downloader(fileWriter, download, start3, end3));
每个“下载程序”线程下载一个块(缓冲)并使用“文件编写器”写入磁盘:
int bytesRead = 0;
byte[] buffer = new byte[1024*1024];
InputStream inStream = entity.getContent();
long seekOffset = chunkStart;
while ((bytesRead = inStream.read(buffer)) != -1)
{
fileWriter.write(buffer, bytesRead, seekOffset);
seekOffset += bytesRead;
}
"file writer"使用 RandomAccessFile
到 seek()
和 write()
磁盘写入磁盘:
public synchronized void write(byte[] bytes, int len, long start) throws IOException
{
output.seek(start);
output.write(bytes, 0, len);
}
考虑到所有事情,这种方法似乎有效 . 但是,它不能很好地工作 . 我对以下几点有一些建议/帮助/意见表示感谢 . 非常感激 .
-
此代码的 CPU usage 是通过屋顶 . 对于这个CPU使用率来自哪里,我有点神秘,因为我没想到这一点 .
-
通常,3个线程中有1个显着 lagging behind . 其他2个线程将完成,之后它需要第三个线程(看起来主要是第一个具有第一个块的线程)30秒或更长时间才能完成 . 我可以从任务管理器看到javaw进程仍在进行小的IO写操作,但是我猜不到竞争条件?) .
-
尽管事实上我已经选择了一个很大的缓冲区(1MB),但我感觉
InputStream
几乎从未实际填充缓冲区,导致更多的IO写入比我想要的更多 . 我确定这是否是最好的方法 . -
我意识到Java可能不是做这样的事情的理想语言,但是我的性能比我目前的实现要好得多 . 在这种情况下,NIO值得探索吗?
注意:我使用Apache HTTPClient进行HTTP交互,这是 entity.getContent()
来自的地方(如果有人想知道的话) .
4 回答
回答我自己的问题:
CPU使用率增加是由于等待线程完成的
while() {}
循环 . 事实证明,awaitTermination
是等待Executor
完成的更好的选择:)(和3和4)这似乎是野兽的本性;最后,我通过仔细同步每个下载大量数据的不同线程(好吧,特别是将这些块写回磁盘)来实现我想要做的事情 .
据推测,Apache HTTP客户端将使用较小的缓冲区进行一些缓冲 . 它需要一个缓冲区来合理地读取HTTP头,并且可能需要处理分块编码 .
我想在Windows上获得最佳性能的想法是使用IO completions ports . 但是,对你来说,重要的是,你可以用JNI推出自己的包装器 .
设置一个非常大的套接字接收缓冲区但实际上,您的性能将受到网络带宽的限制,而不受CPU带宽的限制 . 你所做的只是为每个下载器分配1/3的网络带宽 . 如果你得到很多好处,我会感到惊讶 .