[英]Reading Huge File in Python
我有一個384MB的文本文件,帶有5000萬行。 每行包含2個以空格分隔的整數:一個鍵和一個值。 該文件按鍵排序。 我需要一種有效的方法來查找Python中約200個鍵的列表的值。
我目前的方法如下。 這需要30秒。 必須有更高效的Python foo才能將其降低到最多幾秒鍾的合理效率。
# list contains a sorted list of the keys we need to lookup
# there is a sentinel at the end of list to simplify the code
# we use pointer to iterate through the list of keys
for line in fin:
line = map(int, line.split())
while line[0] == list[pointer].key:
list[pointer].value = line[1]
pointer += 1
while line[0] > list[pointer].key:
pointer += 1
if pointer >= len(list) - 1:
break # end of list; -1 is due to sentinel
編碼的二進制搜索+搜索解決方案(感謝kigurai!):
entries = 24935502 # number of entries
width = 18 # fixed width of an entry in the file padded with spaces
# at the end of each line
for i, search in enumerate(list): # list contains the list of search keys
left, right = 0, entries-1
key = None
while key != search and left <= right:
mid = (left + right) / 2
fin.seek(mid * width)
key, value = map(int, fin.readline().split())
if search > key:
left = mid + 1
else:
right = mid - 1
if key != search:
value = None # for when search key is not found
search.result = value # store the result of the search
如果您只需要5000萬行中的200行,那么將其全部讀入內存是一種浪費。 我將對搜索鍵列表進行排序,然后使用seek()或類似方法將二進制搜索應用於文件。 這樣,您就不會將整個文件讀取到內存中,我認為這樣可以加快速度。
S.Lotts的略微優化答案:
from collections import defaultdict
keyValues= defaultdict(list)
targetKeys= # some list of keys as strings
for line in fin:
key, value = line.split()
if key in targetKeys:
keyValues[key].append( value )
由於我們使用的是字典而不是列表,因此鍵不必是數字。 這將保存map()操作和每行的字符串到整數的轉換。 如果您希望鍵為數字,則只需要對每個鍵執行一次即可,而不是對5000萬行的每一行都進行一次轉換。
目前尚不清楚“ list [pointer]”的含義。 但是考慮一下。
from collections import defaultdict
keyValues= defaultdict(list)
targetKeys= # some list of keys
for line in fin:
key, value = map( int, line.split())
if key in targetKeys:
keyValues[key].append( value )
我會使用內存映射: http : //docs.python.org/library/mmap.html 。
這樣,您可以像將文件存儲在內存中一樣使用文件,但是操作系統會決定應從文件中實際讀取哪些頁面。
這是對文本文件的遞歸二進制搜索
import os, stat
class IntegerKeyTextFile(object):
def __init__(self, filename):
self.filename = filename
self.f = open(self.filename, 'r')
self.getStatinfo()
def getStatinfo(self):
self.statinfo = os.stat(self.filename)
self.size = self.statinfo[stat.ST_SIZE]
def parse(self, line):
key, value = line.split()
k = int(key)
v = int(value)
return (k,v)
def __getitem__(self, key):
return self.findKey(key)
def findKey(self, keyToFind, startpoint=0, endpoint=None):
"Recursively search a text file"
if endpoint is None:
endpoint = self.size
currentpoint = (startpoint + endpoint) // 2
while True:
self.f.seek(currentpoint)
if currentpoint <> 0:
# may not start at a line break! Discard.
baddata = self.f.readline()
linestart = self.f.tell()
keyatpoint = self.f.readline()
if not keyatpoint:
# read returned empty - end of file
raise KeyError('key %d not found'%(keyToFind,))
k,v = self.parse(keyatpoint)
if k == keyToFind:
print 'key found at ', linestart, ' with value ', v
return v
if endpoint == startpoint:
raise KeyError('key %d not found'%(keyToFind,))
if k > keyToFind:
return self.findKey(keyToFind, startpoint, currentpoint)
else:
return self.findKey(keyToFind, currentpoint, endpoint)
在jEdit中創建的示例文本文件似乎可以正常工作:
>>> i = integertext.IntegerKeyTextFile('c:\\sampledata.txt')
>>> i[1]
key found at 0 with value 345
345
絕對可以通過緩存找到的密鑰並使用緩存確定將來的起始搜索點來進行改進。
如果您可以控制文件的格式,則“排序和二進制搜索”響應是正確的。 詳細信息是,這僅適用於固定大小和偏移量的記錄(嗯,我應該說它僅適用於固定長度的記錄)。
使用固定長度的記錄,您可以輕松地在已排序的文件周圍搜尋()來查找密鑰。
一種可能的優化方法是使用file.readlines(..)中的sizehint
選項進行一些緩沖。 這使您可以在內存中加載多行,總計大約為sizehint
字節。
您需要使用seek()實現二進制搜索
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.