Próbuję dowiedzieć się, czy jest jakaś różnica w wydajności (lub zaletach), gdy używamy nio w FileChannel
porównaniu z normalnym FileInputStream/FileOuputStream
do odczytu i zapisu plików w systemie plików. Zauważyłem, że na moim komputerze oba działają na tym samym poziomie, również wielokrotnie FileChannel
droga jest wolniejsza. Czy mogę poznać więcej szczegółów porównujących te dwie metody. Oto kod, którego użyłem, plik, z którym testuję, jest w pobliżu 350MB
. Czy dobrym rozwiązaniem jest używanie klas opartych na NIO do wejścia / wyjścia plików, jeśli nie patrzę na dostęp losowy lub inne takie zaawansowane funkcje?
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
optimization
file
nio
operations
Keshav
źródło
źródło
transferTo
/transferFrom
byłoby bardziej konwencjonalne przy kopiowaniu plików. Niezależnie od tego, która technika nie powinna przyspieszyć ani zwolnić dysku twardego, chociaż myślę, że może wystąpić problem, jeśli odczytuje małe fragmenty na raz i powoduje, że głowa spędza nadmierną ilość czasu na szukaniu.Odpowiedzi:
Moje doświadczenie z większymi rozmiarami plików
java.nio
jest szybsze niżjava.io
. Solidnie szybciej. Jak w zakresie> 250%. To powiedziawszy, eliminuję oczywiste wąskie gardła, na które, jak sądzę, może ucierpieć Twój mikro-benchmark. Potencjalne obszary do zbadania:Rozmiar bufora. Algorytm, który w zasadzie masz, to
Z własnego doświadczenia wynika, że ten rozmiar bufora jest gotowy do dostrojenia. Zdecydowałem się na 4KB dla jednej części mojej aplikacji, 256KB dla innej. Podejrzewam, że Twój kod cierpi z powodu tak dużego bufora. Uruchom testy porównawcze z buforami 1KB, 2KB, 4KB, 8KB, 16KB, 32KB i 64KB, aby to udowodnić.
Nie wykonuj testów porównawczych Java, które odczytują i zapisują na tym samym dysku.
Jeśli tak, to naprawdę testujesz dysk, a nie Javę. Sugerowałbym również, że jeśli twój procesor nie jest zajęty, prawdopodobnie masz inne wąskie gardło.
Nie używaj bufora, jeśli nie musisz.
Po co kopiować do pamięci, jeśli celem jest inny dysk lub karta sieciowa? W przypadku większych plików powstałe opóźnienie jest nietrywialne.
Jak powiedzieli inni, użyj
FileChannel.transferTo()
lubFileChannel.transferFrom()
. Kluczową zaletą jest to, że JVM wykorzystuje dostęp systemu operacyjnego do DMA ( bezpośredni dostęp do pamięci ), jeśli jest obecny. (Jest to zależne od implementacji, ale nowoczesne wersje Sun i IBM na procesorach ogólnego przeznaczenia są dobre.) Co się dzieje, to dane są przesyłane bezpośrednio do / z dysku, do magistrali, a następnie do celu ... omijając dowolny obwód przez RAM lub procesor.Aplikacja internetowa, nad którą pracowałem przez całe dnie i noc, jest bardzo wymagająca. Zrobiłem też mikro testy porównawcze i testy porównawcze w świecie rzeczywistym. A wyniki są na moim blogu, spójrzcie:
Korzystaj z danych i środowisk produkcyjnych
Mikro-benchmarki są podatne na zniekształcenia. Jeśli możesz, postaraj się zebrać dane dokładnie tego, co planujesz, z oczekiwanym obciążeniem, na sprzęcie, którego oczekujesz.
Moje testy porównawcze są solidne i niezawodne, ponieważ odbywały się na systemie produkcyjnym, solidnym systemie, systemie pod obciążeniem, zebranym w dziennikach. Nie jest to 2,5-calowy dysk SATA 7200 obr./min mojego notebooka, podczas gdy intensywnie obserwowałem pracę maszyny JVM z dyskiem twardym.
Po czym biegasz? To ma znaczenie.
źródło
Jeśli rzeczą, którą chcesz porównać, jest wydajność kopiowania plików, to dla testu kanału powinieneś zrobić to:
Nie będzie to wolniejsze niż buforowanie się z jednego kanału do drugiego i potencjalnie będzie znacznie szybsze. Według Javadocs:
źródło
Na podstawie moich testów (Win7 64bit, 6GB RAM, Java6), transfer z NIO jest szybki tylko w przypadku małych plików i staje się bardzo wolny w przypadku większych plików. Przerzucanie bufora danych NIO zawsze przewyższa standardowe IO.
Kopiowanie 1000x2MB
Kopiowanie 100x20mb
Kopiowanie 1x1000mb
Metoda transferTo () działa na fragmentach pliku; nie był pomyślany jako metoda kopiowania plików wysokiego poziomu: jak skopiować duży plik w systemie Windows XP?
źródło
Odpowiadając na część pytania dotyczącą użyteczności:
Jedną raczej subtelną wadą używania
FileChannel
overFileOutputStream
jest to, że wykonanie którejkolwiek z operacji blokujących (np.read()
Lubwrite()
) z wątku, który jest w stanie przerwania , spowoduje nagłe zamknięcie kanałujava.nio.channels.ClosedByInterruptException
.To mogłoby być dobrą rzeczą, gdyby cokolwiek
FileChannel
było używane, jest częścią głównej funkcji wątku, a projekt wziął to pod uwagę.Ale może być również nieznośny, jeśli jest używany przez jakąś funkcję pomocniczą, taką jak funkcja rejestrowania. Na przykład możesz znaleźć wyjście rejestrowania nagle zamknięte, jeśli funkcja rejestrowania zostanie wywołana przez wątek, który również został przerwany.
Szkoda, że jest to tak subtelne, ponieważ nieuwzględnienie tego może prowadzić do błędów wpływających na integralność zapisu. [1] [2]
źródło
Przetestowałem wydajność FileInputStream w porównaniu z FileChannel do dekodowania plików zakodowanych w base64. W moich doświadczeniach testowałem dość duży plik i tradycyjne io było zawsze nieco szybsze niż nio.
FileChannel mógł mieć przewagę we wcześniejszych wersjach jvm ze względu na narzut synchronizacji w kilku klasach powiązanych z operacjami io, ale nowoczesne jvm całkiem dobrze radzi sobie z usuwaniem niepotrzebnych blokad.
źródło
Jeśli nie używasz funkcji transferTo lub funkcji nieblokujących, nie zauważysz różnicy między tradycyjnymi IO i NIO (2), ponieważ tradycyjne IO są mapowane na NIO.
Ale jeśli możesz korzystać z funkcji NIO, takich jak transfer z / do, lub chcesz korzystać z buforów, to oczywiście NIO jest najlepszym rozwiązaniem.
źródło
Z mojego doświadczenia wynika, że NIO działa znacznie szybciej z małymi plikami. Ale jeśli chodzi o duże pliki, FileInputStream / FileOutputStream jest znacznie szybszy.
źródło
java.nio
jest szybszy w przypadku większych plików niżjava.io
, a nie mniejszych.java.nio
jest szybki, o ile plik jest wystarczająco mały, aby można go było zmapować do pamięci. Jeśli robi się większy (200 MB i więcej),java.io
jest szybszy.FileChannel.read()
. Nie ma tylko jednego podejścia do odczytu plików przy użyciujava.nio
.