OVH Cloud OVH Cloud

Erreur Properties.hashCode()

8 réponses
Avatar
alexandre cartapanis
Bonjour,
J'ai une erreur tres tres etrange:

Properties props1 = new Properties();
props1.setProperty("test1", "test1");
System.err.println(props1.hashCode());

Ce code m'affiche 0 sur la sortie!

autrement dit, il semblerait que la classe Properties ne respecte pas
son contrat. En effet, dans la documentation officiel, il est dit que
Properties.hashCode() "Returns the hash code value for this Map as per
the definition in the Map interface". L'interface Map definit hashCode()
comme suit: "Returns the hash code value for this map. The hash code of
a map is defined to be the sum of the hashCodes of each entry in the
map's entrySet view. This ensures that t1.equals(t2) implies that
t1.hashCode()==t2.hashCode() for any two maps t1 and t2, as required by
the general contract of Object.hashCode."

Or, ce contrat n'est clairement pas respecté, je ne comprends pas
pourquoi...

Quelqu'un a t'il une idée du pourquoi?

8 réponses

Avatar
alexandre cartapanis
Bonjour,
J'ai une erreur tres tres etrange:

Properties props1 = new Properties();
props1.setProperty("test1", "test1");
System.err.println(props1.hashCode());

Ce code m'affiche 0 sur la sortie!

autrement dit, il semblerait que la classe Properties ne respecte pas
son contrat. En effet, dans la documentation officiel, il est dit que
Properties.hashCode() "Returns the hash code value for this Map as per
the definition in the Map interface". L'interface Map definit hashCode()
comme suit: "Returns the hash code value for this map. The hash code of
a map is defined to be the sum of the hashCodes of each entry in the
map's entrySet view. This ensures that t1.equals(t2) implies that
t1.hashCode()==t2.hashCode() for any two maps t1 and t2, as required by
the general contract of Object.hashCode."

Or, ce contrat n'est clairement pas respecté, je ne comprends pas
pourquoi...

Quelqu'un a t'il une idée du pourquoi?


Bon ben j'ai compris pourquoi: si la valeur et la clé sont egales, alors
la methode de calcul implique que hashCode=0. En effet, il fait la somme
de chaque Map.Entry, et Map.Entry.hashCode calcul la valeur en utilisant:

(e.getKey()==null ? 0 : e.getKey().hashCode()) ^ (e.getValue()==null ?
0 : e.getValue().hashCode())

Est-ce que l'on peut considerer ca comme un bug? Parce que ca me pose
probleme la...

Avatar
Laurent Bossavit
Alexandre,

Est-ce que l'on peut considerer ca comme un bug? Parce que ca me pose
probleme la...


En quoi est-ce que le contrat n'est pas respecté ?

(La règle pour un hash est que l'identité de deux valeurs implique
l'identité de leurs deux hash, mais bien évidemment pas l'inverse. Des
collision de hachage sont parfaitement possibles.)

Laurent

Avatar
alexandre cartapanis
Alexandre,


Est-ce que l'on peut considerer ca comme un bug? Parce que ca me pose
probleme la...



En quoi est-ce que le contrat n'est pas respecté ?

(La règle pour un hash est que l'identité de deux valeurs implique
l'identité de leurs deux hash, mais bien évidemment pas l'inverse. Des
collision de hachage sont parfaitement possibles.)

Laurent


Effectivement, je me suis un peu emballé dans mon premier post. Le
contrat est respecté, mais bon, la methode de calcul je la trouve un peu
bancal. Je veux bien qu'un "^" soit plus rapide qu'un "+" mais qd meme...


Avatar
Syrion
alexandre cartapanis wrote:


Effectivement, je me suis un peu emballé dans mon premier post. Le
contrat est respecté, mais bon, la methode de calcul je la trouve un peu
bancal. Je veux bien qu'un "^" soit plus rapide qu'un "+" mais qd meme...


^ est plus rapide mais effectivement ce n'est pas très bon car la
répartition n'est aps super (nombre de collision supérieur).
L'idéal, C'est * et + combinés, avec un attribut calculé dans la classe
si le hashCode est problématique pour les perf (l'attribut calculé
mémorisant le hashCode tant que les attributs ne changent pas).
Josh Blosh (le mec qui a conçu l'API java.lang.util de Java 1.2 (alias
"Collections framework") recommande l'usage de * et + combiné.

