2009-08-19 10 views
17

¿Cuál es el hash más corto (en forma de nombre de archivo, como un hexdigest) disponible en python? Mi aplicación quiere guardar archivos de caché para algunos objetos. Los objetos deben tener repr() único, por lo que se usan para 'sembrar' el nombre del archivo. Quiero producir un nombre de archivo posiblemente único para cada objeto (no tantos). No deberían colisionar, pero si lo hacen, mi aplicación simplemente carecerá de memoria caché para ese objeto (y tendrá que volver a indexar los datos de ese objeto, un costo menor para la aplicación).El hash más corto en python para nombrar archivos de caché

Por lo tanto, si hay una colisión perdemos un archivo de caché, pero es el ahorro acumulado de almacenar en caché todos los objetos hace que la aplicación se inicie mucho más rápido, por lo que no importa mucho.

Ahora mismo estoy usando ABS (hash (repr (obj))); ¡Así es, el hash de cuerdas! Aún no he encontrado ninguna colisión, pero me gustaría tener una mejor función hash. hashlib.md5 está disponible en la biblioteca de python, pero el hexdigest es realmente largo si se coloca en un nombre de archivo. Alternativas, con una razonable resistencia a la colisión?

Edit: El caso de uso es así: El cargador de datos recibe una nueva instancia de un objeto que transmite datos. Los tipos únicos tienen repr exclusivo. entonces, si existe un archivo de caché para hash(repr(obj)), deshago ese archivo de caché y reemplazo obj con el objeto no caído. Si hubo una colisión y el caché fue una coincidencia falsa lo noté. Entonces, si no tenemos memoria caché o tenemos una coincidencia falsa, en su lugar, inicio OBJ (recargando sus datos).

Conclusiones (?)

El hash str en Python puede ser lo suficientemente bueno, fue sólo preocupado por su resistencia al choque. Pero si puedo usar hash 2**16 objetos con él, será más que suficiente.

descubrí cómo tomar un hash hexagonal (de cualquier fuente de hash) y almacenarlo de forma compacta con base 64:

# 'h' is a string of hex digits 
bytes = "".join(chr(int(h[i:i+2], 16)) for i in xrange(0, len(h), 2)) 
hashstr = base64.urlsafe_b64encode(bytes).rstrip("=") 
+1

¿Por qué se preocupan por la longitud de los nombres de archivo? Eso no importa en absoluto, a menos que esté usando un sistema de archivos estúpido –

+0

Es feo. Y todos los programadores quieren expresar menos con más, y aquí sé que puedo, un hash criptográfico completo es exagerado. – u0b34a0f6ae

+0

en el ejemplo final, para un hash hashlib de python, puede usar bytes = (..). Digest() por supuesto. – u0b34a0f6ae

Respuesta

35

El birthday paradox aplica: dado una buena función de hash, el número esperado de hashes antes de que ocurra una colisión es de aproximadamente sqrt (N), donde N es el número de valores diferentes que la función hash puede tomar. (La entrada de wikipedia que he señalado proporciona la fórmula exacta). Así, por ejemplo, si desea utilizar no más de 32 bits, sus preocupaciones de colisión son graves por alrededor de 64 K (es decir, objetos Objetos - 2**16 la raíz cuadrada de los valores diferentes 2**32 su función hash puede tomar). ¿Cuántos objetos espera tener, como un orden de magnitud?

Como mencionas que una colisión es una molestia menor, te recomiendo que busques una longitud de hash que sea aproximadamente el cuadrado de la cantidad de objetos que tendrás, o un poco menos pero no MUCHO menos que eso.

¿Desea crear un nombre de archivo? ¿Es eso en un sistema de archivos sensible a las mayúsculas y minúsculas, como es típico en Unix, o tiene que atender también a los sistemas que no distinguen entre mayúsculas y minúsculas? Esto es importante porque busca nombres de archivos cortos, pero el número de bits por carácter que puede usar para representar su hash como nombre de archivo cambia drásticamente en sistemas sensibles a mayúsculas y minúsculas frente a sistemas insensibles.

En un sistema de mayúsculas y minúsculas, puede utilizar el módulo de la biblioteca estándar base64 (recomiendo el "urlsafe" versión de la codificación, es decir this función, como evitar '/' caracteres que podrían estar presentes en la base 64 liso es importante en nombres de archivo Unix). Esto le da 6 bits utilizables por carácter, mucho mejor que los 4 bits/char en hex.

Incluso en un sistema de mayúsculas y minúsculas, todavía se puede hacer mejor que hexagonal - base64.b32encode utilizar y obtener 5 bits por carácter.

Estas funciones toman y devuelven cadenas; use el módulo struct para convertir números en cadenas si su función hash elegida genera números.

Si usted tiene unas pocas decenas de miles de objetos creo que se le multa de almohadilla incorporada (32 bits, por lo 6-7 caracteres en función de su codificación elegida). Para un millón de objetos, querrás 40 bits más o menos (7 u 8 caracteres): puedes doblar (xor, no truncar ;-) un sha256 por debajo de un largo con un número razonable de bits, digamos 128 o menos , y use el operador % para cortarlo más allá de la longitud deseada antes de codificar.

+1

muy buena regla para elegir la longitud del hash – u0b34a0f6ae

3

