2008-11-14 138 views
18

我有一个巨大的文本文件(〜1GB),可悲的是我使用的文本编辑器不会读取这么大的文件。但是,如果我可以将它分成两部分或三部分,那么我会很好,所以,作为一个练习,我想用python编写一个程序来完成它。如何分割python中的巨大文本文件

我想我希望程序做的是找到一个文件的大小,除以数量为多个部分,并为每个部分,读取高达块这一点,写入 .nnn输出文件,然后读取到下一个分行并写入,然后关闭输出文件等。显然,最后一个输出文件只是复制到输入文件的末尾。

你可以帮助我关键的文件系统相关的部分:文件大小,读取和写入块和阅读到换行?

我会测试先行编写这些代码,所以没有必要给我一个完整的答案,除非它的一个班轮;-)

+4

不受欢迎的建议:获得更好的文本编辑器。 :-)如果你在Windows上,EmEditor是我知道的,它可以无缝地编辑文件,而无需将它们完全加载到内存中。 – bobince 2008-11-15 13:00:35

回答

14

的文件大小和file.readlines([sizehint])退房os.stat()。这两个功能应该是您阅读部分所需的全部内容,并希望您知道如何写作:)

+0

感谢您的答案 - 您的建议迄今为止阅读文件时运作良好。当我完成时,我还会尝试一次不读取一行的二进制版本。 – quamrana 2008-11-15 20:04:04

+5

