2016-05-26 35 views
1

Edit (Ich habe den Titel angepasst): Ich verwende CSV.foreach, aber das beginnt in der ersten Zeile. Ich möchte beginnen, eine Datei an einer beliebigen Zeile zu lesen, ohne die Datei in den Speicher zu laden. CSV.foreach funktioniert gut zum Abrufen von Daten am Anfang einer Datei, aber nicht für Daten, die ich am Ende einer Datei benötige.Wählen Sie die Startzeile für CSV.foreach oder ähnliche Methode? Ich möchte keine Datei in den Speicher laden

This answer ist vergleichbar mit dem, was ich suche, aber es lädt die gesamte Datei in den Speicher; was ich nicht machen will.

Ich habe eine 10GB-Datei und die key Spalte aufsteigend sortiert wird:

# example 10gb file rows 
key,state,name 
1,NY,Jessica 
1,NY,Frank 
1,NY,Matt 
2,NM,Jesse 
2,NM,Saul 
2,NM,Walt 
etc.. 

finde ich die Linie, die ich mit diesem Weg beginnen soll ...

file = File.expand_path('~/path/10gb_file.csv') 

File.open(file, 'rb').each do |line| 
    if line[/^2,/] 
    puts "#{$.}: #{line}" # 5: 2,NM,Jesse 
    row_number = $. # 5 
    break 
    end 
end 

... und ich möchte row_number nehmen und etwas zu tun, nicht aber die 10GB-Datei in den Speicher laden:

CSV.foreach(file, headers: true).drop(row_number) { |row| "..load data..." } 

Zuletzt behandle ich es momentan wie das nächste Snippet; Es funktioniert einwandfrei, wenn die Zeilen zur Vorderseite der Datei zeigen, aber nicht, wenn sie sich in der Nähe des Endes befinden.

CSV.foreach(file, headers: true) do |row| 
    next if row['key'].to_i < row_number.to_i 
    break if row['key'].to_i > row_number.to_i 

    "..load data..." 
end 

Ich versuche CSV.foreach zu verwenden, aber ich bin offen für Vorschläge.

  • Verwenden IO oder File und lesen Sie die Datei Zeile für Zeile
  • Erhalten Sie die Kopfzeile und bauen die hash: Ein alternativer Ansatz ich überlege aber scheint nicht für Zahlen in Richtung der Mitte einer Datei effizient zu sein manuell
  • Read the file from the bottom für Zahlen in der Nähe der max key Wert
+1

Es gibt Möglichkeiten, auf eine Datei mit einem bestimmten Offset zuzugreifen. Siehe die Dokumentation von IO.read, IO.seek und et. al. ('File' ist ein' IO'). – Raffael

+1

Erwägen Sie, Ihre Daten in einer Datenbank zu speichern. Diese Dinge sind wahnsinnig gut beim Zugriff auf Daten auf verschiedene Arten :) – Raffael

+0

@Raffael Danke für die Vorschläge, ich werde in IO.read' und 'IO.seek' schauen. Ich habe darüber nachgedacht, die Daten auch in der Datenbank zu speichern, wollte aber sehen, ob ich das Laden einer CSV optimieren könnte, weil der Datensatz, den ich verwende, ziemlich häufig ersetzt wird. – dwyd

Antwort

2

Ich denke, Sie haben die richtige Idee. Da Sie gesagt haben, dass Sie sich keine Gedanken über Felder machen, die mehrere Zeilen umfassen, können Sie mithilfe von IO-Methoden eine bestimmte Zeile in der Datei suchen und dort analysieren. Hier ist, wie Sie könnte es tun:

begin 
    file = File.open(FILENAME) 

    # Get the headers from the first line 
    headers = CSV.parse_line(file.gets) 

    # Seek in the file until we find a matching line 
    match = "2," 
    while line = file.gets 
    break if line.start_with?(match) 
    end 

    # Rewind the cursor to the beginning of the line 
    file.seek(-line.size, IO::SEEK_CUR) 

    csv = CSV.new(file, headers: headers) 

    # ...do whatever you want... 
