Недавно, работая над проектом, который потребовал больше взаимодействия с IO, чем я привык, я чувствовал, что хотел посмотреть прошлые регулярные библиотеки (в частности, Commons 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;
}
"Файловый писатель" записывает на диск с помощью 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% каждого из 2-х ядер) для этого, что по экспоненте больше, чем сопоставимые инструменты загрузки, которые практически не влияют на процессор. Я немного озадачен тем, откуда происходит это использование ЦП, поскольку я этого не ожидал.
- Обычно существует 1 из 3 потоков, которые отстают от значительно. Остальные 2 потока завершатся, после чего он возьмет третий поток (который, по-видимому, является главным образом первым потоком с первым фрагментом), для завершения 30 или более секунд. Я вижу из диспетчера задач, что процесс javaw все еще делает небольшие записи ввода-вывода, но я не знаю, почему это происходит (я предполагаю условия гонки?).
- Несмотря на то, что я выбрал достаточно большой буфер (1 МБ), у меня возникает ощущение, что
InputStream
почти никогда не заполняет буфер, что вызывает больше записи ввода-вывода, чем хотелось бы. У меня создалось впечатление, что в этом сценарии было бы лучше сохранить доступ к IO до минимума, но я не знаю точно, подходит ли это лучший подход. - Я понимаю, что Java не может быть идеальным языком, чтобы делать что-то подобное, но я убежден, что гораздо больше производительности, чем в моей текущей реализации. Рассматривается ли NIO в этом случае?
Примечание. Я использую HTTP-клиент Apache для взаимодействия с HTTP, откуда приходит entity.getContent()
(в случае, если кто-то задается вопросом).