javase-collection-HashMap-210623-01
- HashMap
HashMapTest01.java
package bgy_hashmap01;
/*
1. HashMap底层用的是 哈希表/散列表 的数据结构
1)哈希表是一个数组和单向链表的结合体
2)它是一个一维数组,数组中每一个元素是一个单向链表
3)数组:查询效率高,随机增删效率低
4)单向链表:随机增删效率高,查询效率低
1) 非线程安全
2) JDK8之后,如果哈希表单向链表中元素超过8个,
单向链表这种数据结构会变成红黑树数据结构,
当红黑树的节点数量小于6时,会重新把红黑树变成单向链表,
这种方法是为了提高检索效率,二叉树的检索会再次缩小范围,提高效率。
2. HashMap 底层源码
public class HashMap {
// HashMap底层实际上就是一个数组(一维数组)
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
// 哈希值
// 哈希值是key的hashCode()方法的执行结果
// 哈希值通过哈希函数(算法),可以转换为存储成数组的下标
final int hash;
final K key; // 存储到Map集合中的key
V value; // 存储Map集合中的value
Node<K,V> next; // 下一个节点的内存地址
......
......
}
}
3. 方法及原理
map.put(k,v);
map.get(k);
4. HashMap特点:
无序,不可重复
因为不一定存储到哪一个单向链表上,所以无序
equals方法来保证HashMap集合中key不可重复,
如果key重复,value会被覆盖
放在HashMap集合key部分的元素其实放到了HashSet集合中了
所以HashMap集合中的元素也需要从写hashCode() 和 equals() 方法
放到HashSet集合中的元素也需要从写hashCode() 和 equals() 方法
5. HashMap,哈希表使用不当时无法发挥性能,以下举例:
如果将所有的hashCode() 返回固定的值,
那么会导致底层哈希表变成了纯单向链表,这种情况称为:散列分布不均匀
1) 散列分布均匀:
有100个元素,10个单向链表,每个单向链表中有10个节点
如果将hashCode()方法返回值都设置不一样,
这样底层导致底层哈希表变成了一维数组,这也是散列分布不均匀。
6. HashMap初始容量为16,默认加载因子0.75
默认加载因子就是,当HashMap的容量达到75%的时候,开始扩容。
扩容之后的容量原容量的2倍。
*** HashMap集合初始化容量必须是2的倍数 (官方推荐) ***
*** 这样做是为了达到散列均匀,为了提高HashMap的存取效率 ***
*/
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapTest01 {
public static void main(String[] args) {
// Integer 已经重写了hashCode() 和 equals()
Map<Integer,String> map = new HashMap<>();
map.put(101,"bgy101");
map.put(202,"bgy202");
map.put(303,"bgy303");
map.put(303,"key重复,覆盖303的value");
System.out.println(map.size()); // 3
Set<Map.Entry<Integer, String>> nodes = map.entrySet();
for (Map.Entry<Integer, String> node : nodes){
System.out.println(node.getKey()+"----->"+node.getValue());
}
}
}
HashMapTest02.java
package bgy_hashmap01;
import java.util.*;
/*
1. 向map中存储元素和取元素,都是先调用hashCode()方法,然后再调用equals()方法
2. equals() 方法可能调用,也可能不调用
put<k,v>() :
1) k.hashCode()方法返回哈希值
2) 哈希值经过哈希算法转换成数组下标
3) 数组下标位置上如果是null,equals()方法就不会执行
get(k) :
1) k.hashCode()方法返回哈希值
2) 哈希值经过哈希算法转换成数组下标
3) 数组下标位置上如果是null,equals()方法就不会执行
3. 如果一个类的equals()方法重写了,那么hashCode()方法必须重写。
并且equals()方法返回如果是true,hashCode()方法返回值必须一样
equals()方法返回true表示两个对象相同,在同一个单向链表上比较。
所以对于同一个单向链表上的节点来说,它们的哈希值都一样,
所以hashCode()方法返回值也一样。
4. 放在HashMap集合的key部分,和放在HashSet集合中的元素,
都需要同时重写hashCode() 和 equals()。
5. 对于哈希表数据结构来说:
1) 如果 Object01 和 Object02 的hash值相同,一定是放到同一个单向链表上,
2) 如果 Object01 和 Object02 的hash值不同,
但由于哈希值算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
*/
public class HashMapTest02 {
public static void main(String[] args) {
Student s1 = new Student("白光一");
Student s2 = new Student("白光一");
System.out.println(s1.equals(s2)); // true
System.out.println(s1+"hashCode is "+s1.hashCode()); // 29815091
System.out.println(s2+"hashCode is "+s2.hashCode()); // 29815091
/*
HashSet 特点:
无序不可重复
*/
Set<Student> set = new HashSet<>();
set.add(s1);
set.add(s2);
System.out.println(set.size()); // 1
}
}
class Student{
String name;
public Student(){}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
HashMapTest03.java
package bgy_hashmap01;
/*
HashMap集合key部分允许null,但是只能有一个,
如果再有ket为null,覆盖value
HashMap集合value部分也允许为null
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class HashMapTest03 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(null,null);
map.put(1,null);
map.put(null,"test...");
System.out.println(map.size()); // 2
Set<Map.Entry<Integer, String>> entries = map.entrySet();
Iterator<Map.Entry<Integer,String>> it = entries.iterator();
while (it.hasNext()){
Map.Entry<Integer, String> next = it.next();
System.out.println(next.getKey()+"------>"+next.getValue());
}
}
}