Bagaimana cara melompat ke baris tertentu dalam file teks besar?


107

Apakah ada alternatif untuk kode di bawah ini:

startFromLine = 141978 # or whatever line I need to jump to

urlsfile = open(filename, "rb", 0)

linesCounter = 1

for line in urlsfile:
    if linesCounter > startFromLine:
        DoSomethingWithThisLine(line)

    linesCounter += 1

Jika saya memproses file teks besar (~15MB)dengan baris yang tidak diketahui tetapi panjangnya berbeda, dan perlu melompat ke baris tertentu, nomor mana yang saya ketahui sebelumnya? Saya merasa tidak enak dengan memprosesnya satu per satu ketika saya tahu saya dapat mengabaikan setidaknya paruh pertama file. Mencari solusi yang lebih elegan jika ada.


Bagaimana Anda tahu bahwa 1/2 pertama dari file bukanlah kumpulan "\ n" sedangkan paruh kedua adalah satu baris? Mengapa Anda merasa buruk tentang ini?
Andrew Dalke

7
Saya pikir judulnya menyesatkan - tbh 15MB sebenarnya bukan "file teks besar", untuk sedikitnya ...
pms

Jawaban:


30

linecache :

The linecacheModul memungkinkan seseorang untuk mendapatkan setiap baris dari file sumber Python, ketika mencoba untuk mengoptimalkan internal, menggunakan cache, kasus umum di mana banyak baris dibaca dari file tunggal. Ini digunakan oleh tracebackmodul untuk mengambil baris sumber untuk dimasukkan dalam pelacakan balik yang diformat ...


165
Saya baru saja memeriksa kode sumber modul ini: seluruh file dibaca di memori! Jadi saya pasti akan mengesampingkan jawaban ini untuk tujuan mengakses dengan cepat baris tertentu dalam sebuah file.
MiniQuark

MiniQuark, saya mencobanya, ini benar-benar berfungsi, dan sangat cepat. Saya perlu melihat apa yang terjadi jika saya mengerjakan selusin file secara bersamaan dengan cara ini, mencari tahu pada titik mana sistem saya mati.
pengguna63503

5
Manajer memori virtual OS Anda cukup membantu, jadi membaca file besar ke dalam memori mungkin tidak akan lambat jika Anda tidak menghasilkan banyak kesalahan halaman :) Sebaliknya, melakukannya dengan "cara bodoh" dan mengalokasikan banyak dan banyak memori bisa sangat cepat. Saya menikmati artikel pengembang FreeBSD Denmark, Poul-Henning Kamp, tentangnya: queue.acm.org/detail.cfm?id=1814327
Morten Jensen

13
coba file 100G, itu menyebalkan. saya harus menggunakan f.tell (), f.seek (), f.readline ()
whi

114

Anda tidak dapat melompat ke depan tanpa membaca file setidaknya sekali, karena Anda tidak tahu di mana letak baris baru. Anda bisa melakukan sesuatu seperti:

# Read in the file once and build a list of line offsets
line_offset = []
offset = 0
for line in file:
    line_offset.append(offset)
    offset += len(line)
file.seek(0)

# Now, to skip to line n (with the first line being line 0), just do
file.seek(line_offset[n])

2
+1, tetapi berhati-hatilah karena ini hanya berguna jika dia akan melompat ke beberapa baris acak! tetapi jika dia hanya melompat ke satu baris, maka ini sia
hasen

3
+1: Selain itu, jika file tidak berubah, indeks nomor baris dapat dijadikan acar dan digunakan kembali, yang selanjutnya mengurangi biaya awal pemindaian file.
S. Lott

Oke, setelah saya lompat ke sana, bagaimana saya memprosesnya baris demi baris mulai dari posisi ini?
pengguna63503

8
Satu hal yang perlu diperhatikan (terutama di windows): hati-hati untuk membuka file dalam mode biner, atau sebagai alternatif gunakan offset = file.tell (). Dalam mode teks di windows, baris akan menjadi satu byte lebih pendek daripada panjang mentahnya pada disk (\ r \ n diganti dengan \ n)
Brian

2
@fotografer: Gunakan read () atau readline (), mereka mulai dari posisi saat ini seperti yang ditetapkan oleh seek.
S. Lotot

22

Anda tidak benar-benar memiliki banyak opsi jika garisnya memiliki panjang yang berbeda ... sayangnya Anda perlu memproses karakter akhir baris untuk mengetahui kapan Anda maju ke baris berikutnya.