ensure 
    # Don't forget the close the file 
    file.close 
end 

Das Ergebnis der oben ist, dass csv eine CSV-Objekt, dessen erste Zeile sein wird, ist die Zeile, die mit 2, beginnt.

Ich Benchmarks dies mit einer 8MB (170k Zeilen) CSV-Datei (von Lahman's Baseball Database) und festgestellt, dass es viel, viel schneller als mit CSV.foreach allein war. Für eine Aufzeichnung in der Mitte der Datei war es etwa 110x schneller und für eine Aufzeichnung gegen Ende etwa 66x schneller.Wenn Sie möchten, können Sie hier einen Blick auf die Benchmark werfen: https://gist.github.com/jrunning/229f8c2348fee4ba1d88d0dffa58edb7

Offensichtlich 8MB ist nichts wie 10GB, also unabhängig davon wird Sie eine lange Zeit dauern. Aber ich bin mir ziemlich sicher, dass dies für Sie ein ganzes Stück schneller sein wird, während Sie gleichzeitig Ihr Ziel erreichen, nicht alle Daten gleichzeitig in der Datei zu lesen.

+0

danke! Ich werde das heute ausprobieren. Ich schätze die Hilfe. – dwyd

+0

Danke nochmal! Das war, was ich brauchte. Um weitere Details für andere hinzuzufügen, die diese Antwort finden könnten, würde ich vorschlagen, 'csv.each {| line | "Daten hier verarbeiten"} 'unter' # ... mach was immer du willst ... 'und gib vielleicht einen Hinweis ein, dass auf Felder innerhalb jeder Schleife zugegriffen werden kann, wie diese 'Zeile [' Feldname ']' '. – dwyd

+0

Ich bin froh, dass es geholfen hat. Ich wäre neugierig zu wissen, welchen Effekt es auf Ihre Laufzeit bei der Verarbeitung dieser 10 GB-Datei hatte. –

0

Foreach werden alles tun, was Sie brauchen. Es streamt, also funktioniert es gut mit großen Dateien.

CSV.foreach('~/path/10gb_file.csv') do |line| 
    # Only one line will be read into memory at a time. 
    line 

end 

schnellste Weg, um Daten zu überspringen, dass wir nicht interessiert sind in durch einen Teil der Datei zu wechseln lesen zu verwenden ist.

File.open("/path/10gb_file.csv") do |f| 
    f.seek(107) # skip 107 bytes eg. one line. (constant time) 
    f.read(50) # read first 50 on second line 
end 
+0

Ja, aber ich suche nach einer Möglichkeit, mit einer beliebigen Zeile zu beginnen. Z.B. Beginnen Sie bei Zeile 100.000 und nicht bei Zeile 1. Würden Sie '' '' '' '' CSV.foreach nicht hinzufügen (Datei, Header: true) .drop (Zeilennummer) {| row | "..load data ..."} 'weil die gesamte Datei in den Speicher gelesen wird, bevor die Drop-Methode verwendet wird? – dwyd

+0

Das Durchforsten der Datei mit foreach könnte eigentlich in Ordnung sein, da diese Methode nicht die ganze Datei im Speicher behalten muss, nur einen kleinen Teil davon gleichzeitig. Es könnte jedoch einige Zeit dauern. – Raffael

+0

@Raffael, Es funktioniert gut, wenn die Daten zum Anfang der Datei sind, aber es dauert Zeit, um zu den Daten gegen Ende der Datei zu gelangen. Wenn ich die ersten 100.000 Zeilen brauche, dauert es etwa 5 Minuten, um in ein Hash-Array zu laden. Wenn ich 100.000 Zeilen in der Mitte der Datei benötige, kann es auf meinem lokalen Computer 30 Minuten oder länger dauern, da zuerst alle nicht benötigten Zeilen übersprungen werden müssen. – dwyd