在原地修改Python文件

8
我有一个很大的xml文件(40 Gb),需要将其拆分成较小的块。由于我的空间有限,因此是否有一种方法可以在将它们写入新文件时从原始文件中删除这些行?
谢谢!

1
抱歉,我不明白这是关于什么的。 - Henrik Hansen
1
请纠正我,但他正在尝试将一个非常大的文件分成较小的部分。然而,由于文件太大,当他创建分区时,他需要从原始文件中删除该部分以节省空间。 - Jesse Vogt
2
抱歉如果我表述不够明确。我需要使用Python将一个40GB的xml文件拆分成多个较小的文件。通常情况下,我会从文件中读取并写入新文件,直到达到所需的大小限制为止。这种方法需要我拥有80GB的硬盘空间。40GB用于原始文件,另外40GB用于拆分后的文件。但我没有80GB的硬盘空间来处理,那么有没有一种方法可以在将数据写入新文件时同时从原文件中删除相应的行呢? - Maulin
一个40GB的XML文件是由不理解XML用途的人创建的。我的意思是,你甚至无法在读取到EOF之前确定它是否格式良好。 - Robert Rossney
嗯,不要说,如果他们有那么多数据,他们可能可以通过使用二进制格式将其大小减少2/3到3/4。(这只是野兽文件!) - NoMoreZealots
7个回答

7
假设您想将文件分成N个部分,那么只需从文件末尾(或者大致上)开始读取,并反复调用truncate

截断文件的大小。如果有可选的大小参数,则文件将被截断为该大小(最多)。大小默认为当前位置。当前文件位置不会改变。...

import os
import stat

BUF_SIZE = 4096
size = os.stat("large_file")[stat.ST_SIZE]
chunk_size = size // N 
# or simply set a fixed chunk size based on your free disk space
c = 0

in_ = open("large_file", "r+")

while size > 0:
    in_.seek(-min(size, chunk_size), 2)
    # now you have to find a safe place to split the file at somehow
    # just read forward until you found one
    ...
    old_pos = in_.tell()
    with open("small_chunk%2d" % (c, ), "w") as out:
        b = in_.read(BUF_SIZE)
        while len(b) > 0:
            out.write(b)
            b = in_.read(BUF_SIZE)
    in_.truncate(old_pos)
    size = old_pos
    c += 1

请注意,我没有测试过任何内容。在截断调用之后可能需要调用flush,而且我不知道文件系统实际上会释放多快的空间。


很详细。我不太擅长Python,无法凭空做出这样的事情。 - NoMoreZealots
有没有一种方法可以从文件中截取前x个字节?Truncate(100)将使文件最多为100个字节,如何删除文件的前100个字节? - Maulin
谢谢大家的帮助。我只是走了捷径,让脚本将每个块在完成时通过ftp发送到一个具有更多空间的服务器。如果我有更多时间,我会尝试Torsten的方法。 - Maulin
删除文件开头的字节的唯一方法是完全重新写入它,或者就地移动所有内容,即读取第100个字节,写入0,读取101,写入1等等,然后在末尾截断。由于您必须一遍又一遍地执行此操作,因此最终会得到O(n^2)的时间复杂度。 - Torsten Marek

2
如果你在使用Linux/Unix系统,为什么不像这个人那样使用分割命令呢?
split --bytes=100m /input/file /output/dir/prefix

编辑:然后使用csplit


1
这样做不行,因为我有一个XML文件。我需要在正确的位置(在完整记录和关闭标签之后)拆分每个文件。 - Maulin
@Maulin。哎呀...虽然让人头疼,但这确实是一个有趣的问题。 - Jesse Vogt

1
我相信这是可以做到的,因为我甚至能够编辑/读取我运行的脚本的源文件,但最大的问题可能是如果你从文件开头开始,所有的移位都会被执行。另一方面,如果您遍历文件并记录所有行的起始位置,然后按位置的反向顺序复制行,一旦完成,您可以返回,逐个使用readlines()生成新文件(如果它们足够小),将列表的顺序反转,然后寻找文件的开头,并用其新顺序中的行覆盖其旧顺序中的行。
(您可以使用truncate()方法在从末尾读取第一块行后截断文件,该方法截断除文件对象之外的任何参数以外的所有数据,假设您正在使用io包中的类或子类之一来读取文件。您只需要确保当前文件位置在要写入新文件的最后一行的开头即可。)

编辑:根据您的评论,您可能还需要开发一种算法来检测这些标签(可能使用peek方法),并在正确的闭合标签处进行分离。


0

这是我的脚本...

import string
import os
from ftplib import FTP

# make ftp connection
ftp = FTP('server')
ftp.login('user', 'pwd')
ftp.cwd('/dir')

f1 = open('large_file.xml', 'r')

size = 0
split = False
count = 0

for line in f1:
  if not split:
    file = 'split_'+str(count)+'.xml'
    f2 = open(file, 'w')
    if count > 0:
      f2.write('<?xml version="1.0"?>\n')
      f2.write('<StartTag xmlns="http://www.blah/1.2.0">\n')
    size = 0
    count += 1 
    split = True    
  if size < 1073741824:
      f2.write(line)
      size += len(line)
  elif str(line) == '</EndTag>\n':
      f2.write(line)
      f2.write('</EndEndTag>\n')
      print('completed file %s' %str(count))
      f2.close()
      f2 = open(file, 'r')
      print("ftp'ing file...")
      ftp.storbinary('STOR ' + file, f2)
      print('ftp done.')
      split = False
      f2.close()
      os.remove(file)
  else:
    f2.write(line)
    size += len(line)

0

如果时间不是主要因素(或磁盘驱动器的磨损):

  1. 打开文件句柄
  2. 读取分区/逻辑断点大小(由于xml)
  3. 将文件的其余部分保存到磁盘(不确定Python如何处理,直接覆盖文件还是使用内存)
  4. 将分区写入磁盘
  5. 返回步骤1

如果Python没有给你这个级别的控制,你可能需要深入C语言。


0

-1

是时候购买新的硬盘了!

在尝试其他解决方案之前进行备份,以免数据丢失 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接