补发:2019-7-20 学习日记之HashMap
1、HashMap的数据存储
HashMap类内部有一个Node节点类,类比C++中链表的实现原理,可以猜测到它的作用也是如此。
HashMap部分源码
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
另外,HashMap类中还有这样一个成员table表:
transient Node<K,V>[] table;
显然,我们能感觉到HashMap使用了数组+链表的方式实现了存储数据,而数据(对象)存放的位置由它们的hashCode决定,也就是传说中的哈希表;
然后我大致浏览了一下HashMap和Map接口的源码,发现原来在Map接口中有这么一个接口
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
}
这个接口的实现类的任务便是保存一个键值对,上面的Node类也实现了这个接口;那么HashMap如何保证Key唯一,才不存放重复的Entry<K,V>呢?
答:HashMap存储对象的过程可以大致这样理解:
1、判断Key是否已经存在,判断过程如下:
a)根据Key获取hashCode值,判断是否相同
b)若不同,则不为同一对象
若相同,则调用Key的equals方法进一步判断,若结果为true则为同一对象,反之亦然。
2、若Key未存在,则根据hashCode放到 table 数组中的某个位置;
然后,若table中已经存在的Key0的hashCode和新Key的值一样,即a情况出现,但b步骤
调用equals方法返回false时,Node的链表功能就有用了,新的 K,V对Key将放在Key0之后。
显然,我们应该尽量避免a情况(hashCode冲突)出现,因为链表的查找是很慢的,而数组查找很快。
2、关于HashMap迭代器的问题
首先,HashMap中没有迭代器,它继承于Map接口而不是Collection。
其次,先引出一个问题,既然Map存放的是K,V对,给我们一个Map,当我们想要知道它里面的Value值时,我们只要通过Key去get就可以了。那么问题来了,我们如何知道Map中有哪些Key呢?
So框架设计者在HashMap中设置了一个集合set
transient Set<Map.Entry<K,V>> entrySet;
我们可以通过:
1、获取 entrySet
2、获取 entrySet的迭代器
来得到 Entry<K,V>对象,从而调用getKey(),getValue(),来完成HashMap的遍历;

那么还有一个小问题,既然迭代器只能迭代一次,那么通过HashMap中的 entrySet()方法得到的
Set被迭代了之后,再次获取,是不是不能迭代了?
HashMap<String,Integer> map
= new HashMap<String,Integer>();
map.put("a",1);
map.put("b",2);
map.put("c",3);
Set set = map.entrySet();
Iterator it = set.iterator();
while(it.hasNext()){//开始迭代
Entry entry = (Entry) it.next();
}
//第二次获取 Set集合 和 Iterator迭代器
Set set2 = map.entrySet();
Iterator it2 = set.iterator();// 还能迭代出数据吗?
答案肯定是能二次获取并迭代;
看源码:
获取Set
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;// 注意这里!new 出新的 EntrySet
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator(); // 注意这里!new 出新的 EntryIterator
}
可以看到调用 HashMap的entrySet()方法时,每次反回的都是new出来新的 EntrySet,并且 EntrySet的 iterator() 方法返回的也都是新的迭代器,所以……懂了
最后,附上HashMap其它相关的知识:
-
HashMap为什么线程不安全?(基于jdk1.7分析,延伸到jdk1.8)
HashMap是线程不安全的,其主要体现:
#1.在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。
#2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。
总结:
1、HashMap数据结构用的是数组+链表(哈希表),存储的单元是Node<K,V>结点
2、遍历HashMap可以通过 entrySet()方法先获取EntrySet集合,再获取EntrySet的迭代器,再迭代即可实现遍历
3、今后在重写一个类的equals()方法时,应也重写其hashCode()方法和toString()方法,三点一线
本文深入解析HashMap的数据存储机制,包括其内部的Node节点类和哈希表实现,探讨HashMap如何确保Key的唯一性,以及其迭代器的工作原理。同时,文章还讨论了HashMap的扩容机制和线程安全性问题。
195

被折叠的 条评论
为什么被折叠?



