2013-06-11 18 views
16

Podczas pracy ze strumieniami binarnymi (tj. Tablice byte[]), głównym punktem używania BinaryReader lub BinaryWriter wydaje się być uproszczony odczyt/zapis pierwotnych typów danych z strumień, używając metod takich jak ReadBoolean() i biorąc pod uwagę kodowanie. Czy to cała historia? Czy istnieje nieodłączna zaleta lub wada, jeśli pracuje się bezpośrednio z urządzeniem Stream, bez korzystania z BinaryReader/BinaryWriter? Większość metod, takich jak Read(), wydaje się być taka sama w obu klasach i przypuszczam, że działają one identycznie pod spodem.Używanie Stream.Read() vs BinaryReader.Read() do przetwarzania strumieni binarnych

Rozważmy prosty przykład przetwarzania pliku binarnego na dwa różne sposoby (edit: Zdaję sobie sprawę w ten sposób jest nieskuteczny i że bufor może być używany, to tylko przykład):

// Using FileStream directly 
using (FileStream stream = new FileStream("file.dat", FileMode.Open)) 
{ 
    // Read bytes from stream and interpret them as ints 
    int value = 0; 
    while ((value = stream.ReadByte()) != -1) 
    { 
     Console.WriteLine(value); 
    } 
} 


// Using BinaryReader 
using (BinaryReader reader = new BinaryReader(FileStream fs = new FileStream("file.dat", FileMode.Open))) 
{ 
    // Read bytes and interpret them as ints 
    byte value = 0;  
    while (reader.BaseStream.Position < reader.BaseStream.Length) 
    { 
     value = reader.ReadByte(); 
     Console.WriteLine(Convert.ToInt32(value)); 
    } 
} 

wyjście być tym samym, ale co się dzieje wewnętrznie (np. z perspektywy OS)? Czy to - ogólnie rzecz biorąc - ważne, która implementacja jest używana? Czy jest jakiś cel używania BinaryReader/BinaryWriter, jeśli nie potrzebujesz dodatkowych metod, które zapewniają? W tym konkretnym przypadku, MSDN mówi, to w odniesieniu do Stream.ReadByte():

Domyślna implementacja na Stream tworzy nową tablicę jednobajtowych a następnie wywołuje Read. Chociaż jest to formalnie poprawne, jest to niewydajne.

Korzystanie GC.GetTotalMemory(), to pierwsze podejście wydaje się przeznaczyć 2x tyle miejsca jako drugi, ale AFAIK nie powinno to mieć miejsce w przypadku bardziej ogólny sposób Stream.Read() jest używany (np czytania w kawałki za pomocą bufor). Mimo to wydaje mi się, że te metody/interfejsy można łatwo zunifikować ...

+0

Domyślna implementacja Stream.ReadByte ma być nadmiernie używana w każdej konkretnej implementacji Stream. Co pozostawia pytanie bez odpowiedzi, dlaczego potrzebujemy nowej klasy StreamReader, zamiast być w stanie polegać na (implementacjach) Stream, aby zrobić to, co trzeba? – yoyo

+0

@yoyo, ponieważ klasa Stream jest ogólnie źle zaprojektowana. To jest zbyt "ogólne". Nie wszystkie strumienie obsługują wyszukiwanie lub czytanie (sprawnie) lub czytanie lub pisanie. To po prostu zły projekt OOP. Zamiast tego powinni używać interfejsów. –

Odpowiedz

11

Nie, nie ma zasadniczej różnicy między tymi dwoma podejściami. Dodatkowy czytnik dodaje trochę buforowania, więc nie powinieneś ich mieszać. Ale nie oczekuj żadnych znaczących różnic w wydajności, wszystkie są zdominowane przez rzeczywiste wejścia/wyjścia.

