Як перейти до певного рядка у величезному текстовому файлі?


107

Чи є альтернативи коду нижче:

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)рядками невідомої, але різної довжини, і мені потрібно перейти до певного рядка, яке число я заздалегідь знаю? Мені стає погано, обробляючи їх по черзі, коли я знаю, що можу проігнорувати принаймні першу половину файлу. Шукаєте більш елегантне рішення, якщо воно є.


Звідки ви знаєте, що перша 1/2 файлу не є "\ n" секцією, а друга половина - це один рядок? Чому ти погано почуваєшся з цього приводу?
Ендрю Далке

7
Я думаю, що заголовок вводить в оману - тбх 15 Мб насправді не "величезний текстовий файл", якщо не сказати ...
pms

Відповіді:


30

лінійний кеш :

linecacheМодуль дозволяє отримати будь-який рядок з вихідного файлу Python, при спробі оптимізувати внутрішньо, використовуючи кеш, загальний випадок , коли багато ліній зчитуються з одного файлу. Цей tracebackмодуль використовується для отримання вихідних рядків для включення у відформатований трекбек ...


164
Я щойно перевірив вихідний код цього модуля: весь файл читається в пам'яті! Тому я б точно виключав цю відповідь з метою швидкого доступу до заданого рядка у файлі.
MiniQuark

MiniQuark, я спробував це, він насправді працює, і дуже швидко. Мені потрібно буде побачити, що станеться, якщо я працюю над десятком файлів одночасно таким чином, щоб дізнатися, в який момент помирає моя система.
user63503

5
Менеджер віртуальної пам’яті вашої ОС допомагає зовсім небагато, тому читання великих файлів у пам'яті може не бути повільним, якщо ви не створюєте багато помилок на сторінках :) Навпаки, робите це «дурним способом» і виділяєте багато-багато пам'яті може бути надзвичайно швидко. Мені сподобалося датська стаття розробника FreeBSD Poul-Henning Kamp: queue.acm.org/detail.cfm?id=1814327
Morten Jensen

13
спробуйте файл 100G, він смокче. я повинен використовувати f.tell (), f.seek (), f.readline ()
whi

114

Ви не можете стрибати вперед, не прочитавши файл хоча б раз, оскільки не знаєте, де переривання рядків. Ви можете зробити щось на кшталт:

# 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, але будьте обережні, що це корисно лише в тому випадку, якщо він перейде на кілька випадкових ліній! але якщо він тільки стрибає на одній лінії, то це марнотратно
HASEN

3
+1: Крім того, якщо файл не змінюється, індекс номера рядка можна маринувати та використовувати повторно, додатково амортизуючи початкову вартість сканування файлу.
С.Лотт

ОК, після того, як я стрибнув туди, як би я обробляв потім рядок за рядком, починаючи з цієї позиції?
user63503

8
Одне, що слід зазначити (особливо у Windows): будьте обережні, щоб відкрити файл у двійковому режимі або використати офсет = file.tell (). У текстовому режимі на Windows рядок буде на байт коротшим, ніж його необмежена довжина на диску (\ r \ n замінено \ n)
Брайан

2
@photographer: Використовуйте read () або readline (), вони починаються з поточного положення, встановленого пошуком.
С.Лотт

22

У вас насправді не так багато варіантів, якщо лінії мають різну довжину ... вам, на жаль, потрібно обробити символи, що закінчуються рядком, щоб знати, коли ви перейшли до наступного рядка.

Однак ви можете це значно прискорити І зменшити використання пам'яті, змінивши останній параметр на "відкрити" на щось не 0.

0 означає, що операція зчитування файлів є нерозподіленою, що дуже повільно і диск. 1 означає, що файл буферний рядок, що було б вдосконаленням. Що-небудь вище 1 (скажімо, 8 к .. тобто 8096 або вище) зчитує фрагменти файлу в пам'яті. Ви все ще отримуєте доступ до нього через for line in open(etc):, але python проходить лише трохи, відкидаючи кожен завантажений фрагмент після його обробки.


6
8K - це 8192, можливо, краще написати 8 << 10, щоб бути на безпечній стороні. :)
розмотуємо

Ви випадково знаєте, чи буферний розмір вказаний на байтах? Який відповідний формат? Чи можу я написати «8k»? Або це має бути "8096"?
користувач63503

