以下のコードの代替手段はありますか?
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つずつ処理するのは気分が悪いです。もしあれば、よりエレガントなソリューションを探しています。
改行がどこにあるかわからないので、少なくとも1回ファイルを読み込まずに先に進むことはできません。次のようなことができます:
# 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])
行の長さが異なる場合、それほど多くのオプションはありません。悲しいことに、行末文字を処理して次の行に進んだことを知る必要があります。
ただし、最後のパラメーターを "open"に変更して0以外の値に変更することにより、これを劇的に高速化し、メモリ使用量を削減できます。
0は、ファイル読み取り操作がバッファリングされないことを意味します。これは非常に遅く、ディスク集中型です。 1は、ファイルが行バッファリングされることを意味します。これは改善されます。 1を超えるもの(たとえば、8k ..:8096以上)は、ファイルのチャンクをメモリに読み取ります。 for line in open(etc):
を介してアクセスすることはできますが、pythonは一度に少ししか処理されず、処理された後、バッファリングされた各チャンクを破棄します。
私はおそらく豊富なラムに甘やかされていますが、15 Mは巨大ではありません。 readlines()
を使用してメモリに読み込むことは、このサイズのファイルで通常行うことです。その後の行へのアクセスは簡単です。
私は誰も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
すべての行の長さを読み取ることなく決定する方法はないため、開始行の前にすべての行を反復する以外に選択肢はありません。できることは、見栄えを良くすることだけです。ファイルが非常に大きい場合は、ジェネレーターベースのアプローチを使用できます。
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)
注:このアプローチでは、インデックスはゼロに基づいています。
ファイル内の位置(行番号ではなく)が事前にわかっている場合は、 file.seek() を使用してその位置に移動できます。
Edit: linecache.getline(filename、lineno) 関数を使用できます。この関数は、linelinenoの内容を返します。 、ただしファイル全体をメモリに読み込んだ後にのみ。ファイル内の行にランダムにアクセスする場合(python自体がトレースバックを印刷する場合があるため)に適していますが、15MBファイルには適していません。
処理するファイルを生成するものは何ですか?あなたの管理下にあるものであれば、ファイルが追加されるときにインデックス(どの行がどの位置にあるか)を生成できます。インデックスファイルは、固定行サイズ(スペースが埋め込まれた数値または0が埋め込まれた数値)である場合があり、間違いなく小さくなります。したがって、読み取りと処理をすばやく行うことができます。
メモリ内のファイル全体を読みたくない場合は、プレーンテキスト以外の形式を考え出す必要があります。
もちろん、それはあなたが何をしようとしているのか、そしてどのくらいの頻度でファイルをジャンプするかに依存します。
たとえば、同じファイル内の行多くの場合にジャンプして、作業中にファイルが変更されないことがわかっている場合、次のようにできます。
最初に、ファイル全体をパススルーし、いくつかのキー行番号(1000行など)の「シーク場所」を記録します。
その後、12005行目が必要な場合は、12000(記録した)の位置にジャンプしてから5行を読むと、12005行目であることがわかります。
同じ問題が発生しました(巨大なファイル固有の行から取得する必要があります)。
確かに、ファイル内のすべてのレコードを実行し、カウンターがターゲット行に等しくなると停止することができますが、複数の特定の行を取得したい場合は効果的に機能しません。これにより、主要な問題が解決されました-ファイルの必要な場所を直接処理する方法。
次の決定を見つけました。まず、各行の開始位置をキーにして辞書を完成させました(キーは行番号で、値は前の行の累積長です)。
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をコミットすると、ターゲット行が取得されます。
このようなアプローチを使用すると、時間を大幅に節約できました。
行自体にインデックス情報が含まれていますか?各行の内容が「<line index>:Data
」のようなものである場合、Data
の量が可変であっても、seek()
アプローチを使用してファイルをバイナリ検索できます。ファイルの中間点にシークし、行を読み取り、そのインデックスが必要なインデックスよりも高いか低いかなどを確認します。
それ以外の場合、できることはreadlines()
だけです。 15MBをすべて読みたくない場合は、sizehint
引数を使用して、少なくとも多くのreadline()
をreadlines()
の呼び出し回数を少なくして置き換えることができます。
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)を使用して、必要な行に移動します
テキストファイルと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)
「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)