HashMap原理

先看看HashMap的结构图:

HashMap原理_第1张图片
9dcbd4fac1074967fe5ae20d6f5aad1c.png

1. 先来认识一下HashMap中定义的一些需要了解的成员变量

// hashMap数组的初始容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 负载因子 0.75f;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 树形化阈值 8
static final int TREEIFY_THRESHOLD = 8;
// 解除树形化阈值 6
static final int UNTREEIFY_THRESHOLD = 6;
// 树形化的另一条件 Map数组的长度阈值 64
static final int MIN_TREEIFY_CAPACITY = 64
// 这个就是hashMap的内部数组了,而Node则是链表节点对象。
transient Node[] table;
// 数组扩容阈值。
int threshold;

initialCapacity 数组的初始容量为16。可以在构造方法中指定。必须是2的幂次方。(16 → 32 → 64 ...)

loadFactor 加载因子 0.75f。 所谓的加载因子就是HashMap的容量达到0.75时的时候会试试扩容resize(), (例:假设有一个 HashMap 的初始容量为 16 ,那么扩容的阀值就是 0.75 * 16 = 12 。也就是说,在你打算存入第 13 个值的时候,HashMap 会先执行扩容)。加载因子也能通过构造方法中指定,如果指定大于1,则数组不会扩容,牺牲了性能不过提升了内存。

TREEIFY_THRESHOLD 树形化阈值。当链表的节点个数大于等于这个值时,会将链表转化为红黑树。

UNTREEIFY_THRESHOLD 解除树形化阈值。当链表的节点个数小于等于这个值时,会将红黑树转换成普通的链表。

MIN_TREEIFY_CAPACITY树形化阈值的第二条件。当数组的长度小于这个值时,就算树形化阈达标,链表也不会转化为红黑树,而是优先扩容数组resize()。

threshold 数组扩容阈值。即:HashMap数组总容量 * 加载因子。当前容量大于或等于该值时会执行扩容resize()。扩容的容量为当前 HashMap 总容量的两倍。比如,当前 HashMap 的总容量为 16 ,那么扩容之后为 32 。

  1. 继承关系
// table 内部数组是节点类型
static class Node implements Map.Entry {
     final int hash; 
     final K key;
     V value;
     Node next; //下一个节点
    //省略...
}

拉链法的散列表是通过链表解决碰撞问题的,所以HashMap的内部数组是节点类型。 hash值是经过hash()方法处理过的hashCode,也就是数组的索引 bucket,为了使hashCode分布更加随机。

java.util.HashMap.Node
    java.util.LinkedMap.Entry
        java.util.HashMap.TreeNOde

TreeNode是Node是子类,继承关系如下:Node是单向链表节点,Entry是双向链表节点,TreeNode是红黑树节点。TreeNode的代码400多行都是写的红黑树。这个有点难度..可以自行去了解。

  1. 先对HashMap的简单总结

HashMap是基于拉链法实现的一个散列表,内部由数组和链表和红黑树实现。

数组的初始容量为16,而容量是以2的次方扩充的,一是为了提高性能使用足够大的数组,二是为了能使用位运算代替取模预算(据说提升了5~8倍)。

数组是否需要扩充是通过负载因子判断的,如果当前元素个数为数组容量的0.75时,就会扩充数组。这个0.75就是默认的负载因子,可由构造传入。我们也可以设置大于1的负载因子,这样数组就不会扩充,牺牲性能,节省内存。

为了解决碰撞,数组中的元素是单向链表类型。当链表长度到达一个阈值时(7或8),会将链表转换成红黑树提高性能。而当链表长度缩小到另一个阈值时(6),又会将红黑树转换回单向链表提高性能,这里是一个平衡点。

对于第三点补充说明,检查链表长度转换成红黑树之前,还会先检测当前数组数组是否到达一个阈值(64),如果没有到达这个容量,会放弃转换,先去扩充数组。所以上面也说了链表长度的阈值是7或8,因为会有一次放弃转换的操作。

你可能感兴趣的:(HashMap原理)