社区类网站建设网页制作与设计课本
目 录
一、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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic 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)); // trueSystem.out.println(s5.hashCode()); // 990368553System.out.println(s6.hashCode()); // 1096979270System.out.println(s5.hashCode() % 16); // 9System.out.println(s6.hashCode() % 16); // 6hashMap.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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic 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);}@Overridepublic 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)); // trueSystem.out.println(s5.hashCode()); // 37783039System.out.println(s6.hashCode()); // 37783039System.out.println(s5.hashCode() % 16); // 15System.out.println(s6.hashCode() % 16); // 15hashMap.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.NullPointerExceptionSet<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, "老李");/*** 特有方法*/// 获取所有的keyEnumeration<Integer> keys = hashtable.keys();while (keys.hasMoreElements()) {System.out.print(keys.nextElement() + "\t");}// 3 2 1System.out.println();// 获取所有的valueEnumeration<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")); // 18System.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:男* */}
}