通过使用mmap和re.findall搜索大文件,在Python中实现MemoryError

通过使用mmap和re.findall搜索大文件,在Python中实现MemoryError,python,pandas,mmap,large-files,re,Python,Pandas,Mmap,Large Files,Re,我希望使用re实现几行python,首先操作一个字符串,然后将该字符串用作正则表达式搜索。我在其中间有“代码> */Cuth>的字符串,即 Ab***CD/, */Cuth>为任意长度。这样做的目的是在文档中执行正则表达式搜索,以提取与起始字符和结束字符匹配的任何行,中间包含任意数量的字符。i、 ab12345cd、ABBCD、ab_fghfghfghcd都是阳性匹配。负匹配的示例:1abcd、agcd、bb111cd 我提出了[\s\s]*?的正则表达式来输入,而不是*。因此,我想从ab***

我希望使用re实现几行python,首先操作一个字符串,然后将该字符串用作正则表达式搜索。我在其中间有“代码> */Cuth>的字符串,即<代码> Ab***CD/<代码>,<代码> */Cuth>为任意长度。这样做的目的是在文档中执行正则表达式搜索,以提取与起始字符和结束字符匹配的任何行,中间包含任意数量的字符。i、 ab12345cd、ABBCD、ab_fghfghfghcd都是阳性匹配。负匹配的示例:1abcd、agcd、bb111cd

我提出了
[\s\s]*?
的正则表达式来输入,而不是
*
。因此,我想从
ab***cd
^ab[\s\s]*?cd
的示例字符串,然后我将使用该字符串对文档进行正则表达式搜索

然后我想在mmap中打开文件,使用正则表达式搜索它,然后将匹配项保存到文件中

import re
import mmap 

def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

def searchFile(list_txt, raw_str):
    search="^"+raw_str #add regex ^ newline operator
    search_rgx=re.sub(r'\*+',r'[\\s\\S]*?',search) #replace * with regex function

    #search file
    with open(list_txt, 'r+') as f: 
        data = mmap.mmap(f.fileno(), 0)
        results = re.findall(bytes(search_rgx,encoding="utf-8"),data, re.MULTILINE)

    #save results
    f1 = open('results.txt', 'w+b')
    results_bin = b'\n'.join(results)
    f1.write(results_bin)
    f1.close()

    print("Found "+str(file_len("results.txt"))+" results")

searchFile("largelist.txt","ab**cd")
现在,使用一个小文件就可以了。但是,当文件变大(1gb的文本)时,我会出现以下错误:

Traceback (most recent call last):
  File "c:\Programming\test.py", line 27, in <module>
    searchFile("largelist.txt","ab**cd")
  File "c:\Programming\test.py", line 21, in searchFile
    results_bin = b'\n'.join(results)
MemoryError
回溯(最近一次呼叫最后一次):
文件“c:\Programming\test.py”,第27行,在
搜索文件(“largelist.txt”,“ab**cd”)
文件“c:\Programming\test.py”,第21行,在searchFile中
结果\u bin=b'\n'。加入(结果)
记忆者
首先,有人能稍微优化一下代码吗?我做错什么了吗?我使用mmap是因为我知道我想查看大文件,我想逐行读取文件,而不是一次全部读取(因此有人建议使用mmap)

我还被告知要去熊猫图书馆查看更多的数据操作。熊猫会取代mmap吗


谢谢你的帮助。正如您所知,我对python非常陌生,因此非常感谢您的帮助。

这个怎么样?在这种情况下,您需要的是以字符串表示的所有行的列表。以下内容模拟了该操作,生成了字符串列表:

import io

longstring = """ab12345cd
abbbcd
ab_fghfghfghcd
1abcd
agcd
bb111cd
"""

list_of_strings = io.StringIO(longstring).read().splitlines()
list_of_strings
输出

['ab12345cd', 'abbbcd', 'ab_fghfghfghcd', '1abcd', 'agcd', 'bb111cd']
0         ab12345cd
1            abbbcd
2    ab_fghfghfghcd
dtype: object
这是最重要的部分

s = pd.Series(list_of_strings)
s[s.str.match('^ab[\s\S]*?cd')]
输出

['ab12345cd', 'abbbcd', 'ab_fghfghfghcd', '1abcd', 'agcd', 'bb111cd']
0         ab12345cd
1            abbbcd
2    ab_fghfghfghcd
dtype: object
Edit2:试试这个:(我看不出你有什么理由想把它作为一个函数,但我已经这样做了,因为你在评论中就是这么做的。)

基于块的方法

import os

def newsearch(filename):
    outpath = 'output.txt'
    if os.path.exists(outpath):
        os.remove(outpath)
    for chunk in pd.read_csv(filename, sep='|', header=None, chunksize=10**6):
        chunk = chunk[chunk[0].str.match('^ab[\s\S]*?cd')]
        chunk[0].to_csv(outpath, index=False, header=False, mode='a')

