2008-10-27 12 views
16

Ok, czytam w plikach danych do tablicy bajtów. Z jakiegoś powodu ludzie, którzy generują te pliki, umieszczają na końcu pliku około półmilionowej wartości bezużytecznych bajtów zerowych. Ktoś zna szybki sposób na wykończenie tego z końca?Usuwanie końcowych wartości null z tablicy bajtów w C#

Pierwsza myśl polegała na rozpoczęciu na końcu tablicy i iteracji wstecznej, aż znalazłem coś innego niż zero, a następnie skopiowałem wszystko do tego punktu, ale zastanawiam się, czy nie ma lepszego sposobu.

Aby odpowiedzieć na kilka pytań: Czy jesteś pewien, że 0 bajtów jest zdecydowanie w pliku, a nie jest błąd w kodzie odczytu pliku? Tak, jestem tego pewien.

Czy na pewno można przyciąć wszystkie końcowe 0? Tak.

Czy w pozostałej części pliku mogą być jakieś 0? Tak, mogą znajdować się inne miejsca w "O", więc nie, nie mogę zaczynać od początku i zatrzymywać się przy pierwszej 0.

Odpowiedz

9

Biorąc pod uwagę dodatkowe pytania, na które odpowiedziano, wygląda na to, że zasadniczo robisz to, co trzeba. W szczególności musisz dotknąć każdego bajtu pliku od ostatniego 0, aby sprawdzić, czy ma on tylko 0.

Teraz, czy musisz skopiować wszystko, czy nie, zależy od tego, co robisz następnie z danymi.

  • Być może mógłbyś zapamiętać indeks i zachować go z danymi lub nazwą pliku.
  • Można skopiować dane do nowej tablicy bajtów
  • Jeśli chcesz się „naprawić” ten plik, można nazwać FileStream.SetLength do obcina pliku

W „masz czytać każdy bajt pomiędzy punktem skracania a końcem pliku "jest jednak najważniejszą częścią.

1

Zakładając 0 = zero, to prawdopodobnie najlepiej ... jako drobna poprawka może chcesz korzystać Buffer.BlockCopy kiedy wreszcie skopiować użytecznych danych ..

2

Jak o tym:

[Test] 
public void Test() 
{ 
    var chars = new [] {'a', 'b', '\0', 'c', '\0', '\0'}; 

    File.WriteAllBytes("test.dat", Encoding.ASCII.GetBytes(chars)); 

    var content = File.ReadAllText("test.dat"); 

    Assert.AreEqual(6, content.Length); // includes the null bytes at the end 

    content = content.Trim('\0'); 

    Assert.AreEqual(4, content.Length); // no more null bytes at the end 
             // but still has the one in the middle 
} 
+0

Traktowanie go jako tekstu wydaje się ryzykowne - plus właśnie potroiłeś plik IO. –

+0

O, i znacznie zwiększono procesor itd. (Kodowanie/dekodowanie zajmuje trochę czasu, nawet dla ASCII) –

+0

Kodowanie było tylko dla testu ... do napisania przykładowego pliku. Traktowanie pliku jako tekstu może jednak stanowić problem. – Rob

0

zawsze jest LINQ odpowiedzieć

byte[] data = new byte[] { 0x01, 0x02, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00 }; 
bool data_found = false; 
byte[] new_data = data.Reverse().SkipWhile(point => 
{ 
    if (data_found) return false; 
    if (point == 0x00) return true; else { data_found = true; return false; } 
}).Reverse().ToArray(); 
+0

Dodałem krótszą alternatywę LINQ w osobnej odpowiedzi. Mam nadzieję, że wam się podoba. –

+1

Jeśli jest to duży bufor, znacznie wydajniejszym rozwiązaniem byłoby po prostu użycie indeksu wstecz. Reverse() jest operacją buforowania i ma koszt wydajności. –

0

można tylko liczyć liczba zero na końcu tablicy i użyj zamiast niej .Length podczas późniejszej iteracji tablicy. Można to jednak zawrzeć w dowolny sposób. Najważniejsze jest to, że nie trzeba go kopiować do nowej struktury. Jeśli są duże, może warto.

16

Zgadzam się z Jonem. Najważniejszy jest to, że musisz "dotknąć" każdy bajt od ostatniego do pierwszego niezerowego bajtu. Coś takiego:

byte[] foo; 
// populate foo 
int i = foo.Length - 1; 
while(foo[i] == 0) 
    --i; 
// now foo[i] is the last non-zero byte 
byte[] bar = new byte[i+1]; 
Array.Copy(foo, bar, i+1); 

Jestem prawie pewny, że jest tak skuteczny, jak będziesz w stanie to zrobić.

