用Python读取大型文件

用Python读取大型文件,python,performance,file-io,large-files,Python,Performance,File Io,Large Files,我有一个384MB的文本文件,有5000万行。每行包含两个空格分隔的整数:一个键和一个值。该文件按键排序。我需要一种在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

我有一个384MB的文本文件,有5000万行。每行包含两个空格分隔的整数:一个键和一个值。该文件按键排序。我需要一种在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#条目数
宽度=18#文件中条目的固定宽度,并用空格填充
#在每行的末尾
对于i,在枚举(列表)中搜索:#列表包含搜索键列表
左,右=0,条目-1
键=无
而钥匙搜索和左键:
左=中+1
其他:
右=中-1
如果是钥匙!=搜索:
值=无#用于未找到搜索键时
search.result=value#存储搜索结果

我会使用内存映射:。
通过这种方式,您可以像存储在内存中一样使用该文件,但操作系统决定应该从该文件中读取哪些页面。

不清楚“列表[指针]”到底是什么意思。不过,请考虑一下。

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 )

如果你只需要5000万行中的200行,那么把它全部读入内存是一种浪费。我会对搜索键列表进行排序,然后使用seek()或类似的方法对文件应用二进制搜索。这样您就不会将整个文件读取到内存中,我认为这样会加快速度。

一种可能的优化方法是使用中的
sizehint
选项进行一些缓冲。这允许您在内存中加载多行,总计约为
sizehint
字节。

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万行中的每行执行一次转换。

如果您可以控制文件格式,“排序和二进制搜索”响应是正确的。细节是,这只适用于固定大小和偏移量的记录(我应该说,它只适用于固定长度的记录)


使用固定长度记录,您可以轻松地在已排序的文件中查找()以查找密钥。

您需要使用seek()实现二进制搜索。

下面是对文本文件的递归二进制搜索

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)

通过缓存找到的密钥并使用缓存来确定未来的起始寻道点,它肯定可以得到改进。

这比我在问题中介绍的方法要慢。:(注:我在我的代码片段中添加了一些注释来更好地解释它。这种方法,再加上Will的固定宽度条目的想法听起来不错。让我快速尝试一下。太好了,这太快了!:我很想看看你的最终解决方案,因为看起来你是从没有提供代码的答案中拼凑出来的。这是一个很好的技巧来确保它从换行开始,但由于我对输入文件有完全的控制权,所以将其格式化为每行具有固定宽度会更快。请参阅原始问题末尾的我的实现。
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)
>>> i = integertext.IntegerKeyTextFile('c:\\sampledata.txt')
>>> i[1]
key found at  0  with value  345
345