2011-08-23 10 views
14

特定のデータを取得するために、非常に大きな(数百メガバイトの)テキストファイルから最後の行を抽出する必要があります。現在、ファイルが空になるまで、すべての行を循環させるためにPythonを使用しています。最後に返された行を処理しますが、これを行うためのより効率的な方法があると確信しています。テキストファイルの最後の行を効率的に見つける

pythonを使用してテキストファイルの最後の行だけを取得する最も良い方法は何ですか?

+0

これはPythonの質問ですか、あるいはawkやsedを使った答えはまあまあですか? –

+1

重要な情報(多くの回答は完全に無視されています):ファイルのエンコーディング。 –

+0

マルチバイトエンコーディング(たとえば、UTF-16またはUTF-32)のみが、指定されたアルゴリズムを破ります。 –

答えて

-1
lines = file.readlines() 
fileHandle.close() 
last_line = lines[-1] 
+1

ガー! 'lines [len(lines)-1]'をしないでください。これは 'O(n)'操作です。 'lines [-1]'は最後のものを取得します。しかも、これは彼がすでに使っているアプローチよりも優れているわけではありません。 –

+0

おっと、私の間違い!この方法は実際にはより効率的です。 –

+7

@gddc: 'lines [len(lines)-1]'はO(n)ではありません( 'lines'は' __len__'のO(n)実装を持つユーザ定義型でない限り、 )。それは悪いスタイルですが、 'lines [len(lines)-1]'は 'lines [-1]'と実質的に同じランタイムコストを持ちます。唯一の違いは、インデックス計算がスクリプトで明示的に実行されるのか、実行時に暗黙的に行われるのかです。 –

14

ないまっすぐ進む道、おそらくはるかに高速、簡単なPython実装より:

line = subprocess.check_output(['tail', '-1', filename]) 
+1

最後に[0:-1]を追加して、何とか最後に '\ n'を追加したいのですが... –

+1

それは非常にpythonの解決策ではありません –

5

は負のオフセットを使用してファイルのseekメソッドを使用しますファイルの最後からブロックを読み取るにはwhence=os.SEEK_ENDを使用します。最後の行末の文字をブロックで検索し、その後のすべての文字を取得します。行末がない場合は、さらに遠くまでバックアップし、プロセスを繰り返します。これは、標準入力またはソケットのような、seekをサポートしていないものに動作しないこと

def last_line(in_file, block_size=1024, ignore_ending_newline=False): 
    suffix = "" 
    in_file.seek(0, os.SEEK_END) 
    in_file_length = in_file.tell() 
    seek_offset = 0 

    while(-seek_offset < in_file_length): 
     # Read from end. 
     seek_offset -= block_size 
     if -seek_offset > in_file_length: 
      # Limit if we ran out of file (can't seek backward from start). 
      block_size -= -seek_offset - in_file_length 
      if block_size == 0: 
       break 
      seek_offset = -in_file_length 
     in_file.seek(seek_offset, os.SEEK_END) 
     buf = in_file.read(block_size) 

     # Search for line end. 
     if ignore_ending_newline and seek_offset == -block_size and buf[-1] == '\n': 
      buf = buf[:-1] 
     pos = buf.rfind('\n') 
     if pos != -1: 
      # Found line end. 
      return buf[pos+1:] + suffix 

     suffix = buf + suffix 

    # One-line file. 
    return suffix 

注意。そのような場合は、(tailコマンドのように)全部読んだままになってしまいます。

3

あなたがラインの最大の長さを知っている場合、あなたは

def getLastLine(fname, maxLineLength=80): 
    fp=file(fname, "rb") 
    fp.seek(-maxLineLength-1, 2) # 2 means "from the end of the file" 
    return fp.readlines()[-1] 

を行うことができます。これは、私のWindowsマシン上で動作します。しかし、バイナリモードでテキストファイルを開くと、他のプラットフォームで何が起きるのか分かりません。 seek()を使用する場合はバイナリモードが必要です。

+2

あなたが最大行長? –

+1

これとマイクの答えはどちらも「正しい方法」ですが、単純な(1バイトのASCIIなどの)テキストエンコーディング以外の問題があります。 unicodeはマルチバイト文字を持つことができます。その場合、(1)指定された最大長の相対オフセットを文字数で知ることができません。(2)文字の「中間」を探すことができます。 –

+0

@Adam通常、保証されている最大値ではないにしても、妥当な行の長さよりも大きな数値を選択することができます。あなたが絶対に何か仮定を立てることができない、または切り捨てられた行を受け入れることができないならば、ファイル全体を読む以外に選択肢はありません。 –

3

ファイルの最後から100バイトほどを探してください。改行を読み込んで検索します。ここに改行がない場合は、100バイトくらいをもう一度検索してください。泡立ち、すすぎ、繰り返します。最終的に改行が見つかります。最後の行は改行の直後に始まります。

ベストケースのシナリオでは、100バイトの読み取りを1回しか行いません。

2

