HashMap的源码分析

hashMap的底层实现是 数组+链表 的数据结构,数组是一个Entry<K,V>[] 的键值对对象数组,在数组的每个索引上存储的是包含Entry的节点对象,每个Entry对象是一个单链表结构,维护这下一个Entry节点的引用;有点绕,用个图来展示吧:

Entry<K,V>[] 数组部分保存的是首个Entry节点;Entry节点包含一个 K值引用  V值引用 以及 引用下一个Entry 节点的next引用;

Entry节点的java代码实现如下:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;    //key 引用
        V value;         //value  引用
        Entry<K,V> next;   //下一个Entry 节点的引用
}

下面再看下HashMap 对象的java实现代码:

包含的属性有:

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * An empty table instance to share when the table is not inflated.
     */
    static final Entry<?,?>[] EMPTY_TABLE = {};

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

    /**
     * The next size value at which to resize (capacity * load factor).
     * @serial
     */
    // If table == EMPTY_TABLE then this is the initial capacity at which the
    // table will be created when inflated.
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;}

比较重要的属性是:

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;  表明这是一个 Entry<K,V>[] 的数组类型;下面看其无参的构造器:
public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

进入以下的构造方法:

 public HashMap(int initialCapacity, float loadFactor) {        //initialCapacity:16   loadFactor 0.75f
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)     //MAXIMUM_CAPACITY   1073741824  false
             initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))   false
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;      //赋值给loadFactor=0.75
        threshold = initialCapacity;       //赋值给threshold=16  当为16是自动扩容
        init();
    }

下面再看看put(E e)的方法:

public V put(K key, V value) {    //如插入  key="city"   value="shanghai"
        if (table == EMPTY_TABLE) {     //true
            inflateTable(threshold);   //参数为16
        }
        if (key == null)        // false
            return putForNullKey(value);
        int hash = hash(key);    //返回key值的hash码; 比如返回为 337
        int i = indexFor(hash, table.length);  //将hash 取模与16 获得的结果为 1
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  //遍历 Entry[1] 中的链表节点对象 包含 原先有的节点和新增进去的节点
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  //当Entry中包含 相同的hash码的key 并且key和要添加的key相等即可以是否重复  则进入以下逻辑:新节点替换重复的节点
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

 下面是 inflateTable(threshold) 方法的源码;

/**
     * Inflates the table.
     */
    private void inflateTable(int toSize) {   //toSize 16
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);  //capacity=16

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); // threshold=16*0.75
        table = new Entry[capacity];     //创建 Entry[] 数组长度为16
        initHashSeedAsNeeded(capacity);   //这个方法可以暂时不用深究
    }  

下面是  roundUpToPowerOf2(int i) 源码

private static int roundUpToPowerOf2(int number) {    //  number=16
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY   //fase  返回 16
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;   //number=16>1  返回 16
    } 

put 方法的源码分析完了之后,接下来再看一下get(Object key) 的方法; 源码:

public V get(Object key) {    //如 key="name"
        if (key == null)      //false
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
 final Entry<K,V> getEntry(Object key) {   //key=name
        if (size == 0) {    //false
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);   //例如 返回hash=337
        for (Entry<K,V> e = table[indexFor(hash, table.length)];  //indexFor(hash, table.length)上面分析过 返回值为 1;遍历 table[1] 中的节点
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))   //如果存在key的hash码相等,并且对象也相等则返回 对应的Entry 节点
                return e;
        }
        return null;   //否则返回null
    } 

到此,hashMap 的源码基本分析完毕了,通过源码分析我们知道HashMap的底层是 数组+链表结构来存数数据的,添加节点存储的位置是根据 key 取hash值 再取模于数组长度:返回的数值就是Entry接在在数组的哪个位置;这种方式的存储方式减少了存储的时间和空间的复杂度;

知道了hashMap是由 数组+链表 的数据结构存储数据后,我们也很容易明白hashMap 的遍历方式:

				
时间: 2024-12-22 05:45:08

HashMap的源码分析的相关文章

【转】java 的HashMap的源码分析

一.HashMap概述二.HashMap的数据结构三.HashMap源码分析     1.关键属性     2.构造方法     3.存储数据     4.调整大小 5.数据读取                       6.HashMap的性能参数                      7.Fail-Fast机制 一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null

Java——HashMap底层源码分析

1.简介 HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap 最多只允许一条记录的key为 null,允许多条value的值为 null. HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致.(如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 Concur

Java源码——HashMap的源码分析及原理学习记录

学习HashMap时,需要带着这几个问题去,会有很大的收获: 一.什么是哈希表 二.HashMap实现原理 三.为何HashMap的数组长度一定是2的次幂? 四.重写equals方法需同时重写hashCode方法 一.什么是哈希表 在了解哈希表之前,先了解下其他数据结构的操作执行性能,数据结构的物理存储结构只有两种方式:顺序存储结构和链式存储结构(栈,队列,数,图等) 数组:采用一段连续的存储单元来存储数据,对于指定下标的查找,时间复杂度为O(1);根据确定的值来查找,需要遍历数组,逐一进行比较

HashMap从源码分析数据结构

1. HashMap在链表中存储的是键值对 2. 数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突.那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式

JAVA入门学习:Java HashMap实例源码分析

本文来源:http://www.zretc.com/technologyDetail/464.html Entry是Map接口中的一个内部接口,它是实现键值对存储关键.在HashMap中,有Entry的实现类,叫做Entry.Entry类很简 单,里面包含key,value,由外部引入的hash,还有指向下一个Entry对象的引用,和数据结构中学的链表中的note节点很类似. 引言 HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢? 一 Entry Entry是Map接口

JDK源码分析--HashMap

HashMap为大家常用的java数据结构工具类,下面对HashMap进行源码分析. 类图结构如下: 其中AbstractMap实现了 public V get(Object key) , public V remove(Object key), public Set<K> keySet(), public Collection<V> values()等常用Map操作方法. 下面先分析下HashMap中的常量定义: /** * The default initial capacit

HashMap源码分析一

HashMap在java编程中,算使用频率top10中的类了.这里是关于HashMap的源码的分析.一个类的源码分析,要看他的来龙去脉,他的历史迭代.一来从以前的版本开始分析,由易到难:二来可以看到他的迭代优化过程.HashMap的源码分析,就从很老以前的一个版本开始分析. 简要说明,HashMap内部是一个数组,Object key 通过hash得到数组的index,如果数组index的位置有碰撞,则通过链表的形式接到那个位置上.取值是先hash到某个位置,然后在比较链表中每个值,获取到要取的

Java基础——HashMap源码分析

本篇介绍的HashMap综合了ArrayList和LinkedList这两个集合的优势,它的底层是基于哈希表实现的,如果不考虑哈希冲突的话,HashMap在增删改查操作上的时间复杂度都能够达到惊人的O(1). 对于HashMap类源码中开头注释翻译: HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同.)此类不保证映射的顺序,特别是它不保

java源码分析之HashSet

http://blog.csdn.net/jzhf2012/article/details/8540696 Java容器类的用途是"保存对象",分为两类:Map--存储"键值对"组成的对象:Collection--存储独立元素.Collection又可以分为List和Set两大块.List保持元素的顺序,而Set不能有重复的元素. 本文分析Set中最常用的HashSet类,并简单介绍和对比LinkedHashSet. 首先对Set接口进行简要的说明. 存入Set的每