我想在文件中特定行的特定列处插入一个字符串,而无需读取和重写整个文件。
假设我有一个文件file.txt
How was the English test?
How was the Math test?
How was the Chemistry test?
How was the test?
我想通过在第 4 行第 13 列添加字符串History
来更改最后一行以表示How was the History test?
。
目前,我读取文件的每一行并将字符串添加到指定位置。
with open("file.txt", "r+") as f:
# Read entire file
lines = f.readlines()
# Update line
lino = 4 - 1
colno = 13 -1
lines[lino] = lines[lino][:colno] + "History " + lines[lino][colno:]
# Rewrite file
f.seek(0)
for line in lines:
f.write(line)
f.truncate()
f.close()
但我觉得我应该能够简单地将行添加到文件中,而无需读取和重写整个文件。
这可能是下面SO线程的副本
在 Python 中从大文件中删除一行的最快方法
在上面是关于删除的讨论,这只是一种操纵,而你的更多的是一种修改。所以代码会像下面这样更新
def update(filename, lineno, column, text):
fro = open(filename, "rb")
current_line = 0
while current_line < lineno - 1:
fro.readline()
current_line += 1
seekpoint = fro.tell()
frw = open(filename, "r+b")
frw.seek(seekpoint, 0)
# read the line we want to update
line = fro.readline()
chars = line[0: column-1] + text + line[column-1:]
while chars:
frw.writelines(chars)
chars = fro.readline()
fro.close()
frw.truncate()
frw.close()
if __name__ == "__main__":
update("file.txt", 4, 13, "History ")
在一个大文件中,直到需要更新的行号之前不进行修改是有意义的,想象一下你有 10K 行的文件,更新需要在 9K 进行,你的代码将不必要地加载内存中的所有9K
行数据。您拥有的代码仍然可以工作,但不是最佳方法
函数readlines()
读取整个文件。 但并非必须如此。 它实际上从当前文件光标位置读取到末尾,这恰好在打开后立即0
。 (要确认这一点,请在with
语句后立即尝试f.tell()
。 如果我们从接近文件末尾开始怎么办?
编写代码的方式意味着对文件内容和布局有一些先验知识。 你能对每行施加任何约束吗? 例如,给定您的示例数据,我们可能会说行保证为 27 字节或更少。 让我们将其四舍五入为 32 以获得"2 的幂",并尝试从文件末尾向后查找。
# note the "rb+"; need to open in binary mode, else seeking is strictly
# a "forward from 0" operation. We need to be able to seek backwards
with open("file.txt", "rb+") as f:
# caveat: if file is less than 32 bytes, this will throw
# an exception. The second parameter, 2, says "from end of file"
f.seek(-32, 2)
last = f.readlines()[-1].decode()
此时,代码仅读取文件的最后 32 个字节。1readlines()
(在字节级别)将查找行结束字节(在 Unix、n
或0x0a
或字节值 10 中),并返回前后。 阐明:
>>> last = f.readlines()
>>> print( last )
[b'hemistry test?n', b'How was the test?']
>>> last = last[-1]
>>> print( last )
b'How was the test?'
至关重要的是,这在 UTF-8 编码下非常有效,因为它利用了UTF-8 属性,即在编码非 ASCII 字节时不会出现低于 128 的 ASCII字节值。 换句话说,确切的字节n
(或0x0a
)只作为换行符出现,而不是作为字符的一部分出现。 如果您使用的是非 UTF-8 编码,则需要检查代码假设是否仍然成立。
另一个注意事项:给定示例数据,32 字节是任意的。 更实际和典型的值可能是 512、1024 或 4096。 最后,把它放回一个工作示例:
with open("file.txt", "rb+") as f:
# caveat: if file is less than 32 bytes, this will throw
# an exception. The second parameter, 2, says "from end of file"
f.seek(-32, 2)
# does *not* read while file, unless file is exactly 32 bytes.
last = f.readlines()[-1]
last_decoded = last.decode()
# Update line
colno = 13 -1
last_decoded = last_decoded[:colno] + "History " + last_decoded[colno:]
last_line_bytes = len( last )
f.seek(-last_line_bytes, 2)
f.write( last_decoded.encode() )
f.truncate()
请注意,不需要f.close()
。with
语句会自动处理该语句。
1迂腐的人会正确地注意到,计算机和操作系统可能至少读取了 512 个字节,如果不是 4096 个字节,则与磁盘或内存中的页面大小有关。
您可以使用这段代码:
with open("test.txt",'r+') as f:
# Read the file
lines=f.readlines()
# Gets the column
column=int(input("Column:"))-1
# Gets the line
line=int(input("Line:"))-1
# Gets the word
word=input("Word:")
lines[line]=lines[line][0:column]+word+lines[line][column:]
# Delete the file
f.seek(0)
for i in lines:
# Append the lines
f.write(i)
这个答案只会循环访问文件一次,并且只在插入后写入所有内容。在插入位于末尾的情况下,几乎没有开销,并且在开头插入时,它并不比完全读写差。
def insert(file, line, column, text):
ln, cn = line - 1, column - 1 # offset from human index to Python index
count = 0 # initial count of characters
with open(file, 'r+') as f: # open file for reading an writing
for idx, line in enumerate(f): # for all line in the file
if idx < ln: # before the given line
count += len(line) # read and count characters
elif idx == ln: # once at the line
f.seek(count + cn) # place cursor at the correct character location
remainder = f.read() # store all character afterwards
f.seek(count + cn) # move cursor back to the correct character location
f.write(text + remainder) # insert text and rewrite the remainder
return # You're finished!
我不确定您是否在更改文件以包含"历史记录"一词时遇到问题,或者您是否想知道如何只重写文件的某些部分,而不必重写整个内容。
如果您遇到一般问题,这里有一些简单的代码应该可以工作,只要您知道文件中要更改的行。只需更改程序的第一行和最后一行即可相应地读取和写入语句。
fileData="""How was the English test?
How was the Math test?
How was the Chemistry test?
How was the test?""" # So that I don't have to create the file, I'm writing the text directly into a variable.
fileData=fileData.split("n")
fileData[3]=fileData[3][:11]+" History"+fileData[3][11:] # The 3 referes to the line to add "History" to. (The first line is line 0)
storeData=""
for i in fileData:storeData+=i+"n"
storeData=storeData[:-1]
print(storeData) # You can change this to a write command.
如果您想知道如何在不重写整个文件的情况下将特定"部分"更改为文件,那么(据我所知)这是不可能的。
假设你有一个文件,上面写着Ths is a TEST file.
,你想把它更正成This is a TEST file.
;从技术上讲,你会改变17个字符,并在末尾添加一个。您要将"s"更改为"i",将第一个空格更改为"s","i"(从"is")更改为空格,等等...当您向前移动文本时。
计算机实际上不能在其他字节之间插入字节。它只能移动数据,以腾出空间。