Java NIO FileChannel и производительность и полезность FileOutputstream
Я пытаюсь выяснить, есть ли разница в производительности (или преимуществах), когда мы используем nio FileChannel
против обычного FileInputStream/FileOuputStream
для чтения и записи файлов в файловую систему. Я заметил, что на моей машине оба выступают на одном уровне, также много раз FileChannel
путь медленнее. Могу ли я, пожалуйста, узнать больше, сравнивая эти два метода. Вот код, который я использовал, файл, который я тестирую, находится вокруг 350MB
. Является ли хорошим вариантом использовать классы на основе NIO для ввода/вывода файлов, если я не ищу случайного доступа или других таких расширенных функций?
package trialjavaprograms;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class JavaNIOTest {
public static void main(String[] args) throws Exception {
useNormalIO();
useFileChannel();
}
private static void useNormalIO() throws Exception {
File file = new File("/home/developer/test.iso");
File oFile = new File("/home/developer/test2");
long time1 = System.currentTimeMillis();
InputStream is = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(oFile);
byte[] buf = new byte[64 * 1024];
int len = 0;
while((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fos.close();
is.close();
long time2 = System.currentTimeMillis();
System.out.println("Time taken: "+(time2-time1)+" ms");
}
private static void useFileChannel() throws Exception {
File file = new File("/home/developer/test.iso");
File oFile = new File("/home/developer/test2");
long time1 = System.currentTimeMillis();
FileInputStream is = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(oFile);
FileChannel f = is.getChannel();
FileChannel f2 = fos.getChannel();
ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
long len = 0;
while((len = f.read(buf)) != -1) {
buf.flip();
f2.write(buf);
buf.clear();
}
f2.close();
f.close();
long time2 = System.currentTimeMillis();
System.out.println("Time taken: "+(time2-time1)+" ms");
}
}
Ответы
Ответ 1
Мой опыт с большими размерами файлов заключался в том, что java.nio
быстрее, чем java.io
. Слишком быстро. Как в диапазоне > 250%. Тем не менее, я устраняю очевидные узкие места, которые, как я полагаю, могут пострадать от вашего микро-теста. Потенциальные области для расследования:
Размер буфера. Алгоритм, который вы в основном используете,
- копирование с диска на буфер
- копирование из буфера на диск
Мой собственный опыт заключался в том, что этот размер буфера спелый для настройки. Я установил 4 КБ для одной части моего приложения, 256 КБ для другого. Я подозреваю, что ваш код страдает от такого большого буфера. Запустите некоторые тесты с буферами 1KB, 2KB, 4KB, 8KB, 16KB, 32KB и 64KB, чтобы доказать это себе.
Не выполняйте тесты Java, которые читают и записывают на один и тот же диск.
Если вы это сделаете, то вы действительно сравниваете диск, а не Java. Я бы также предположил, что если ваш процессор не занят, то, вероятно, вы столкнулись с каким-то другим узким местом.
Не используйте буфер, если вам не нужно.
Зачем копировать в память, если ваша цель - другой диск или сетевой адаптер? При больших файлах задержка не является тривиальной.
Как и другие, используйте FileChannel.transferTo()
или FileChannel.transferFrom()
. Ключевым преимуществом здесь является то, что JVM использует доступ к ОС для DMA (Прямой доступ к памяти), если присутствует. (Это зависит от реализации, но современные версии Sun и IBM для процессоров общего назначения хороши.) Что происходит, так это то, что данные поступают прямо на/из диска, на шину, а затем в пункт назначения... минуя любую схему через RAM или CPU.
Веб-приложение, в котором я провел дни и ночи, очень тяжело. Я сделал микро-тесты и реальные тесты. И результаты в моем блоге, посмотрите:
Использовать производственные данные и среды
Микро-бенчмарки склонны к искажению. Если вы можете, приложите усилия для сбора данных именно от того, что вы планируете делать с ожидаемой нагрузкой на ожидаемом вами оборудовании.
Мои тесты являются прочными и надежными, потому что они происходили на производственной системе, в мясной системе, системе под нагрузкой, собранной в журналах. Не мой ноутбук 7200 об/мин 2,5-дюймовый SATA-накопитель, в то время как я наблюдал, как JVM работает на моем жестком диске.
Чем вы работаете? Это важно.
Ответ 2
Если вещь, которую вы хотите сравнить, - это производительность копирования файлов, то для теста канала вы должны сделать это:
final FileInputStream inputStream = new FileInputStream(src);
final FileOutputStream outputStream = new FileOutputStream(dest);
final FileChannel inChannel = inputStream.getChannel();
final FileChannel outChannel = outputStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
inputStream.close();
outputStream.close();
Это не будет медленнее, чем буферизация себя с одного канала на другой, и потенциально будет значительно быстрее. Согласно Javadocs:
Многие операционные системы могут передавать байты непосредственно из кэша файловой системы на целевой канал, не копируя их.
Ответ 3
Основываясь на моих тестах (Win7 64bit, 6GB RAM, Java6), NIO transferFrom быстро работает только с небольшими файлами и становится очень медленным в больших файлах. Флажок NIO databuffer всегда превосходит стандартный IO.
-
Копирование 1000x2MB
- NIO (transferFrom) ~ 2300ms
- NIO (прямой datababuffer 5000b flip) ~ 3500 мс
- Стандартный IO (буфер 5000b) ~ 6000 мс
-
Копирование 100x20mb
- NIO (прямой datababuffer 5000b flip) ~ 4000 мс
- NIO (transferFrom) ~ 5000ms
- Стандартный IO (буфер 5000b) ~ 6500 мс
-
Копирование 1x1000mb
- NIO (прямой datababuffer 5000b flip) ~ 4500s
- Стандартный IO (буфер 5000b) ~ 7000 мс
- NIO (transferFrom) ~ 8000ms
Метод transferTo() работает с кусками файла; не был предназначен как способ копирования файлов высокого уровня:
Как скопировать большой файл в Windows XP?
Ответ 4
Я тестировал производительность FileInputStream и FileChannel для декодирования файлов с кодировкой base64. В моих экспериментах я тестировал довольно большой файл, а традиционный io всегда был быстрее, чем nio.
FileChannel, возможно, имел преимущество в предыдущих версиях jvm из-за накладных расходов на синхронизацию в нескольких связанных с ним классах, но современные jvm неплохо удаляют ненужные блокировки.
Ответ 5
Если вы не используете функцию transferTo или неблокирующие функции, вы не заметите разницы между традиционными IO и NIO (2), поскольку традиционные IO-карты соответствуют NIO.
Но если вы можете использовать функции NIO, такие как transferFrom/To или хотите использовать Buffers, то, конечно, NIO - это путь.
Ответ 6
Отвечая на вопрос "полезность" вопроса:
Одним из довольно тонких способов использования FileChannel
over FileOutputStream
является выполнение любой из своих операций блокировки (например, read()
или write()
) из потока, который в прерванное состояние приведет к резкому закрытию канала с помощью java.nio.channels.ClosedByInterruptException
.
Теперь это может быть хорошей вещью, если бы те, что использовали FileChannel
, были частью основной функции потока, и дизайн учитывал это.
Но это также может быть надоедливым, если использовать какую-то вспомогательную функцию, такую как функция каротажа. Например, вы можете обнаружить, что ваш выход в журнал неожиданно закрыт, если функция журнала вызвана потоком, который также прервался.
Это, к сожалению, так тонко, потому что не учет этого может привести к ошибкам, которые влияют на целостность записи. [1] [2]
Ответ 7
Мой опыт в том, что NIO намного быстрее с небольшими файлами. Но когда дело доходит до больших файлов, FileInputStream/FileOutputStream выполняется намного быстрее.