Anda dapat, bagaimanapun, secara dramatis mempercepat ini DAN mengurangi penggunaan memori dengan mengubah parameter terakhir menjadi "terbuka" menjadi sesuatu yang bukan 0.

0 berarti operasi pembacaan file tidak disangga, yang sangat lambat dan intensif disk. 1 berarti file tersebut memiliki buffering baris, yang akan menjadi peningkatan. Apa pun di atas 1 (katakanlah 8k .. yaitu: 8096, atau lebih tinggi) membaca potongan file ke dalam memori. Anda masih mengaksesnya for line in open(etc):, tetapi python hanya berjalan sedikit demi sedikit, membuang setiap potongan yang di-buffer setelah diproses.


6
8K sama dengan 8192, mungkin lebih baik menulis 8 << 10 agar aman. :)
bersantai

Apakah Anda kebetulan tahu apakah buffer ditentukan pada byte? Apa format yang sesuai? Bisakah saya menulis '8k'? Atau harus '8096'?
pengguna63503

1
HAHAHA ... harus hari Jumat ... saya jelas tidak bisa mengerjakan matematika. Ukuran buffer memang merupakan bilangan bulat yang mengekspresikan byte, jadi tulis 8192 (bukan 8096 :-)), daripada 8
Jarret Hardie

Kesenangan saya - semoga berhasil. Pada sistem modern, Anda mungkin bisa sedikit meningkatkan ukuran buffer. 8k hanyalah sisa-sisa dalam ingatan saya untuk beberapa alasan yang tidak dapat saya identifikasi.
Jarret Hardie

Saya telah melakukan beberapa pengujian di sini, dan menyetelnya ke -1 (os default, seringkali 8k, tetapi seringkali sulit untuk dikatakan), tampaknya secepat yang didapat. Yang mengatakan, sebagian dari itu mungkin saya uji di server virtual.
Oscar Smith

12

Saya mungkin dimanjakan oleh ram yang melimpah, tapi 15 M tidaklah besar. Membaca ke dalam memori dengan readlines() adalah apa yang biasanya saya lakukan dengan file sebesar ini. Mengakses baris setelah itu sepele.


Mengapa saya sedikit ragu-ragu untuk membaca seluruh file - Saya mungkin menjalankan beberapa proses tersebut, dan jika selusin dari mereka membaca 12 file masing-masing 15MB, itu mungkin tidak baik. Tapi saya perlu mengujinya untuk mengetahui apakah itu akan berhasil. Terima kasih.
pengguna63503

4
Hrm, dan bagaimana jika itu file 1GB?
Noah

@ fotografer: bahkan proses "beberapa" yang membaca dalam file 15MB seharusnya tidak menjadi masalah pada mesin modern pada umumnya (tentu saja bergantung pada apa yang Anda lakukan dengan mereka).
Jacob Gabrielson

Jacob, ya, aku harus mencoba. Proses ini sedang berjalan di mesin virtual selama berminggu-minggu jika vm tidak macet. Sayangnya terakhir kali itu crash setelah 6 hari. Saya harus melanjutkan dari tempat tiba-tiba berhenti. Masih perlu mencari cara untuk menemukan di mana itu ditinggalkan.
pengguna63503

@Noah: tapi tidak! Mengapa Anda tidak melangkah lebih jauh? Bagaimana jika file 128TB? Karena banyak OS tidak akan dapat mendukungnya. Mengapa tidak memecahkan masalah saat mereka datang?
SilentGhost

7

Saya heran tidak ada yang menyebutkan islice

line = next(itertools.islice(Fhandle,index_of_interest,index_of_interest+1),None) # just the one line

atau jika Anda menginginkan seluruh file lainnya

rest_of_file = itertools.islice(Fhandle,index_of_interest)
for line in rest_of_file:
    print line

atau jika Anda ingin setiap baris lain dari file tersebut

rest_of_file = itertools.islice(Fhandle,index_of_interest,None,2)
for odd_line in rest_of_file:
    print odd_line

5

Karena tidak ada cara untuk menentukan panjang semua garis tanpa membacanya, Anda tidak punya pilihan selain mengulang semua garis sebelum garis mulai. Yang bisa Anda lakukan hanyalah membuatnya terlihat bagus. Jika file sangat besar maka Anda mungkin ingin menggunakan pendekatan berbasis generator:

