HashMap的数据结构:
- 首先,HashMap中数据的存储是由数组与链表一起实现的。
- 数组是在内存中开辟一段连续的空间,因此,只要知道了数组首个元素的地址,在数组中寻址就会非常容易,其时间复杂度为O(1)。但是当要插入或删除数据时,时间复杂度就会变为O(n)。
- 链表是内存中一系列离散的空间,其插入和删除操作的内存复杂度为O(1),但是寻址操作的复杂度却是O(n)。那有没有一种方法可以结合两者的优点,即寻址,插入删除快呢?这个方法就是HashMap。
- 我们看看HashMap的拉链式实现方法。如下图
- Entry是一个static class,其中包含了key和value,也就是键值对,另外还包含了一个next的Entry指针。可以看出:Entry就是数组中的元素,每个Entry其实就是一个key-value对,它特有一个指向下一个元素的引用,这就构成了链表。
1.HashMap中定义了一个Entry类的数组table
- 其中table数组就是buckets(桶),其中数组和链表的数据区保存的就是一个Entry对象,Entry对象中保存的就是HashMap中的键值对(key-value)
2.HashMap中两个重要的属性:capacity(容量)和load factor(加载因子)
- capacity为buckets的容量,load factor是衡量buckets填满程度的比例。如果对迭代性能要求很高的话不要把capacity设置过大,也不要把load factor设置过小。当buckets(桶)填充的数组(即hashmap中元素的个数)大于capacity*load factor时就要把buckets扩充为原来的两倍。
3.put函数(存储)的实现
- 先根据key的hashcode()计算出hash值,并用hash值计算出放在数组中的哪个位置
- 判断该位置是否为空,若为空,则直接调用addEntry()方法将元素加入
- 若该位置不为空(产生数据冲突),将这个位置上的元素统一向这个位置所连的链表后推一格,然后将要加入的元素放在链表头部(头插法)
- 如果数据冲突导致链表过长,就把链表转换成红黑树
- 如果新添加对象中的key值与之前添加的Entry对象通过equals比较返回true时,新添加Entry的value将覆盖集合中原有Entry的value,但key不会覆盖。(保证key的唯一性,不能重复)
- 如果buckets满了(超过capacity*load factor),就要resize(在resize的过程,简单的说就是把buckets扩充为2倍,之后重新计算数组index,把节点再放到新的buckets中)
4.get函数(获取)的实现
- 首先计算key的hashcode,找到数组中对应位置的某一元素
- 如果有冲突,通过key的equals方法在对应位置的链表中找到需要的元素
- 若为树,则在树中通过key.equals()去查找,O(logn)
- 若为链表,则在链表中通过key.equals()去查找,O(n)
- 在java1.8之前的是实现中是用链表解决冲突的,在产生碰撞的情况下,进行get时,两步的时间复杂度是O(1)+O(n)。因此,当碰撞很厉害的时候n很大,O(n)的速度显然是影响速度的。
- 因此在java1.8中,利用红黑树替换链表,这样复杂度就变成了O(1)+O(logn)了,这样在n很大的时候,能够比较理想的解决这个问题。
5.案例
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class 测试HashMap {
public static void main(String[] args) {
test1();
}
public static void test1(){
HashMap<String, String> map = new HashMap<>();
map.put("CN", "中华人民共和国");
map.put("US", "美利坚合众国");
map.put("RU", "俄罗斯");
map.put("FR", "法兰西共和国");
map.put("JP", "日本");
map.put("CN", "中国");
map.put("aa","俄罗斯");
map.put(null, null);
Set<String> set1 = map.keySet();
for (String i : set1){
System.out.println(i);
}
System.out.println("---------------");
Collection<String> values = map.values();
for (String a : values){
System.out.println(a);
}
System.out.println("---------------");
Set<Map.Entry<String, String>> set = map.entrySet();
for (Map.Entry<String, String> e : set){
System.out.println(e.getKey()+"\t\t"+e.getValue());
}
}
}
aa
null
RU
JP
CN
FR
US
---------------
俄罗斯
null
俄罗斯
日本
中国
法兰西共和国
美利坚合众国
---------------
aa 俄罗斯
null null
RU 俄罗斯
JP 日本
CN 中国
FR 法兰西共和国
US 美利坚合众国
参考资料: