#JAVA笔记HashMap底层源码

一、HashMap

1.HashMap中元素的特点

①HashMap中的所有的Key彼此之间是不可重复的,无序的。所有的Key就构成一个Set集合。—>key所在的类要重写hashCode()方法和equals()

②HashMap中的所有的value彼此之间是可重复的,无序的。所有的value就构成一个Collection集合。——>value所在的类要重写equals()

③HashMap中的key-value,就构成了一个entry。

④HashMap中的所有的entry彼此之间是不可重复的,无序的。所有的entry就构成了一个Set集合。

2.HashMap源码解析:

2.1 jdk7中创建对象和添加数据过程(以JDK1.7.0_07为例说明):

//创建对象的过程中,底层会初始化数组Entry[] table = new Entry[16];

HashMap<String,Integer> map = new HashMap<>();

源码:

public HashMap(int initialCapacity, float loadFactor) {
    //...略...

//通过此循环,得到capacity的最终值,此最终值决定了Entry数组的长度。此时的capacity一定是2的整数倍
int capacity = 1;
while (capacity < initialCapacity)
    capacity <<= 1;

this.loadFactor = loadFactor; //确定了加载因子的值0.75
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);  //确定了临界值
table = new Entry[capacity]; //初始化数组,长度为capacity

//..略..

}

map.put("AA",78); //"AA"和78封装到一个Entry对象中,考虑将此对象添加到table数组中。

添加/修改的过程:

将(key,value1)添加到当前的map中:

首先,需要调用key1所在类的hashCode()方法,计算key1对应的哈希值1,此哈希值1经过某种算法(hash())之后,得到哈希值2.

哈希值2在经过某种算法(indexFor())之后,就确定了(key1,value1)在数组table中的索引位置i。

1.1如果次索引位置i的数组上没有元素,则(key1,value1)添加成功。---->情况1

1.2如果次索引位置i的数组上有元素(key2,value2),则需要比较key1和key2的哈希值2—>哈希冲突

​ 2.1如果key1的哈希值2与key2的哈希值2不相同,则(key1,value1)添加成功------>情况2

​ 2.2如果key1的哈希值2与key2的哈希值2相同,则需要继续比较key1和key2的equals().要调用key1所在类的equals(),将key2作为参数传递进去。

​ 3.1调用equals(),返回false:则(key1,value1)添加成功。---->情况3

​ 3.2调用equals(),返回true:则认为key1和key2是相同的,默认情况下替换原有的value2.

说明:情况1:将(key1,value1)存放到数组的索引i的位置

​ 情况2,情况3:(key1,value1)元素与现有的(key2,value2)构成单向链表结构, (key1,value1)指向(key2,value2)

随着不断地添加元素,在满足如下的条件的情况下,会考虑扩容:

(size >= threshold) && (null != table[i])

当元素的个数达到临界值(->数组的长度 * 加载因子)时,就会考虑扩容。默认的临界值=16*0.75---->12

2.2jdk8与jdk7的不同之处(以jdk1.8.0_271为例):

①在jdk8中,的那个我们创建了HashMap实例以后,底层并没有初始化table数组。当首次添加(key,value)时,进行判断,

如果发现table尚未初始化,则对数组进行初始化。

②在jdk8中,HashMap底层定义了Node内部类,替换jdk7中的Entry内部类,意味着,我们创建的数组是Node[]

③在jdk8中,如果当前的(key,value)经过一系列判断之后,可以添加到当前的数组角标i中。如果此时角标i位置上有元素。在jdk7中是将新的(key.value)指向已有的旧的元素(头插法),而在jdk8中是旧的元素指向新的(key,value)元素(尾插法)。“七上八下”

jdk7:数组+单向链表

jdk8:数组+单向链表+红黑树

什么时候会使用单向链表变为红黑树:如果数组索引i位置上的个数达到8,并且数组的长度打到64时,我们就将此索引i位置上的多个元素改为使用红黑树的结构进行存储。(为什么修改呢?红黑树进行put()、get()/remove()操作的时间复杂度O(logn),比单向链表的时间复杂度O(n)的好,性能更高)

什么时候会使用红黑树变为单线链表:当使用红黑树的索引i位置上的元素的个数低于6的时候,就会将红黑树结构退化为单向链表。

2.3 属性/字段:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的初始容量 16
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量  1 << 30
static final float DEFAULT_LOAD_FACTOR = 0.75f;  //默认加载因子
static final int TREEIFY_THRESHOLD = 8; //默认树化阈值8,当链表的长度达到这个值后,要考虑树化
static final int UNTREEIFY_THRESHOLD = 6;//默认反树化阈值6,当树中结点的个数达到此阈值后,要考虑变为链表
public class MapExer {
    public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap<>();
        map.put(31, "张三");
        map.put(31, "李四");
        map.put(47, "王五");
        map.put(45, "赵六");
    }

}

在这里插入图片描述

在类中重写了hashcode()的方法,通过hash值索引位置,张三跟李四的索引位置相同,而且hash值也相同,key也相同,也就替换了。

王五与李四的hash值索引的位置相同,当时当比较hash值时却不同,通过尾插法插入链表当中。

public class Person {
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    //其中Person类中重写了hashCode()和equal()方法

    @Override
    public boolean equals(Object o) {
        System.out.println("Person equals()....");
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (id != person.id) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
public class HashSetDemo {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        Person p1 = new Person(1001,"AA");//新建两个实例的Person对象
        Person p2 = new Person(1002,"BB");
        set.add(p1);//添加到HashSet当中
        set.add(p2);
        System.out.println(set);//打印set表

        p1.name = "CC";//修改p1中的name的值
        set.remove(p1);//移除p1,因为p1所在的位置是名字为"AA"的索引位置,当改为"CC"时,通过计算Hash值时与之前的不一样,从而并没有真正移除p1
        System.out.println(set);

        set.add(new Person(1001,"CC"));//添加一个对象,如下图1,通过hash计算的索引位置为7,但是由于hash值不同,尾插法插到后面去
        System.out.println(set);

        set.add(new Person(1001,"AA"));//虽然索引位置与hash值与索引位置相同,但是通过equals比值发现不同,直接尾插法
        System.out.println(set);

    }
}

在这里插入图片描述
图1
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值