巨大なテキストファイルの特定の行にジャンプする方法


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つずつ処理することで気分が悪くなります。もしあれば、よりエレガントなソリューションを探しています。


ファイルの前半が "\ n"の束ではなく、後半が1行であることがどうしてわかりますか?なぜあなたはこれについて気分が悪いのですか?
Andrew Dalke

7
タイトルが誤解を招くと思います-控えめに言っても、tbh 15MBは実際には「巨大なテキストファイル」ではありません...
pms

回答:


30

linecache

このlinecacheモジュールを使用すると、Pythonのソースファイルから任意の行を取得でき、キャッシュを使用して内部で最適化を試みます。これは、単一のファイルから多数の行が読み取られる一般的なケースです。これはtraceback、フォーマットされたトレースバックに含めるソース行を取得するためにモジュールによって使用されます...


164
このモジュールのソースコードを確認したところ、ファイル全体がメモリに読み込まれました!したがって、ファイル内の特定の行にすばやくアクセスするために、この答えは間違いなく除外します。
MiniQuark 2009年

MiniQuark、私はそれを試しました、それは実際に機能し、そして本当に迅速に。この方法で同時に多数のファイルを操作するとどうなるかを確認する必要があります。システムがいつ停止するかを確認してください。
user63503 2009年

5
お使いのOSの仮想メモリマネージャはかなり役に立ちます。そのため、大量のページフォールトを生成していない場合、メモリへの大きなファイルの読み取りが遅くなることはありません。のメモリは非常に高速です。私はデンマークのFreeBSD開発者Poul-Henning Kampの記事、queue.acm.org
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ですが、これが役立つのは、彼がいくつかのランダムな行にジャンプする場合のみです。しかし、彼が1行だけにジャンプしている場合、これは無駄です
2009年

3
+1:また、ファイルが変更されない場合は、行番号インデックスをピクルス化して再利用できるため、ファイルのスキャンの初期コストがさらに償却されます。
S.Lott、2009年

OK、ジャンプした後、この位置から行ごとにどのように処理しますか?
user63503 2009年

8
注意すべきこと(特にWindowsの場合):ファイルをバイナリモードで開くように注意するか、またはoffset = file.tell()を使用してください。Windowsのテキストモードでは、行はディスク上の実際の長さよりも1バイト短い(\ r \ nは\ nに置き換えられます)
Brian

2
@photographer:read()またはreadline()を使用します。シークで設定された現在の位置から開始します。
S.Lott、2009年

22

行の長さが異なる場合、実際にはそれほど多くのオプションはありません...次の行にいつ進んだかを知るために悲しいことに行末文字を処理する必要があります。

ただし、最後のパラメーターを「open」に変更して0以外の値にすることで、これを大幅に高速化し、メモリ使用量を削減できます。

0は、ファイルの読み取り操作がバッファリングされていないことを意味します。これは非常に遅く、ディスクに負荷がかかります。1は、ファイルが行バッファリングされることを意味しますが、これは改善されます。1を超えるもの(8kなど)は、ファイルのチャンクをメモリに読み込みます。を介して引き続きアクセスしますfor line in open(etc):が、Pythonは一度に少しずつしか処理せず、バッファリングされた各チャンクは処理後に破棄されます。


6
8Kは8192です。安全のために8 << 10と書く方が良いでしょう。:)
リラックス

buffersizeがバイトで指定されていることを知っていますか?適切な形式は何ですか?「8k」と書いてもいいですか?それとも「8096」とすべきですか?
user63503 2009年

1
ははは...金曜日だと思うけど... バッファサイズは実際にバイトを表す整数なので、8ではなく8192(8096ではない:-))を書き込みます
Jarret Hardie

私の喜び-それがうまくいくことを願っています。最近のシステムでは、おそらくバッファサイズをかなり増やすことができます。8kは、私が識別できない何らかの理由で、私の記憶に残っています。
ジャレットハーディー

私はここでいくつかのテストを行い、それを-1(デフォルトでは、多くの場合8kですが、多くの場合見分けるのが難しい)に設定すると、ほぼ同じくらい速いようです。そうは言っても、その一部は仮想サーバーでテストしていることかもしれません。
オスカースミス

12

たっぷりのラムにだまされているかもしれませんが、15Mは巨大ではありません。でのメモリへの読み込みreadlines() は、このサイズのファイルで通常行うことです。その後の行へのアクセスは簡単です。