from itertools import dropwhile

def iterate_from_line(f, start_from_line):
    return (l for i, l in dropwhile(lambda x: x[0] < start_from_line, enumerate(f)))

for line in iterate_from_line(open(filename, "r", 0), 141978):
    DoSomethingWithThisLine(line)

Catatan: indeks berbasis nol dalam pendekatan ini.


4

Jika Anda tidak ingin membaca seluruh file di memori .. Anda mungkin perlu membuat beberapa format selain teks biasa.

tentu saja itu semua tergantung pada apa yang Anda coba lakukan, dan seberapa sering Anda akan melompati file.

Misalnya, jika Anda akan berpindah ke baris berkali-kali dalam file yang sama, dan Anda tahu bahwa file tidak berubah saat bekerja dengannya, Anda dapat melakukan ini:
Pertama, lewati seluruh file, dan catat " seek-location "dari beberapa nomor-baris-kunci (seperti, pernah 1000 baris),
Kemudian jika Anda menginginkan baris 12005, lompat ke posisi 12000 (yang telah Anda rekam) kemudian baca 5 baris dan Anda akan mengenal Anda berada di baris 12005 dan seterusnya


3

Jika Anda mengetahui sebelumnya posisi di file (bukan nomor baris), Anda dapat menggunakan file.seek () untuk menuju ke posisi itu.

Edit : Anda dapat menggunakan fungsi linecache.getline (nama file, lineno) , yang akan mengembalikan konten baris lineno, tetapi hanya setelah membaca seluruh file ke dalam memori. Baik jika Anda mengakses baris secara acak dari dalam file (seperti yang mungkin ingin dilakukan python untuk mencetak traceback) tetapi tidak bagus untuk file 15MB.


Saya pasti tidak akan menggunakan linecache untuk tujuan ini, karena linecache membaca seluruh file di memori sebelum mengembalikan baris yang diminta.
MiniQuark

Ya, kedengarannya terlalu bagus untuk menjadi kenyataan. Saya masih berharap ada modul untuk melakukan ini secara efisien, tetapi cenderung menggunakan metode file.seek () sebagai gantinya.
Noah

3

Apa yang menghasilkan file yang ingin Anda proses? Jika itu adalah sesuatu di bawah kendali Anda, Anda dapat membuat indeks (baris mana di posisi mana.) Pada saat file ditambahkan. File indeks dapat berukuran baris tetap (spasi berisi atau 0 angka berlapis) dan pasti akan lebih kecil. Dan dengan demikian bisa dibaca dan diproses secara cepat.

  • Jalur mana yang Anda inginkan ?.
  • Hitung offset byte dari nomor baris terkait dalam file indeks (mungkin karena ukuran baris file indeks konstan).
  • Gunakan seek atau apapun untuk langsung melompat untuk mendapatkan baris dari file indeks.
  • Parse untuk mendapatkan offset byte untuk baris yang sesuai dari file aktual.

3

Saya memiliki masalah yang sama (perlu mengambil dari baris khusus file besar).

Tentunya, saya dapat setiap saat menjalankan semua catatan dalam file dan menghentikannya ketika penghitung akan sama dengan baris target, tetapi itu tidak bekerja secara efektif dalam kasus ketika Anda ingin mendapatkan jumlah jamak dari baris tertentu. Itu menyebabkan masalah utama diselesaikan - bagaimana menangani langsung ke tempat file yang diperlukan.

Saya menemukan keputusan berikutnya: Pertama saya menyelesaikan kamus dengan posisi awal setiap baris (kuncinya adalah nomor baris, dan nilai - panjang kumulatif baris sebelumnya).

t = open(file,’r’)
dict_pos = {}

kolvo = 0
length = 0
for each in t:
    dict_pos[kolvo] = length
    length = length+len(each)
    kolvo = kolvo+1

akhirnya, fungsi tujuan:

def give_line(line_number):
    t.seek(dict_pos.get(line_number))
    line = t.readline()
    return line

t.seek (line_number) - perintah yang menjalankan pemangkasan file hingga awal baris. Jadi, jika Anda selanjutnya melakukan readline - Anda mendapatkan garis target Anda.

Dengan menggunakan pendekatan seperti itu, saya telah menghemat sebagian besar waktu.


3

Anda dapat menggunakan mmap untuk menemukan offset garis. MMap tampaknya menjadi cara tercepat untuk memproses file

