键值对
- Key是无序、不重复的,Value是无序可重复的
- 线程不安全
- 有参构造时,底层数组长度是最接近参数的2的幂次方
JDK1.7,HashMap的底层结构是数组(长度16)+链表
JDK1.8,HashMap的底层结构是数组(长度16)+链表+红黑树
为啥线程不安全?
JDK1.7 中,由于多线程对HashMap进行扩容,调用了HashMap#transfer(),具体原因:某个线程执行过程中,被挂起,其他线程已经完成数据迁移,等CPU资源释放后被挂起的线程重新执行之前的逻辑,数据已经被改变,造成死循环、数据丢失。
JDK1.8 中,由于多线程对HashMap进行put操作,调用了HashMap#putVal(),具体原因:假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码(判断是否出现hash碰撞)后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。
引用自https://juejin.cn/post/6917526751199526920
无序性
无序性不等同于随机性,向多个HashMap添加相同顺序的元素,遍历顺序是一致的,无序是指元素在底层数组中的存储顺序不是按照添加顺序来存储,而是按照哈希值来存储。
负载因子0.75
数组元素个数 > 负载因子*数组长度,则扩容
负载因子的大小决定了HashMap的数据密度
HashMap的key可以存null,value也可以存null
public static void main(String[] args) {
HashMap map = new HashMap<>();
map.put(null, "a");
map.put(null, null);
map.put("a", null);
for(Map.Entry entry: map.entrySet()){
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
底层数组长度是2的幂次方大小
通过(n-1) & hash确定put元素时在数组中的位置
hash%length == hash&(length-1) 的前提是 length 是 2 的 n 次方,位运算的计算速度比取余运算的计算速度要快
LinkedHashMap
public static void main(String[] args) {
LinkedHashMap tt = new LinkedHashMap<>();
tt.put("Mary",1);
tt.put("Box",2);
tt.put("Alex",3);
for(Map.Entry entry: tt.entrySet()){
System.out.println(entry.getKey() + " " + entry.getValue());
}
System.out.println("-------------------");
HashMap aa = new HashMap<>();
aa.put("Mary", 1);
aa.put("Box", 2);
aa.put("Alex", 3);
for(Map.Entry entry: aa.entrySet()){
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
- 线程安全,方法用synchronized修饰,性能低
- 底层是数组,默认容量为11
不可以存null的key和null的value
线程安全
JDK1.7时,底层结构是分段数组+链表
引用自JavaGuide
JDK1.8时,底层与HashMap一样,均为数组+链表+红黑树
引用自JavaGuide
- 底层是红黑树
- 可以按照key进行排序,默认是升序
如果key是对象,可实现Comparable接口,重写compareTo方法,从而实现定制排序
public class Person implements Comparable{
private int age;
public Person(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age;
}
@Override
public int hashCode() {
return Objects.hash(age);
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
public int compareTo(Object o) {
Person p = (Person) o;
int re = this.age - p.age;
return re;
}
}
//重写compareTo方法
public static void main(String[] args) {
TreeMap treeMap = new TreeMap<>();
treeMap.put(new Person(20), "Ming");
treeMap.put(new Person(25),"Hong");
treeMap.put(new Person(13),"Li");
for(Map.Entry entry: treeMap.entrySet()){
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
使用匿名内部类或lambda表达式,重写Comparator接口的compare方法,从而实现定制排序
//匿名内部类
public static void main(String[] args) {
TreeMap treeMap = new TreeMap<>(new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
treeMap.put("Ming", new Person(20));
treeMap.put("Hong", new Person(25));
treeMap.put("Li", new Person(13));
for(Map.Entry entry: treeMap.entrySet()){
System.out.println(entry.getKey() + " " + entry.getValue());
}
}