在Python中从二进制文件提取字符串
我有一个项目,需要从一个文件中提取字符串。可以把这个过程想象成在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块的末尾有两个字符,而下一个块的开头也有两个字符时,就会出现问题。