【问题标题】:read a very big single line txt file and split it读取一个非常大的单行 txt 文件并将其拆分
【发布时间】:2013-05-11 02:52:02
【问题描述】:

我有以下问题: 我有一个将近 500mb 的文件。它的文本,都在一行中。文本以虚拟行结尾分隔,称为 ROW_DEL,在文本中如下所示:

this is a line ROW_DEL and this is a line

现在我需要做以下事情,我想把这个文件分成几行,所以我得到一个像这样的文件:

this is a line
and this is a line

问题,即使我用windows文本编辑器打开它,它也会因为文件太大而中断。

是否可以像我提到的那样用 C#、Java 或 Python 分割这个文件?最好不要过度使用我的 CPU。

【问题讨论】:

  • 您不能使用sed 或任何脚本工具吗?
  • 你为什么称 ROW_DEL 为虚拟结局? ROW_DEL 是否是文件中的连续字符?我认为你的问题很容易解决,但这点让我很困惑。
  • 您可以尝试以固定大小的块读取文件,查看 StreamReader 文档 (docs.python.org/release/2.4/lib/stream-reader-objects.html) 中的 read 文档
  • ROW_DEL 是文件中的连续字符。
  • 我对 sed 的投票:如果需要,可以使用 windows 端口。

标签: c# java python


【解决方案1】:

这是我的解决方案。
原则上很容易(ŁukaszW.pl 给出了它),但如果想要处理特殊情况(ŁukaszW.pl 没有),编码就不那么容易了。

特殊情况是分隔符 ROW_DEL 被分成两个读取块(正如 I4V 指出的那样),甚至更微妙的是,如果有两个连续的 ROW_DEL 其中第二个被分成两个读取块。

由于 ROW_DEL 比任何可能的换行符('\r'、'\n'、'\r\n')都长,它可以在文件中被操作系统使用的换行符替换。这就是我选择自己重写文件的原因。
为此,我使用模式'r+',它不会创建新文件。
使用二进制模式'b' 也是绝对必要的。

原理是读取一个chunk(例如在现实生活中它的大小为262144)和x个附加字符,其中x是分隔符的长度—— 1.
然后检查分隔符是否存在于块的末尾 + x 字符。
根据是否存在,在执行 ROW_DEL 的转换之前将块缩短或不缩短,并在原地重写。

裸码是:

text = ('The hospital roommate of a man infected ROW_DEL'
        'with novel coronavirus (NCoV)ROW_DEL'
        '—a SARS-related virus first identified ROW_DELROW_DEL'
        'last year and already linked to 18 deaths—ROW_DEL'
        'has contracted the illness himself, ROW_DEL'
        'intensifying concerns about the ROW_DEL'
        "virus's ability to spread ROW_DEL"
        'from person to person.')

with open('eessaa.txt','w') as f:
    f.write(text)

with open('eessaa.txt','rb') as f:
    ch = f.read()
    print ch.replace('ROW_DEL','ROW_DEL\n')
    print '\nlength of the text : %d chars\n' % len(text)

#==========================================

from os.path import getsize
from os import fsync,linesep

def rewrite(whichfile,sep,chunk_length,OSeol=linesep):
    if chunk_length<len(sep):
        print 'Length of second argument, %d , is '\
              'the minimum value for the third argument'\
              % len(sep)
        return

    x = len(sep)-1
    x2 = 2*x
    file_length = getsize(whichfile)
    with open(whichfile,'rb+') as fR,\
         open(whichfile,'rb+') as fW:
        while True:
            chunk = fR.read(chunk_length)
            pch = fR.tell()
            twelve = chunk[-x:] + fR.read(x)
            ptw = fR.tell()

            if sep in twelve:
                pt = twelve.find(sep)
                m = ("\n   !! %r is "
                     "at position %d in twelve !!" % (sep,pt))
                y = chunk[0:-x+pt].replace(sep,OSeol)
            else:
                pt = x
                m = ''
                y = chunk.replace(sep,OSeol)

            pos = fW.tell()
            fW.write(y)
            fW.flush()
            fsync(fW.fileno())

            if fR.tell()<file_length:
                fR.seek(-x2+pt,1)
            else:
                fW.truncate()
                break

rewrite('eessaa.txt','ROW_DEL',14)

with open('eessaa.txt','rb') as f:
    ch = f.read()
    print '\n'.join(repr(line)[1:-1] for line in ch.splitlines(1))
    print '\nlength of the text : %d chars\n' % len(ch)

为了跟踪执行,这是另一个一直打印消息的代码:

text = ('The hospital roommate of a man infected ROW_DEL'
        'with novel coronavirus (NCoV)ROW_DEL'
        '—a SARS-related virus first identified ROW_DELROW_DEL'
        'last year and already linked to 18 deaths—ROW_DEL'
        'has contracted the illness himself, ROW_DEL'
        'intensifying concerns about the ROW_DEL'
        "virus's ability to spread ROW_DEL"
        'from person to person.')

