2012-12-21 11 views
11

Mam plik CSV o pojemności 60 GB, aby dokonać pewnych modyfikacji. Klient chce pewnych zmian w danych plików, ale nie chcę regenerować danych w tym pliku, ponieważ zajęło to 4 dni.Jak odczytać plik csv po jednej linii na raz i zastępować/edytować niektóre linie podczas podróży?

Jak mogę odczytać plik, linia po linii (nie ładuję go do pamięci!) I wprowadzać zmiany w tych liniach, kiedy je zastępuję, zastępując określone wartości itp.?

+1

w takim razie dlaczego nie spróbować zmniejszyć stosując Hadoop Mapa .... –

+0

Będziesz w stanie zrobić zmiany tylko w przypadku zmian linia będzie mieć długość, która nie przekracza pierwotną długość linii –

+0

Dlaczego nie wystarczy napisać do nowy plik? A więc: 1. Przeczytaj 2. Modyfikuj 3.Write, aby skopiować. Czy to coś, czego nie chcesz robić, czy po prostu szukasz "eleganckiego" sposobu na zrobienie tego? – StampedeXV

Odpowiedz

13

Proces byłoby coś takiego:

  1. Otwórz StreamWriter do pliku tymczasowego.
  2. Otwórz plik StreamReader w pliku docelowym.
  3. Dla każdej linii:
    1. Podziel tekst na kolumny na podstawie ogranicznika.
    2. Sprawdź kolumny wartości, które chcesz zastąpić i zastąp je.
    3. Połącz wartości kolumn z powrotem za pomocą ogranicznika.
    4. Napisz wiersz do pliku tymczasowego.
  4. Po zakończeniu usuń plik docelowy i przenieś plik tymczasowy do ścieżki pliku docelowego.

Uwaga dotycząca kroków 2 i 3.1: Jeśli masz pewność co do struktury pliku i jest to dość proste, możesz to zrobić po wyjęciu z pudełka zgodnie z opisem (za chwilę dołączę próbkę). Istnieją jednak czynniki w pliku CSV, które mogą wymagać uwagi (na przykład rozpoznanie, kiedy ogranicznik jest używany dosłownie w wartości kolumny). Możesz sam tego dokonać lub wypróbować numer existing solution.


Podstawowe przykład tylko przy użyciu StreamReader i StreamWriter:

var sourcePath = @"C:\data.csv"; 
var delimiter = ","; 
var firstLineContainsHeaders = true; 
var tempPath = Path.GetTempFileName(); 
var lineNumber = 0; 

var splitExpression = new Regex(@"(" + delimiter + @")(?=(?:[^""]|""[^""]*"")*$)"); 

using (var writer = new StreamWriter(tempPath)) 
using (var reader = new StreamReader(sourcePath)) 
{ 
    string line = null; 
    string[] headers = null; 
    if (firstLineContainsHeaders) 
    { 
     line = reader.ReadLine(); 
     lineNumber++; 

     if (string.IsNullOrEmpty(line)) return; // file is empty; 

     headers = splitExpression.Split(line).Where(s => s != delimiter).ToArray(); 

     writer.WriteLine(line); // write the original header to the temp file. 
    } 

    while ((line = reader.ReadLine()) != null) 
    { 
     lineNumber++; 

     var columns = splitExpression.Split(line).Where(s => s != delimiter).ToArray(); 

     // if there are no headers, do a simple sanity check to make sure you always have the same number of columns in a line 
     if (headers == null) headers = new string[columns.Length]; 

     if (columns.Length != headers.Length) throw new InvalidOperationException(string.Format("Line {0} is missing one or more columns.", lineNumber)); 

     // TODO: search and replace in columns 
     // example: replace 'v' in the first column with '\/': if (columns[0].Contains("v")) columns[0] = columns[0].Replace("v", @"\/"); 

     writer.WriteLine(string.Join(delimiter, columns)); 
    } 

} 

File.Delete(sourcePath); 
File.Move(tempPath, sourcePath); 
+0

To zdecydowanie prosta i najprostsza droga. – richard

+0

Zaktualizowałem go, aby obsługiwał dosłowne wystąpienia ogranicznika. – HackedByChinese

+0

Po pierwsze, nie myślałem o rozmiarze.Ostateczne "File.Move" będzie prawdopodobnie bardzo wolne. Zamiast tego możesz po prostu utworzyć plik tymczasowy w tym samym folderze co plik źródłowy, a następnie usunąć źródło i zmienić jego nazwę (zamiast używać "GetTempFileName" i "File.Move"). – HackedByChinese

1

Wystarczy przeczytać plik linia po linii, z StreamReader, a następnie użyć wyrażenia regularnego! Najbardziej niesamowite narzędzie na świecie.

using (var sr = new StreamReader(new FileStream(@"C:\temp\file.csv", FileMode.Open))) 
     { 
      var line = sr.ReadLine(); 
      while (!sr.EndOfStream) 
      { 
       // do stuff 

       line = sr.ReadLine(); 
      } 

     } 
Powiązane problemy