1
ХАХАХА ... повинен бути в п'ятницю ... Я явно не можу займатися математикою. Розмір буфера - це дійсно ціле вираження байтів, тому пишіть 8192 (а не 8096 :-)), а не 8
Jarret Hardie

Моє задоволення - сподіваюся, що це виходить. У сучасній системі, ймовірно, ви можете трохи збільшити розмір буфера. 8k - це просто затримка в моїй пам’яті з якихось причин я не можу ідентифікувати.
Джаррет Харді

Я тут провів тестування, і встановлення його на -1 (за замовчуванням, часто 8 к, але часто важко сказати), здається, приблизно так само швидко, як це стає. Однак, частиною цього може бути те, що я тестую на віртуальному сервері.
Оскар Сміт

12

Я, мабуть, зіпсований рясним бараном, але 15 М - це не величезна кількість. Читання в пам'яті з readlines() - це те, що я зазвичай роблю з файлами такого розміру. Доступ до лінії після цього тривіальний.


Чому я злегка вагався читати весь файл - у мене може бути кілька таких процесів, і якщо з десяток з них прочитали 12 файлів по 15 МБ, це може бути недобре. Але мені потрібно перевірити це, щоб з’ясувати, чи спрацює він. Дякую.
user63503

4
Hrm, а що, якщо це файл 1 Гб?
Ной

@photographer: навіть "кілька" процесів читання у файлах 15 МБ не повинні мати значення на типовій сучасній машині (звичайно, залежно від того, що саме ви робите з ними).
Джейкоб Габріельсон

Якову, так, я б просто спробував. Процес (и) працює / працює на віртуальній машині протягом тижнів, якщо vm не відбувається збій. На жаль, востаннє він зазнав аварії через 6 днів. Мені потрібно продовжити з того місця, де воно раптово зупинилося. Ще потрібно розібратися, як знайти, де воно залишилось.
user63503

@Noah: але це не так! Чому ти не підеш далі? Що робити, якщо файл 128 ТБ? Тож багато ОС не змогли б його підтримати. Чому б не вирішити проблему по мірі їх приходу?
SilentGhost

7

Я здивований, що ніхто не згадує Islice

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

або якщо ви хочете весь решта файлу

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

або якщо потрібно будь-який інший рядок із файлу

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

5

Оскільки немає можливості визначити довжину всіх рядків, не читаючи їх, у вас немає іншого вибору, крім того, щоб повторити всі рядки перед початковим рядком. Все, що ви можете зробити, це зробити його красивим. Якщо файл дійсно величезний, можливо, ви захочете використовувати підхід на основі генератора:

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)

Примітка: індекс дорівнює нулю, виходячи з цього підходу.


4

Якщо ви не хочете читати весь файл у пам'яті .., можливо, вам знадобиться якийсь формат, відмінний від простого тексту.

звичайно, все залежить від того, що ви намагаєтеся зробити, і від того, як часто ви переходите через файл.

Наприклад, якщо ви збираєтесь багато разів переходити до рядків у одному файлі, і знаєте, що файл не змінюється під час роботи з ним, ви можете зробити це:
Спочатку пройдіть весь файл і запишіть " шукати місцеположення "деяких номерів ключових рядків (наприклад, коли-небудь 1000 рядків).
Тоді, якщо ви хочете, щоб лінія 12005 перейшла до позиції 12000 (яку ви записали), тоді прочитайте 5 рядків, і ви будете знати вас перебуває в рядку 12005 тощо


3

Якщо ви заздалегідь знаєте позицію у файлі (скоріше номер рядка), ви можете скористатися file.seek (), щоб перейти до цього положення.

Редагувати : ви можете використовувати функцію linecache.getline (назва файлу, lineno) , яка поверне вміст рядка lineno, але лише після прочитання всього файлу в пам'яті. Добре, якщо ви випадково отримуєте доступ до рядків всередині файлу (як це може зробити сам python для друку прослідковування), але це не добре для файлу 15 Мб.


Я б точно не використовував лінійний кеш для цієї мети, тому що він читає весь файл у пам'яті перед поверненням запитуваного рядка.
MiniQuark