Comme de plus il est mauvais de retourner 0 en cas de nullité ou de
valua tion à 0 de ts les attributs, il faut toujours initialiser avec
une constante.
int hashCode(){
int result; //traditionnellement un nb premier
result*result+champ1;//si champ1 est de type primitif de taille <= int
result*result+champ2!=null?champ2.hashCode():0;//si champ2 est un
type objet.
//etc...
return result;
}

On peut rajouter des champs (boolean isHashComputed et int computedHash)
pour éviter de recalculer à chaque fois si le hashCode est consommateur
en CPU. ne pas oublier de remettre isHashComputed à false si un champ
pris en compte dans hashCode() (et donc dans equals(Object)) change.
+ à rajouter sur le tas une synchronisation si contexte multi-thread
dans ce cas précis.

Notez que pour les attributs long on coupe en 2 int (et on fait un ^ sur
ces derniers, ou on considère que ce sont 2 champs différents)

pour les double et float, récupérer leur valeur en tableau de byte

pour les tableau ça peut être long, d'où l'idée de mémoriser le hash.

Voilà, ^ n'est donc pas l'idéal, même si c'est ultra rapide. si vous
hachez avec ^, les perfs de vos maps seront médiocres (collisions plus
nombreuses).

Avatar
alexandre cartapanis
alexandre cartapanis wrote:


Effectivement, je me suis un peu emballé dans mon premier post. Le
contrat est respecté, mais bon, la methode de calcul je la trouve un peu
bancal. Je veux bien qu'un "^" soit plus rapide qu'un "+" mais qd meme...



^ est plus rapide mais effectivement ce n'est pas très bon car la
répartition n'est aps super (nombre de collision supérieur).
L'idéal, C'est * et + combinés, avec un attribut calculé dans la classe
si le hashCode est problématique pour les perf (l'attribut calculé
mémorisant le hashCode tant que les attributs ne changent pas).
Josh Blosh (le mec qui a conçu l'API java.lang.util de Java 1.2 (alias
"Collections framework") recommande l'usage de * et + combiné.

Comme de plus il est mauvais de retourner 0 en cas de nullité ou de
valua tion à 0 de ts les attributs, il faut toujours initialiser avec
une constante.
int hashCode(){
int result; //traditionnellement un nb premier
result*result+champ1;//si champ1 est de type primitif de taille <= int
result*result+champ2!=null?champ2.hashCode():0;//si champ2 est un
type objet.
//etc...
return result;
}

On peut rajouter des champs (boolean isHashComputed et int computedHash)
pour éviter de recalculer à chaque fois si le hashCode est consommateur
en CPU. ne pas oublier de remettre isHashComputed à false si un champ
pris en compte dans hashCode() (et donc dans equals(Object)) change.
+ à rajouter sur le tas une synchronisation si contexte multi-thread
dans ce cas précis.

Notez que pour les attributs long on coupe en 2 int (et on fait un ^ sur
ces derniers, ou on considère que ce sont 2 champs différents)

pour les double et float, récupérer leur valeur en tableau de byte

pour les tableau ça peut être long, d'où l'idée de mémoriser le hash.

Voilà, ^ n'est donc pas l'idéal, même si c'est ultra rapide. si vous
hachez avec ^, les perfs de vos maps seront médiocres (collisions plus
nombreuses).

ben moi c'est toujours ce que je fais :)

Voila comment j'ai resolu le probleme:

int res = 17;
res = 37 * res + this.name.hashCode();
res += 17;
for (Map.Entry<?, ?> entry: this.props.entrySet()) {
res += 17;
res += (entry.getKey() == null ? 0 : entry.getKey().hashCode()) +
(entry.getValue() == null ? 0 : entry.getValue().hashCode());
}
res += 17;
return res;

Ca correspond tout a fais au recommandation de Blosh, mais c'est sur que
c'est un peu plus long a calculer...


Avatar
Hervé AGNOUX
Syrion wrote:

