目 录
一、HashMap
1.key 存储自定义类型
2.Hash 表存储原理
3.重写 hashCode 和 equals 方法
4.key 为 null
5.jdk 8 后新特性
(1)初始化时
(2)插入
(3)数据结构
6.容量
二、LinkedHashMap
1.说明
2.实例
三、Hashtable
1.说明
2.实例
3.特有方法
四、Properties
1.说明
2.实例
一、HashMap
1.key 存储自定义类型
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
}
public class HashMapTest {
public static void main(String[] args) {
HashMap<Student, Integer> hashMap = new HashMap<>();
Student s1 = new Student("瑶", 18);
Student s2 = new Student("刘禅", 19);
Student s3 = new Student("朵莉亚", 20);
Student s4 = new Student("蔡文姬", 21);
Student s5 = new Student("钟馗", 22);
Student s6 = new Student("钟馗", 22);
System.out.println(s5.equals(s6)); // true
System.out.println(s5.hashCode()); // 990368553
System.out.println(s6.hashCode()); // 1096979270
System.out.println(s5.hashCode() % 16); // 9
System.out.println(s6.hashCode() % 16); // 6
hashMap.put(s1, 1);
hashMap.put(s2, 2);
hashMap.put(s3, 3);
hashMap.put(s4, 4);
hashMap.put(s5, 5);
hashMap.put(s6, 6);
Set<Map.Entry<Student, Integer>> entries = hashMap.entrySet();
for (Map.Entry<Student, Integer> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
/*
* Student{name='钟馗', age=22}:6
* Student{name='刘禅', age=19}:2
* Student{name='蔡文姬', age=21}:4
* Student{name='朵莉亚', age=20}:3
* Student{name='瑶', age=18}:1
* Student{name='钟馗', age=22}:5
* */
}
}
思考:为什么重写了 equals 方法,证明 5,6 两个 key 相同,但仍然会存储呢?
2.Hash 表存储原理
- 先调用 key 的 hashCode 方法,生成哈希值;
- 将哈希值对数组长度进行取模运算,即【 哈希值 % 数组长度 】,计算出对应的索引值;
- 如果索引处没有存储元素,则将键值对封装为 Node 对象,然后存入该位置中;
- 如果索引处有元素,则遍历整个单链表。若遍历出节点的 key 与添加键值对的 key 相同,则做覆盖操作;若遍历出无 key 相同,则把添加的键值对封装成 Node 对象,然后插入单链表的末尾;
- 产生哈希冲突的情况:
- 不同的 key 获得相同的哈希值;
- 通过 key 得到不同的哈希值,但是通过【 哈希值 % 数组长度 】得到的结果相同。
- 一个好的哈希函数,是散列分布均匀的;
- 解决哈希冲突:将冲突的结点挂在同一链表上或同一红黑树上。
3.重写 hashCode 和 equals 方法
对于 1 中的思考,应该从以下方面考虑。
如果调用 equals 方法,结果是 true,说明两个对象相同,但仍然存储说明没有发生哈希碰撞。由 2 的 Hash 表存储原理可知,要发生哈希碰撞有两种情况,此时应该要两个相同 key 生成的哈希值相同,才能避免重复存储。所以需要同时重写 hashCode 方法和 equals 方法。
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class HashMapTest {
public static void main(String[] args) {
HashMap<Student, Integer> hashMap = new HashMap<>();
Student s1 = new Student("瑶", 18);
Student s2 = new Student("刘禅", 19);
Student s3 = new Student("朵莉亚", 20);
Student s4 = new Student("蔡文姬", 21);
Student s5 = new Student("钟馗", 22);
Student s6 = new Student("钟馗", 22);
System.out.println(s5.equals(s6)); // true
System.out.println(s5.hashCode()); // 37783039
System.out.println(s6.hashCode()); // 37783039
System.out.println(s5.hashCode() % 16); // 15
System.out.println(s6.hashCode() % 16); // 15
hashMap.put(s1, 1);
hashMap.put(s2, 2);
hashMap.put(s3, 3);
hashMap.put(s4, 4);
hashMap.put(s5, 5);
hashMap.put(s6, 6);
Set<Map.Entry<Student, Integer>> entries = hashMap.entrySet();
for (Map.Entry<Student, Integer> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
/*
* Student{name='瑶', age=18}:1
* Student{name='刘禅', age=19}:2
* Student{name='蔡文姬', age=21}:4
* Student{name='朵莉亚', age=20}:3
* Student{name='钟馗', age=22}:6
* */
}
}
4.key 为 null
key 可以为 null,但只能存在一个,多者会被覆盖。
public class HashMapTest {
public static void main(String[] args) {
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(null,"齐");
hashMap.put(1,"楚");
hashMap.put(2,"秦");
hashMap.put(3,"燕");
hashMap.put(4,"赵");
hashMap.put(5,"魏");
hashMap.put(null,"韩");
System.out.println(hashMap); // {null=韩, 1=楚, 2=秦, 3=燕, 4=赵, 5=魏}
}
}
5.jdk 8 后新特性
(1)初始化时
- jdk 8 之前,构造方法执行时初始化 table 数组;
- jdk 8 之后,第一次调用 put 方法时初始化 table 数组。
(2)插入
- jdk 8 之前,头插法;
- jdk 8 之后,尾插法。
(3)数据结构
jdk 8 之前,是数组与单向链表的结合;
jdk 8 之后,是数组、单向链表和红黑树的结合;
最开始使用单向链表解决哈希冲突。若 结点数 >= 8 且 table 长度 >= 64,则单向链表转换为红黑树;
当删除红黑树上的结点时,当 结点数 <= 6 时,红黑树转换为单向链表。
6.容量
- 默认情况下,数组长度为 16;
- HashMap 容量永远是 2 的次幂,例如:2、4、8、16、32、64……。原因有两点:为了提高哈希计算效率和减少哈希冲突,让散列分布更均匀;
- 哈希表中元素越来越多时,因为数组长度固定,所以散列碰撞的几率也随之增大,从而导致单链表过长,降低了哈希表的性能;
- 当执行 put 操作时,如果 HashMap 中存储元素的个数超过【数组长度 * 负载因子】的结果(负载因子即 loadFactor,默认值一般为 0.75 ),则需要扩容;
- 扩容即把数组大小扩大一倍,然后遍历哈希表中元素,将其重新均匀分散;
- 扩容是一个非常消耗性能的操作,所以建议预测需要存储元素的个数;
- 例如:设置哈希表的容量为 15,实际创建完 HashMap对象后,实际容量是 12 。因为 HashMap 的容量永远为 2 的次幂,最接近 15 的是 16,16 * 0.75 = 12 。
二、LinkedHashMap
1.说明
- 是 HashMap 的子类,两者用法基本一致;
- 但 LinkedHashMap 是有序不可重复的(插入顺序与读取顺序一致且不可重复);
- 通过双向链表记录来保证插入顺序;
- 效率较 HashMap 低一些;
- 其 key 也需要同时重写 equals 和 hashCode 方法;
- 底层是哈希表与双向链表结合的数据结构;
- key 可以为 null,但只能存在一个,多者会被覆盖。
2.实例
public class LinkedHashMapTest {
public static void main(String[] args) {
LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put(1, "小禾");
linkedHashMap.put(2, "小明");
linkedHashMap.put(3, "小红");
linkedHashMap.put(4, "小雯");
linkedHashMap.put(5, "小凡");
linkedHashMap.put(5, "小州");
Set<Map.Entry<Integer, String>> entries = linkedHashMap.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
/*
* 1:小禾
* 2:小明
* 3:小红
* 4:小雯
* 5:小州
* */
}
}
三、Hashtable
1.说明
- 和 HashMap 一样,底层也是哈希表;
- Hashtable 是线程安全的,方法上都有 synchronized 关键字;
- 初始化容量是 11,默认负载因子是 0.75;
- 扩容后的容量是原容量的 2 倍;
- key 和 value 都不能是 null 。
2.实例
public class HashtableTest {
public static void main(String[] args) {
Hashtable<Integer, String> hashtable = new Hashtable<>();
hashtable.put(1, "老张");
hashtable.put(2, "老王");
hashtable.put(3, "老李");
hashtable.put(4, "老赵");
hashtable.put(5, "老孙");
// hashtable.put(null, "老冯"); // java.lang.NullPointerException
// hashtable.put(6, null); // java.lang.NullPointerException
Set<Map.Entry<Integer, String>> entries = hashtable.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
/*
* 5:老孙
* 4:老赵
* 3:老李
* 2:老王
* 1:老张
* */
}
}
3.特有方法
ublic class HashtableTest {
public static void main(String[] args) {
Hashtable<Integer, String> hashtable = new Hashtable<>();
hashtable.put(1, "老张");
hashtable.put(2, "老王");
hashtable.put(3, "老李");
/**
* 特有方法
*/
// 获取所有的key
Enumeration<Integer> keys = hashtable.keys();
while (keys.hasMoreElements()) {
System.out.print(keys.nextElement() + "\t");
}
// 3 2 1
System.out.println();
// 获取所有的value
Enumeration<String> values = hashtable.elements();
while (values.hasMoreElements()) {
System.out.print(values.nextElement() + "\t");
}
// 老李 老王 老张
}
}
四、Properties
1.说明
- 属性类;
- 继承 Hashtable,也是一个线程安全的 Map 集合;
- 一般和 java 程序中属性配置文件联合使用;
- 该类不支持泛型,key 和 value 都是固定的 String 类型。
2.实例
public class PropertiesTest {
public static void main(String[] args) {
Properties p = new Properties();
p.setProperty("name", "张三");
p.setProperty("age", "18");
p.setProperty("sex", "男");
p.setProperty("address", "北京");
System.out.println(p.getProperty("name")); // 张三
System.out.println(p.getProperty("age")); // 18
System.out.println(p.getProperty("sex")); // 男
System.out.println(p.getProperty("address")); // 北京
Enumeration<?> enumeration = p.propertyNames();
while (enumeration.hasMoreElements()) {
String name = (String) enumeration.nextElement();
String value = p.getProperty(name);
System.out.println(name + ":" + value);
}
/*
* address:北京
* age:18
* name:张三
* sex:男
* */
}
}