Büyük bir metin dosyasında belirli bir satıra nasıl atlanır?


107

Aşağıdaki koda herhangi bir alternatif var mı:

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

(~15MB)Bilinmeyen ancak farklı uzunlukta satırlara sahip büyük bir metin dosyasını işliyorsam ve önceden bildiğim belirli bir satıra atlamam gerekirse? Dosyanın en az ilk yarısını görmezden gelebileceğimi bildiğimde onları tek tek işleyerek kendimi kötü hissediyorum. Varsa daha şık bir çözüm aramak.


Dosyanın ilk 1 / 2'sinin bir grup "\ n" olmadığını ve ikinci yarısının tek bir satır olduğunu nasıl anlarsınız? Neden bu konuda kötü hissediyorsun?
Andrew Dalke

7
Başlığın yanıltıcı olduğunu düşünüyorum - tbh 15MB, en azından söylemek gerekirse, gerçekten "büyük metin dosyası" değil ...
pms

Yanıtlar:


30

linecache :

linecacheModül, içten optimize çalışan bir önbellek, birçok çizgiler tek dosyadan okunan ortak davasını kullanırken, Python kaynak dosyadan herhangi hat almak için bir olanak sağlar. Bu, tracebackmodül tarafından biçimlendirilmiş traceback'e dahil edilmek üzere kaynak satırlarını almak için kullanılır ...


165
Bu modülün kaynak kodunu kontrol ettim: tüm dosya bellekte okunur! Bu nedenle, bir dosyadaki belirli bir satıra hızlıca erişmek amacıyla bu yanıtı kesinlikle ekarte ediyorum.
MiniQuark

MiniQuark, denedim, gerçekten çalışıyor ve gerçekten hızlı. Bu şekilde aynı anda bir düzine dosya üzerinde çalışırsam ne olacağını görmem gerek, sistemimin hangi noktada öldüğünü bul.
user63503

5
İşletim sisteminizin sanal bellek yöneticisi oldukça yardımcı oluyor, bu nedenle çok fazla sayfa hatası oluşturmuyorsanız büyük dosyaları belleğe okumak yavaş olmayabilir :) Aksine, bunu "aptalca" şekilde yapmak ve çok şey ayırmak bellek son derece hızlı olabilir. Danimarkalı FreeBSD geliştiricisi Poul-Henning Kamp'ın bu konudaki
Morten Jensen

