HashMap实现原理(jdk1.7),源码分析

HashMap实现原理(jdk1.7),源码分析

? HashMap是一个用来存储Key-Value键值对的集合,每一个键值对都是一个Entry对象,这些Entry被以某种方式分散在一个数组中,这个数组就是HashMap的主干。

一、几大常量

//默认容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30; 

//默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f; 

//空的哈希表
static final Entry<?,?>[] EMPTY_TABLE = {};  

//实际使用的哈希表,存储数据的数组,Entry类型,每个键值对都是一个Entry
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//数组
transient int size; 

//阈值
int threshold; 

//负载因子
final float loadFactor;  

//修改次数,用于多线程问题
transient int modCount;

//使用替代哈希的默认阀值
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

//随机的哈希种子, 有助于减少哈希碰撞的次数
transient int hashSeed = 0;

二、构造器

//以两参构造器为例
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    //传入容量大于最大容量,以最大容量初始容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();
}

三、put方法

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        //1.如果hash表为空,则根据阈值扩展
        inflateTable(threshold);
    }
    if (key == null)
        //2.键为空的处理方式
        return putForNullKey(value);
    //3.计算key的哈希值
    int hash = haSsh(key);
    //4.根据上一步的哈希值得到其再数组中的位置i
    int i = indexFor(hash, table.length);
    //5.如果i上有元素,则对当前位置的链表进行遍历(HashMap是由数组加链表构成的,一个位置上可能不止有一个元素)
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        //5.1. 查看该链表是否有键相同的元素,如果有,就以新值替换旧值,并返回旧值
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    //6.修改次数+1
    modCount++;
    //7.使用头插法插入Entry对象
    addEntry(hash, key, value, i);
    return null;
}

put内的方法深入分析:

1. inflateTable(threshold);

private void inflateTable(int toSize) {
    //算出一个大于等于toSize的 2的次方数 作为哈希表的容量
    int capacity = roundUpToPowerOf2(toSize);
    //新的阈值,大小为 容量*负载因子
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}

2. putForNullKey(value);

private V putForNullKey(V value) {
    //这里可以看到,key为null时和正常的put没什么区别,只不过是直接以数组第一个位置作为插入点
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);
    return null;
}

3. indexFor(hash, table.length);

//使算得的hash值与 数组长度-1 进行"与"运算
static int indexFor(int h, int length) {
    //官方源码注释说的很清楚了,长度必须是非零的2次方数
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}
/*
那么为什么一定要是2的次方数呢
假设length=16,那么15的二进制则为0000 1111(以8位表示),
如果h = xxxx 0101, h = xxxx 1111,下面进行"与"运算

    0000 1111           0000 1111           0000 1111
    xxxx 0101           xxxx 1111           xxxx xxxx
& ——————————————    & ——————————————    & ——————————————
    0000 0101           0000 1111           0000 xxxx

根据上述结果我们可以得出这样的结论:任何一个hash值 & (length-1) 的结果只与hash的后n位有关,n取决于length是2的几次幂,这样,运算出来的结果一定在0 ~ (length-1)这个范围之内

*/

4. addEntry(hash, key, value, i);

void addEntry(int hash, K key, V value, int bucketIndex) {
    //扩容操作,当hash表元素个数大于等于阈值了,并且要插入的位置已经有值了,才进行扩容
    if ((size >= threshold) && (null != table[bucketIndex])) {
        //扩容位原来容量的2倍
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        //计算元素在数组插入的位置
        bucketIndex = indexFor(hash, table.length);
    }
    //创建新的Entry对象放到对应的位置,使用的是头插法
    createEntry(hash, key, value, bucketIndex);
}

5. resize(2 * table.length);

//这是addEntry中的方法,用于对hash表进行扩容,其实就是新建一个长度为newCapaciy的数组,然后把旧数组的元素放到新数组
void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    //这就是新旧数组元素转移方法,下面有源码分析
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    //重新指定为新数组引用
    table = newTable;
    //重新计算阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

//transfer分析
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    //遍历原hash表,e指向当前元素,next指向e的下一个元素
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //重新计算元素在新数组中应该存入的位置
            int i = indexFor(e.hash, newCapacity);
            //使用头插法,依次插入新的位置
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

四、get方法

public V get(Object key) {
    if (key == null)
        //key为空的情况,和上述的putForNullKey相同,也是从数组第一个位置寻找
        return getForNullKey();
    //遍历数组,根据key的hash值找到数组的位置,然后遍历链表
    Entry<K,V> entry = getEntry(key);
    //返回key对应的值
    return null == entry ? null : entry.getValue();
}

