2008-11-14 107 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