目录
容器
Map接口
HashMap存数据
HashMap取数据
容器
Map接口
Map就是用来存储“键(key)-值(value) 对”的。 Map类中存储的“键值对”通过键来标识,所以“键对象”不能重复。
键值对举例:1个手机号只能对应一个微信账号
import java.util.HashMap;
import java.util.Map;Map<Integer, String> m1 = new HashMap<>();
public class TestMap {
public static void main(String[] args) {
Map<Integer, String> m1 = new HashMap<>();
m1.put(1,"one");
m1.put(2,"two");
m1.put(3,"three");
System.out.println(m1.get(1));
System.out.println(m1.size());
System.out.println(m1.containsKey(2));
Map<Integer, String> m2 = new HashMap<>();
m2.put(3,"三");
m2.put(4,"four");
m1.putAll(m2);
System.out.println(m1);
}
}
运行结果:
package Test;
import java.util.HashMap;
import java.util.Map;
public class TestMap2 {
public static void main(String[] args) {
Employee e1 = new Employee(1001,"高一",50000);
Employee e2 = new Employee(1002,"高二",60000);
Employee e3 = new Employee(1003,"高三",70000);
Map<Integer, Employee> map = new HashMap<>();
map.put(1,e1);
map.put(2,e2);
map.put(3,e3);
Employee emp = map.get(1);
Employee emp3 = map.get(3);
System.out.println(emp.getEname());
System.out.println(emp3.toString());
}
}
class Employee {
private int id;
private String ename;
private double salary;
public Employee(int id, String ename, double salary) {
this.id = id;
this.ename = ename;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", ename='" + ename + '\'' +
", salary=" + salary +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
运行结果:
HashMap在查找、删除、修改方面都有非常高的效率。
HashMap存数据
Hashmap底层实现采用了哈希表。哈希表的基本结构就是:“数组+链表”。
(1) 数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。
(2) 链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。
Entry[] table 就是HashMap的核心数组结构,我们也称之为“位桶数组”。
一个Entry对象存储了:
1. key:键对象 value:值对象
2. next:下一个节点
3. hash: 键对象的hash值
关于Hash码(hashcode)
我的理解:哈希码应该就是 值的ASCII码乘上不同权重(对于不同位置),最后相加的结果。
String str1 = "ABC";
String str2 = "ABC";
String str3 = new String("ABC");
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
System.out.println(str3.hashCode());
结果:
key对象的hashcode,转化为hash值用来对应数组的存储位置。
将”key-value两个对象” 成对 存放到HashMap的Entry[]数组中的步骤:
(1)获得key对象的hashcode。 调用key对象的hashcode()方法获得hashcode。
(2)根据hashcode计算出hash值 (要求在[0, 数组长度-1]区间)。这一步称为哈希方法。
约定数组长度必须为2的整数幂,采用位运算实现取余的效果,hash值 = hashcode&(数组长度-1)。
& | 位运算 - 与 | 两个位都为1时,结果才为1 |
为了获得更好的散列效果,JDK对hashcode进行了两次散列处理(核心目标就是为了分布更散更均匀)。
(3)生成Entry对象。
(4) 将Entry对象放到table数组中。
关键总结:
当添加一个元素(key-value)时,首先计算key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,就形成了链表,同一个链表上的Hash值是相同的,所以说数组存放的是链表。 JDK8中,当链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。
HashMap取数据
(1) 获得key的hashcode。通过hash()散列算法得到hash值,进而定位到数组的位置。
(2) 在链表上挨个比较key对象。调用equals()方法,将key对象和链表上所有节点的key对象进行比较,直到碰到返回true的节点对象为止。
(3) 返回equals()为true的节点对象的value对象。
Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashCode。
关于扩容:
HashMap的位桶数组,初始大小为16。实际使用时,显然大小是可变的。如果位桶数组中的元素达到(0.75*数组 length), 就重新调整数组大小变为原来2倍大小。
扩容很耗时。扩容的本质是定义新的更大的数组,并将旧数组内容挨个拷贝到新数组中。
注:HashMap在存储一个元素时,当对应链表长度大于8时,链表就转换为红黑二叉树。