代码之家  ›  专栏  ›  技术社区  ›  moinudin

用Python读取大型文件

  •  13
  • moinudin  · 技术社区  · 17 年前

    我有一个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 # 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
    
    8 回复  |  直到 16 年前
        1
  •  11
  •   Hannes Ovrén    17 年前

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

        2
  •  7
  •   Chris Upchurch    17 年前

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

        3
  •  4
  •   S.Lott    17 年前

    现在还不清楚“列表[指针]”是关于什么的。然而,考虑一下这一点。

    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 )
    
        4
  •  3
  •   Bastien Léonard    17 年前
        5
  •  3
  •   Joe Koberg    17 年前

    这里是对文本文件的递归二进制搜索

    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
    

        6
  •  2
  •   Will Hartung    17 年前

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

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

        7
  •  0
  •   Il-Bhima    17 年前

    一种可能的优化是使用 sizehint 选择权 file.readlines(..) sizehint 字节。

        8
  •  0
  •   vartec    17 年前

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