with open('eessaa.txt','w') as f:
    f.write(text)

with open('eessaa.txt','rb') as f:
    ch = f.read()
    print ch.replace('ROW_DEL','ROW_DEL\n')
    print '\nlength of the text : %d chars\n' % len(text)

#==========================================

from os.path import getsize
from os import fsync,linesep

def rewrite(whichfile,sep,chunk_length,OSeol=linesep):
    if chunk_length<len(sep):
        print 'Length of second argument, %d , is '\
              'the minimum value for the third argument'\
              % len(sep)
        return

    x = len(sep)-1
    x2 = 2*x
    file_length = getsize(whichfile)
    with open(whichfile,'rb+') as fR,\
         open(whichfile,'rb+') as fW:
        while True:
            chunk = fR.read(chunk_length)
            pch = fR.tell()
            twelve = chunk[-x:] + fR.read(x)
            ptw = fR.tell()

            if sep in twelve:
                pt = twelve.find(sep)
                m = ("\n   !! %r is "
                     "at position %d in twelve !!" % (sep,pt))
                y = chunk[0:-x+pt].replace(sep,OSeol)
            else:
                pt = x
                m = ''
                y = chunk.replace(sep,OSeol)
            print ('chunk  == %r   %d chars\n'
                   ' -> fR now at position  %d\n'
                   'twelve == %r   %d chars   %s\n'
                   ' -> fR now at position  %d'
                   % (chunk ,len(chunk),      pch,
                      twelve,len(twelve),m,   ptw) )

            pos = fW.tell()
            fW.write(y)
            fW.flush()
            fsync(fW.fileno())
            print ('          %r   %d long\n'
                   ' has been written from position %d\n'
                   ' => fW now at position  %d'
                   % (y,len(y),pos,fW.tell()))

            if fR.tell()<file_length:
                fR.seek(-x2+pt,1)
                print ' -> fR moved %d characters back to position %d'\
                       % (x2-pt,fR.tell())
            else:
                print (" => fR is at position %d == file's size\n"
                       '    File has thoroughly been read'
                       % fR.tell())
                fW.truncate()
                break

            raw_input('\npress any key to continue')


rewrite('eessaa.txt','ROW_DEL',14)

with open('eessaa.txt','rb') as f:
    ch = f.read()
    print '\n'.join(repr(line)[1:-1] for line in ch.splitlines(1))
    print '\nlength of the text : %d chars\n' % len(ch)

为了检测 ROW_DEL 是否跨越两个块以及是否有两个 ROW_DEL 是连续的,对块末端的处理有一些微妙之处。这就是为什么我花了很长时间发布我的解决方案:我终于不得不写 fR.seek(-x2+pt,1) 而不仅仅是 fR.seek(-2*x,1) 或 fR.seek(-x,1) 根据 sep 是否跨越(2*x 是代码中的 x2,其中 ROW_DEL x 和 x2 分别为 6 和 12)。对此感兴趣的任何人都可以通过更改与if 'ROW_DEL' is in twelve 一致的部分中的代码来检查它。

【讨论】:

    【解决方案2】:

    实际上 500mb 的文本并没有那么大,只是记事本很烂。由于您在 Windows 上,您可能没有 sed 可用,但至少尝试在 python 中天真的解决方案,我认为它会正常工作:

    import os
    with open('infile.txt') as f_in, open('outfile.txt', 'w') as f_out:
      f_out.write(f_in.read().replace('ROW_DEL ', os.linesep))
    

    【讨论】:

    • +1 不知道为什么这被否决了它实际上可以立即工作并回答问题。也许'ROW_DEL ' 应该是' ROW_DEL '
    • 我认为你的答案在阅读更大的文件时可能是致命的。如果您通过 char 读取来填充长度等于搜索到的子字符串长度的向量,弹出前面,向后推并比较,您可以使用更耗时但更安全的方法。
    【解决方案3】:

    分块读取这个文件,例如在c#中使用StreamReader.ReadBlock。您可以在此处设置要读取的最大字符数。

    对于每个读取的块,您可以将 ROW_DEL 替换为 \r\n 并将其附加到新文件中。

    只需记住将当前索引增加您刚刚阅读的字符数。

    【讨论】:

    • 如果 ROW_DEL 被分成两个块怎么办?
    • Right.. 在这种情况下,只需检查你的块最后一个字母是否是ROW_DEL 的一部分,如果需要,再读几个字符。您可以完全控制要阅读的内容,所以这应该不是问题。
    猜你喜欢
    • 2016-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-13
    • 1970-01-01
    • 1970-01-01
    • 2013-05-31
    • 1970-01-01
    相关资源
    最近更新 更多