HashMap的工作原理
2016-04-23 22:27
302 查看
在面试的时候,会经常问到HashMap的一些情况。如果不了解HashMap的源码,对于该问题,实在无法完美的回答。大致问题如下:
1.HashMap和Hashtable的区别:
HashMap允许null值的键和值,而Hashtable不能;HashMap非前程安全,而Hashtable是线程安全的。所以HashMap的效率要比Hashtable高。
2.HashMap的工作原理:
底层使用哈希表来存储数据,也即数组+链表的形式。每一个bucket的位置都存放的是一个Entry对象。Entry对象有三个重要的属性:key,value,next。我们使用put(key,value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们使用put方法来传递key和value的时候,会先调用hashCode()方法,返回的hashCode用于找到bucket来存放Entry对象。HashMap是在bucket中存储键对象和值对象,作为Map.Entry。
3.put的原理,首先看一下源码,如下:
在Entry对象里面,有next属性,作用是指向下一个Entry。比如,刚开始的时候,是A,通过计算其key的hashCode,放在了index=0这个位置,记作Entry[0]=A。后来又来了一个B,计算其key值也应该在index=0这个位置。此时HashMap会使B.next=A,Entry[0]=B。再后来,来了一个C,index=0,那么C.next=B,Entry[0]=C。可以发现,在index=0这个位置,存放了A,B,C这三个键值对,他们通过next属性来进行链接在一起。也就是数组中存放的是最后插入的元素。
4.当两个对象的hashcode相同会发生什么?
因为hashcode相同,所以会发生“碰撞“,又因为HashMap使用LinkedList存储对象,这个Entry会存储在LinkedList中。
5.如果hashcode相同,它们是如何获取到对象的?
首先看一下get的源码:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
通过源码可以看到,当我们调用get()方法的时候,HashMap会使用键对象的hashcode找到bucket位置,然后调用key.equals()方法去找到LinkedList中正确的节点。在此之前,hashCode()屡屡出现,而equals()方法仅仅在获取值对象的时候才出现。
6.如果HashMap的大小超过了负载因子定义的容量,会怎么办?
HashMap默认的负载因子是0.75,也就是说,当一个map填充了75%的bucket的时候,将会创建原来HashMap大小两倍的bucket数组,来重新调整map的大小,并将原来的对象放入到新的bucket数组中。这个过程叫做再散列。
7.重新调整HashMap大小会出现什么问题?
当重新调整HashMap的时候,存在条件竞争,如果两个线程都发现HashMap需要重新调整大小,它们会同时试着调整大小。在调整的过程中,存储在LinkedList中的元素的次序会反过来,因为移动到新的bucket的时候,HashMap并不会将元素放在LinkedList的尾部,而是放在头部。如果资源竞争了,就会出现死锁。不过,在多线程中一般不用HashMap。
8.最后总结
HashMap采用hashing的原理,通过put()和get()来存储和获取对象。当我们将键值对传递给put()方法时,它调用对象的hashCode()方法计算出hashcode,然后找到对应的bucket位置来存放对象。当获取对象时,通过键对象的equals()方法找到正确的键值,然后返回值对象。HashMap使用LinkedList来解决碰撞冲突,当发生碰撞,对象将会存储在LinkedList的下一个节点当中。
1.HashMap和Hashtable的区别:
HashMap允许null值的键和值,而Hashtable不能;HashMap非前程安全,而Hashtable是线程安全的。所以HashMap的效率要比Hashtable高。
2.HashMap的工作原理:
底层使用哈希表来存储数据,也即数组+链表的形式。每一个bucket的位置都存放的是一个Entry对象。Entry对象有三个重要的属性:key,value,next。我们使用put(key,value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们使用put方法来传递key和value的时候,会先调用hashCode()方法,返回的hashCode用于找到bucket来存放Entry对象。HashMap是在bucket中存储键对象和值对象,作为Map.Entry。
3.put的原理,首先看一下源码,如下:
public V put(K key, V value) { if (key == null) return putForNullKey(value); //null值总是放在数组的第一个链表中 int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 如果key在链表中存在,就替换为新的value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold) // 如果超过threshold,就进程扩容。再散列 resize(2 * table.length); }
在Entry对象里面,有next属性,作用是指向下一个Entry。比如,刚开始的时候,是A,通过计算其key的hashCode,放在了index=0这个位置,记作Entry[0]=A。后来又来了一个B,计算其key值也应该在index=0这个位置。此时HashMap会使B.next=A,Entry[0]=B。再后来,来了一个C,index=0,那么C.next=B,Entry[0]=C。可以发现,在index=0这个位置,存放了A,B,C这三个键值对,他们通过next属性来进行链接在一起。也就是数组中存放的是最后插入的元素。
4.当两个对象的hashcode相同会发生什么?
因为hashcode相同,所以会发生“碰撞“,又因为HashMap使用LinkedList存储对象,这个Entry会存储在LinkedList中。
5.如果hashcode相同,它们是如何获取到对象的?
首先看一下get的源码:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
通过源码可以看到,当我们调用get()方法的时候,HashMap会使用键对象的hashcode找到bucket位置,然后调用key.equals()方法去找到LinkedList中正确的节点。在此之前,hashCode()屡屡出现,而equals()方法仅仅在获取值对象的时候才出现。
6.如果HashMap的大小超过了负载因子定义的容量,会怎么办?
HashMap默认的负载因子是0.75,也就是说,当一个map填充了75%的bucket的时候,将会创建原来HashMap大小两倍的bucket数组,来重新调整map的大小,并将原来的对象放入到新的bucket数组中。这个过程叫做再散列。
7.重新调整HashMap大小会出现什么问题?
当重新调整HashMap的时候,存在条件竞争,如果两个线程都发现HashMap需要重新调整大小,它们会同时试着调整大小。在调整的过程中,存储在LinkedList中的元素的次序会反过来,因为移动到新的bucket的时候,HashMap并不会将元素放在LinkedList的尾部,而是放在头部。如果资源竞争了,就会出现死锁。不过,在多线程中一般不用HashMap。
8.最后总结
HashMap采用hashing的原理,通过put()和get()来存储和获取对象。当我们将键值对传递给put()方法时,它调用对象的hashCode()方法计算出hashcode,然后找到对应的bucket位置来存放对象。当获取对象时,通过键对象的equals()方法找到正确的键值,然后返回值对象。HashMap使用LinkedList来解决碰撞冲突,当发生碰撞,对象将会存储在LinkedList的下一个节点当中。
相关文章推荐
- IE9 WIN7 卸载后解决安装的问题
- java中常用的数据类型
- ACM第二专题—搜索总结
- Android Studio Install
- 第三章 使用CSS技术美化
- 自学成才的数据科学家告诉你5个学习大数据的正确姿势!
- 玩转VIM编辑器
- 每天一linux命令 scp命令
- 简单的计数排序
- JavaScript实现本地存储
- 欢迎使用CSDN-markdown编辑器
- 冲刺阶段站立会议每日任务5
- 《J2EE,J2SE,J2ME》
- 所谓完整的linux系统包括哪些部分呢?【转】
- 数据库索引的那些事
- 关于springmvc静态资源访问
- JDK
- Function javascript
- 第89课程 Spark STREAMING kafka 测试完成!生产者发数据,消费者收数据
- The 13th Zhejiang Provincial Collegiate Programming Contest - K Highway Project