ファイル全体を読み取るのに少し躊躇した理由-私はそれらのプロセスのいくつかを実行している可能性があり、それらの12がそれぞれ15MBの12個のファイルを読み取る場合、それは良くないかもしれません。しかし、動作するかどうかを確認するためにテストする必要があります。ありがとうございました。
user63503 2009年

4
Hrm、それが1GBファイルの場合はどうなりますか?
ノア

@photographer:15MBのファイルを読み込む「いくつかの」プロセスでさえ、典型的な現代のマシンでは問題になりません(もちろん、それらを使って何をしているのかによって異なります)。
Jacob Gabrielson

ジェイコブ、はい、ただやってみるべきです。vmがクラッシュしていない場合、プロセスは仮想マシン上で数週間実行されています。残念ながら前回は6日後にクラッシュしました。急に止まったところから続ける必要があります。それが残っている場所を見つける方法を見つける必要があります。
user63503 2009年

@ノア:しかし、そうではありません!さらに進んでみませんか?128TBのファイルならどうでしょう?多くのOSはそれをサポートできません。彼らが来るときに問題を解決してみませんか?
SilentGhost 2009年

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

または、ファイルの1行おきに必要な場合

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(filename、lineno)関数を使用できます。この関数は、lineno行の内容を返しますが、ファイル全体をメモリに読み込んだ後のみです。ファイル内からランダムに行にアクセスしている場合(Python自体がトレースバックを印刷する場合があるため)は問題ありませんが、15MBのファイルには適していません。


要求された行を返す前にメモリ内のファイル全体を読み取るため、この目的でラインキャッシュを使用することは絶対にありません。
MiniQuark 2009年

ええ、それは本当であるには余りにも良いように聞こえました。これを効率的に行うためのモジュールがあればなおよいと思いますが、代わりにfile.seek()メソッドを使用する傾向があります。
ノア

3

処理したいファイルを生成するものは何ですか?それが管理下にある場合、ファイルが追加されるときにインデックス(どの行がどの位置にあるか)を生成できます。インデックスファイルは固定行サイズ(スペースが埋め込まれるか0が埋め込まれる)にすることができ、間違いなく小さくなります。したがって、読み取りと処理を迅速に行うことができます。

  • どのラインが欲しいですか。
  • インデックスファイルの対応する行番号のバイトオフセットを計算します(インデックスファイルの行サイズが一定であるために可能です)。
  • インデックスファイルから行を取得するには、シークなどを使用して直接ジャンプします。
  • 実際のファイルの対応する行のバイトオフセットを取得するために解析します。

3

私は同じ問題を抱えていました(巨大なファイル固有の行から取得する必要があります)。

確かに、ファイル内のすべてのレコードを毎回実行して、counterがtarget行と等しくなるときに停止することができますが、複数の特定の行を取得したい場合は効果的に機能しません。これにより、主要な問題が解決されました-ファイルの必要な場所を直接処理する方法。

私は次の決定を見つけました。最初に、各行の開始位置でディクショナリを完成させました(キーは行番号、値は前の行の累積された長さ)。

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)–ファイルのプルーニングを行頭まで実行するコマンド。したがって、次にreadlineをコミットすると、ターゲット行が取得されます。

このようなアプローチを使用することで、時間を大幅に節約できました。


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(offsets)を使用して、必要な行に移動します


2

行自体にインデックス情報が含まれていますか?各行の内容が " <line index>:Data"のようなものである場合、このseek()アプローチを使用して、ファイル全体のバイナリ検索を行うことができます。Dataが可変ます。ファイルの中間点にシークし、行を読み取り、そのインデックスが必要なインデックスよりも高いか低いかなどを確認します。

そうでなければ、あなたができる最善はただreadlines()です。15MB全体を読みたくない場合は、sizehint引数を使用して、少なくとも多くreadline()のをより少ない数ので置き換えることができます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)

もちろん、head / tailをサポートしていないWindowsやある種のLinuxシェルとは互換性がありません。
Wizmann、

これはPythonで行うよりも速いですか?
Shamoon

これは複数の行を取得できますか?
Shamoon

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!

これには多くのファイルシークが含まれますが、ファイル全体をメモリに収めることができない場合に役立ちます。行の場所を取得するために最初の1回の読み取りを行い(ファイル全体を読み取りますが、すべてをメモリに保持するわけではありません)、その後、アクセスごとにファイルのシークを行います。

ユーザーの裁量で、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

OPは、行に非標準の改行がある行があることについては言及していません。その場合、部分的な改行について、少なくとも1つのifステートメントで各行を解析する必要があります。
1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.