java中的hashCode和equels学习理解
hash表
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。例如:给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
简单来说,哈希表就是通过关键字计算出数据存放地址来进行访问的一种数据结构。能够做到随机访问从而提高访问速度,理论上访问速度应该是O(1)。
java中的hashCode
Object中的hashCode函数就是用来计算对象的hash值用的,Object中的该函数是native函数,个人认为该值应该和对象的地址有关,就是不知道该值和对象地址的映射关系什么,不同地址的Object返回的hashCode是否有可能冲突。
java中的equals
Object中的equals函数没有被override之前比较的是两个对象的地址,该函数等同于 == 运算符。(顺带一提,== 运算法比较的就是地址,Object中的equals内部就是调用的==运算符对两个对象进行比较)
为什么equals和hashCode需要同时重写
一般情况下,我们重写equals函数的时候还需要重写hashCode函数。这个其实和java中的散列表有关。
我们可以看AbstractHashedMap的源码,大致流程如下:
当我们往散列表里面put(key,Object)的时候,首先会根据key生成hash值(用到了key的hashCode函数),然后根据hash找到对象在表中的索引,根据索引查看该位置有无对象,如果没有则可以将Object放进去,如果有就会判断该对象的key对应的hash值和需要放入的Object对应的key生成的hash值是否相同,相同则继续调用两个key的equals(用到了key的equals函数)进行比较,如果还是相同则认为两个key是逻辑相同的,所以就会对原来的对象进行更新;否则认为两个key逻辑上是不相同的,会进行线性再散列或者其他措施解决冲突。
//-----------------------------------------------------------------------
/**
* Puts a key-value mapping into this map.
*
* @param key the key to add
* @param value the value to add
* @return the value previously mapped to this key, null if none
*/
@Override
public V put(final K key, final V value) {
final Object convertedKey = convertKey(key);
final int hashCode = hash(convertedKey);
final int index = hashIndex(hashCode, data.length);
HashEntry<K, V> entry = data[index];
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(convertedKey, entry.key)) {
final V oldValue = entry.getValue();
updateEntry(entry, value);
return oldValue;
}
entry = entry.next;
}
addMapping(index, hashCode, key, value);
return null;
}
可以看出如果只重写了equals而不重写hashCode就可能会导致,我们主观上认为逻辑相同的两个对象当放入set或者作为key放入HashMap中的时候可能就会放入两个对象。
HashMap HashTable ConcurrentHashMap
HashMap是线程不安全的,(可以用null作为key或value),后两个是线程安全的,HashTable就是在操作上进行加锁(synchronized),而ConcurrentHashMap则是引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。