我有一个文本文件,每行包含一个时间戳。我的目标是找到时间范围。所有时间都井井有条,因此第一行将是最早的时间,最后一行将是最新的时间。我只需要第一行和最后一行。在python中获取这些行的最有效方法是什么?
注意:这些文件的长度相对较大,每个文件大约1-2百万行,我必须对几百个文件执行此操作。
Answers:
with open(fname, 'rb') as fh:
first = next(fh).decode()
fh.seek(-1024, 2)
last = fh.readlines()[-1].decode()
此处的变量值为1024:它表示平均字符串长度。例如,我仅选择1024。如果您估算出平均线长,则可以将该值乘以2。
由于您不知道行长的可能上限,因此显而易见的解决方案是遍历文件:
for line in fh:
pass
last = line
您无需费心可以使用的二进制标志open(fname)
。
预计到达时间(ETA):由于您要处理的文件很多,因此可以使用创建数十个文件的示例,random.sample
并在其上运行此代码以确定最后一行的长度。先验地获得较大的位置偏移值(假设为1 MB)。这将帮助您估算整个运行的价值。
fh.seek(-1024, os.SEEK_END)
而不是fh.seek(-1024, 2)
增加可读性。
open(fname)
。用b
旗帜开头至关重要。如果你使用open(fname)
的不是open(fname, 'rb')
你会得到 io.UnsupportedOperation:不能做非零最终相对寻求。
要读取文件的第一行和最后一行,您可以...
readline()
,...def readlastline(f):
f.seek(-2, 2) # Jump to the second last byte.
while f.read(1) != b"\n": # Until EOL is found ...
f.seek(-2, 1) # ... jump back, over the read byte plus one more.
return f.read() # Read all data from this point on.
with open(file, "rb") as f:
first = f.readline()
last = readlastline(f)
跳到第二个直接倒数个字节,以防止尾随换行符导致返回空行*。
每次读取一个字节时,当前偏移量将向前推一,因此一次后退一步将完成两个字节,越过最近读取的字节和下一个要读取的字节。
whence
传递给的参数fseek(offset, whence=0)
表示fseek
应该寻找offset
相对于...的位置字节
0
或os.SEEK_SET
=文件的开头。1
或os.SEEK_CUR
=当前位置。2
要么 os.SEEK_END
=文件末尾。*可以预期,大多数应用程序(包括print
和)的默认行为echo
是在写入的每一行后附加一个,并且对缺少尾随换行符的行没有影响。
每个1-2百万行,我必须为数百个文件执行此操作。
我对这种方法进行了计时,并将其与最佳答案进行了比较。
10k iterations processing a file of 6k lines totalling 200kB: 1.62s vs 6.92s.
100 iterations processing a file of 6k lines totalling 1.3GB: 8.93s vs 86.95.
数以百万计的行会增加差很多了。
用于计时的Exakt代码:
with open(file, "rb") as f:
first = f.readline() # Read and store the first line.
for last in f: pass # Read all lines, keep final value.
一种更复杂,更难阅读的变体,用于解决此后提出的评论和问题。
还增加了对多字节定界符的支持readlast(b'X<br>Y', b'<br>', fixed=False)
。
请注意,由于文本模式需要非相对偏移量,因此这种变化对于大型文件来说确实很慢。根据您的需要进行修改,或者根本不使用它,因为最好使用f.readlines()[-1]
在文本模式下打开的文件。
#!/bin/python3
from os import SEEK_END
def readlast(f, sep, fixed=True):
r"""Read the last segment from a file-like object.
:param f: File to read last line from.
:type f: file-like object
:param sep: Segment separator (delimiter).
:type sep: bytes, str
:param fixed: Treat data in ``f`` as a chain of fixed size blocks.
:type fixed: bool
:returns: Last line of file.
:rtype: bytes, str
"""
bs = len(sep)
step = bs if fixed else 1
if not bs:
raise ValueError("Zero-length separator.")
try:
o = f.seek(0, SEEK_END)
o = f.seek(o-bs-step) # - Ignore trailing delimiter 'sep'.
while f.read(bs) != sep: # - Until reaching 'sep': Read sep-sized block
o = f.seek(o-step) # and then seek to the block to read next.
except (OSError,ValueError): # - Beginning of file reached.
f.seek(0)
return f.read()
def test_readlast():
from io import BytesIO, StringIO
# Text mode.
f = StringIO("first\nlast\n")
assert readlast(f, "\n") == "last\n"
# Bytes.
f = BytesIO(b'first|last')
assert readlast(f, b'|') == b'last'
# Bytes, UTF-8.
f = BytesIO("X\nY\n".encode("utf-8"))
assert readlast(f, b'\n').decode() == "Y\n"
# Bytes, UTF-16.
f = BytesIO("X\nY\n".encode("utf-16"))
assert readlast(f, b'\n\x00').decode('utf-16') == "Y\n"
# Bytes, UTF-32.
f = BytesIO("X\nY\n".encode("utf-32"))
assert readlast(f, b'\n\x00\x00\x00').decode('utf-32') == "Y\n"
# Multichar delimiter.
f = StringIO("X<br>Y")
assert readlast(f, "<br>", fixed=False) == "Y"
# Make sure you use the correct delimiters.
seps = { 'utf8': b'\n', 'utf16': b'\n\x00', 'utf32': b'\n\x00\x00\x00' }
assert "\n".encode('utf8' ) == seps['utf8']
assert "\n".encode('utf16')[2:] == seps['utf16']
assert "\n".encode('utf32')[4:] == seps['utf32']
# Edge cases.
edges = (
# Text , Match
("" , "" ), # Empty file, empty string.
("X" , "X" ), # No delimiter, full content.
("\n" , "\n"),
("\n\n", "\n"),
# UTF16/32 encoded U+270A (b"\n\x00\n'\n\x00"/utf16)
(b'\n\xe2\x9c\x8a\n'.decode(), b'\xe2\x9c\x8a\n'.decode()),
)
for txt, match in edges:
for enc,sep in seps.items():
assert readlast(BytesIO(txt.encode(enc)), sep).decode(enc) == match
if __name__ == "__main__":
import sys
for path in sys.argv[1:]:
with open(path) as f:
print(f.readline() , end="")
print(readlast(f,"\n"), end="")
tail_n
。
File "mapper1.2.2.py", line 17, in get_last_line f.seek(-2, 2) IOError: [Errno 22] Invalid argument
io.UnsupportedOperation: can't do nonzero end-relative seeks
,则必须分两个步骤进行操作:首先找到文件的长度,然后添加偏移量,然后将其传递给f.seek(size+offset,os.SEEK_SET)
可以使用Unix命令吗?我认为使用head -1
和tail -n 1
可能是最有效的方法。另外,您也可以使用简单的fid.readline()
方法获取第一行和fid.readlines()[-1]
,但这可能会占用太多内存。
os.popen("tail -n 1 %s" % filename).read()
可以很好地获得最后一行。
os.popen("tail -n 1 %s" % filename).read()
->自2.6版起弃用
这是我的解决方案,也与Python3兼容。它还可以处理边境案件,但缺少utf-16支持:
def tail(filepath):
"""
@author Marco Sulla (marcosullaroma@gmail.com)
@date May 31, 2016
"""
try:
filepath.is_file
fp = str(filepath)
except AttributeError:
fp = filepath
with open(fp, "rb") as f:
size = os.stat(fp).st_size
start_pos = 0 if size - 1 < 0 else size - 1
if start_pos != 0:
f.seek(start_pos)
char = f.read(1)
if char == b"\n":
start_pos -= 1
f.seek(start_pos)
if start_pos == 0:
f.seek(start_pos)
else:
char = ""
for pos in range(start_pos, -1, -1):
f.seek(pos)
char = f.read(1)
if char == b"\n":
break
return f.readline()
它是由ispired Trasp的回答和AnotherParker的评论。
首先以读取模式打开文件,然后使用readlines()方法逐行读取所有行存储在列表中,现在您可以使用列表切片来获取文件的第一行和最后一行。
a=open('file.txt','rb')
lines = a.readlines()
if lines:
first_line = lines[:1]
last_line = lines[-1]
w=open(file.txt, 'r')
print ('first line is : ',w.readline())
for line in w:
x= line
print ('last line is : ',x)
w.close()
该for
环通过线运行,并x
获得在最后一次迭代的最后一行。
with open("myfile.txt") as f:
lines = f.readlines()
first_row = lines[0]
print first_row
last_row = lines[-1]
print last_row
f.readlines()[-1]
新变量的insead。0 =第一行,1 =第二行, -1 =最后一行,-2 =最后一行之前的一行...
这是@Trasp答案的扩展,它具有其他逻辑来处理只有一行的文件的特殊情况。如果您反复想要读取不断更新的文件的最后一行,则处理这种情况可能很有用。没有这个,如果您尝试获取刚刚创建的文件的最后一行,并且只有一行,IOError: [Errno 22] Invalid argument
则会引发该错误。
def tail(filepath):
with open(filepath, "rb") as f:
first = f.readline() # Read the first line.
f.seek(-2, 2) # Jump to the second last byte.
while f.read(1) != b"\n": # Until EOL is found...
try:
f.seek(-2, 1) # ...jump back the read byte plus one more.
except IOError:
f.seek(-1, 1)
if f.tell() == 0:
break
last = f.readline() # Read last line.
return last
没有人提到使用反向:
f=open(file,"r")
r=reversed(f.readlines())
last_line_of_file = r.next()
with open(filename, "rb") as f:#Needs to be in binary mode for the seek from the end to work
first = f.readline()
if f.read(1) == '':
return first
f.seek(-2, 2) # Jump to the second last byte.
while f.read(1) != b"\n": # Until EOL is found...
f.seek(-2, 1) # ...jump back the read byte plus one more.
last = f.readline() # Read last line.
return last
上面的答案是上面的答案的修改版本,用于处理文件中只有一行的情况