¿Puedo depender de que los valores de GetHashCode() sean consistentes?

StackOverflow https://stackoverflow.com/questions/53086

  •  09-06-2019
  •  | 
  •  

Pregunta

¿Se garantiza que el valor de retorno de GetHashCode() será coherente suponiendo que se utilice el mismo valor de cadena?(C#/ASP.NET)

Subí mi código a un servidor hoy y, para mi sorpresa, tuve que volver a indexar algunos datos porque mi servidor (win2008 de 64 bits) devolvía valores diferentes en comparación con mi computadora de escritorio.

¿Fue útil?

Solución

Si no me equivoco, GetHashCode es consistente dado el mismo valor, pero NO se garantiza que sea consistente en diferentes versiones del marco.

De los documentos de MSDN en String.GetHashCode():

El comportamiento de GetHashCode depende de su implementación, que puede cambiar de una versión de Common Language Runtime a otra.Una razón por la que esto podría suceder es para mejorar el rendimiento de GetHashCode.

Otros consejos

Tuve un problema similar en el que llené una tabla de base de datos con información que dependía de String.GetHashCode (no es la mejor idea) y cuando actualicé el servidor en el que estaba trabajando a x64 noté que los valores que estaba obteniendo de String.GetHashCode eran incompatible con lo que ya estaba en el cuadro.Mi solución fue usar mi propia versión de GetHashCode que devuelve el mismo valor que String.GetHashCode en un marco x86.

Aquí está el código, no olvides compilarlo con "Permitir código no seguro":

    /// <summary>
    /// Similar to String.GetHashCode but returns the same as the x86 version of String.GetHashCode for x64 and x86 frameworks.
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static unsafe int GetHashCode32(string s)
    {
        fixed (char* str = s.ToCharArray())
        {
            char* chPtr = str;
            int num = 0x15051505;
            int num2 = num;
            int* numPtr = (int*)chPtr;
            for (int i = s.Length; i > 0; i -= 4)
            {
                num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
                if (i <= 2)
                {
                    break;
                }
                num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
                numPtr += 2;
            }
            return (num + (num2 * 0x5d588b65));
        }
    }

La implementación depende de la versión del framework pero también depende del arquitectura.La implementación de string.GetHashCode() es diferente en las versiones x86 y x64 del framework incluso si tienen el mismo número de versión.

Me pregunto si existen diferencias entre los sistemas operativos de 32 y 64 bits, porque estoy seguro de que tanto mi servidor como mi computadora personal ejecutan la misma versión de .NET.

Siempre estuve cansado de usar GetHashCode(), podría ser una buena idea para mí simplemente implementar mi propio algoritmo hash.Bueno, al menos terminé escribiendo una página .aspx de reindexación rápida por eso.

¿Está ejecutando Win2008 x86 como escritorio?Porque Win2008 incluye la versión 2.0.50727.1434, que es una versión actualizada de 2.0 incluida en Vista RTM.

Sin embargo, lo que notamos, cuando un objeto está en un objeto de recolección de hash (un hashtable, un diccionario, etc.), cuando 2 objetos no son únicos, pero sus hashcodes lo son, el código hash solo se usa como una primera búsqueda de opciones, si no hay no hay que no hay Los códigos de hash unique que se utilizan, el operador de igualdad siempre se usa como una caída de la igualdad para detirmar la igualdad.

Así es como funcionan las búsquedas de hash, ¿verdad?Cada depósito contiene una lista de elementos que tienen el mismo código hash.

Entonces, para encontrar el elemento correcto en estas condiciones, se realiza una búsqueda lineal mediante comparación de igualdad de valores.

Y si su implementación de hash logra una buena distribución, esta búsqueda no es necesaria, es decir, un elemento por depósito.

¿Es correcto mi entendimiento?

No es una respuesta directa a su pregunta, que Jonas ha respondido bien; sin embargo, esto puede ser útil si le preocupan las pruebas de igualdad en hashes.

Según nuestras pruebas, dependiendo de lo que requiera con los códigos hash, en C#, los códigos hash no necesitan ser únicos para las operaciones de Igualdad.Como ejemplo, considere lo siguiente:

Teníamos el requisito de sobrecargar el operador igual y, por lo tanto, la función GetHashCode de nuestros objetos, ya que se habían vuelto volátiles y sin estado, y se obtenían directamente de los datos, por lo que en un lugar de la aplicación necesitábamos asegurarnos de que un objeto fuera visto. como igual a otro objeto si se obtuvo de los mismos datos, no solo si fuera la misma referencia.Nuestros identificadores de datos únicos son Guías.

El operador igual fue fácil de atender ya que acabamos de verificar el Guid del registro (después de verificar si hay nulos).

Desafortunadamente, el tamaño de los datos de HashCode (que es un int) depende del sistema operativo y, en nuestro sistema de 32 bits, el código hash sería de 32 bits.Matemáticamente, cuando anulamos la función GetHashCode, es imposible generar un código hash único a partir de un guid mayor de 32 bits (mírelo a la inversa, ¿cómo traduciría un entero de 32 bits a un guid?).

Luego hicimos algunas pruebas en las que tomamos el Guid como una cadena y devolvimos el HashCode del Guid, que casi siempre devuelve un identificador único en nuestras pruebas, pero no siempre.

Sin embargo, lo que sí notamos es que cuando un objeto está en un objeto de colección hash (una tabla hash, un diccionario, etc.), cuando 2 objetos no son únicos pero sus códigos hash sí lo son, el código hash solo se usa como primera opción de búsqueda, si no hay -códigos hash únicos que se utilizan, El operador de igualdad siempre se utiliza como alternativa para determinar la igualdad..

Como dije, esto puede ser relevante o no para su situación, pero si lo es, es un consejo útil.

ACTUALIZAR

Para demostrarlo, tenemos una Hashtable:

Clave: Objeto A (Código Hash 1), valor Objeto A1

Clave: Objeto B (Código Hash 1), valor Objeto B1

Clave: Objeto C (Código Hash 1), valor Objeto C1

Clave: Objeto D (código Hash 2), valor Objeto D1

Clave: Objeto E (Código Hash 3), valor Objeto E1

Cuando llamo a la tabla hash para el objeto con la clave del Objeto A, el objeto A1 se devolverá después de 2 pasos, una llamada al código hash 1, luego una verificación de igualdad en el objeto clave ya que no hay una clave única con el código hash 1.

Cuando llamo a la tabla hash para el objeto con la clave del Objeto D, el objeto D1 se devolverá después de 1 paso, una búsqueda de hash

    /// <summary>
    /// Default implementation of string.GetHashCode is not consistent on different platforms (x32/x64 which is our case) and frameworks. 
    /// FNV-1a - (Fowler/Noll/Vo) is a fast, consistent, non-cryptographic hash algorithm with good dispersion. (see http://isthe.com/chongo/tech/comp/fnv/#FNV-1a)
    /// </summary>
    private static int GetFNV1aHashCode(string str)
    {
        if (str == null)
            return 0;
        var length = str.Length;
        // original FNV-1a has 32 bit offset_basis = 2166136261 but length gives a bit better dispersion (2%) for our case where all the strings are equal length, for example: "3EC0FFFF01ECD9C4001B01E2A707"
        int hash = length;
        for (int i = 0; i != length; ++i)
            hash = (hash ^ str[i]) * 16777619;
        return hash;
    }

Esta implementación puede ser más lenta que la insegura publicada anteriormente.Pero mucho más sencillo y seguro.

Tendría que decir... no puedes confiar en ello.Por ejemplo, si ejecuto el archivo 1 a través del código hash md5 de C# y copio y pego el mismo archivo en un nuevo directorio... el código hash sale diferente aunque sea el mismo archivo.Obviamente es la misma versión .net, todo igual.Lo único que cambió fue el camino.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top