java HashMap源码分析

原创 2016年08月28日 15:17:44

HashMap是java中用来存放多组键值对的一种数据结构,该类不保证映射的顺序。本文主要分析HashMap的源码。

1.HashMap中的属性

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{
	//默认初始化容量为16
    static final int DEFAULT_INITIAL_CAPACITY = 16;
	//
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //默认加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
	//Entry数组
    transient Entry[] table;
   //存放元素个数
    transient int size;
	//临界值 加载因子*容量
    int threshold;
   //加载因子
    final float loadFactor;
	//修改次数
    transient volatile int modCount;
<span style="font-family:Arial, Helvetica, sans-serif;">...</span>
<span style="font-family: Arial, Helvetica, sans-serif;">}</span>

2.HashMap的创建
HashMap根据默认初始化容量(DEFAULT_INITIAL_CAPACITY=16),默认加载因子(DEFAULT_LOAD_FACTOR = 0.75f)创建一个初始化容量大小的Entry数组(Entry[] table),当使用指定初始化容量的构造方法创建HashMap时,会使用能容纳指定大小的最邻近的2的n次幂的容量去创建数组(table)

构造方法如下

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);
        int capacity = 1;
        //只要容量小于你要分配的容量,就左移,直到可以容纳,结果为2幂
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }


3.get方法
HashMap通过get()方法取值时,如果key为null,调用getForNullKey()到table[0]位置上查找。根据要取的entry的key的hashCode()计算hash值,到指定位置去取(Entry<K,V> e = table[indexFor(hash, table.length)])。如果要取的位置存放多个entry,遍历该entry链,直到找到(一个entry的hash值和要查询的key的hash值相同 且(key的equals()相同或==)),返回查询到的元素,否则返回null。

public V get(Object key) {
    	//key为null,调用getForNullKey()
        if (key == null)
            return getForNullKey();//(1)
        //根据key的hashCode值,调用hash方法计算hash值   
        int hash = hash(key.hashCode());//(2)
        //由hash值得到entry下标,遍历entry链
        for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null; e = e.next) {//(3)
            Object k;
            //如果有一个entry的key和要查找的key“相同”,返回value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }
(1)处调用了getForNullKey()方法,用来处理key为null时应该返回的value,方法如下

 private V getForNullKey() {
     //如果key为null,hash值计算出来的结果为0,所以只要遍历table[0]
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

(2)处调用了hash方法,该方法根据key的hashCode返回值计算hash值

static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
(3)处调用了indexFor方法,该方法根据hash值确定Entry在数组中存储的下标

static int indexFor(int h, int length) {
        return h & (length-1);
    }

4.put方法

put
在将键值对存入Map时,会根据key对象中重写的hashCode()值,调用HashMap中的hash()方法,根据得到的hash值确定该键值对在HashMap中的存储位置。
遍历当前位置的entry链,如果找到key和要存入元素key相同的entry,替换value并返回oldVaule。否则调用addEntry(hash, key, value, i);存入要存入的元素,并指向原先的entry。

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        //遍历Entry链表
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如果发现key相同,则替换value,并返回oldValue
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        //否则调用addEntry方法,将要存储的键值对放到Entry链里
        addEntry(hash, key, value, i);//(4)
        return null;
    }
(4)处调用了addEntry方法,该方法主要用来添加一个新的Entry

void addEntry(int hash, K key, V value, int bucketIndex) {
		Entry<K,V> e = table[bucketIndex];
		//在当前下标处,创建一个新的Entry对象,并指向之前的Entry
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        //如果数组大小超出临界值,扩容为容量的2倍
        if (size++ >= threshold)
            resize(2 * table.length);//()
    }

以上代码在创建Entry对象时,调用了Entry类的构造方法,Entry类是HashMap的一个内部类,主要用来封装同一下标处的Entry链。

  static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;//一个Entry链表的一个结点指向的next仍为Entry
        final int hash;

        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;}
当调用addEntry方法时,如果数组大小超出了临界值,调用resize方法对数组重新扩容,resize方法如下

 void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //最大容量也只能是Integer.MAX_VALUE
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);//将所有元素复制到新的数组中,并重新散列,给每个元素分配位置
        table = newTable;//赋给当前table
        threshold = (int)(newCapacity * loadFactor);//更新临界值
    }

transfer方法如下

 void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    //计算新的下标
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }








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

这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一样,原来他们没有指定JDK版本,很多文章都是旧版本JD...

《Java源码分析》:HashMap

《Java源码分析》:HashMap 看过很多次HashMap的源码了,但是,每次都没有做记录,因此,每次记忆都不太深,今天在看别人博客时提到Hashtable是线程安全的,Hashtable中...
  • Solar24
  • Solar24
  • 2017年12月01日 16:03
  • 5

java核心之集合框架——HashMap源码分析

——每天的寥寥几笔,加持下去,将会是一份沉甸甸的积累。

Java源码分析之HashMap

成员变量//默认的初始容量,空间必须为2的幂 static final int DEFAULT_INITIAL_CAPACITY = 1 ...

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

一、HashMap概述 HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现。与HashTable主要区别有不支持同步和允许null作为key和value。由于HashMap不是线...

Java 集合框架源码分析(三)——HashMap

介绍HashMap 是Java 集合框架中重要的组成部分,也是平常使用频率很高的一个集合类,典型使用方式如下:Map map=new HashMap(); map.put(1,"Java EE"); ...

JAVA源码分析之HashMap

前言 从事了好长时间的开发工作,平时只注重业务代码的开发,而忽略了java本身一些基础,所以从现在开始阅读以下jdk的源代码,首先从集合开始吧!这一篇先看下HashMap的源码。 java集合...

Java集合框架07--HashMap和源码分析

原文链接:http://blog.csdn.net/eson_15/article/details/51154989               上一章总体分析了Map架构,并简单分析了一下Abstr...

从源码分析java集合【HashMap】

Map如我们所知,存储的是键值对,它的基本单位是实现了Map.Entry的Node,Node 的属性如下: static class Node implements Map.Entry { ...

java HashMap源码分析

HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:java HashMap源码分析
举报原因:
原因补充:

(最多只允许输入30个字)