“os.path.getsize(filename)`有什么问题? – jfs 2008-11-16 18:02:57

3

您可以使用wcsplit(请参阅各自的手册页)以获得所需的效果。在bash

split -dl$((`wc -l 'filename'|sed 's/ .*$//'`/3 + 1)) filename filename-chunk. 

产生3份相同linecount的(具有舍入误差,在过去,当然),命名filename-chunk.00filename-chunk.02

+1

是的,它不是Python,但为什么用螺丝刀来涂指甲呢? – Svante 2008-11-16 01:05:56

+0

那么它不是一个真正的螺丝刀对指甲... python通常是完成这样简单任务的好方法。我不想抨击庆典(双关语意),但确实是不是真的......可读:) – Agos 2010-02-04 23:22:53

+0

这是非常可读的,你只需要知道的语言。 – Svante 2010-02-05 21:28:50

0

或者,WC和分裂的一个python版本:

lines = 0 
for l in open(filename): lines += 1 

然后一些代码来读取所述第一行/ 3成一个文件,下一行/ 3为另一种,等等

3

我已经编写了程序,它似乎工作正常。所以感谢卡米尔基西尔让我开始。
(注意,FileSizeParts()是一个函数,这里没有显示)
后来我可以绕过去做一个二进制读取的版本,看看它是否更快。

def Split(inputFile,numParts,outputName): 
    fileSize=os.stat(inputFile).st_size 
    parts=FileSizeParts(fileSize,numParts) 
    openInputFile = open(inputFile, 'r') 
    outPart=1 
    for part in parts: 
     if openInputFile.tell()<fileSize: 
      fullOutputName=outputName+os.extsep+str(outPart) 
      outPart+=1 
      openOutputFile=open(fullOutputName,'w') 
      openOutputFile.writelines(openInputFile.readlines(part)) 
      openOutputFile.close() 
    openInputFile.close() 
    return outPart-1 
31

Linux有一个拆分命令

分裂-l 100000 file.txt的

将分成相等的10万线大小的文件

4

没有为随机存取忘记seek()mmap()到文件。

def getSomeChunk(filename, start, len): 
    fobj = open(filename, 'r+b') 
    m = mmap.mmap(fobj.fileno(), 0) 
    return m[start:start+len] 
5

这个生成器方法是一种(慢)的方式来获得一行而不会炸掉你的记忆。

import itertools 

def slicefile(filename, start, end): 
    lines = open(filename) 
    return itertools.islice(lines, start, end) 

out = open("/blah.txt", "w") 
for line in slicefile("/python27/readme.txt", 10, 15): 
    out.write(line) 
9

作为另一种方法,使用记录库:

>>> import logging.handlers 
>>> log = logging.getLogger() 
>>> fh = logging.handlers.RotatingFileHandler("D://filename.txt", 
    maxBytes=2**20*100, backupCount=100) 
# 100 MB each, up to a maximum of 100 files 
>>> log.addHandler(fh) 
>>> log.setLevel(logging.INFO) 
>>> f = open("D://biglog.txt") 
>>> while True: 
...  log.info(f.readline().strip()) 

您的文件将显示如下:

FILENAME.TXT(文件的结束)
FILENAME.TXT .1
filename.txt.2
...
filena me.txt.10(文件开始)

这是一个快速简便的方法,可以使您的RotatingFileHandler实现的巨大日志文件相匹配。

1

这为我工作

import os 

fil = "inputfile" 
outfil = "outputfile" 

f = open(fil,'r') 

numbits = 1000000000 

for i in range(0,os.stat(fil).st_size/numbits+1): 
    o = open(outfil+str(i),'w') 
    segment = f.readlines(numbits) 
    for c in range(0,len(segment)): 
     o.write(segment[c]+"\n") 
    o.close() 
0

我有一个要求,用于导入CSV文件分割成Dynamics CRM中,因为导入的文件大小限制为8MB,我们收到的文件较大。该程序允许用户输入FileNames和LinesPerFile,然后将指定的文件分割成所需的行数。我无法相信它有多快!

# user input FileNames and LinesPerFile 
FileCount = 1 
FileNames = [] 
while True: 
    FileName = raw_input('File Name ' + str(FileCount) + ' (enter "Done" after last File):') 
    FileCount = FileCount + 1 
    if FileName == 'Done': 
     break 
    else: 
     FileNames.append(FileName) 
LinesPerFile = raw_input('Lines Per File:') 
LinesPerFile = int(LinesPerFile) 

for FileName in FileNames: 
    File = open(FileName) 

    # get Header row 
    for Line in File: 
     Header = Line 
     break 

    FileCount = 0 
    Linecount = 1 
    for Line in File: 

     #skip Header in File 
     if Line == Header: 
      continue 

     #create NewFile with Header every [LinesPerFile] Lines 
     if Linecount % LinesPerFile == 1: 
      FileCount = FileCount + 1 
      NewFileName = FileName[:FileName.find('.')] + '-Part' + str(FileCount) + FileName[FileName.find('.'):] 
      NewFile = open(NewFileName,'w') 
      NewFile.write(Header) 

     NewFile.write(Line) 
     Linecount = Linecount + 1 

    NewFile.close() 
3

虽然Ryan Ginstrom's answer是正确的,但它需要更长的时间比它应该(因为他已经注意到)。这里有一个方法依次遍历打开的文件描述符的多次调用规避到itertools.islice

def splitfile(infilepath, chunksize): 
    fname, ext = infilepath.rsplit('.',1) 
    i = 0 
    written = False 
    with open(infilepath) as infile: 
     while True: 
      outfilepath = "{}{}.{}".format(fname, i, ext) 
      with open(outfilepath, 'w') as outfile: 
       for line in (infile.readline() for _ in range(chunksize)): 
        outfile.write(line) 
       written = bool(line) 
      if not written: 
       break 
      i += 1 
2

用法 - split.py名splitsizeinkb

import os 
import sys 

def getfilesize(filename): 
    with open(filename,"rb") as fr: 
     fr.seek(0,2) # move to end of the file 
     size=fr.tell() 
     print("getfilesize: size: %s" % size) 
     return fr.tell() 

def splitfile(filename, splitsize): 
    # Open original file in read only mode 
    if not os.path.isfile(filename): 
     print("No such file as: \"%s\"" % filename) 
     return 

    filesize=getfilesize(filename) 
    with open(filename,"rb") as fr: 
    counter=1 
    orginalfilename = filename.split(".") 
    readlimit = 5000 #read 5kb at a time 
    n_splits = filesize//splitsize 
    print("splitfile: No of splits required: %s" % str(n_splits)) 
    for i in range(n_splits+1): 
     chunks_count = int(splitsize)//int(readlimit) 
     data_5kb = fr.read(readlimit) # read 
     # Create split files 
     print("chunks_count: %d" % chunks_count) 
     with open(orginalfilename[0]+"_{id}.".format(id=str(counter))+orginalfilename[1],"ab") as fw: 
      fw.seek(0) 
      fw.truncate()# truncate original if present 
      while data_5kb:     
       fw.write(data_5kb) 
       if chunks_count: 
        chunks_count-=1 
        data_5kb = fr.read(readlimit) 
       else: break    
     counter+=1 

if __name__ == "__main__": 
    if len(sys.argv) < 3: print("Filename or splitsize not provided: Usage:  filesplit.py filename splitsizeinkb ") 
    else: 
     filesize = int(sys.argv[2]) * 1000 #make into kb 
     filename = sys.argv[1] 
     splitfile(filename, filesize) 
0

这里是一个Python脚本,你可以使用分裂使用subprocess大文件:

""" 
Splits the file into the same directory and 
deletes the original file 
""" 

import subprocess 
import sys 
import os 

SPLIT_FILE_CHUNK_SIZE = '5000' 
SPLIT_PREFIX_LENGTH = '2' # subprocess expects a string, i.e. 2 = aa, ab, ac etc.. 

if __name__ == "__main__": 

    file_path = sys.argv[1] 
    # i.e. split -a 2 -l 5000 t/some_file.txt ~/tmp/t/ 
    subprocess.call(["split", "-a", SPLIT_PREFIX_LENGTH, "-l", SPLIT_FILE_CHUNK_SIZE, file_path, 
        os.path.dirname(file_path) + '/']) 

    # Remove the original file once done splitting 
    try: 
     os.remove(file_path) 
    except OSError: 
     pass 

,可在外部调用它:

import os 
fs_result = os.system("python file_splitter.py {}".format(local_file_path)) 

您还可以导入subprocess并直接在程序中运行它。

此方法的问题是内存使用率高:subprocess创建一个内存占用空间与您的进程大小相同的分叉,并且如果进程内存已经很大,它会在运行时加倍。与os.system同样的事情。

这里是这样做的另一个纯Python的方式,虽然我没有测试它的巨大的文件,它会慢一些,但对于内存精简:

CHUNK_SIZE = 5000 

def yield_csv_rows(reader, chunk_size): 
    """ 
    Opens file to ingest, reads each line to return list of rows 
    Expects the header is already removed 
    Replacement for ingest_csv 
    :param reader: dictReader 
    :param chunk_size: int, chunk size 
    """ 
    chunk = [] 
    for i, row in enumerate(reader): 
     if i % chunk_size == 0 and i > 0: 
      yield chunk 
      del chunk[:] 
     chunk.append(row) 
    yield chunk 

with open(local_file_path, 'rb') as f: 
    f.readline().strip().replace('"', '') 
    reader = unicodecsv.DictReader(f, fieldnames=header.split(','), delimiter=',', quotechar='"') 
    chunks = files.yield_csv_rows(reader, CHUNK_SIZE) 
    for chunk in chunks: 
     if not chunk: 
      break 
     # Do something with your chunk here