Estoy seguro de que hay una aplicación CRC32 en Python, pero que pueden ser demasiado corto (8 dígitos hexadecimales). Por el lado positivo, es muy rápido.

encontró, binascii.crc32

+1

exactamente es muy rápido, lo cual es bueno. ¿Pero viendo que no se recomienda como función hash, quizás el hash de cadena() es igual de bueno? – u0b34a0f6ae

+0

CRC no se recomienda como hash ya que generará colisiones y es relativamente fácil hacerlo ** a propósito **. Esto lo hace inseguro, por ejemplo, para contraseñas hash. Pero * es * una función hash, solo genera un hash muy corto. Esto significa muchas más colisiones potenciales. Aunque es rápido y pequeño, su aplicación normal es la comprobación de la cordura. Si hay 2^32 opciones son suficientes, entonces CRC32 está bien (o al parecer el 'almohadilla()' función en Python genera 2^32 también. No sé esto, yo realmente no uso de Python) –

1

Si usted tiene una colisión, ¿cómo vas a decir que en realidad pasó?

Si yo fuera usted, me gustaría utilizar hashlib a sha1() la repr(), y luego acaba de obtener una subcadena limitada de la misma (primeros 16 caracteres, por ejemplo).

A menos que esté hablando de un gran número de estos objetos, le sugiero que simplemente use el hash completo. Entonces, la oportunidad de colisión es tan, tan, tan, tan pequeña, que nunca vivirás para ver que ocurra (probablemente).

Además, si se trata de que muchos archivos, supongo que su técnica de almacenamiento en caché se debe ajustar para acomodarlo.

+0

I unpickle la caché y observe cuando algo está mal, por lo que las colisiones son solo la molestia de que dos objetos colisionen, uno siempre está sin caché al inicio de la aplicación. Pero esta es una muy buena sugerencia, ya que sha1 es el tipo de función hash que no colisiona mucho, y rebanar el hash fue algo en lo que no pensé. – u0b34a0f6ae

+0

En realidad, por diversas razones matemáticas, el uso de una subcadena de hash genera muchas más colisiones que el simple uso de una función hash más corta. Consulte, por ejemplo, los protocolos que generan colisiones SHA1 parciales en tiempo real como parte del protocolo. –

+0

En el pasado, se tomó medio de un MD5, lo convirtió en un entero de 64 bits, y se almacenan en una base de datos que (el rendimiento fue crítico en ese caso, con 100.000.000 de registros> – gahooa

27

La función hash incorporada de cadenas es bastante libre de colisiones, y también bastante corta. Tiene valores de 2**32, por lo que es bastante improbable que encuentre colisiones (si usa su valor de abs, solo tendrá valores de 2**31).

Has estado solicitando la función hash más corta.Eso sería sin duda

def hash(s): 
    return 0 

pero supongo que en realidad no quiere decir que de esa manera ...

+4

así lo desean evitar colisiones :-) – u0b34a0f6ae

+4

se encuentran en http://roflcopter.pl/5257: D – Frizi

0

hashes cortos significa que usted puede tener el mismo hash para dos archivos diferentes. Lo mismo puede suceder para los hashes grandes también, pero es mucho más raro. Tal vez estos nombres de archivo deberían variar según otras referencias, como microtime (a menos que estos archivos se creen demasiado rápido).

7

Usted puede hacer cualquier picadillo te gusta más corto simplemente truncándolo. md5 siempre tiene 32 dígitos hexadecimales, pero una subcadena arbitraria de ella (o cualquier otro hash) tiene las cualidades adecuadas de un hash: los valores iguales producen hash iguales, y los valores se reparten en un montón.

+0

Cuanto más se trunca mayores serán las probabilidades de que el mismo valor hash para dos archivos diferentes La pregunta es "¿qué posibilidades son aceptables?" Cuando truncas, sufres "falsos positivos": hash coincide, pero los objetos son diferentes. –

+0

Sí, exactamente. Con cualquier hash, debe decidir qué riesgo de colisión es aceptable y evaluar su riesgo. –

1

Utilizamos hashlib.sha1.hexdigest(), que produce cadenas aún más largos, para los objetos de caché con buen éxito. Nadie está mirando realmente los archivos de caché de todos modos.

1

Condsidering su caso de uso, si no tiene su corazón puesto en utilizar archivos de caché separados y no está muy lejos en esa ruta de desarrollo, puede considerar el uso del módulo shelve.

Esto le dará un diccionario persistente (almacenada en un solo archivo dBm) en la que se almacenan los objetos. El decapado/descortezado se realiza de forma transparente, y no tiene que preocuparse por hashing, colisiones, archivos de E/S, etc.

Para las claves del diccionario de archivado, solo debe usar repr (obj) y dejar que shelve con esconder tus objetos por ti. Un ejemplo simple:

import shelve 
cache = shelve.open('cache') 
t = (1,2,3) 
i = 10 
cache[repr(t)] = t 
cache[repr(i)] = i 
print cache 
# {'(1, 2, 3)': (1, 2, 3), '10': 10} 
cache.close() 

cache = shelve.open('cache') 
print cache 
#>>> {'(1, 2, 3)': (1, 2, 3), '10': 10} 
print cache[repr(10)] 
#>>> 10 
Cuestiones relacionadas