最大の行の長さを選ぶことができれば、読み込みを開始する前に、ファイルの最後まで検索することができます。

myfile.seek(-max_line_length, os.SEEK_END) 
line = myfile.readlines()[-1] 
+0

readlines()には行終端文字が含まれているので、シークでさらに1バイト進む必要があると思います。 – rocksportrocker

0

あなたはその後、mmapにファイルをロードmmap.rfind使用することができます(文字列[、開始[エンド]])ファイルの2番目の最後のEOL文字を見つけるには?ファイル内のその点を探すことは、私が思う最後の行を指摘するはずです。

0

ここでの非効率性は、実際にはPythonによるものではなく、ファイルの読み込みの性質によるものです。最後の行を見つける唯一の方法は、ファイルを読み込み、行末を見つけることです。ただし、シーク操作を使用してファイル内の任意のバイトオフセットにスキップすることができます。最後の行の終わりが検出されるまでは、そのため、ファイルの終わりに非常に近く開始し、必要に応じてますます大きなチャンクをつかむことができます。

from os import SEEK_END 

def get_last_line(file): 
    CHUNK_SIZE = 1024 # Would be good to make this the chunk size of the filesystem 

    last_line = "" 

    while True: 
    # We grab chunks from the end of the file towards the beginning until we 
    # get a new line 
    file.seek(-len(last_line) - CHUNK_SIZE, SEEK_END) 
    chunk = file.read(CHUNK_SIZE) 

    if not chunk: 
     # The whole file is one big line 
     return last_line 

    if not last_line and chunk.endswith('\n'): 
     # Ignore the trailing newline at the end of the file (but include it 
     # in the output). 
     last_line = '\n' 
     chunk = chunk[:-1] 

    nl_pos = chunk.rfind('\n') 
    # What's being searched for will have to be modified if you are searching 
    # files with non-unix line endings. 

    last_line = chunk[nl_pos + 1:] + last_line 

    if nl_pos == -1: 
     # The whole chunk is part of the last line. 
     continue 

    return last_line 
+0

'file.seek(-n、os.SEEK_END)'は 'n 'がファイルサイズより大きい場合、' IOError:[Errno 22] Invalid argument'を送出します。 –

0

ここでは、わずかに異なる解決策です。複数行ではなく、最後の行に焦点を当て、一定のブロックサイズではなく、動的(倍増)ブロックサイズを持っています。詳細はコメントを参照してください。

# Get last line of a text file using seek method. Works with non-constant block size. 
# IDK if that speed things up, but it's good enough for us, 
# especially with constant line lengths in the file (provided by len_guess), 
# in which case the block size doubling is not performed much if at all. Currently, 
# we're using this on a textfile format with constant line lengths. 
# Requires that the file is opened up in binary mode. No nonzero end-rel seeks in text mode. 
REL_FILE_END = 2 
def lastTextFileLine(file, len_guess=1): 
    file.seek(-1, REL_FILE_END)  # 1 => go back to position 0; -1 => 1 char back from end of file 
    text = file.read(1) 
    tot_sz = 1    # store total size so we know where to seek to next rel file end 
    if text != b'\n':  # if newline is the last character, we want the text right before it 
     file.seek(0, REL_FILE_END) # else, consider the text all the way at the end (after last newline) 
     tot_sz = 0 
    blocks = []   # For storing succesive search blocks, so that we don't end up searching in the already searched 
    j = file.tell()   # j = end pos 
    not_done = True 
    block_sz = len_guess 
    while not_done: 
     if j < block_sz: # in case our block doubling takes us past the start of the file (here j also = length of file remainder) 
      block_sz = j 
      not_done = False 
     tot_sz += block_sz 
     file.seek(-tot_sz, REL_FILE_END)   # Yes, seek() works with negative numbers for seeking backward from file end 
     text = file.read(block_sz) 
     i = text.rfind(b'\n') 
     if i != -1: 
      text = text[i+1:].join(reversed(blocks)) 
      return str(text) 
     else: 
      blocks.append(text) 
      block_sz <<= 1 # double block size (converge with open ended binary search-like strategy) 
      j = j - block_sz  # if this doesn't work, try using tmp j1 = file.tell() above 
    return str(b''.join(reversed(blocks)))  # if newline was never found, return everything read 

これは、クラスLastTextFileLineでラップし、行の移動平均を追跡するのが理想的です。これはあなたに良いlen_guessを与えるでしょう。 f.readlinesで

-1

は/ usr/binに/ pythonのラインのための

数= 0

F =オープン( 'last_line1'、 'R')

を():!

line = line.strip() 

count = count + 1 

print line 

印刷回数

f.close()

h.readlinesのラインのための( 'last_line1'、 'R')

オープン= 0

H =()

COUNT1:

line = line.strip() 

count1 = count1 + 1 

if count1 == count: 

    print line   #------------------------- this is the last line 

h.close()

2
with open('output.txt', 'r') as f: 
    lines = f.read().splitlines() 
    last_line = lines[-1] 
    print last_line 
関連する問題