Я пытаюсь выяснить, есть ли разница в производительности (или преимуществах), когда мы используем 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");
}
}
Мой опыт с большими размерами файлов заключался в том, что 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 работает на моем жестком диске.
Чем вы работаете? Это важно.
Если вещь, которую вы хотите сравнить, - это производительность копирования файлов, то для теста канала вы должны сделать это:
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:
Многие операционные системы могут передавать байты непосредственно из кэша файловой системы на целевой канал, не копируя их.
Основываясь на моих тестах (Win7 64bit, 6GB RAM, Java6), NIO transferFrom быстро работает только с небольшими файлами и становится очень медленным в больших файлах. Флажок NIO databuffer всегда превосходит стандартный IO.
Копирование 1000x2MB
Копирование 100x20mb
Копирование 1x1000mb
Метод transferTo() работает с кусками файла; не был предназначен как способ копирования файлов высокого уровня: Как скопировать большой файл в Windows XP?
Я тестировал производительность FileInputStream и FileChannel для декодирования файлов с кодировкой base64. В моих экспериментах я тестировал довольно большой файл, а традиционный io всегда был быстрее, чем nio.
FileChannel, возможно, имел преимущество в предыдущих версиях jvm из-за накладных расходов на синхронизацию в нескольких связанных с ним классах, но современные jvm неплохо удаляют ненужные блокировки.
Отвечая на вопрос "полезность" вопроса:
Одним из довольно тонких способов использования FileChannel
over FileOutputStream
является выполнение любой из своих операций блокировки (например, read()
или write()
) из потока, который в прерванное состояние приведет к резкому закрытию канала с помощью java.nio.channels.ClosedByInterruptException
.
Теперь это может быть хорошей вещью, если бы те, что использовали FileChannel
, были частью основной функции потока, и дизайн учитывал это.
Но это также может быть надоедливым, если использовать какую-то вспомогательную функцию, такую как функция каротажа. Например, вы можете обнаружить, что ваш выход в журнал неожиданно закрыт, если функция журнала вызвана потоком, который также прервался.
Это, к сожалению, так тонко, потому что не учет этого может привести к ошибкам, которые влияют на целостность записи. [1] [2]
Если вы не используете функцию transferTo или неблокирующие функции, вы не заметите разницы между традиционными IO и NIO (2), поскольку традиционные IO-карты соответствуют NIO.
Но если вы можете использовать функции NIO, такие как transferFrom/To или хотите использовать Buffers, то, конечно, NIO - это путь.
Мой опыт в том, что NIO намного быстрее с небольшими файлами. Но когда дело доходит до больших файлов, FileInputStream/FileOutputStream выполняется намного быстрее.
java.nio
что java.nio
быстрее с большими файлами, чем java.io
, а не меньше.
java.nio
работает быстро, пока файл достаточно мал, чтобы отображаться в память. Если он становится больше (200 МБ и более), java.io
быстрее.
transferTo
/transferFrom
будет более обычным для копирования файлов. Какая бы техника не делала ваш жесткий диск быстрее или медленнее, хотя я думаю, что это может быть проблемой, если он читает маленькие куски за раз и заставляет голову тратить слишком много времени на поиск.