HashMap源码分析一

HashMap在java编程中,算使用频率top10中的类了。这里是关于HashMap的源码的分析。一个类的源码分析,要看他的来龙去脉,他的历史迭代。一来从以前的版本开始分析,由易到难;二来可以看到他的迭代优化过程。HashMap的源码分析,就从很老以前的一个版本开始分析。

简要说明,HashMap内部是一个数组,Object key 通过hash得到数组的index,如果数组index的位置有碰撞,则通过链表的形式接到那个位置上。取值是先hash到某个位置,然后在比较链表中每个值,获取到要取的值。

这里先重点分析java1.2中HashMap的源码。

private transient Entry table[];

    private transient int count;

    private int threshold;

    private float loadFactor;

    private transient int modCount = 0;

 // 构造方法一
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Initial Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0)
            throw new IllegalArgumentException("Illegal Load factor: "+
                                               loadFactor);
        if (initialCapacity==0)
            initialCapacity = 1;
    this.loadFactor = loadFactor;
    table = new Entry[initialCapacity];
    threshold = (int)(initialCapacity * loadFactor);
    }
// 构造方法二
    public HashMap(int initialCapacity) {
    this(initialCapacity, 0.75f);
    }
// 构造方法三
    public HashMap() {
    this(101, 0.75f);
    }
// 构造方法四
    public HashMap(Map t) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
    }

属性数组table就是HashMap内部存放各种实体的数组。

count是当前存放实体的数量。

modCount是HashMap修改的次数,这里不是做什么统计用,在后面的HashIterator里要用到,HashIterator计算中,不希望HashMap被修改的。

initialCapacity是HashMap内数组的初始长度。构造方法三中,默认初始长度为101,不太清楚这个101是怎么定义出来的。在构造方法四中,他的初始长度是11和入参map长度的2倍之间的大值。这里的11和101比较诡异。

capacity是HashMap里数组的最大长度,随着HashMap里数组长度的变化而变化。

threshold是用来提醒数组table该增大了的一个阀值。

loadFactor是threshold值计算的一个系数,默认0.75,  threshold = capacity * loadFactor 。

基本的方法:

 // 查看HashMap存放数,count是HashMap的存放数属性
    public int size() {
       return count;
    }

    // 查看HashMap存放数是否为空,count==0表示存放数为空
    public boolean isEmpty() {
       return count == 0;
    }

对于调用方存入的一个个key和value,HashMap内部存放的是一个个内部实体对象,这里分析他的内部实体类。

