目录

容器

Map接口

HashMap存数据

HashMap取数据


容器

Map接口

Map就是用来存储“键(key)-值(value) 对”的。 Map类中存储的“键值对”通过键来标识,所以“键对象”不能重复

键值对举例:1个手机号只能对应一个微信账号

Java有键值对的集合_java

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);
    }
}

运行结果:

Java有键值对的集合_java_02

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;
    }
}

运行结果:

Java有键值对的集合_java_03

HashMap在查找、删除、修改方面都有非常高的效率。

HashMap存数据

Hashmap底层实现采用了哈希表。哈希表的基本结构就是:“数组+链表”

 (1) 数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。

 (2) 链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。

Java有键值对的集合_hashmap_04

Entry[] table 就是HashMap的核心数组结构,我们也称之为“位桶数组”。

一个Entry对象存储了:

 1. key:键对象 value:值对象

 2. next:下一个节点

 3. hash: 键对象的hash值

Java有键值对的集合_Java有键值对的集合_05

关于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());

结果:

Java有键值对的集合_java_06

key对象的hashcode,转化为hash值用来对应数组的存储位置

Java有键值对的集合_Java有键值对的集合_07

将”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时,链表就转换为红黑二叉树。