17

特定の行セット (4003 ~ 4005 行など) を読み取りたいファイルに ASCII テーブルがあります。問題は、このファイルが非常に長くなる可能性があること (たとえば、数百行から数百行) であり、私はできるだけ早くこれを実行したいと考えています。

悪い解決策: ファイル全体を読み込んで、それらの行に移動します。

f = open('filename')
lines = f.readlines()[4003:4005]

より良い解決策enumerateすべてがメモリ内にあるわけではないように、各行で(a la https://stackoverflow.com/a/2081880/230468

f = open('filename')
lines = []
for i, line in enumerate(f):
    if i >= 4003 and i <= 4005: lines.append(line)
    if i > 4005: break                                    # @Wooble

最善の解決策は?

ただし、これにはまだ各行を通過する必要があります。特定の回線にアクセスするための (速度/効率の点で) より良い方法はありますか? ファイルに 1 回しかアクセスしないのに (通常) 、ラインキャッシュを使用する必要がありますか?

代わりにバイナリ ファイルを使用することもできます。この場合、先にスキップする方が簡単かもしれませんが、私はむしろそれを避けたいと思います。

4

3 に答える 3

19

私はおそらくただ使うでしょうitertools.islice。ファイル ハンドルのような iterable に対して islice を使用すると、ファイル全体がメモリに読み込まれることはなく、最初の 4002 行ができるだけ早く破棄されます。必要な 2 行をかなり安価にリストにキャストすることもできます (行自体がそれほど長くないと仮定します)。次に、withブロックを終了して、ファイルハンドルを閉じることができます。

from itertools import islice
with open('afile') as f:
    lines = list(islice(f, 4003, 4005))
do_something_with(lines)

アップデート

しかし、神聖な牛は、複数のアクセスに対してラインキャッシュが高速です。islice と linecache を比較するために 100 万行のファイルを作成しましたが、linecache はそれを吹き飛ばしました。

>>> timeit("x=islice(open('afile'), 4003, 4005); print next(x) + next(x)", 'from itertools import islice', number=1)
4003
4004

0.00028586387634277344
>>> timeit("print getline('afile', 4003) + getline('afile', 4004)", 'from linecache import getline', number=1)
4002
4003

2.193450927734375e-05

>>> timeit("getline('afile', 4003) + getline('afile', 4004)", 'from linecache import getline', number=10**5)
0.14125394821166992
>>> timeit("''.join(islice(open('afile'), 4003, 4005))", 'from itertools import islice', number=10**5)
14.732316970825195

ファイルを常に再インポートして再読み込みする:

これは実用的なテストではありませんが、各ステップでラインキャッシュを再インポートしても islice よりわずか 1 秒遅いだけです。

>>> timeit("from linecache import getline; getline('afile', 4003) + getline('afile', 4004)", number=10**5)
15.613967180252075

結論

はい、linecache は、常に linecache を再作成する以外はすべて islice よりも高速ですが、誰がそれを行うのでしょうか? 可能性のあるシナリオ (数行のみを 1 回読み取り、多数の行を 1 回読み取り) では、linecache の方が高速で簡潔な構文を示しますが、islice構文も非常にクリーンで高速であり、ファイル全体をメモリに読み取ることはありません。 . RAM が不足している環境では、このisliceソリューションが正しい選択になる場合があります。非常に高速な要件の場合は、ラインキャッシュの方が適している場合があります。ただし、実際には、ほとんどの環境では、どちらの時間も十分に小さいため、ほとんど問題にはなりません。

于 2013-10-04T20:27:49.373 に答える
8

ここでの主な問題は、改行が他の文字とまったく変わらないことです。したがって、OS にはその行にスキップする方法がありません。

とはいえ、いくつかのオプションがありますが、すべてのオプションについて、何らかの方法で犠牲を払わなければなりません.

あなたはすでに最初のものを述べました:バイナリファイルを使用してください。行の長さが固定されている場合は、バイトをseek進めline * bytes_per_lineてその行に直接ジャンプできます。

次のオプションはインデックスを使用することです。2 番目のファイルを作成し、このインデックス ファイルのすべての行に、データファイルの行のバイト インデックスを書き込みます。データファイルへのアクセスには、2 つのシーク操作 (lineインデックスへのスキップindex_value、データファイルへのスキップ) が含まれるようになりましたが、それでもかなり高速です。プラス: 行の長さが異なる可能性があるため、ディスク容量を節約できます。マイナス:エディタでデータファイルに触れることはできません。

もう 1 つのオプション: (私はこれを使用すると思います) は、1 つのファイルのみを使用し、すべての行を行番号と何らかの区切り文字で開始することです。(例: 4005: 私のデータ行)。これで、バイナリ検索https://en.wikipedia.org/wiki/Binary_search_algorithmの修正版を使用して、行を探すことができます。これlog(n)により、n が行の総数であるシーク操作が回避されます。プラス: ファイルを編集でき、固定長の行に比べてスペースを節約できます。そして、それはまだ非常に速いです。100 万行の場合でも、これは約 20 回のシーク操作にすぎません。マイナス: これらの可能性の中で最も複雑です。(でもやってて楽しいです;)

編集:もう1つの解決策:ファイルを多くの小さなファイルに分割します。非常に長い「行」がある場合、これはファイルごとに 1 行と同じくらい小さい可能性があります。しかし、その後、4/0/05 などのフォルダにグループ分けします。しかし、短い行でもファイルを分割します-大まかに言って-1MBのチャンクに1000.txt、2000.txtという名前を付け、行に完全に一致する1つ(または2つ)を読み取ると、実装が非常に簡単になります。

于 2013-10-04T20:24:07.847 に答える