Python随机从大文件N行(没有重复的行)

Fab*_*aze 3 python random line readline large-files

我需要使用python从大型txt文件中获取N行.这些文件基本上是制表符分隔的表.我的任务有以下限制:

  • 这些文件可能包含标题(某些文件包含多行标题).
  • 标题需要以相同的顺序出现在输出中.
  • 每行只能使用一次.
  • 目前最大的文件大约是150GB(大约6,000,000行).
  • 行在文件中的长度大致相同,但可能在不同文件之间有所不同.
  • 我通常会随机抽取5000行(我可能需要多达1 000 000行)

目前我已经编写了以下代码:

inputSize=os.path.getsize(options.input)
usedPositions=[] #Start positions of the lines already in output

with open(options.input) as input:
    with open(options.output, 'w') as output:

        #Handling of header lines
        for i in range(int(options.header)):
            output.write(input.readline())
            usedPositions.append(input.tell())

        # Find and write all random lines, except last
        for j in range(int(args[0])):
            input.seek(random.randrange(inputSize)) # Seek to random position in file (probably middle of line)
            input.readline() # Read the line (probably incomplete). Next input.readline() results in a complete line.
            while input.tell() in usedPositions: # Take a new line if current one is taken
                input.seek(random.randrange(inputSize))
                input.readline() 
            usedPositions.append(input.tell()) # Add line start position to usedPositions
            randomLine=input.readline() # Complete line
            if len(randomLine) == 0: # Take first line if end of the file is reached
                input.seek(0)
                for i in range(int(options.header)): # Exclude headers
                    input.readline()
                randomLine=input.readline()
            output.write(randomLine)            
Run Code Online (Sandbox Code Playgroud)

此代码似乎正常工作.

我知道这段代码更喜欢输入中最长行的行,因为seek()最有可能返回最长行的位置,下一行写入输出.这是无关紧要的,因为输入文件中的行长度大致相同.另外我知道如果N大于输入文件中的行数,则此代码会导致无限循环.我不会对此进行检查,因为获取行计数需要花费大量时间.

RAM和硬盘限制是无关紧要的.我只关心程序的速度.有没有办法进一步优化此代码?或许还有更好的方法?

编辑:为了澄清,一个文件中的行具有大致相同的长度.但是,我有多个文件需要运行此脚本,并且这些文件的行的平均长度将不同.例如,文件A每行可以有大约100个字符,每行可以有B~50000个字符.我事先并不知道任何文件的平均行长度.

jsb*_*eno 6

只有一种方法可以避免顺序读取所有文件直到您正在采样的最后一行 - 我很惊讶到目前为止没有一个答案提到它:

您必须寻找文件中的任意位置,读取一些字节,如果您有一个典型的行长度,正如您所说,该值应该是该值的3或4倍.然后在新行字符("\n")上拆分您读取的块,然后选择第二个字段 - 即随机位置的一行.

此外,为了能够始终如一地进入文件,应该以"二进制读取"模式打开,因此,应该手动处理行结束标记的转换.

此技术无法为您提供已读取的行号,因此您可以在文件中保留选定的行偏移量以避免重复:

#! /usr/bin/python
# coding: utf-8

import random, os


CHUNK_SIZE = 1000
PATH = "/var/log/cron"

def pick_next_random_line(file, offset):
    file.seek(offset)
    chunk = file.read(CHUNK_SIZE)
    lines = chunk.split(os.linesep)
    # Make some provision in case yiou had not read at least one full line here
    line_offset = offset + len(os.linesep) + chunk.find(os.linesep) 
    return line_offset, lines[1]

def get_n_random_lines(path, n=5):
    lenght = os.stat(path).st_size
    results = []
    result_offsets = set()
    with open(path) as input:
        for x in range(n):
            while True:
                offset, line = pick_next_random_line(input, random.randint(0, lenght - CHUNK_SIZE))
                if not offset in result_offsets:
                    result_offsets.add(offset)
                    results.append(line)
                    break
    return results

if __name__ == "__main__":
    print get_n_random_lines(PATH)
Run Code Online (Sandbox Code Playgroud)