使用python快速地在大文本文件中修改某行?
我在使用文本文件處理一張tif圖像,像元值值域為0~63,int,nodatavalue為-9999,在文本文件中一行記錄一個像元值,總共約7億8千萬行。現在需要修改其中任意行的數值以實現圖像處理的功能,應該如何做呢?
1. 問題規模
先來估算一下文件大小,大概是0.7G x 5 (全是-9999)約3.5G。全部裝載到內存中不太合適,所以一般來說是一邊讀,一邊寫。按照20 - 40MB/s的讀寫速度,完全讀寫文件大約需要3-5分鐘2. 局部讀寫
對於行寬不等的文件來說,定位到某一行或者某幾行,需要O(n)的時間;如果需要修改並寫入,那也是O(n)的時間;對於行寬相等的文件,這兩個時間都是O(1)。在讀寫可控的情況下,最好能使每一行的長度都相同。3. 只讀索引
我遇到的問題是對於多個大文件(單個文件&>20G,有大約3000-4000個10M-20G不等的文件),部分內容的多次Parse,而不是讀寫,所以我為文件建立了行-位置的索引,並且我有大量的相鄰行的行寬是相同的,所以使用了簡單的RLE壓縮。這樣在閱讀文件之前載入索引即可實現O(1)的定位和讀取。4. 顯示和修改
因為不能把所有數據都放在內存中,所以你需要自建一個Cache,配合顯示控制項的Virtual Mode,實現固定內存消耗的數據顯示。可以考慮file.seek方法直接定位到那個位置。不過修改的長度要和你改的長度一樣,不然還是會造成類似數組元素後移的情況導致效率降低。比如當前目錄下aa.txt包含文本為
abcdefghijk
運行代碼(python 3.x)
f = open("aa.txt", "rb+")
f.seek(1)
f.write(bytes("z", "utf-8"))
f.close()
運行後可以修改文件為
azcdefghijk
但是要注意要修改的內容和你最後改的內容的長度要相等才不會影響效率。
讀寫大文件可以使用 mmap 模塊,然後可以把文件當做一個列表來用。我不清楚tif圖像是不是像bmp一樣每行長度一樣的,如果是的話直接計算偏移量就行。否則掃描一次記錄每行的位置。
但是圖片處理為什麼不用PIL模塊,這是python不是c語言!你這樣操作寫出來效率估計還不如PIL。
王克純提醒用mmap模塊寫,確實很好用。今天寫好了一個比較完整的方法,可以作為一個實現途徑。我寫了一個RasterMap類,可以用於實現近似使用坐標對Raster對象訪問的一些功能。
import mmap
import Convert
class RasterMap:
def __init__(self, o_r, o_a):
self.origin_raster = o_r
self.origin_ascii = o_a
self.raster_properties = Convert.raster_properties(o_r)
self.ascii_info = self.ASCII_info()
f = open(self.origin_ascii, "r+b")
self.map_obj = mmap.mmap(f.fileno(),0)
def ASCII_info(self):
oa = open(self.origin_ascii, "r")
line_length = len(oa.readline())
line_count = 1
try:
while True:
oa.next()
line_count += 1
except StopIteration:
pass
oa.seek(0)
return {"line_length": line_length, "line_count": line_count}
def read(self, x, y):
width = int(self.raster_properties["width"])
line_length = int(self.ascii_info["line_length"])
offset = (x * width + y) * (line_length + 1)
self.map_obj.seek(offset)
line = int(self.map_obj.readline())
self.map_obj.seek(0)
return line
def write(self, x, y, value):
width = int(self.raster_properties["width"])
line_length = int(self.ascii_info["line_length"])
offset = (x * width + y) * (line_length + 1)
self.map_obj.seek(offset)
self.map_obj.write(str(value))
self.map_obj.write(" " * (12-len(str(value))))
self.map_obj.seek(0)
這裡使用的原文件是多行一列,每行長度為13的ASCII碼文件,或txt文件。
mmap.write()方法要求寫入不超出原文件長度。
運行一次讀取寫入操作大約花費10秒,佔用內存約40MB。實例化花費的時間較長。文中提到的Convert模塊用于格式轉換和提取元信息,可以無視。推薦閱讀:
※如何實現Perl與Python混合編程?
※為什麼大家都在黑 perl?
※Rust 中的 ptr::Shared 使用問題?
※Rust 語言現在什麼情況,為什麼知乎上不怎麼討論Rust語言了呢?
※如何看待12306同一個人,同一次列車不能分段買兩張票?