原文地址:https://www.cnblogs.com/songjilong/p/12264168.html

时间: 2024-08-27 11:56:55

HashMap实现原理(jdk1.7),源码分析的相关文章

ConcurrentHashMap底层实现原理(JDK1.8)源码分析

ConcurrentHashMap数据结构 ConcurrentHashMap相比HashMap而言,是多线程安全的,其底层数据与HashMap的数据结构相同,数据结构如下: 说明:ConcurrentHashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树是为了提高查找效率. ConcurrentHashMap源码分析 1.类的继承关系 public class ConcurrentHashMap<K,V> extends AbstractMap<K

Java中HashMap底层实现原理(JDK1.8)源码分析

这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一样,原来他们没有指定JDK版本,很多文章都是旧版本JDK1.6.JDK1.7的.现在我来分析一哈最新的JDK1.8的HashMap及性能优化. 在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效

集合之HashSet(含JDK1.8源码分析)

一.前言 我们已经分析了List接口下的ArrayList和LinkedList,以及Map接口下的HashMap.LinkedHashMap.TreeMap,接下来看的是Set接口下HashSet和LinkedHashSet,其实在分析完了HashMap.LinkedHashMap之后,再来看HashSet和LinkedHashSet就会非常简单. 二.hashSet的数据结构 因为hashSet的底层是基于hashMap或linkedHashMap的(new hashSet的时候可以指定),

SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-convert.html 目录 前言 现象 源码分析 实例讲解 关于配置 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-in

【转】HashMap,ArrayMap,SparseArray源码分析及性能对比

HashMap,ArrayMap,SparseArray源码分析及性能对比 jjlanbupt 关注 2016.06.03 20:19* 字数 2165 阅读 7967评论 13喜欢 43 ArrayMap及SparseArray是android的系统API,是专门为移动设备而定制的.用于在一定情况下取代HashMap而达到节省内存的目的. 一.源码分析(由于篇幅限制,源码分析部分会放在单独的文章中)二.实现原理及数据结构对比 三.性能测试对比四.总结 一.源码分析稍后会在下一篇文章中补充(都写

集合之LinkedHashSet(含JDK1.8源码分析)

一.前言 上篇已经分析了Set接口下HashSet,我们发现其操作都是基于hashMap的,接下来看LinkedHashSet,其底层实现都是基于linkedHashMap的. 二.linkedHashSet的数据结构 因为linkedHashSet的底层是基于linkedHashMap实现的,所以linkedHashSet的数据结构就是linkedHashMap的数据结构,因为前面已经分析过了linkedHashMap的数据结构,这里不再赘述.集合之LinkedHashMap(含JDK1.8源

集合之TreeSet(含JDK1.8源码分析)

一.前言 前面分析了Set接口下的hashSet和linkedHashSet,下面接着来看treeSet,treeSet的底层实现是基于treeMap的. 四个关注点在treeSet上的答案 二.treeSet的数据结构 因为treeSet的底层是基于treeMap的,所以treeSet的数据结构就是treeMap的数据结构:红黑树,因为前面已经分析过了treeMap的数据结构,这里不再赘述.集合之TreeMap(含JDK1.8源码分析). 三.treeSet源码分析-属性及构造函数 3.1 类

HashMap和LinkedHashMap的迭代器源码分析

一.前言 在遍历HashMap与LinkedHashMap时,我们通常都会使用到迭代器,而HashMap的迭代器与LinkedHashMap迭代器是如何工作的呢?下面我们来一起分析分析. 二.迭代器继承图 三.HashMap迭代器 3.1 HashIterator HashIterator是一个抽象类,封装了迭代器内部工作的一些操作. HashIterator类属性 abstract class HashIterator { // 下一个结点 Node<K,V> next; // next e

JDK1.8源码分析之HashMap(一)

一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也可以使用红黑树进行存储,总之,目标只有一个,那就是在安全和功能性完备的情况下让其速度更快,提升性能.好~下面就开始分析源码. 二.HashMap数据结构 说明:上图很形象的展示了HashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树的引入是为了提高效率.所以可见

【集合框架】JDK1.8源码分析之HashMap(一) 转载

一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也可以使用红黑树进行存储,总之,目标只有一个,那就是在安全和功能性完备的情况下让其速度更快,提升性能.好~下面就开始分析源码. 二.HashMap数据结构 说明:上图很形象的展示了HashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树的引入是为了提高效率.所以可见