newsearch('list.txt')
import dask.dataframe as dd

def newsearch(filename):
    chunk = dd.read_csv(filename, header=None, blocksize=25e6)
    chunk = chunk[chunk[0].str.match('^ab[\s\S]*?cd')]
    chunk[0].to_csv('output.txt', index=False, header=False, single_file = True)

newsearch('list.txt')
dask方法

import os

def newsearch(filename):
    outpath = 'output.txt'
    if os.path.exists(outpath):
        os.remove(outpath)
    for chunk in pd.read_csv(filename, sep='|', header=None, chunksize=10**6):
        chunk = chunk[chunk[0].str.match('^ab[\s\S]*?cd')]
        chunk[0].to_csv(outpath, index=False, header=False, mode='a')

newsearch('list.txt')
import dask.dataframe as dd

def newsearch(filename):
    chunk = dd.read_csv(filename, header=None, blocksize=25e6)
    chunk = chunk[chunk[0].str.match('^ab[\s\S]*?cd')]
    chunk[0].to_csv('output.txt', index=False, header=False, single_file = True)

newsearch('list.txt')

您正在逐行处理,因此希望避免在内存中积累数据。在这里,常规的文件读写应该可以很好地工作
mmap
由虚拟内存支持,但在读取时,虚拟内存必须转换为真实内存。在
findall
中累积结果也是一种内存占用。请尝试以下替代方法:

import re

# buffer to 1Meg but any effect would be modest
MEG = 2**20

def searchFile(filename, raw_str):
    # extract start and end from "ab***cd"
    startswith, endswith = re.match(r"([^\*]+)\*+?([^\*]+)", raw_str).groups()
    with open(filename, buffering=MEG) as in_f, open("results.txt", "w", buffering=MEG) as out_f:
        for line in in_f:
            stripped = line.strip()
            if stripped.startswith(startswith) and stripped.endswith(endswith):
                out_f.write(line)

# write test file

test_txt = """ab12345cd
abbbcd
ab_fghfghfghcd
1abcd
agcd
bb111cd
"""

want = """ab12345cd
abbbcd
ab_fghfghfghcd
"""

open("test.txt", "w").write(test_txt)

searchFile("test.txt", "ab**cd")

result = open("results.txt").read()
print(result == want)

我不确定您认为使用
mmap
打开输入文件会有什么好处,但由于必须匹配的每个字符串都由新行分隔(根据您的注释),因此我将使用以下方法(请注意,它是Python,但故意保留为伪代码):

打开(输入文件路径,“r”)作为输入文件:
打开(输出文件路径)时,“x”作为输出文件:
对于输入_文件中的行:
如果匹配(行):
打印(行,文件=输出文件)
可能会根据需要调整
print
功能的
endline
参数

这样,结果在生成时就被写入,并且您可以避免在写入之前在内存中有一个大的
结果。

此外,您不需要关注换行符。只需关注每一行是否匹配。

可能的重复或不重复。这个问题的答案帮助我达到了这一点。您可能确实可以在这里使用pandas,但我认为这取决于您的数据结构。pandas有
pandas.Series.str.match
,它将返回de>True
在符合正则表达式的单元格中。因此,在这种情况下,文档中的每一行都可以是一个单元格,并且可以在符合条件的行/单元格上获得匹配项。嗨,Bertil,数据是一个大文本文件,在换行符上有不同的字符串:
ab123 ccv444 sdads444
类似于:)-看来评论不会把东西放到新词上,但希望你能理解我的意思?好的,好的@TomOldy。检查我的答案,有意义吗?嗨,谢谢。我明白了,你不是在整个搜索中使用正则表达式,你只是在看每行的开始字符和结束字符?如果它们匹配输入字符串
ab**cd
,它会写入文件吗?对不起,这里有点混乱apologies@TomOldy-我可能误解了这个问题,但我认为您有用星号分隔的开始字符串和结束字符串,并且您希望在数据集中找到具有相同开始字符串和结束字符串的字符串。因此,我使用正则表达式获取它们,然后切换到一个更简单的检查目标字符串是否以这些发现的字符串开始和结束。谢谢-您如何将示例正则表达式提供给此搜索?例如
^ab^s[\s\s]*?cd
??@TomOldy首先,我可能会使用
*
而不是
[\s\s]*
,然后我不太明白你对
的用法。在任何情况下,如果您只检查起始字符和结束字符,我会解析输入字符串并使用
myString.startswith(head)
myString.endswith(tail)
@TomOldy我已经更新了我的伪代码,以适应您的结果始终是输入行(如果匹配)。注释不用于扩展讨论;这段对话已经结束。