参考资料:
吊打面试官
面试必备:HashMap源码解析(JDK8)
Java 8系列之重新认识HashMap
HashMap的loadFactor为什么是0.75
HashMap类的注释翻译
一、HashMap定义
- HashMap实现接口Map,键值允许为null,HashMap和Hashtable相似【hashMap是非同步的,键值允许为null】
- HashMap中get和put性能是恒定的。迭代器视图和容量有关,如果要使用迭代器,初始容量不宜设置过高(或加载因子过低)。
- HashMap 中两个重要的参数,初始容量和负载因子,一般0.75是时间和空间比较折中的方案。较高的值会降低空间开销,但提高查找成本(体现在大多数的HashMap类的操作,包括get和put)。初始值尽量考虑map的容量,减少刷新操作。
- 如果存在大量键的hashcode相同,hashmap的性能会降低,可使用key实现
- hashMap采用的是外部同步
二、源码分析
既然是hashmap,hash方法肯定是关键,我们首先分析hash
hash:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里所有的键,通过hash方法做了一次处理,而不是直接使用键的hashcode。目的是因为,在查找数组下标时,使用了容量进行与运算,只有小于容量的二进制位会参与运算。通过将高16位与低16位参与运算,增加分布。
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
参考资料:
JDK 源码中 HashMap 的 hash 方法原理是什么?-知乎
tableSizeFor:
this.threshold = tableSizeFor(initialCapacity);
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
获取容量,是容量成为2的倍数。
算法,整数32位,总存在一个最高位1,通过位移将1变为11然后在位移,变成1111,在变成11111111.
HashMap源码注解 之 静态工具方法hash()、tableSizeFor()(四)
put:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab数组,p每个桶
Node<K,V>[] tab; Node<K,V> p; int n, i;
//tab为空创建tab
if ((tab = table) == null || (n = tab.length) == 0)
//resize进行扩容
n = (tab = resize()).length;
//index = (n - 1) & hash 下表位置
if ((p = tab[i = (n - 1) & hash]) == null)
//创建一个新的Node元素
tab[i] = newNode(hash, key, value, null);
else {
//指定位置已经有元素,也就是说产生hash碰撞
Node<K,V> e; K k;
//判断节点是否存在,覆盖原来原来的value
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//判断是否是红黑树
//是红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//不是红黑树,遍历链表准备插入
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//尾插法添加元素
p.next = newNode(hash, key, value, null);
//TREEIFY_THRESHOLD默认为8,大于8,转换为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//如果达到这个阈值转为红黑树
treeifyBin(tab, hash);
break;
}
//如果节点key存在,则覆盖原来位置的key
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//检查e是否存在相应的key,如果存在就更新value,并且返回
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//判断hashmap是否需要resize扩容
if (++size > threshold)
resize();
//留给子类LinkedHashMap来实现的
afterNodeInsertion(evict);
return null;
}
逻辑还是比较简单,
1、不存在table或容量不足,重新扩容初始化。 resize();
2、hash对应数组位置为空,直接赋值
3、hash对应数组位置不为空,且节点为树,则直接调用 ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
4、hash对应数组位置不为空,则节点不为树,则遍历链表,如果相等,赋值,否则添加到尾节点。如果大于容量TREEIFY_THRESHOLD ,元素添加到尾节点,并转化为红黑树 treeifyBin(tab, hash);
5、如果结构变化,记录一次结构变化,结构变化定义
这个方法中,我们需要对几个方法进行进一步解释:
- resize:扩容
- putTreeVal:红黑树中放入节点
- treeifyBin:链表转化为红黑树
resize
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //创建一个oldTab数组用于保存之前的数组
int oldCap = (oldTab == null) ? 0 : oldTab.length; //获取原来数组的长度
int oldThr = threshold; //原来数组扩容的临界值
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) { //如果原来的数组长度大于最大值(2^30)
threshold = Integer.MAX_VALUE; //扩容临界值提高到正无穷
return oldTab; //返回原来的数组,也就是系统已经管不了了,随便你怎么玩吧
}
//else if((新数组newCap)长度乘2) < 最大值(2^30) && (原来的数组长度)>= 初始长度(2^4))
//这个else if 中实际上就是咋判断新数组(此时刚创建还为空)和老数组的长度合法性,同时交代了,
//我们扩容是以2^1为单位扩容的。下面的newThr(新数组的扩容临界值)一样,在原有临界值的基础上扩2^1
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0)
newCap = oldThr; //新数组的初始容量设置为老数组扩容的临界值
else { // 否则 oldThr == 0,零初始阈值表示使用默认值
newCap = DEFAULT_INITIAL_CAPACITY; //新数组初始容量设置为默认值
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //计算默认容量下的阈值
}
if (newThr == 0) { //如果newThr == 0,说明为上面 else if (oldThr > 0)
//的情况(其他两种情况都对newThr的值做了改变),此时newCap = oldThr;
float ft = (float)newCap * loadFactor; //ft为临时变量,用于判断阈值的合法性
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE); //计算新的阈值
}
threshold = newThr; //改变threshold值为新的阈值
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab; //改变table全局变量为,扩容后的newTable
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) { //遍历数组,将老数组(或者原来的桶)迁移到新的数组(新的桶)中
Node<K,V> e;
if ((e = oldTab[j]) != null) { //新建一个Node<K,V>类对象,用它来遍历整个数组
oldTab[j] = null;
if (e.next == null)
//将e也就是oldTab[j]放入newTab中e.hash & (newCap - 1)的位置,
newTab[e.hash & (newCap - 1)] = e; //这个我们之前讲过,是一个取模操作
else if (e instanceof TreeNode) //如果e已经是一个红黑树的元素,这个我们不展开讲
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
//命名两组对象
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//这个是新旧数组中,位置不变,原理:原始坐标计算方式:i = (oldCap - 1) & hash,
//那么新坐标应该是inew= (oldCap<<1 - 1) & hash,从二进制角度看,只有最高位 发生变化
//假设容量为16,hash二进制为0010 0110,老的位置应该是0010 0110&0000 1111=0000 0110
//现在 容量翻倍,新的位置0010 0110&0001 1111=0000 0110,所以只需要判定最高位是否为0即可,为0则位置不变
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
resize核心点在于,
1、hash的不变式:容量翻倍后,元素位置要么在原始位置n,要么在n+oldCap,即如下:
if ((e.hash & oldCap) == 0) {
2、红黑树进行拆分重组
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
split方法
/**
* Splits nodes in a tree bin into lower and upper tree bins,
* or untreeifies if now too small. Called only from resize;
* see above discussion about split bits and indices.
*
* @param map the map
* @param tab the table for recording bin heads
* @param index the index of the table being split
* @param bit the bit of hash to split on
*/
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
//原始位置链表
TreeNode<K,V> loHead = null, loTail = null;
//加长度bit位置链表
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
//循环链表
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
//原始位置逻辑
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
//加bit位,位置逻辑
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
//链表逻辑
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null) // (else is already treeified),红黑树生成逻辑
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
split方法还是比较简单的,通过next遍历树节点,生成原始位置和原始位置+bit位链表,判断元素是否需要链表化。
其中: if (hiHead != null) // (else is already treeified)
这段代码,判定很奇怪,思考?
treeify方法
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
//设置根节点黑色。
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
//非根节点
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
//从根节点循环查找应插入的位置
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h) //通过hash判定为左子树
dir = -1;
else if (ph < h) //通过hash判定为右子树
dir = 1;
else if ((kc == null && //hash相等,使用key判定
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk); //key也相等,使用类名判定
TreeNode<K,V> xp = p;
//赋值,并判定是否到达二叉树尾节点,如果是,则插入数据
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
//二叉树就行树平衡,二叉搜索树转红黑树,插入平衡
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
https://blog.csdn.net/weixin_42340670/article/details/80531795
treeify方法主要实现了二叉搜索树插入逻辑。并每次插入完成,进行红黑树平衡操作balanceInsertion。由于平衡操作后,根节点可能发生变化,所以根节点,需要重新赋值。
balanceInsertion方法
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
//红黑树逻辑,插入节点均为红色,然后在进行颜色,位置调整
x.red = true;
//xp=父亲,xpp=祖父,xppl
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
//红黑树无父节点,说明为根节点,根基点,直接图为黑色即可。
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//父亲为黑色,添加一个红色,无影响
//祖父为null,说明只有父节点,即父节点为根,为根必为黑色
else if (!xp.red || (xpp = xp.parent) == null)
return root;
//父亲是左子树,且为红色
if (xp == (xppl = xpp.left)) {
//父亲的兄弟节点为红色。,一起变红为黑。祖父变红,以祖父为新节点,递归
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
//兄弟为黑色逻辑
else {
//左父亲的右孩子,需要旋转为左父亲的左孩子,父亲变为左孩子,右孩子变为父亲。所以添加值进行重定位为左孩子,即未旋转以前的父亲
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//右旋进行平衡操作,父亲变为黑色,祖父表位红色,父亲变为顶点,祖父变为父亲的右孩子。进行下一次循环,发现父亲为黑色,完成操作。
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
//逻辑相反
else {
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
红黑树平衡操作,参考维基百科https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91
简而言之,分为三种情况
1、新加入节点为根,直接变为黑色
2、新加入节点为黑色,不用做任何处理
3、父节点红色,有三种情况
- uncle为红色,把父节点和uncle一起图黑,祖父节点图红,以祖父节点为标准进行下次判定
- uncle为黑色,【内在逻辑,因为祖父和兄弟都是黑色,所以可以在祖父和兄弟之间插入一个红色,不影响红黑树平衡,所以需要进行旋转来实现】
moveRootToFront 方法
作用:Ensures that the given root is the first node of its bin.
/**
* Ensures that the given root is the first node of its bin.
*/
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
//判定存在
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash;
//获取bins中first node
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
//不相等,做处理,设置根为root,把root从链表中拿出来,root。prv.next=root.next,修改firt的prev
if (root != first) {
Node<K,V> rn;
tab[index] = root;
TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}
好的,到此,hashmap中的resize方法分析完成。
我们继续返回putVal中的剩下的两个方法。
putTreeVal方法
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
//已经存在键,直接返回
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
//hash相同,键不同,依然发生碰撞,
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
//已经到尾节点,赋值,并添加到红黑树,平衡,设置根节点
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
逻辑就是放入hash中
treeifyBin方法
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
代码很简单,先组装TreeNode链表,然后树化 hd.treeify(tab);
get方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//hash相等,切key相等,直接返回,表示查找到
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果是链表循环查找,分为红黑树查找和链表线性查找
if ((e = first.next) != null) {
if (first instanceof TreeNode)
//红黑树查找
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
查找逻辑先查找第一个元素是否相等,不等,则判定是链表还是红黑树,链表直接线性查找,红黑树进行二叉搜索树查找。
remove方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//hash对应的bins里有值
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//以下if和else逻辑同get
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//node查找以后,准备进行处理
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//从红黑树中移除,最复杂
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//与链表第一个元素相等,如果只有一个,next=null,如果有多个,即指向第二个
else if (node == p)
tab[index] = node.next;
//是链表,但不在第一个位置,则node是p的next,移除node,使p指向node.next.
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
移除方法,首先依然是查找元素,然后判定元素是数组,链表还是红黑树,进行相应处理。
红黑树调用方法:removeTreeNode
removeTreeNode方法
红黑树的删除,一堆if else,看的我蛋疼,说说原理,就是先转化为二叉搜索树删除元素,然后进行红黑树平衡操作balanceDeletion(root, replacement);。
红黑树相关内容,参考:
好的,至此,我们现在再回过头,来看第一篇参考文章,吊打面试官,看看能吊打了不
1、是链表的头尾插法问题,然后还涉及到了库容,解释这样做的原因是多线程才不会出问题,,excuse me???hashmap源码里连一个锁都没有,连cas都没有,这样的东西,能用到多线程中吗?是不是有点过度解读拟?把一个单线程的玩意,按照多线程来解读,还横纵向对比jdk7,说有坑,现在解决了
2、初始容量16问题
首先,如果手动设置容量会发现,进行了如下转换,即其实我们传入任何值,都可以,但是得明白,会转化成2的N次方
this.threshold = tableSizeFor(initialCapacity);
其实这个问题,就转化为为什么需要2的N次方,主要还是因为取索引代码
[i = (n - 1) & hash]
为了快速获取索引,我们只需要容量所有二进制为1,这样直接通过位运算,快速获取值,分布也均匀,如果扩容,位置只会是n或n+size,因为只有高位变化了1个
哟小家伙,知道的确实很多,那我问你个问题,为啥我们重写equals方法的时候需要重写hashCode方法呢?
你能用HashMap给我举个例子么?
其实这个问题,我们可以看object.hashCode定义
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
总的来说,hashcode就是拿来支持hashtables的,方便key进行hash操作。
有以下要求:
1、equals相同,那么hashcode必须相同。
2、equals不同,那么hashcode可以相同。
3、如对象为键,那么为了实现hashtables高性能,那么最好,equals不同,hashcode不同。
从hashmap的角度来分析这个问题:
如果equals相同,但是hashcode不同,我就无法找到存入hash中的元素了
举例:
对象有个id属性,equals中判定,id相等,即为同一个对象,现在将对象放入hashmap中,过一段时间,我要获取它,重新构造了一个相同id的对象,但是hashcode不一样,我查找不到元素。获取失败。
网友评论