2013-09-03 15 views
44

Próbowałem odczytać plik do tablicy przy użyciu FileInputStream, a plik ~ 800 KB wymagał około 3 sekund, aby odczytać go w pamięci. Następnie wypróbowałem ten sam kod, z wyjątkiem FileInputStream opakowanego w BufferedInputStream i zajęło to około 76 milisekund. Dlaczego czytanie bajtów pliku przez bajt jest wykonywane o wiele szybciej za pomocą BufferedInputStream, mimo że nadal czytam bajt po bajcie? Oto kod (reszta kodu jest całkowicie nieistotna). Zauważ, że jest to "szybki" kod. można po prostu usunąć BufferedInputStream jeśli chcesz „powolny” Kod:Dlaczego użycie BufferedInputStream do odczytywania bajtu pliku przez bajt jest szybsze niż użycie FileInputStream?

InputStream is = null; 

    try { 
     is = new BufferedInputStream(new FileInputStream(file)); 

     int[] fileArr = new int[(int) file.length()]; 

     for (int i = 0, temp = 0; (temp = is.read()) != -1; i++) { 
      fileArr[i] = temp; 
     } 

BufferedInputStream jest ponad 30 razy szybciej. Znacznie więcej. Dlaczego tak jest i czy można uczynić ten kod bardziej wydajnym (bez korzystania z zewnętrznych bibliotek)?

Odpowiedz

88

W metodzie FileInputStream, metoda read() odczytuje pojedynczy bajt. Z kodu źródłowego:

/** 
* Reads a byte of data from this input stream. This method blocks 
* if no input is yet available. 
* 
* @return  the next byte of data, or <code>-1</code> if the end of the 
*    file is reached. 
* @exception IOException if an I/O error occurs. 
*/ 
public native int read() throws IOException; 

Jest to macierzyste połączenie z systemem operacyjnym, które używa dysku do odczytywania pojedynczego bajtu. To jest ciężka operacja.

W przypadku metody BufferedInputStream metoda deleguje na przeciążoną metodę read(), która odczytuje 8192 ilość bajtów i buforuje je, aż będą potrzebne. Wciąż zwraca tylko jeden bajt (ale pozostawia pozostałe w rezerwie). W ten sposób BufferedInputStream generuje mniej natywnych wywołań systemu operacyjnego w celu odczytania z pliku.

Na przykład Twój plik ma długość 32768 bajtów. Aby uzyskać wszystkie bajty w pamięci za pomocą FileInputStream, będziesz potrzebował 32768 natywnych wywołań do systemu operacyjnego. Z numerem BufferedInputStream będziesz potrzebował tylko 4, niezależnie od liczby połączeń read(), które będziesz wykonywać (nadal 32768).

Jeśli chodzi o to, jak zrobić to szybciej, warto rozważyć klasę Java 7 NIO FileChannel, ale nie mam dowodów na poparcie tego.

+1

Aah Widzę, powinienem był najpierw sprawdzić API przed pytaniem. Jest to po prostu wewnętrzny bufor 8K. To ma sens. Dzięki. Jeśli chodzi o część "bardziej wydajną", nie jest to konieczne, ale myślałem, że mój kod mógł być w jakiś sposób nadmiernie nadmiarowy. Myślę, że to nie jest. – ZimZim

+9

@ user1007059 Nie ma za co. Zauważ, że jeśli zamiast tego użyłeś metody 'read (byte [], int, int)' z '' byte [], int, int) '' byte [> 8192] 'nie musiałbyś owijać pliku' BufferedInputStream'. –

+0

@SotiriosDelimanolis Kiedy używać bajtu 'read() bajt po bajcie i kiedy używać bajtu' read (byte []) '. Myślę, że czytanie tablicy jest zawsze lepsze. następnie możesz podać przykład użycia bajtów 'read()' bajt po bajcie OR 'read (byte [])' bajt. OR 'BufferedInputStream'.? – UnKnown

1

BufferedInputStream owinięty wokół FileInputStream, zażąda danych z FileInputStream w dużych porcjach (domyślnie 512 bajtów, tak myślę.) Tak więc, jeśli czytasz 1000 znaków po jednym na raz, FileInputStream będzie musiał tylko przejść na dysk dwa razy. To będzie znacznie szybciej!

+3

Może to być [zależne od platformy] (http://stackoverflow.com/questions/16973843/bufferedreader-default-buffer-size), ale jest [** 8192 ** na bieżącym systemie Android] (https://github.com /google/j2objc/blob/master/jre_emul/android/libcore/luni/src/main/java/java/io/BufferedInputStream.java#L44). – pevik

+0

To samo, 8K, dla większości platform. –

0

FileReader

FileReader jest przeznaczona do czytania strumienie znaków.

BufferedReader

Przeczytaj tekst znak ze strumienia wejściowego, buforowanie znaków, tak aby zapewnić sprawnego czytania znaków, tablic i linii.

Według dokumentów FileReader wysyła żądanie do każdej operacji odczytu. Jest to kosztowne.
Gdzie BufferedReader wysyła żądanie, gdy jego bufor się zapełni.

Według docs

Generalnie każdy czytać wniosek złożony z czytnika powoduje odpowiedni wniosek do przeczytania być wykonany z podstawowej postaci bajtów lub strumienia. Dlatego wskazane jest objęcie BufferedReadera wokół dowolnego czytnika Reader, którego operacje read() mogą być kosztowne, takie jak FileReaders i InputStreamReaders.

Przeczytaj ten http://oopweb.com/Java/Documents/JavaNotes/Volume/chap84/ch84_3.html

+2

Te klasy nie są wymienione w pytaniu, a łącza do ich interfejsów API są zepsute. – beldaz

0

To właśnie z powodu kosztów dostępu do dysku. Załóżmy, że masz plik o rozmiarze 8kb. 8 * 1024 razy dostęp do dysku będzie potrzebny do odczytania tego pliku bez BufferedInputStream.

W tym momencie BufferedStream pojawia się na scenie i pośredniczy między FileInputStream a plikiem do odczytania.

W jednym ujęciu, otrzyma fragmenty bajtów domyślnie jest 8kb do pamięci, a następnie FileInputStream odczyta bajty od tego środkowego człowieka. Spowoduje to skrócenie czasu operacji.

private void exercise1WithBufferedStream() { 
     long start= System.currentTimeMillis(); 
     try (FileInputStream myFile = new FileInputStream("anyFile.txt")) { 
      BufferedInputStream bufferedInputStream = new BufferedInputStream(myFile); 
      boolean eof = false; 
      while (!eof) { 
       int inByteValue = bufferedInputStream.read(); 
       if (inByteValue == -1) eof = true; 
      } 
     } catch (IOException e) { 
      System.out.println("Could not read the stream..."); 
      e.printStackTrace(); 
     } 
     System.out.println("time passed with buffered:" + (System.currentTimeMillis()-start)); 
    } 


    private void exercise1() { 
     long start= System.currentTimeMillis(); 
     try (FileInputStream myFile = new FileInputStream("anyFile.txt")) { 
      boolean eof = false; 
      while (!eof) { 
       int inByteValue = myFile.read(); 
       if (inByteValue == -1) eof = true; 
      } 
     } catch (IOException e) { 
      System.out.println("Could not read the stream..."); 
      e.printStackTrace(); 
     } 
     System.out.println("time passed without buffered:" + (System.currentTimeMillis()-start)); 
    } 
Powiązane problemy