13
100G dosyasını deneyin, berbat. i) (f.tell kullanmak f.seek (), f.readline () var
WHI

115

Satır sonlarının nerede olduğunu bilmediğiniz için dosyayı en az bir kez okumadan ileri atlayamazsınız. Şunun gibi bir şey yapabilirsiniz:

# 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, ancak bunun yalnızca birkaç rastgele satıra atlayacaksa işe yarayacağına dikkat edin! ama eğer sadece bir satıra
atlıyorsa

3
+1: Ayrıca, dosya değişmezse, satır numarası indeksi temizlenebilir ve yeniden kullanılabilir, bu da dosyanın ilk tarama maliyetini daha da amorti eder.
S.Lott

Tamam, oraya atladıktan sonra bu pozisyondan başlayarak satır satır nasıl işlerim?
user63503

8
Unutulmaması gereken bir şey (özellikle Windows'ta): Dosyayı ikili modda açmaya dikkat edin veya alternatif olarak offset = file.tell () kullanın. Windows'ta metin modunda, satır diskteki ham uzunluğundan daha kısa bir bayt olacaktır (\ r \ n yerine \ n)
Brian

2
@photographer: read () veya readline () kullanın, bunlar arama ile ayarlandığı gibi mevcut konumdan başlar.
S.Lott

22

Eğer satırlar farklı uzunlukta ise o kadar çok seçeneğiniz yok ... ne yazık ki sonraki satıra ne zaman ilerlediğinizi bilmek için satır sonundaki karakterleri işlemeniz gerekiyor.

Bununla birlikte, son parametreyi 0 olmayan bir şeye "açık" olarak değiştirerek bunu önemli ölçüde hızlandırabilir VE bellek kullanımını azaltabilirsiniz.

0, dosya okuma işleminin arabelleğe alınmadığı anlamına gelir, bu çok yavaş ve disk yoğun. 1, dosyanın satır arabelleğe alındığı anlamına gelir ve bu bir gelişme olur. 1'in üzerindeki herhangi bir şey (diyelim ki 8k .. yani: 8096 veya üstü) dosyanın parçalarını belleğe okur. Buna hala erişiyorsunuz for line in open(etc):, ancak python bir seferde sadece biraz ilerliyor ve işlendikten sonra arabelleğe alınmış her parçayı atıyor.


6
8K, 8192'dir, belki de güvenli tarafta olmak için 8 << 10 yazmak daha iyidir. :)
gevşeyin

Arabellek boyutunun bayt üzerinde belirtildiğini biliyor musunuz? Uygun format nedir? '8k' yazabilir miyim? Yoksa '8096' mı olmalı?
user63503

1
HAHAHA ... cuma olmalı ... Açıkçası matematik yapamam. Arabellek boyutu gerçekten bayt ifade eden bir tamsayıdır, bu nedenle 8 yerine 8192 yazın (8096 :-) değil)
Jarret Hardie

Benim için zevk - umarım işe yarar. Modern bir sistemde, muhtemelen arabellek boyutunu biraz artırabilirsiniz. 8k hafızamda bir sebepten dolayı tanımlayamadığım bir engel.
Jarret Hardie

Burada bazı testler yaptım ve -1 olarak ayarladım (varsayılan işletim sistemi, genellikle 8k, ancak genellikle söylemesi zor), olabildiğince hızlı görünüyor. Bununla birlikte, bunun bir kısmı sanal bir sunucuda test ediyor olmam olabilir.
Oscar Smith

12

Muhtemelen bol koç tarafından şımartıldım ama 15 M çok büyük değil. Hafızaya okumak, readlines() genellikle bu boyuttaki dosyalarla yaptığım şeydir. Bundan sonra bir hatta erişmek önemsizdir.


Neden tüm dosyayı okumakta biraz tereddüt ettim - Bu işlemlerin birçoğu çalışıyor olabilir ve bunlardan bir düzine 12 dosya her biri 15 MB okursa iyi olmayabilir. Ama işe yarayıp yaramayacağını öğrenmek için test etmem gerekiyor. Teşekkür ederim.
user63503

4
Hrm, peki ya 1GB'lık bir dosyaysa?
Noah

@photographer: 15MB'lık dosyaları okuyan "birkaç" işlem bile tipik bir modern makinede önemli olmamalıdır (elbette, onlarla tam olarak ne yaptığınıza bağlı olarak).
Jacob Gabrielson

Jacob, evet, denemeliyim. Eğer vm kilitlenmemişse süreç (ler) sanal bir makinede haftalarca çalışıyor. Maalesef en son 6 gün sonra çöktü. Aniden durduğu yerden devam etmem gerekiyor. Hala nerede kaldığını bulmaya ihtiyacım var.
user63503

@Hayır: ama değil! Neden daha ileri gitmiyorsun? Ya 128TB dosyası? Çoğu işletim sistemi onu destekleyemez. Sorun geldikçe neden çözülmesin?
SilentGhost

7

Şaşırdım kimse islice'den bahsetmedi

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

veya dosyanın geri kalanını istiyorsanız

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

ya da dosyadaki her satırı istiyorsanız

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

5

Okumadan tüm satırların uzunluğunu belirlemenin bir yolu olmadığından, başlangıç ​​satırınızdan önce tüm satırları yinelemekten başka seçeneğiniz yoktur. Yapabileceğin tek şey güzel görünmesini sağlamak. Dosya gerçekten çok büyükse, jeneratör tabanlı bir yaklaşım kullanmak isteyebilirsiniz:

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)

Not: Bu yaklaşıma göre indeks sıfırdır.


4

Dosyanın tamamını bellekte okumak istemiyorsanız .. düz metin dışında bir format bulmanız gerekebilir.

Tabii ki bunların hepsi ne yapmaya çalıştığınıza ve dosyada ne sıklıkla atlayacağınıza bağlıdır.

Örneğin , aynı dosyada birçok kez satırlara atlayacaksanız ve dosyanın üzerinde çalışırken değişmeyeceğini biliyorsanız, şunu yapabilirsiniz:
Önce, tüm dosyayı geçirin ve " Bazı anahtar satır numaralarının (örneğin, her 1000 satır gibi) yerini arama ",
Sonra 12005 satırını istiyorsanız, 12000 konumuna atlayın (kaydettiğiniz) sonra 5 satırı okuyun ve sizi tanıyacaksınız 12005 sırasındayım ve benzeri


3

Dosyadaki konumu (satır numarası yerine) önceden biliyorsanız, o konuma gitmek için file.seek () öğesini kullanabilirsiniz .

