持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
为了深入了解、首先要看为什么重写equals方法还要重写hashcode? Object类有hashcode方法,hashcode底层是C语言写的。根据对象内存地址来的,转换成整数类型
定律:
- 如果两个对象的hashcode的值相等,内容值是不一定相等。
- 如果使用equals方法比较两个对象的内容值,相等的情况下,则两个对象的hashcode相等。
- equals在Object类中,默认采用==比较内存地址是否相等。(String类对equals方法和hashcode方法进行了重写)
hashMap1.7的底层实现
hashMap和hashTable的区别: hashMap线程不安全、允许key为空,放在数组为0的位置。 hashTable线程安全,key不允许为空。
- 基于arrayList集合实现,优点不需要考虑hash碰撞的问题。缺点从头开始遍历效率低,时间复杂度是O(n)
- 基于数组加链表方式(1.7)如果链表过长时间复杂度是O(n)
- 基于数组加链表加红黑树(1.8)如果链表过长时间复杂度是O(n),数组容量>=64&&链表长度超过8位,就使用红黑树时间复杂度O(logN)
HashMap类似于容器。使用Entry对象存放键值对。
- 时间复杂度:
- O(1):只需要查询一次,使用数组查找Array[1],查询效率非常高
- O(n):循环从头查到尾,查询效率非常低
- O(logN):平方,二叉树,红黑树。2的10次方,只需要查10次就能查到2的10次方的数据量
hashMap如果没有发生hash碰撞,时间复杂度就是O(1) hash碰撞问题:
hash碰撞是怎么产生的?hashcode相等,内容不相等。 解决方法:jdk1.7数组加单向链表的方式,同一链表内hashcode值相同,值不相同。如果从链表中取值,时间复杂度为O(n)
如果key存放null,值会放到数组的0位,如果0位有值的话,会放到链表中 hashMap所有数据遍历效率非常低,因为会去遍历所有的链表、红黑树。
//基于arraList
public class ArrayListHashMap<K,V> {
private List<Entry<K,V>> entrys = new ArrayList<>();
class Entry<K,V>{
K k;
V v;
public Entry(K k,V v){
this.k = k;
this.v = v;
}
}
public void put(K k,V v){
entrys.add(new Entry<K,V>(k,v));
}
public V get(K k){
for (Entry entry:
entrys) {
if(entry.k.equals(k)){
return (V) entry.v;
}
}
return null;
}
public static void main(String[] args) {
ArrayListHashMap<Object, String> arrayListHashMap = new ArrayListHashMap<>();
arrayListHashMap.put("a","a");
arrayListHashMap.put(97,"97");
System.out.println(arrayListHashMap.get("a"));
System.out.println(arrayListHashMap.get(97));
}
}
//基于数组加链表
public class ExtHashMap<K,V> {
//hashMap初始容量16
private Entry[] entrys = new Entry[10000];
class Entry<K,V>{
K k;
V v;
Entry<K,V> next;
public Entry(K k,V v){
this.k = k;
this.v = v;
}
}
public void put(K k,V v){
//如果key等于null,则放到0位
int index = k == null ? 0 : k.hashCode() % entrys.length;
//判断key是否发生冲突
Entry oldEntry = entrys[index];
if(oldEntry == null){
//没有发生hash碰撞,直接放到数组中
entrys[index] = new Entry<K,V>(k,v);
}else{
oldEntry.next = new Entry<K,V>(k,v);
}
}
public V get(K k){
int index = k == null ? 0 : k.hashCode() % entrys.length;
for(Entry oldEntry = entrys[index];oldEntry != null;oldEntry = oldEntry.next ){
if((k == null && oldEntry.k == null)||oldEntry.k.equals(k) ){
return (V) oldEntry.v;
}
}
return (V) entrys[index].v;
}
public static void main(String[] args) {
ExtHashMap<Object, String> extHashMap = new ExtHashMap<>();
extHashMap.put("a","a");
extHashMap.put(97,"97");
extHashMap.put(98,"98");
extHashMap.put(null,"123");
//a的hashcode和97的hashcode都是97,会出现hashcode碰撞
//hashMap如果没有发生hash碰撞,时间复杂度就是O(1)
System.out.println(extHashMap.get(97));
System.out.println(extHashMap.get(98));
System.out.println(extHashMap.get(null));
}
}
计算hash得出index key.hashcode()%entry.length冲突会非常大,放到链表和红黑树,时间复杂度O(n)和O(logN)。 降低hash冲突,均匀的放到数组下标位置,时间复杂度O(1)
//求hash值源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
核心代码两行(必须理解透):
(h = key.hashCode()) ^ (h >>> 16); //计算hash值
i = (n - 1) & hash //得到数组的坐标
n-1,因为每次扩容,2的幂次方,都是偶数,二进制都是100000。。。,1开头000。。。结束,二进制只有一个1,冲突概率非常大,减一变成奇数后,二进制产生1的概率就增大,11得1就会增加,再去与运算,减少hash冲突