2011-07-24 35 views
5

Tengo un proyecto en el que me dan un archivo y necesito extraer las cadenas del archivo. Básicamente, piense en el comando "strings" en Linux, pero estoy haciendo esto en Python. La siguiente condición es que el archivo se me proporcione como una secuencia (por ejemplo, cadena), por lo que la respuesta obvia de usar una de las funciones de subproceso para ejecutar cadenas tampoco es una opción.extraer cadenas de un archivo binario en python

de escribir este código:

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 

Esta técnica funciona, pero es lento camino. Por ejemplo, pude usar el comando de cadenas en un ejecutable de 500 Mega y produjo 300k de cadenas en menos de 1 segundo. Ejecuté el mismo archivo a través del código anterior y me llevó 16 minutos.

¿Hay una biblioteca por ahí que me permita hacer esto sin la carga de la latencia de Python?

Gracias!

+0

Si puede leer C, [el código fuente de las cadenas GNU] (http://sourceware.org /cgi-bin/cvsweb.cgi/src/binutils/strings.c?rev=1.48&content-type=text/x-cvsweb-markup&cvsroot=src) podría ser útil. Son solo unos cientos de líneas, así que no es tan malo. –

Respuesta

7

de velocidad similar a David Wolever de, utilizando re, biblioteca de expresiones regulares de Python. La breve historia de la optimización es que cuanto menos código escribas, más rápido es. Una función de biblioteca que se repite a menudo se implementa en C y será más rápida de lo que puede esperar. Lo mismo ocurre con el char in set() que es más rápido que usted mismo. Python es lo opuesto a C en ese aspecto.

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 

Trabajando en trozos 4k sería inteligente, pero es un poco más complicado en el borde-casos con re. (donde dos caracteres están en el extremo del bloque 4k y los siguientes 2 están al comienzo del siguiente bloque)

+0

Buena sugerencia. Estoy de acuerdo: para transmisiones más pequeñas (es decir, que pueden caber en la memoria), esto es ciertamente preferible. Apuesto a que incluso podrías dividir la secuencia, luego ejecuta esta expresión regular a través de fragmentos divididos en caracteres no imprimibles ... Hhmm ... –

+0

@dougallj: Esto es perversamente rápido. ¡Gracias! Ahora, si puede hacer que encuentre cadenas de caracteres unicode, también le compraré una cerveza ;-) No habría pensado (y obviamente no lo hice) en usar esto para esto. Pude procesar mi archivo de prueba 500Meg en 33 segundos.Eso está dentro de mis límites de especificación de diseño. – tjac

+0

@tjac: ¿Qué es una cadena Unicode? –

5

Al menos uno de los problemas es que usted está leyendo toda la secuencia en la memoria (… = len(stream)), y la otra es que su función isStringChar es muy lenta (las llamadas a funciones son relativamente lento, y que está haciendo una gran cantidad de ellos).

mejor sería algo como esto:

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 

Esto será mucho más rápido debido a que:

  • El "carácter imprimible es" búsqueda se realiza con una consulta de conjunto (y O (1) operación) que llama directamente (si no me equivoco) a una función C (que será muy rápida).
  • La secuencia se procesa en trozos de 4k, lo que mejorará el uso de la memoria y el tiempo de ejecución en las entradas grandes, ya que no será necesario el intercambio.
+2

Necesita un 'else: found_str =" "'. El código imprime actualmente 4 caracteres imprimibles con los que tropieza, independientemente de si están en una fila. – dougallj

+0

D'oh. Gracias. Corregido ahora. (Si eso no lo hizo obvio, en realidad * no he probado * este código ...) –

Cuestiones relacionadas