+1

Tylko jeśli koniecznie musisz skopiować dane :) Inną opcją byłoby potraktowanie go jako tablicę szerszego typu, np. int lub długi. Prawdopodobnie wymagałoby to niebezpiecznego kodu i musiałbyś poradzić sobie z końcem tablicy osobno, gdyby miał, powiedzmy, nieparzystą liczbę bajtów (kontynuacja) –

+0

, ale prawdopodobnie byłaby bardziej wydajna w części "znajdowanie". Ja * na pewno * nie zacznę tego próbować dopóki nie udowodniłem, że to jest wąskie gardło :) –

+1

Możesz dodać minimalną kontrolę w tym 'czasie', albo skończysz próbując odczytać indeks -1, jeśli tablica ma tylko 0 bajtów. – Nyerguds

6

@Factor Mystic,

myślę, że jest to najkrótsza droga:

var data = new byte[] { 0x01, 0x02, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00 }; 
var new_data = data.TakeWhile((v, index) => data.Skip(index).Any(w => w != 0x00)).ToArray(); 
+1

Interesujące. Czy ktokolwiek ma jakieś testy porównawcze, aby zobaczyć, jak to się porównuje do metody "surowej"? Nie jest to jednak coś, do czego użyłbym LINQ. –

+1

Po prostu przetestowano to pod kątem rozwiązania @ Coderer, jest około 9 razy wolniejsze. – Michael

0

jeśli w pliku zerowe bajty mogą być prawidłowe wartości, czy wiesz, że ostatni bajt w pliku nie może być zero. jeśli tak, to iteracja wstecz i szukanie pierwszego niepustego wpisu jest prawdopodobnie najlepsza, jeśli nie, nie ma możliwości określenia, gdzie znajduje się faktyczny koniec pliku.

Jeśli wiesz więcej na temat formatu danych, na przykład nie może być ciąg bajtów o wartości większej niż dwa bajty (lub niektóre podobne ograniczenia). Wtedy możesz być w stanie przeprowadzić binarne wyszukiwanie "punktu przejścia". Powinno to być znacznie szybsze niż wyszukiwanie liniowe (zakładając, że możesz przeczytać w całym pliku).

Podstawową ideą (używając moje wcześniejsze założenie o braku rzędu zerowego bajtów), byłoby:

var data = (byte array of file data...); 
var index = data.length/2; 
var jmpsize = data.length/2; 
while(true) 
{ 
    jmpsize /= 2;//integer division 
    if(jmpsize == 0) break; 
    byte b1 = data[index]; 
    byte b2 = data[index + 1]; 
    if(b1 == 0 && b2 == 0) //too close to the end, go left 
     index -=jmpsize; 
    else 
     index += jmpsize; 
} 

if(index == data.length - 1) return data.length; 
byte b1 = data[index]; 
byte b2 = data[index + 1]; 
if(b2 == 0) 
{ 
    if(b1 == 0) return index; 
    else return index + 1; 
} 
else return index + 2; 
1

testu to:

private byte[] trimByte(byte[] input) 
    { 
     if (input.Length > 1) 
     { 
      int byteCounter = input.Length - 1; 
      while (input[byteCounter] == 0x00) 
      { 
       byteCounter--; 
      } 
      byte[] rv = new byte[(byteCounter + 1)]; 
      for (int byteCounter1 = 0; byteCounter1 < (byteCounter + 1); byteCounter1++) 
      { 
       rv[byteCounter1] = input[byteCounter1]; 
      } 
      return rv; 
     } 
+0

Cóż, istnieje Array.Copy() dla takich operacji kopiowania zbiorczego, więc może być bardziej wydajne, ale dla reszty masz dobry pomysł. – Nyerguds

-2

W moim przypadku podejście LINQ nie ukończył ^))) Powoli pracować z tablicami bajtów!

Chłopaki, dlaczego nie używasz metody Array.Copy()?

/// <summary> 
    /// Gets array of bytes from memory stream. 
    /// </summary> 
    /// <param name="stream">Memory stream.</param> 
    public static byte[] GetAllBytes(this MemoryStream stream) 
    { 
     byte[] result = new byte[stream.Length]; 
     Array.Copy(stream.GetBuffer(), result, stream.Length); 

     return result; 
    } 
+0

Jak to rozwiązuje problem? – Kevin

+0

stream.GetArray() byłoby lepszym wezwaniem do zrobienia w tym przypadku, ponieważ nie zwraca całego bufora pamięci, tylko dane, które zostały zapisane w buforze. – Gusdor

+0

... to powinno być stream.ToArray(). Mój błąd. Nie odpowiada jednak na pytanie. – Gusdor

Powiązane problemy