Więc

  • używać strumienia kiedy trzeba (tylko) byte[] przenieść. Jak to często bywa w przypadku wielu scenariuszy transmisji strumieniowej.
  • użyj BinaryWriter i BinaryReader, gdy masz inny podstawowy typ (w tym prosty byte) danych do przetworzenia. Ich głównym celem jest konwersja wbudowanych typów ramowych na byte[].
7

Jedną z dużych różnic jest sposób buforowania wejść/wyjść. Jeśli piszesz/czytasz tylko kilka bajtów tutaj lub tam, BinaryWriter/BinaryReader będzie działać poprawnie. Ale jeśli musisz odczytać MB danych, odczytanie jednego byte, Int32, itp. ... w tym samym czasie będzie nieco powolne. Zamiast tego możesz czytać większe fragmenty i parsować.

Przykład:

// Using FileStream directly with a buffer 
using (FileStream stream = new FileStream("file.dat", FileMode.Open)) 
{ 
    // Read bytes from stream and interpret them as ints 
    byte[] buffer = new byte[1024]; 
    int count; 
    // Read from the IO stream fewer times. 
    while((count = stream.Read(buffer, 0, buffer.Length)) > 0) 
     for(int i=0; i<count; i++) 
      Console.WriteLine(Convert.ToInt32(buffer[i])); 
} 

Teraz jest to trochę off topic ... ale będę ją wyrzucić tam: Jeśli chciał uzyskać bardzo przebiegły ... i naprawdę dać sobie wzrost wydajności ...(Choć to może być uważane za niebezpieczne) Zamiast analizowania KAŻDĄ Int32, można zrobić je wszystkie na raz za pomocą Buffer.BlockCopy()

Inny przykład:

// Using FileStream directly with a buffer and BlockCopy 
using (FileStream stream = new FileStream("file.dat", FileMode.Open)) 
{ 
    // Read bytes from stream and interpret them as ints 
    byte[] buffer = new byte[1024]; 
    int[] intArray = new int[buffer.Length >> 2]; // Each int is 4 bytes 
    int count; 
    // Read from the IO stream fewer times. 
    while((count = stream.Read(buffer, 0, buffer.Length)) > 0) 
    { 
     // Copy the bytes into the memory space of the Int32 array in one big swoop 
     Buffer.BlockCopy(buffer, 0, intArray, count); 

     for(int i=0; i<count; i+=4) 
      Console.WriteLine(intArray[i]); 
    } 
} 

kilka rzeczy, o których należy pamiętać o tym przykładzie: To trwa 4 bajty na Int32 zamiast jednego ... Więc przyniesie różne wyniki. Możesz to zrobić również w przypadku innych typów danych niż Int32, ale wielu twierdzi, że powinno się kierować zestawem. (Chciałem tylko przedstawić coś, o czym warto pomyśleć ...)

+0

Dziękuję. Podany przeze mnie przykład był hipotetyczny, zdaję sobie sprawę, że nie byłby skuteczny w przypadku dużych plików w rzeczywistości - zawsze robię buforowane podejście (twój pierwszy przykład) w tym przypadku. Sposób, w jaki to rozumiem, zarówno 'Stream.Read()', jak i 'BinaryReader.Read()' pozwalają zrobić to samo. Ponadto, od .NET 4, 'Stream.CopyTo()' robi to za Ciebie. – w128

+1

Inną rzeczą, o której należy pomyśleć, jest to, że robisz coś w rodzaju drżenia ręki TCP ... jeśli piszesz po jednym bajcie, a klient czyta w blokach ... wtedy będziesz miał problem. Tak więc w tym przypadku, kiedy piszę, robię to w blokach, a kiedy czytam, czytam z 'BinaryReader'. W ten sposób jestem objęty. – Andrew

+0

Andrew: W takim scenariuszu, uważam, że zachowanie byłoby takie samo przy użyciu zarówno 'Stream.Read()' jak i 'BinaryReader.Read()' gdybyś użył bufora jednego bajtu w obu przypadkach. – w128