У меня огромный текстовый файл (~ 1 ГБ), и, к сожалению, текстовый редактор, который я использую, не читает такой большой файл. Однако, если я могу просто разделить его на две или три части, я буду в порядке, поэтому в качестве упражнения я хотел написать программу на python, чтобы сделать это.
Я думаю, что мне нужно, чтобы программа находила размер файла, делила это число на части и для каждой части считывала до этого момента порциями, записывала в выходной файл имя файла.nnn, а затем считывала- до следующего разрыва строки и напишите это, затем закройте выходной файл и т. д. Очевидно, что последний выходной файл просто копируется в конец входного файла.
Можете ли вы помочь мне с ключевыми частями, связанными с файловой системой: размер файла, чтение и запись кусками и чтение до разрыва строки?
Я буду писать этот код сначала тестовым, поэтому нет необходимости давать мне полный ответ, если только он не однострочный ;-)






Проверьте размер файла os.stat() и file.readlines([sizehint]). Эти две функции должны быть всем, что вам нужно для чтения, и, надеюсь, вы знаете, как писать :)
Спасибо за ответ - пока ваши предложения по чтению файла работают хорошо. Когда я закончу, я также попробую двоичную версию, которая не читает по одной строке за раз.
Вы можете использовать wc и split (см. Соответствующие страницы руководства), чтобы получить желаемый эффект. В bash:
split -dl$((`wc -l 'filename'|sed 's/ .*$//'` / 3 + 1)) filename filename-chunk.
производит 3 части одного и того же количества строк (конечно, с ошибкой округления в последнем) с именами от filename-chunk.00 до filename-chunk.02.
Да, это не Python, но зачем использовать отвертку, чтобы наклеивать гвоздь?
Ну, на самом деле это не отвертка против гвоздя ... python часто является отличным способом для выполнения таких простых задач, как эта. И я не хочу bash bash (каламбур), но это не совсем ... читабельно :)
@chrisfs: Naja, rückblickend würde ich vielleicht eher awk '{print $1}' statt der sed-Konstruktion verwenden. Trotzdem kann man ziemlich direkt sehen, был пассивен: wc zählt die Zeilen, sed zieht die reine Zahl aus der Ausgabe, die wird durch drei geteilt und um 1 erhöht; split erzeugt dann Teile dieser Länge aus filename und benennt sie filename.chunk. plus fortlaufende Nummer. Es wäre natürlich nett, wenn wc eine Option hätte, direkt nur die Zahl auszugeben, aber auch so kann man damit gut arbeiten.
Или версия wc и split для Python:
lines = 0
for l in open(filename): lines += 1
Затем какой-нибудь код для чтения первых строк / 3 в один файл, следующих строк / 3 в другой и т. д.
Нет необходимости вести счет вручную, используйте enumerate: для l, строка в enumerate (open (filename)): ...
Я написал программу и вроде работает нормально. Так что спасибо Камилу Кисиэлю за то, что заставил меня начать.
(Обратите внимание, что 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
в linux есть команда разделения
разделить -l 100000 file.txt
разделится на файлы размером 100000 строк
А если вашей базовой ОС является Windows, вы можете получить Cygwin для доступа практически ко всем классным утилитам командной строки.
Unixtools для Windows также имеет инструмент разделения: split.exe.
У меня есть файл на 120 ГБ. При использовании этой команды он застревает после 1928613 строк. Никаких дальнейших действий не происходит. Я пытался сделать то, что было сказано в stackoverflow.com/a/291759/6143004, но возникла та же проблема.
не забудьте стремиться() и mmap () для произвольного доступа к файлам.
def getSomeChunk(filename, start, len):
fobj = open(filename, 'r+b')
m = mmap.mmap(fobj.fileno(), 0)
return m[start:start+len]
Этот метод генератора - (медленный) способ получить кусок строк, не увеличивая вашу память.
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)
В качестве альтернативного метода с использованием библиотеки журналов:
>>> 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 (end of file)
filename.txt.1
filename.txt.2
...
filename.txt.10 (start of file)
Это быстрый и простой способ привести огромный файл журнала в соответствие с вашей реализацией RotatingFileHandler.
поскольку он разбивается построчно, как это сделать быстрее?
Это сработало для меня
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()
У меня было требование разделить файлы CSV для импорта в Dynamics CRM, поскольку ограничение на размер файла для импорта составляет 8 МБ, а файлы, которые мы получаем, намного больше. Эта программа позволяет пользователю вводить 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()
Хотя Ответ Райана Гинстрома верен, это занимает больше времени, чем должно (как он уже отмечал). Вот способ обойти множественные вызовы 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
использование - 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)
Сработал у меня отлично в 2017 году! Большое спасибо @Mudit
Можете ли вы сделать так, чтобы этот код извлекался построчно, а не по символам. Есть ли способ узнать количество символов в следующей строке?
Вот скрипт 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.
Вот еще один способ сделать это на чистом питоне, хотя я не тестировал его на огромных файлах, он будет медленнее, но с меньшим объемом памяти:
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 = yield_csv_rows(reader, CHUNK_SIZE)
for chunk in chunks:
if not chunk:
break
# Do something with your chunk here
Вот еще один пример использования readlines():
"""
Simple example using readlines()
where the 'file' is generated via:
seq 10000 > file
"""
CHUNK_SIZE = 5
def yield_rows(reader, chunk_size):
"""
Yield row chunks
"""
chunk = []
for i, row in enumerate(reader):
if i % chunk_size == 0 and i > 0:
yield chunk
del chunk[:]
chunk.append(row)
yield chunk
def batch_operation(data):
for item in data:
print(item)
with open('file', 'r') as f:
chunks = yield_rows(f.readlines(), CHUNK_SIZE)
for _chunk in chunks:
batch_operation(_chunk)
Пример readlines демонстрирует, как разбить данные на части для передачи фрагментов функции, которая ожидает фрагменты. К сожалению, readlines открывает весь файл в памяти, поэтому для повышения производительности лучше использовать пример чтения. Хотя, если вы можете легко поместить то, что вам нужно, в память и обрабатывать его по частям, этого должно быть достаточно.
Первый - вызвать внешнюю команду linux, я не понял ... Во-вторых, строки чтения будут читать весь файл, который потребляет много памяти, кроме того, зачем нам нужны другие куски для этого ???
Использование команды linux split во многих случаях быстрее, использует больше памяти, так как используется подпроцесс ... все это объясняется в ответе. Пример readlines демонстрирует, как разбить данные на части для передачи фрагментов функции, которая ожидает фрагменты.
Теперь доступен модуль pypi, который можно использовать для разделения файлов любого размера на куски. Проверь это
https://pypi.org/project/filesplit/
Поддерживает ли этот пакет разбиение по количеству строк? Я вижу, что он разделен по заданному размеру.
Вы можете разделить любой файл на фрагменты, как показано ниже, здесь CHUNK_SIZE составляет 500000 байт (500 КБ), а содержимое может быть любым файлом:
for idx,val in enumerate(get_chunk(content, CHUNK_SIZE)):
data=val
index=idx
def get_chunk(content,size):
for i in range(0,len(content),size):
yield content[i:i+size]
Нежелательное предложение: найдите лучший текстовый редактор. :-) Если вы работаете в Windows, я знаю, что EmEditor будет легко редактировать файлы без необходимости полностью загружать их в память.