HashMap类使用详解
HashMap采用哈希算法实现,是Map接口最常用的实现类。 由于底层采用了哈希表存储数据,我们要求键不能重复,如果发生重复,新键值对会替换旧的键值对。 HashMap在查找、删除、修改方面都有非常高的效率。
HashMap 集合中的 key 不能重复(key可以为null),我们可以通过重写 hashCode() 与 equals()方法来保证键的唯一。
7.3.1、HashMap的使用
HashMap 中key为 JavaAPI 中提供的类型元素时,不需要重写元素的 hashCode 和 equals 方法,因为这两个方法,在 JavaAPI 的每个类中已经重写完毕,如 String 类、Integer 类等。
【示例】HashMap中key为 String类型
public class Demo {
public static void main(String[] args) {
// 初始化HashMap对象
Map<String, Integer> map = new HashMap<String, Integer>();
// 添加元素
map.put("a", 111);
map.put("c", 222); // 被覆盖
map.put("e", 333);
map.put("d", 444);
map.put("b", 555);
map.put("c", 222); // 与第二个添加的key相同,那么覆盖第二个的value值
// 遍历元素
Iterator<String> iterator = map.keySet().iterator();
while(iterator.hasNext()) {
String key = iterator.next();
System.out.println("key:" + key + " value:" + map.get(key));
}
}
}
输出结果如下:
key:a value:111
key:b value:555
key:c value:222
key:d value:444
key:e value:333
注意:当在HashMap 中put的key在之前已经存过,则不会重复存储,会覆盖之前key对应的value。
7.3.2、存储自定义对象
当给 HashMap 中存放自定义对象时,如果自定义对象作为 key 存在,这时要保证对象唯一,必须重写对象的 hashCode 和 equals 方法,建立自己的比较方式,才能保证 HashMap 集合中的对象唯一。
例如,每位学生(姓名,年龄)都有自己的家庭住址。那么既然有对应关系,则将学生对象和家庭住址存储到HashMap 集合中。学生作为键(key),家庭住址作为值(value),当学生姓名相同并且年龄相同视为同一名学生。
【示例】创建自定义对象 Person
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写equals方法
@Override
public boolean equals(Object obj) {
Person p = (Person) obj;
return name.equals(p.name) && age == p.age;
}
// 重写hashCode方法,
@Override
public int hashCode() {
return name.hashCode() + age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
【示例】创建 HashMap 集合
public class Demo {
public static void main(String[] args) {
// 初始化HashMap对象
Map<Person, String> map = new HashMap<Person, String>();
// 添加元素
map.put(new Person("小明", 18), "重庆");
map.put(new Person("张三", 33), "成都");
map.put(new Person("李四", 19), "武汉");
map.put(new Person("王麻子", 28), "北京");
map.put(new Person("王麻子", 28), "上海"); // 只能输出一个
// 遍历元素
Iterator<Person> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Person p = iterator.next();
System.out.println("key:" + p + " value:" + map.get(p));
}
}
}
输出结果如下
key:Person [name=张三, age=33] value:成都
key:Person [name=王麻子, age=28] value:上海
key:Person [name=小明, age=18] value:重庆
key:Person [name=李四, age=19] value:武汉
注意:当自定义对象作为HashMap的key时,一定得重写自定义类的 hashCode 和 equals 方法,,建立自己的比较方式,才能保证 HashMap 集合中的对象唯一。
7.3.3、HashMap:类底层实现
哈希表也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如:Redis)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出现在各类的面试题中,重要性可见一斑。
在讨论哈希表之前,我们先回顾一下数组和链表来实现对数据的存储的优缺点:
jdk1.7:数组+链表
数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。
链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。
从上分析我们知道,数组优势是查询效率高,链表的优势是增删效率高。那么有没有一种数据结构能结合“数组+链表”的双方优点呢?答案就是“哈希表”。
哈希表的本质就是“数组+链表”,这是一种非常重要的数据结构。在哈希表中进行添加、删除和查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成。
我们知道,数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构。而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组。
我们打开HashMap源码,发现有如下两个核心内容:
其中的,Node[] table 就是HashMap的核心数组结构,我们也称之为“位桶数组”。我们再继续看Node是什么,源码如下:
一个Node对象存储了:
key:键对象
value:值对象
next:下一个节点
hash:键对象的hash值
显然就是一个单向链表结构,我们使用图形表示一个Entry的典型示意:
然后我们画出Node[]数组的结构(这也是HashMap的结构) :
由图可知,哈希表就是数组链表,底层还是数组但是这个数组每一项就是一个链表。
接下来我们来基于JDK1.7来模拟HashMap的实现,本章节重点模拟HashMap的put()方法和get()方法,在进行模拟put()方法和get()方法的实现之前,我们先做好相关的准备工作。
首先创建一个Node节点类,Node节点类是HashMap的内部类,它有几个重要的属性:键对象(key) 、值对象(value)、键对象的hash值(hash)和下一个节点(next)。
代码实现如下:
class MyHashMap<K, V> {// Node节点,是一个单链表 static class Node<K, V> { int hash; // 键对象的hash值 K key; // 键对象 V value; // 值对象 Node<K, V> next; // 下一个节点 // 构造方法 public Node(int hash, K key, V value, Node<K, V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } }}
Nod