Недавно работая над проектом, который требовал большего количества операций ввода-вывода, чем я привык, я почувствовал, что хочу заглянуть в обычные библиотеки (в частности, Commons IO) и заняться более глубокими проблемами ввода-вывода.
В качестве академического теста я решил реализовать базовый многопоточный загрузчик HTTP.Идея проста: укажите URL для загрузки, и код загрузит файл.Чтобы увеличить скорость загрузки, файл разделяется на части, и каждый блок загружается одновременно (используя заголовок HTTP Range: bytes=x-x
), чтобы использовать максимально возможную пропускную способность.
У меня есть рабочий прототип, но, как вы уже догадались,это не совсем идеально.На данный момент я вручную запускаю 3 "загрузчика" темы, каждая из которых загружает 1/3 файла.Эти потоки используют общий синхронизированный экземпляр «File Writer» для фактической записи файлов на диск.Когда все потоки завершены, «средство записи файлов» завершается, и все открытые потоки закрываются.Некоторые фрагменты кода, чтобы дать вам представление:
Запуск потока:
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;
}
«Модуль записи файлов» записывает на диск, используя RandomAccessFile
to seek()
и write()
фрагменты на диск:
public synchronized void write(byte[] bytes, int len, long start) throws IOException
{
output.seek(start);
output.write(bytes, 0, len);
}
Все вещисчитается, что этот подход, кажется, работает.Тем не менее, это не очень хорошо работает.Я был бы признателен за некоторые советы / помощь / мнения по следующим пунктам.Очень признателен.
- Использование ЦП этого кода не за горами.Для этого он использует половину моего ЦП (50% каждого из двух ядер), что в геометрической прогрессии больше, чем сопоставимые инструменты загрузки, которые практически не нагружают ЦП.Я немного озадачен тем, откуда происходит это использование процессора, поскольку я не ожидал этого.
- Обычно, кажется, 1 из 3 потоков, который отстает от значительно.Другие 2 потока завершатся, после чего третьему потоку (который, как представляется, в основном первый поток с первым чанком) требуется 30 или более секунд для завершения.Из диспетчера задач я вижу, что процесс javaw все еще выполняет небольшие записи ввода-вывода, но я не знаю, почему это происходит (я предполагаю условия гонки?).
- Несмотря на то, что я 'выбрав довольно большой буфер (1 МБ), у меня возникает ощущение, что
InputStream
практически никогда не заполняет буфер, что вызывает больше операций ввода-вывода, чем хотелось бы.У меня сложилось впечатление, что в этом сценарии было бы лучше сохранить доступ к IO как минимум, но я не знаю точно, является ли это лучшим подходом. - Я понимаю, что Java может небыть идеальным языком, чтобы делать что-то подобное, но я убежден, что производительность будет намного выше, чем в текущей реализации.Стоит ли исследовать NIO в этом случае?
Примечание: Я использую Apache HTTPClient для выполнения HTTP-взаимодействия, откуда берется entity.getContent()
(на случай, если кому-то интересно).