Так, це звучало занадто добре, щоб бути правдою. Я все ще хотів, щоб був модуль, щоб це зробити ефективно, але, як правило, замість цього використовується метод file.seek ().
Ной

3

Що створює файл, який ви хочете обробити? Якщо це щось під вашим контролем, ви можете створити індекс (який рядок знаходиться в якій позиції.) Під час додавання файлу. Індексний файл може мати фіксований розмір рядка (пробіл або 0 прокладених номерів) і, безумовно, буде меншим. І таким чином можна читати і обробляти швидко.

  • Який рядок ви хочете ?.
  • Обчисліть зміщення байтів відповідного номера рядка у файлі індексу (можливо, оскільки розмір рядка індексного файлу є постійним).
  • Використовуйте шукати або що завгодно, щоб безпосередньо перейти, щоб отримати рядок з індексного файлу.
  • Розбір, щоб отримати байтове зміщення для відповідного рядка фактичного файлу.

3

У мене була така ж проблема (потрібно витягнути з величезного рядка конкретного файлу).

Звичайно, я можу щоразу пробирати всі записи у файлі і зупиняти його, коли лічильник буде дорівнює цільовій лінії, але це не працює ефективно у випадку, коли ви хочете отримати множину певних рядків. Це призвело до вирішення основної проблеми - як звертатися безпосередньо до потрібного місця файлу.

Я з'ясував наступне рішення: По-перше, я доповнив словник із початковою позицією кожного рядка (ключ - номер рядка, а значення - сукупна довжина попередніх рядків).

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

в кінцевому рахунку, цільова функція:

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

t.seek (line_number) - команда, що виконує обрізку файлу до початку створення рядка. Таким чином, якщо ви наступного введення читання рядка - ви отримуєте цільовий рядок.

Використовуючи такий підхід, я зекономив значну частину часу.


3

Ви можете використовувати mmap, щоб знайти зміщення рядків. MMap, здається, є найшвидшим способом обробки файлу

приклад:

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

потім використовуйте f.seek (зсуви), щоб перейти до потрібної лінії


2

Чи містять самі рядки інформацію про індекс? Якщо вміст кожного рядка був чимось на зразок " <line index>:Data", тоді seek()підхід можна було б використовувати для двійкового пошуку через файл, навіть якщо сумаData змінної. Ви б шукали до середини файлу, читали рядок, перевіряли, чи є його індекс вищим чи нижчим за потрібний і т.д.

В іншому випадку найкраще, що ви можете зробити, це просто readlines(). Якщо ви не хочете прочитати всі 15 Мб, ви можете використовувати sizehintаргумент, щоб принаймні замінити багато readline()s на меншу кількість дзвінків на readlines().


2

Якщо ви маєте справу з текстовим файлом та на базі системи Linux , ви можете використовувати команди Linux.
Для мене це спрацювало добре!

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)

звичайно, це не сумісно з Windows або якимись оболонками Linux, які не підтримують голову / хвіст.
Wizmann

Це швидше, ніж робити це в Python?
Шамон

Чи може це отримати кілька ліній?
Шамон

1

Ось приклад, що використовує 'readlines (sizehint)', щоб прочитати шматок рядків за один раз. DNS вказав на це рішення. Я написав цей приклад, тому що інші приклади тут орієнтовані на одну лінію.

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

Жодна з відповідей не є особливо задовільною, тому ось невеликий фрагмент для допомоги.

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

Приклад використання:

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!

Це передбачає велику кількість пошукових файлів, але корисно для тих випадків, коли ви не можете вмістити весь файл у пам'яті. Він робить одне початкове зчитування, щоб отримати місця розташування рядків (тому він читає весь файл, але не зберігає все в пам'яті), а потім кожен доступ шукає файл за фактом.

Я пропоную фрагмент вище за ліцензією MIT або Apache на розсуд користувача.


-1

Можна використовувати цю функцію для повернення рядка n:

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

Ця логіка не працює, якщо є безперервні порожні рядки, fi.next () пропускає відразу всі порожні рядки, інакше це добре :)
Anvesh Yalamarthy

ОП не згадує, що у рядках є лінії з нестандартними розривами ліній. У цьому випадку вам доведеться проаналізувати кожен рядок принаймні одним твердженням if для часткових розривів рядків.
цілувався
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.