int result; //traditionnellement un nb premier


Pourquoi ? (pour ma culture générale)



--
Hervé AGNOUX
http://www.diaam-informatique.com

Avatar
Syrion
Hervé AGNOUX wrote:
Syrion wrote:


int result; //traditionnellement un nb premier



Pourquoi ? (pour ma culture générale)



Je ne saurais te dire, Josh Blosh reste évasif sur le sujet. j'imagine

que ça a un sens pour un matheux, et vu qu'on utilise une hash avec
multiplication ça doit avoir un rôle... mais ça serait très intéressant
de creuser !
Peut-être en cherchant du côté des hash sécurisé aura-t-on un élément de
réponse (là les algos sont chiadés à mort et chaque détail joue un
rôle). En tout cas le bouquin "Java efficace" de Josh Blosh ne précise
pas si ça joue en faveur d'une algo de hash + fort. J'ai adopté cette
astuce parcequ'avant de lire le livre de Josh j'utilisais déjà les nbs
premier parceque je trouvait ça marrant dans les hash... si ça se trouve
c'est juste un moyen pour les connaisseurs de frimer, mais dans le doute
j'utilise les nbs premiers.


Avatar
Patrick Gras
Hello,

Pour se simplifier la vie, utiliser
org.apache.commons.lang.builder.HashCodeBuilder

(http://jakarta.apache.org/commons/lang/index.html)

L'algo est le même mais c'est plus 'agréable' à utiliser...

-Patrick
"alexandre cartapanis" wrote in message
news:431d494e$0$17204$
alexandre cartapanis wrote:


Effectivement, je me suis un peu emballé dans mon premier post. Le
contrat est respecté, mais bon, la methode de calcul je la trouve un
peu



bancal. Je veux bien qu'un "^" soit plus rapide qu'un "+" mais qd
meme...





^ est plus rapide mais effectivement ce n'est pas très bon car la
répartition n'est aps super (nombre de collision supérieur).
L'idéal, C'est * et + combinés, avec un attribut calculé dans la classe
si le hashCode est problématique pour les perf (l'attribut calculé
mémorisant le hashCode tant que les attributs ne changent pas).
Josh Blosh (le mec qui a conçu l'API java.lang.util de Java 1.2 (alias
"Collections framework") recommande l'usage de * et + combiné.

Comme de plus il est mauvais de retourner 0 en cas de nullité ou de
valua tion à 0 de ts les attributs, il faut toujours initialiser avec
une constante.
int hashCode(){
int result; //traditionnellement un nb premier
result*result+champ1;//si champ1 est de type primitif de taille < int
result*result+champ2!=null?champ2.hashCode():0;//si champ2 est un
type objet.
//etc...
return result;
}

On peut rajouter des champs (boolean isHashComputed et int computedHash)
pour éviter de recalculer à chaque fois si le hashCode est consommateur
en CPU. ne pas oublier de remettre isHashComputed à false si un champ
pris en compte dans hashCode() (et donc dans equals(Object)) change.
+ à rajouter sur le tas une synchronisation si contexte multi-thread
dans ce cas précis.

Notez que pour les attributs long on coupe en 2 int (et on fait un ^ sur
ces derniers, ou on considère que ce sont 2 champs différents)

pour les double et float, récupérer leur valeur en tableau de byte

pour les tableau ça peut être long, d'où l'idée de mémoriser le hash.

Voilà, ^ n'est donc pas l'idéal, même si c'est ultra rapide. si vous
hachez avec ^, les perfs de vos maps seront médiocres (collisions plus
nombreuses).

ben moi c'est toujours ce que je fais :)

Voila comment j'ai resolu le probleme:

int res = 17;
res = 37 * res + this.name.hashCode();
res += 17;
for (Map.Entry<?, ?> entry: this.props.entrySet()) {
res += 17;
res += (entry.getKey() == null ? 0 : entry.getKey().hashCode()) +
(entry.getValue() == null ? 0 : entry.getValue().hashCode());
}
res += 17;
return res;

Ca correspond tout a fais au recommandation de Blosh, mais c'est sur que
c'est un peu plus long a calculer...