在Python中从二进制文件提取字符串

6 投票
2 回答
16205 浏览
提问于 2025-04-16 22:09

我有一个项目,需要从一个文件中提取字符串。可以把这个过程想象成在Linux中使用“strings”命令,但我是在用Python来做。还有一个条件是,这个文件是以流的形式给我的(比如说字符串),所以直接用一些子进程的函数去运行strings命令就不行了。

我写了以下代码:

def isStringChar(ch):
    if ord(ch) >= ord('a') and ord(ch) <= ord('z'): return True
    if ord(ch) >= ord('A') and ord(ch) <= ord('Z'): return True
    if ord(ch) >= ord('0') and ord(ch) <= ord('9'): return True

    if ch in ['/', '-', ':', '.', ',', '_', '$', '%', '\'', '(', ')', '[', ']', '<', '>', ' ']: return True

# default out
return False

def process(stream):
dwStreamLen = len(stream)
if dwStreamLen < 4: return None

dwIndex = 0;
strString = ''
for ch in stream:
    if isStringChar(ch) == False:
        if len(strString) > 4:
            #print strString
            strString = ''
    else:
        strString += ch

从技术上讲,这段代码是能工作的,但速度慢得离谱。举个例子,我用strings命令处理一个500MB的可执行文件,结果不到1秒就提取出了30万条字符串。而我用上面的代码处理同一个文件,却花了整整16分钟。

有没有什么库可以让我在不受Python延迟影响的情况下完成这个任务呢?

谢谢!

2 个回答

5

你遇到的问题之一是你把整个数据流都读到内存里了(… = len(stream)),还有一个问题是你的 isStringChar 函数运行得很慢(因为函数调用本身就比较慢,而你调用了很多次)。

更好的做法是这样的:

import sys
import string

printable = set(string.printable)

def process(stream):
    found_str = ""
    while True:
        data = stream.read(1024*4)
        if not data:
            break
        for char in data:
            if char in printable:
                found_str += char
            elif len(found_str) >= 4:
                yield found_str
                found_str = ""
            else:
                found_str = ""

 if __name__ == "__main__":
     for found_str in process(sys.stdin):
        print found_str

这样做会快很多,原因有:

  • 检查“字符是否可打印”的操作只需要一次查找(这是一个 O(1) 的操作),而且直接调用了一个 C 语言的函数(这个速度会非常快)。
  • 数据流是以 4k 的块来处理的,这样可以更好地利用内存,并且在处理大数据时运行速度也会更快,因为不需要进行数据交换。
10

使用Python的正则表达式库re,速度和David Wolever的差不多。优化的简单道理就是,写的代码越少,执行得越快。库函数通常是用C语言实现的,速度会比你自己写的快得多。比如,使用char in set()会比你自己去检查要快,这一点和C语言正好相反。

import sys
import re

chars = r"A-Za-z0-9/\-:.,_$%'()[\]<> "
shortest_run = 4

regexp = '[%s]{%d,}' % (chars, shortest_run)
pattern = re.compile(regexp)

def process(stream):
    data = stream.read()
    return pattern.findall(data)

if __name__ == "__main__":
    for found_str in process(sys.stdin):
        print found_str

分成4k的小块来处理是个聪明的主意,但在一些特殊情况下用re会有点棘手。比如,当4k块的末尾有两个字符,而下一个块的开头也有两个字符时,就会出现问题。

撰写回答