contoh:

with open('input_file', "r+b") as f:
    mapped = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    i = 1
    for line in iter(mapped.readline, ""):
        if i == Line_I_want_to_jump:
            offsets = mapped.tell()
        i+=1

kemudian gunakan f.seek (offset) untuk berpindah ke baris yang Anda butuhkan


2

Apakah baris itu sendiri berisi informasi indeks? Jika konten setiap baris adalah seperti " <line index>:Data", maka seek()pendekatan tersebut dapat digunakan untuk melakukan pencarian biner melalui file tersebut, bahkan jika jumlahnya Dataadalah variabel. Anda akan mencari titik tengah file, membaca baris, memeriksa apakah indeksnya lebih tinggi atau lebih rendah dari yang Anda inginkan, dll.

Jika tidak, hal terbaik yang dapat Anda lakukan adalah adil readlines(). Jika Anda tidak ingin membaca semua 15MB, Anda dapat menggunakan sizehintargumen untuk setidaknya mengganti banyak readline()dengan jumlah panggilan ke yang lebih kecil readlines().


2

Jika Anda berurusan dengan file teks & berbasis sistem linux , Anda dapat menggunakan perintah linux.
Bagi saya, ini bekerja dengan baik!

import commands

def read_line(path, line=1):
    return commands.getoutput('head -%s %s | tail -1' % (line, path))

line_to_jump = 141978
read_line("path_to_large_text_file", line_to_jump)

tentu saja ini tidak kompatibel dengan windows atau sejenis shell linux yang tidak mendukung head / tail.
Wizmann

Apakah ini lebih cepat daripada melakukannya dengan Python?
Shamoon

Bisakah ini mendapatkan banyak baris?
Shamoon

1

Berikut adalah contoh menggunakan 'readlines (sizehint)' untuk membaca potongan baris pada satu waktu. DNS menunjukkan solusi itu. Saya menulis contoh ini karena contoh lain di sini berorientasi pada garis tunggal.

def getlineno(filename, lineno):
    if lineno < 1:
        raise TypeError("First line is line 1")
    f = open(filename)
    lines_read = 0
    while 1:
        lines = f.readlines(100000)
        if not lines:
            return None
        if lines_read + len(lines) >= lineno:
            return lines[lineno-lines_read-1]
        lines_read += len(lines)

print getlineno("nci_09425001_09450000.smi", 12000)

0

Tidak ada jawaban yang memuaskan, jadi berikut ini cuplikan kecil untuk membantu.

class LineSeekableFile:
    def __init__(self, seekable):
        self.fin = seekable
        self.line_map = list() # Map from line index -> file position.
        self.line_map.append(0)
        while seekable.readline():
            self.line_map.append(seekable.tell())

    def __getitem__(self, index):
        # NOTE: This assumes that you're not reading the file sequentially.  
        # For that, just use 'for line in file'.
        self.fin.seek(self.line_map[index])
        return self.fin.readline()

Contoh penggunaan:

In: !cat /tmp/test.txt

Out:
Line zero.
Line one!

Line three.
End of file, line four.

In:
with open("/tmp/test.txt", 'rt') as fin:
    seeker = LineSeekableFile(fin)    
    print(seeker[1])
Out:
Line one!

Ini melibatkan melakukan banyak pencarian file, tetapi berguna untuk kasus di mana Anda tidak dapat memasukkan seluruh file ke dalam memori. Itu melakukan satu pembacaan awal untuk mendapatkan lokasi baris (jadi itu membaca seluruh file, tetapi tidak menyimpan semuanya di memori), dan kemudian setiap akses file mencari fakta.

Saya menawarkan potongan di atas di bawah lisensi MIT atau Apache atas kebijaksanaan pengguna.


-1

Dapat menggunakan fungsi ini untuk mengembalikan baris n:

def skipton(infile, n):
    with open(infile,'r') as fi:
        for i in range(n-1):
            fi.next()
        return fi.next()

Logika ini tidak berfungsi jika ada baris kosong terus menerus, fi.next () melewati semua baris kosong sekaligus, jika tidak maka bagus :)
Anvesh Yalamarthy

OP tidak menyebutkan bahwa garis memiliki garis dengan jeda baris non-standar. Dalam hal ini, Anda harus mengurai setiap baris dengan setidaknya satu pernyataan-if untuk pemisah baris parsial.
ksed
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.