HashMap的底层实现原理及与HashTable的区别。

1 HashMap的底层实现原理

我们知道数组和链表在存储数据时,有不同的性能表现:

  • 数组:占用连续的存储空间,询址查询速度快,增删速度慢
  • 链表:占用空间不连续,查询速度慢,增删快。

HashMap 结合了二者的优点,定义了链表数组。在jdk1.8 之前为Entry[],jdk1.8 中为Node[],Entry和Node类似,本质上就是一个单向的链表。

// JDK1.8 HashMap Node节点部分源码
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        }

HashMap存储键值对的过程

  1. 调用key.hashcode()计算出键的hashcode值。
  2. 调用hashmap的hash()散列算法算出hash值。
  3. 根据hash值确定填充的数组位置。
  4. 如果hash值相同,则调用key.equals()方法,如果相同,则不存,如果不同,则进行尾插。

ps:HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。

HashMap的取值过程

  1. 调用get(key)方法
  2. 调用key.hashcode()的方法得到hashcode值
  3. 调用散列算法hash()计算出hash值,依据hash值定位查询的数组位置
  4. 遍历链表,比较key.equals()是否为true,为ture时找到。

ps:两个内容相同(equals()为true)的对象必须具有相同的hashcode;

hash()散列算法:

  1. hash = hashcode % 数组长度
    这种算法因为使用了除法,所以效率较低
  2. hash = hashcode&(数组长度-1)*
    hashcode对数组的长度-1进行按位与运算,数组的长度必须为2的整数幂

HashMap的扩容问题:
HashMap中Node()数组的默认初始长度为16,当存储的数据不断增加时,因为数组的位置有限,导致链表的长度增加,hashmap的效率降低。所以HashMap内部引入了扩容机制。
HaspMap的扩容涉及到了四个关键的参数:size,threshhold,DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR。在HashMap中的定义如下:

/**
* 数组中的元素个数
* The number of key-value mappings contained in this map.
*/
transient int size;
/**
* 扩容的阈值
* The next size value at which to resize (capacity * load factor).
*/   
int threshold;
/**
* 默认的初始数组大小为16 (必须为2的指数次幂)
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 默认加载因子0.75
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;

当 size > threshold 时,会调用resize()方法进行数组的扩容。
ps:jdk1.8对hashmap的优化

  1. jdk8中 当链表长度大于8时,链表转换为红黑树,提高查找效率。
  2. jdk8中对扩容方法resize()进行了优化,当数组的元素数量大于capacity*load_factor时,会进行2次幂的扩展(例:原数组的capacity为16,扩容后为32),此举的目的是不用重新计算每个元素的hash值,每个元素的在新数组的位置只有两种可能:要么在原位置,要么在原位置的基础上移动原capacity个长度。

*为什么要使用hash = hashcode&(数组长度-1)这种形式的散列算法?
问题的关键在于为什么是(数组长度-1) ?,因为(数组长度-1)是一个奇数。偶数在转化为二进制时低位为0,奇数在转化为二进制时低位为1,在进行按位与运算时,如果低位为0,那么位运算的结果低位一定为0,转化为十进制后hash值只能是偶数;而如果低位为1,那么位运算的结果低位可能位0也可能为1,转化为十进制后即hash值有可能是奇数,也有可能是偶数。我们知道hash值对应的就是数组的位置索引,如果hash值只能为偶数,那么最多只能利用到数组一半的空间。故采用(数组长度-1)的目的,就是是计算出的hash值即可以为奇数,也可以为偶数,充分利用数组的空间。

2 HashMap与HashTable的区别

简单来说,分为以下几个方面:

  1. 线程安全性不同
    HashTable的方法是线程安全的,HashMap不支持线程同步,所以效率上HashMap一般高于HashTable
  2. key和value是否允许null值
    Hashtable中,key和value都不能为null,而HashMap中可以允许key和value为null,且存储在数组索引为0处;
  3. 扩容
    HashTable中,数组的默认大小是11,扩容的方式是 原数组长度*2+1
    HashMap中,数组默认大小是16,扩容是按2倍的大小
  4. hash值
    HashTable中,直接使用key的hashcode
    HashMap中,使用的是key的hashcode 高低16位异或的结果
  5. hashmap 使用的是Iterator hashtable使用的是Enumeration

你可能感兴趣的:(Java集合原理)