//  HashMap内部实体类
    private static class Entry implements Map.Entry {
        int hash; // 存入HashMap key值的hashCode值
        Object key; // 存入HashMap的key值
        Object value; // 存入HashMap的value值
        Entry next; // 存入HashMap中下一个值,这里是为产生Hash碰撞后,链表结构准备的,单向链表

        // 构造方法
        Entry(int hash, Object key, Object value, Entry next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        // 对象克隆方法
        protected Object clone() {
            return new Entry(hash, key, value,
                     (next==null ? null : (Entry)next.clone()));
        }

        public Object getKey() {
            return key;
        }

        public Object getValue() {
            return value;
        }

        // 设置value值,并返回老的value值
        public Object setValue(Object value) {
            Object oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        // 判断实体对象的相等方法;只有在实体的key和value都相等时,才相等
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
            return false;
            Map.Entry e = (Map.Entry)o;

            return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
               (value==null ? e.getValue()==null : value.equals(e.getValue()));
        }

        // hashCode值,实体的hashCode值为key的hashCode和value的hashCode的异或值
        public int hashCode() {
            return hash ^ (value==null ? 0 : value.hashCode());
        }

        public String toString() {
            return key+"="+value;
        }
    }

HashMap同时也实现了Iterator接口,这里分析他的实现逻辑

// Iterator的类型
    private static final int KEYS = 0; // Iterator对key进行操作
    private static final int VALUES = 1;// Iterator对value进行操作
    private static final int ENTRIES = 2;// iterator对entry进行操作

    // HashMap对Iterator接口的实现
    private class HashIterator implements Iterator {
        Entry[] table = HashMap.this.table;
        int index = table.length;
        Entry entry = null; // 临时变量,存放hasNext方法时,遍历到的数组位链表对象
        Entry lastReturned = null; // 临时变量,存放next方法时,遍历到的对象;为remove方法中准备
        int type; // iterator的类型

        // 存放HashMap修改的次数
        private int expectedModCount = modCount;

        // 构造方法,入参为设定Iterator的类型
        HashIterator(int type) {
            this.type = type;
        }

        // 判断容器内是否有值
        public boolean hasNext() {
            while (entry==null && index>0)
            entry = table[--index];

            return entry != null;
        }

        // 获取下一个值
        public Object next() {
            // 遍历过程中,HashMap是不允许有改动,否则抛出异常ConcurrentModificationException
            if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
            // 遍历过程中,处理那种没有先调用hasNext方法的情况
            while (entry==null && index>0)
            entry = table[--index];
            // 如果entry不为null,处理entry
            if (entry != null) {
            // entry存放在临时变量lastReturned
            Entry e = lastReturned = entry;
            // entry存放链表的下一个节点,为下一次调用next方法做准备
            entry = e.next;
            // 根据type的类型,返回不同的值
            return type == KEYS ? e.key : (type == VALUES ? e.value : e);
            }
            throw new NoSuchElementException();
        }

        // 移除实体对象
        public void remove() {
            // remove调用之前,需要先调用hasNext方法,否则lastReturned=null,抛出异常
            if (lastReturned == null)
              throw new IllegalStateException();
            // 移除操作,HashMap也不希望在其他地方有修改
            if (modCount != expectedModCount)
            throw new ConcurrentModificationException();

            Entry[] tab = HashMap.this.table;
            // 找到lastReturned对应数组的位置
            int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;

            // 找到对应数组的位置后,删除对应链表中的lastReturned值,下面是链表的操作
            for (Entry e = tab[index], prev = null; e != null;
              prev = e, e = e.next) {
                if (e == lastReturned) {
                    modCount++;
                    expectedModCount++;
                    if (prev == null)
                    tab[index] = e.next;
                    else
                    prev.next = e.next;
                    count--;
                    lastReturned = null;
                    return;
                }
            }
            throw new ConcurrentModificationException();
        }
    }

下面主要分析HashMap的添删查

// 判断HashMap是否包含key值
    public boolean containsKey(Object key) {
        Entry tab[] = table;

        // key值不为null
        if (key != null) {
            // 通过key计算出key对应数组table的下标,其实这里是可以封装成一个方法的,多个地方都要用到,后面jdk的版本都有改进
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;
            // 查找链表,判断key是否存在
            for (Entry e = tab[index]; e != null; e = e.next)
                if (e.hash==hash && key.equals(e.key))
                    return true;
        } else { // key为null
            // 查找链表,判断key是否存在
            for (Entry e = tab[0]; e != null; e = e.next)
                if (e.key==null)
                    return true;
        }

        return false;
    }

    // 判断HashMap是否包含value值
    // 这个方法实际效率是很低的,需要先遍历数组,在一个个遍历链表,没有用到hash特性
    public boolean containsValue(Object value) {
        Entry tab[] = table;
        // value值为null
        if (value==null) {
            // 遍历数组
            for (int i = tab.length ; i-- > 0 ;)
            // 遍历链表
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (e.value==null)
                return true;
        } else {// value值不为null
            // 遍历数组
            for (int i = tab.length ; i-- > 0 ;)
            // 遍历链表
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                return true;
        }

        return false;
    }

    // 获取key对应的value值
    public Object get(Object key) {
        Entry tab[] = table;
        // key不为null
        if (key != null) {
          // key的hashCode值
            int hash = key.hashCode();
          // key的hashCode计算出的hash值
            int index = (hash & 0x7FFFFFFF) % tab.length;
          // 通过index定位到数组,然后遍历对应的链表结构
            for (Entry e = tab[index]; e != null; e = e.next)
                if ((e.hash == hash) && key.equals(e.key))
                    return e.value;
        } else {
               // 如果key为null,直接遍历对应的链表结构
                for (Entry e = tab[0]; e != null; e = e.next)
                    if (e.key==null)
                        return e.value;
            }

        return null;
    }

    // 往HashMap中存放key和value
    public Object put(Object key, Object value) {

        Entry tab[] = table;
        int hash = 0;
        int index = 0;

        // 这部分操作是当数组下标的对应位置已经有值,并且
        // key不为null
        if (key != null) {
            // 获得key对应数组的下标
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
            // 在数组对应的下标有值的情况下
            // key已经存在,把新的value代替老的value,并返回老的value
            for (Entry e = tab[index] ; e != null ; e = e.next) {
                if ((e.hash == hash) && key.equals(e.key)) {
                    Object old = e.value;
                    e.value = value;
                    return old;
                }
            }
        } else { // key为null,数组的下标是确定的,为0
            // 在数组对应的下标有值的情况下
            // key已经存在,把新的value代替老的value,并返回老的value
            for (Entry e = tab[0] ; e != null ; e = e.next) {
                if (e.key == null) {
                    Object old = e.value;
                    e.value = value;
                    return old;
                }
            }
        }

        // HashMap的修改次数加一
        modCount++;
        // 如果数组的使用长度大于或等于阀值threshold后
        if (count >= threshold) {
            // 扩展数组
            rehash();

            // 重新赋值tab
            tab = table;
            // 重新计算index
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // 在原HashMap中,key没找到对应的value时
        // 这里先构造一个实体,把实体的next属性指向数组下标index对应的位置
        Entry e = new Entry(hash, key, value, tab[index]);
        // 数组下标对应的位置赋值实体e
        tab[index] = e;
        // 实体数量加一
        count++;
        return null;
    }

    // 推展数组
    private void rehash() {
        // 原数组长度
        int oldCapacity = table.length;
        // 原数组
        Entry oldMap[] = table;

        // 新数组长度,这里数组的增长策略是2倍加一的增
        int newCapacity = oldCapacity * 2 + 1;
        // 重新创建数组
        Entry newMap[] = new Entry[newCapacity];

        // HashMap的修改次数加一
        modCount++;
        // 更新HashMap的扩展数组的阀值
        threshold = (int)(newCapacity * loadFactor);
        // 赋值新数组到HashMap的数组
        // 这个地方存在并发问题,因为此时数组虽然创建了,但是实体值还是没有迁移过来的。
        // 如果此时有人读取value值,可能会返回为空
        table = newMap;

        // 把老数组里的值迁移到新数组里
        // 遍历数组
        for (int i = oldCapacity ; i-- > 0 ;) {
            // 遍历链表
            for (Entry old = oldMap[i] ; old != null ; ) {
                Entry e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newMap[index];
                newMap[index] = e;
            }
        }
    }

    // 移除实体
    public Object remove(Object key) {
        Entry tab[] = table;
        // key不为null
        if (key != null) {
            // 获得key对应的hash值和对应的数组下标
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;

            // 根据对应的数组下标,找到链表对象
            // 遍历链表
            for (Entry e = tab[index], prev = null; e != null;
                 prev = e, e = e.next) {
                if ((e.hash == hash) && key.equals(e.key)) {
                    modCount++;
                    if (prev != null)
                        prev.next = e.next;
                    else
                        tab[index] = e.next;

                    count--;
                    Object oldValue = e.value;
                    e.value = null;
                    return oldValue;
                }
            }
        } else {// key为null,对应的数组下标为0
            // 根据对应的数组下标,找到链表对象
            // 遍历链表
            for (Entry e = tab[0], prev = null; e != null;
                 prev = e, e = e.next) {
                if (e.key == null) {
                    modCount++;
                    if (prev != null)
                        prev.next = e.next;
                    else
                        tab[0] = e.next;

                    count--;
                    Object oldValue = e.value;
                    e.value = null;
                    return oldValue;
                }
            }
        }

        return null;
    }

    // 批量添加
    public void putAll(Map t) {
        Iterator i = t.entrySet().iterator();
        while (i.hasNext()) {
            Map.Entry e = (Map.Entry) i.next();
            put(e.getKey(), e.getValue());
        }
    }

    // 清除数组内容
    public void clear() {
        Entry tab[] = table;
        modCount++;
        for (int index = tab.length; --index >= 0; )
            tab[index] = null;
        count = 0;
    } 
时间: 2024-11-01 13:06:38

HashMap源码分析一的相关文章

[Java] HashMap源码分析

1.概述 Hashmap继承于AbstractMap,实现了Map.Cloneable.java.io.Serializable接口.它的key.value都可以为null,映射不是有序的. Hashmap不是同步的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Collections.synchronizedMap(new HashMap()); (除了不同步和允许使用 null 之

【JAVA集合】HashMap源码分析(转载)

原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储的对象是一个键值对对象(Entry<K,V>): HashMap补充说明 基于数组和链表实现,内部维护着一个数组table,该数组保存着每个链表的表头结点:查找时,先通过hash函数计算hash值,再根据hash值计算数组索引,然后根据索引找到链表表头结点,然后遍历查找该链表: HashMap数据

HashMap源码分析(转载)

一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同.)此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Coll

Java集合系列之HashMap源码分析

一.HashMap简介 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能. ps:本文中的源码来自jdk1.8.0_45/src. 1.重要参数 HashMap的实例有两个参数影响其性能. 初始容量:哈希表中桶的数量 加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度 当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的

Java集合之HashMap源码分析

一.HashMap简介 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能. ps:本文中的源码来自jdk1.8.0_45/src. 1.重要参数 HashMap的实例有两个参数影响其性能. 初始容量:哈希表中桶的数量 加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度 当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的

Java中HashMap源码分析

一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtable大致相同)此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Collections.sync

Java进阶之----HashMap源码分析

今天我们接着来看HashMap的源码,对几个常用的方法进行分析.在分析之前,我们还是要先对HashMap的结构有一个了解.看过之前我分析的ArrayList和LinkedList源码的朋友应该清楚,ArrayList内部是以数组实现的,LinkedList内部是以链表实现的.而HashMap则是对数组和链表的结合,虽然看上去复杂了一些,不过仔细分析一下,还是很好理解的.我们来看一张图片,是我根据我的理解画的. 我们在来看看Entry的内部结构是什么: 以上两个图,相信大家对HashMap的结构有

Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现

视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HASH出来的值,然后通过值定位到这个MAP,然后value存储到这个MAP中的HASHMAP基本原理 1. KEY 是否可以为空?可以,Null当成一个Key来存储 2. 如果Hash KEY重复了会覆盖吗?会覆盖,但返回旧的值 3. HASHMAP什么时候做扩容?put 的时候,阀值高于或等于0.7

HashMap源码分析--jdk1.8

JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8 HashMap概述 ??1. HashMap是可以动态扩容的数组,基于数组.链表.红黑树实现的集合.??2. HashMap支持键值对取值.克隆.序列化,元素无序,key不可重复value可重复,都可为null.??3. HashMap初始默认长度16,超出扩容2倍,填充因子0.75f.??4.HashMap当链表的长度大于8的且数组大小大于64时,链表结构

HashMap源码分析(基于JDK1.6)

在Java集合类中最常用的除了ArrayList外,就是HashMap了.本文尽自己所能,尽量详细的解释HashMap的源码.一山还有一山高,有不足之处请之处,定感谢指定并及时修正. 在看HashMap源码之前先复习一下数据结构. Java最基本的数据结构有数组和链表.数组的特点是空间连续(大小固定).寻址迅速,但是插入和删除时需要移动元素,所以查询快,增加删除慢.链表恰好相反,可动态增加或减少空间以适应新增和删除元素,但查找时只能顺着一个个节点查找,所以增加删除快,查找慢.有没有一种结构综合了