Düzenleme : linecache.getline (dosya adı, lineno) işlevini kullanabilirsiniz, bu işlev, lineno satırının içeriğini ancak tüm dosyayı belleğe okuduktan sonra döndürecektir. Dosyanın içinden rasgele satırlara erişiyorsanız (python'un kendisi bir izleme geri yazdırmak isteyebileceği için) iyi, ancak 15MB'lık bir dosya için iyi değil.


Bu amaçla kesinlikle linecache kullanmam çünkü istenen satırı döndürmeden önce tüm dosyayı bellekte okur.
MiniQuark

Evet, kulağa gerçek olamayacak kadar iyi geldi. Yine de bunu verimli bir şekilde yapacak bir modül olmasını diliyorum, ancak bunun yerine file.seek () yöntemini kullanma eğilimindeyim.
Noah

3

İşlemek istediğiniz dosyayı ne oluşturur? Kontrolünüz altında bir şeyse, dosyanın eklendiği anda bir dizin (hangi satırın hangi konumda olduğu) oluşturabilirsiniz. Dizin dosyası sabit satır boyutunda (boşluk doldurulmuş veya 0 dolgulu sayı) olabilir ve kesinlikle daha küçük olacaktır. Ve böylece hızlı bir şekilde okunabilir ve işlenebilir.

  • Hangi satırı istiyorsun?
  • İndeks dosyasında karşılık gelen satır numarasının bayt uzaklığını hesaplayın (indeks dosyasının satır boyutu sabit olduğundan mümkündür).
  • Dizin dosyasından satırı almak için doğrudan atlamak için arama veya herhangi bir şeyi kullanın.
  • Gerçek dosyanın karşılık gelen satırı için bayt uzaklığını elde etmek için ayrıştırın.

3

Aynı sorunu yaşadım (büyük dosyaya özel satırdan geri alma ihtiyacı duydum).

Şüphesiz, her defasında dosyadaki tüm kayıtları inceleyebilir ve sayaç hedef satıra eşit olduğunda durdurabilirim, ancak çok sayıda belirli satır elde etmek istediğiniz bir durumda etkili bir şekilde çalışmaz. Bu, ana sorunun çözülmesine neden oldu - doğrudan gerekli dosya yerine nasıl müdahale edildi.

Bir sonraki kararı buldum: İlk olarak sözlüğü her satırın başlangıç ​​konumu ile tamamladım (anahtar satır numarası ve değer - önceki satırların toplam uzunluğu).

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

sonuçta amaç işlevi:

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

t.seek (line_number) - satır başlangıcına kadar dosyanın kısaltılmasını gerçekleştiren komut. Yani, bir sonraki okuma satırı işlerseniz - hedef satırınızı elde edersiniz.

Bu yaklaşımı kullanarak zamanın önemli bir kısmında tasarruf ettim.


3

Çizgilerin ofsetini bulmak için mmap kullanabilirsiniz. MMap, bir dosyayı işlemenin en hızlı yolu gibi görünüyor

misal:

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

daha sonra ihtiyacınız olan çizgiye gitmek için f.seek (ofsetler) kullanın


2

Satırların kendileri herhangi bir dizin bilgisi içeriyor mu? Her satırın içeriği " <line index>:Data" gibi bir şeyse , bu durumda seek()yaklaşım, miktarı Datadeğişken olsa bile dosya içinde ikili arama yapmak için kullanılabilir . Dosyanın orta noktasını ararsınız, bir satırı okursunuz, dizininin istediğinizden daha yüksek veya daha düşük olup olmadığını kontrol edersiniz, vb.

Aksi takdirde, yapabileceğiniz en iyi şey adildir readlines(). 15 MB'nin tamamını okumak istemiyorsanız, sizehintbağımsız değişkeni en azından çok sayıda readline()s'yi daha az sayıda çağrı ile değiştirmek için kullanabilirsiniz readlines().


2

Bir metin dosyasıyla uğraşıyorsanız ve linux sistemini temel alıyorsanız , linux komutlarını kullanabilirsiniz.
Benim için bu iyi çalıştı!

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)

Tabii ki pencerelerle veya baş / kuyruğu desteklemeyen bir tür linux kabuklarıyla uyumlu değildir.
Wizmann

Bu, Python'da yapmaktan daha hızlı mı?
Shamoon

Bu birden fazla satır alabilir mi?
Shamoon

1

Burada, bir seferde bir dizi satırı okumak için 'readlines (sizehint)' kullanan bir örnek var. DNS bu çözüme işaret etti. Bu örneği yazdım çünkü buradaki diğer örnekler tek satır odaklı.

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

Cevapların hiçbiri özellikle tatmin edici değil, bu yüzden işte size yardımcı olacak küçük bir pasaj.

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()

Örnek kullanım:

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!

Bu, çok fazla dosya arama yapmayı içerir, ancak tüm dosyayı belleğe sığdıramadığınız durumlar için kullanışlıdır. Satır konumlarını almak için bir ilk okuma yapar (böylece tüm dosyayı okur, ancak hepsini hafızada tutmaz) ve ardından her erişim, gerçeğin ardından bir dosya arar.

Yukarıdaki pasajı, kullanıcının takdirine bağlı olarak MIT veya Apache lisansı altında sunuyorum.


-1

N satırını döndürmek için bu işlevi kullanabilir:

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

Sürekli boş satırlar varsa bu mantık çalışmaz, fi.next () tüm boş satırları bir kerede atlar, aksi halde iyidir :)
Anvesh Yalamarthy

OP, satırların standart olmayan satır sonları olan satırlara sahip olduğundan bahsetmiyor. Bu durumda, kısmi satır sonları için her satırı en az bir if-ifadesiyle ayrıştırmanız gerekir.
2019
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.