`

HashMap源码解析

    博客分类:
  • JDK
阅读更多
HashMap源码解析
 public class HashMap<K,V> 
        extends AbstractMap<K,V>
           implements Map<K,V>, Cloneable, Serializable

HashMap是继承自AbstractMap<K,V>,那我们先来看一个AbstractMap<K,V>
   public abstract class AbstractMap<K,V> implements Map<K,V> {

AbstractMap<K,V>是接口Map<K,V>的实现,那我们看一个Map<K,V>
   public interface Map<K,V> {

       Map<K,V>定义了一系列方法的接口,AbstractMap<K,V>对Map<K,V>进行了方法的抽象,基本
   上实现在Map的方法,留了一个接口
public abstract Set<Entry<K,V>> entrySet();

       以下是子类的这现方法:
    /**
     * Returns a collection view of the mappings contained in this map. 
       Each
     * element in the returned collection is a <tt>Map.Entry</tt>.  The
     * collection is backed by the map, so changes to the map are 
       reflected in
     * the collection, and vice-versa.
     */
   public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es = entrySet;
        return (es != null ? es : (entrySet = (Set<Map.Entry<K,V>>) 
       (Set) new EntrySet()));
    }
   

     有人可能郁闷了,这怎么实现的?,我们来看一个EntrySet类
    private class EntrySet extends AbstractSet/*<Map.Entry<K,V>>*/ {
        public Iterator/*<Map.Entry<K,V>>*/ iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

这里使用了一个AbstractSet,抽象集合类型,使用Hashmap的属性来初始化Set中的四个方法,从而达到初始化entrySet的目的.
我们来看一个这个Iterator:
private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;        // next entry to return
        int expectedModCount;        // For fast-fail 
        int index;                // current slot 
        Entry<K,V> current;        // current entry

        HashIterator() {
            expectedModCount = modCount;
            Entry[] t = table;//table数据过去了
            int i = t.length;
            Entry<K,V> n = null;
            if (size != 0) {  
                while (i > 0 && (n = t[--i]) == null)
                    ;
            }
            next = n;
            index = i;
        } 
        public boolean hasNext() {
            return next != null;
        }
        

        Entry<K,V> nextEntry() { 
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null) 
                throw new NoSuchElementException();
                
            Entry<K,V> n = e.next; 
            Entry[] t = table;
            int i = index; 
            while (n == null && i > 0)
                n = t[--i]; 
            index = i;
            next = n;
            return current = e;
        }

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }

    }

这个遍历器实现了hasNext(),Hashmap下一个元素是否为空,nextEntry()为Itarator的next()方法.
这HashIterator()方法初始化好了第一个元素,从HashMap的最后一个数开始查找,只到第一个不为空的元素,设置为next
此下标的设置为index,在调用next()时,首先把next附值给e,然后再调用e.next,如果e.next不为空,则下一个元素为同一木桶链,
如果为空,则查找数组table中的下一个元素.然后把当前元互设置为e,下一个元素设置为e.next(table[index]).这里还重写了Itrator的
remove()方法,移除当前key实体.
 /**
     * Removes and returns the entry associated with the specified key
     * in the HashMap.  Returns null if the HashMap contains no mapping
     * for this key.
     */
    Entry<K,V> removeEntryForKey(Object key) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            if (e.hash == hash && eq(k, e.key)) {
                modCount++;
                size--;
                if (prev == e) 
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }
   
        return e;
    }

这是移除一个木桶的方法,如果当前待移除的木桶对象和当前待移除的木桶的上一个元素相同,则移走这个木桶,当前下标为下一个元素的对象,则当前元素的和下一个元素不相同,则当前的下一个为下一个元素.当前素和下一个元素相同的情况只有在一级木桶的时候出现.
transient Entry[] table;

有人称table对象为一个视图,我称之为木桶,如下图:



蓝色框里的为一级木桶,下面的为木桶链.其实HashMap使用的就是这种原理实现的.
知道了HashMap是如何实例化实体对象后,以下我来介绍一个HashMap是如何put,get相对就应的实体对象.
    public V put(K key, V value) {
	K k = maskNull(key); 
        int hash = hash(k);
        int i = indexFor(hash, table.length);
 
 

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            if (e.hash == hash && eq(k, e.key)) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, k, value, i);
        return null;
    }

这是put方法,很简单,首先通过你put进来的对象的hashCode()(经过位运算)和table对象的大小进行位&运算得到一个0到table.length的下标,如果此下标有值,并且key也相同,则发生替代行为,如果此下标没值,或者有值,则添加这个实体对象.
    void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

这里添加这个实体对象,如果大小不够的话,会自动添加Hashmap的大小.这里的bucketIndex是一个木桶,可能只有一个也可能是一个链.
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        final int hash;
        Entry<K,V> next;
 
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public K getKey() {
            return HashMap.<K>unmaskNull(key);
        }

        public V getValue() {
            return value;
        }
    
        public V setValue(V newValue) {
	    V oldValue = value;
            value = newValue;
            return oldValue;
        }
    
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2))) 
                    return true;
            }
            return false;
        }
    
        public int hashCode() {
            return (key==NULL_KEY ? 0 : key.hashCode()) ^
                   (value==null   ? 0 : value.hashCode());
        }
     
        public String toString() {
            return getKey() + "=" + getValue();
        }
 
        void recordAccess(HashMap<K,V> m) {
        }
 
        void recordRemoval(HashMap<K,V> m) {
        }
    }

这是实体对象,本身就有一个next指向实体对象本身,这就形成了一个木桶链.
以上是put方法,下面介绍一个get方法:
    public V get(Object key) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> e = table[i];//得到Entry.
        while (true) {
            if (e == null)
                return null;
            if (e.hash == hash && eq(k, e.key)) 
                return e.value;
            e = e.next;
        }
    }

通过key的hashCode()换算,直接到得对象在table[]中的index.得到对象后,循环得到这个对象链直到e.hash=hash,eq(k,e.key).然后返回这个值,如果找到最后都没有则返回null.
HashMap之后以速度会快,是因为对象的HashCode()都是唯一的,经过优化的hash算法可以实现对象链的减少,能够快速定位到对象本身[数组下标].
2010-10-14 日添加说明如下:
HashMap:
其实现方式
transient Entry[] table;
采用实体数组的方式存储,以一个hashCode[经过换算后的数组下标]
具体算法如下:
int hash = hash(key.hashCode());
static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
        return h & (length-1);
}
这里先根据对象本身的hashCode()方法,然后再移位操作,最后执行与操作,生成数组下标。
有人会问了,原来HashMap就是一个数组啊,对了,就是数组,只不过他在数组的级别上稍微做了改变,比如其下标的生成方式,还有一点也是HashMap特有的,散列表:
如果数组下标相同,那么这个数组的值就会被重写,HashMap也一样:
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
}
这里的e.value = value就是把原先的值改为新的值,这里的e是对象的引用.Entry是一个Map的内部封装类型,其作为对象存储在table的值中.
接下来当hash值在同一个范围,key不同时,那么生成的下标原先存在,而值却不同,接下来就看HashMap的散列表:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
}
如果pucketIndex[根据hash生成的数组下标]在table中不存在,那么e就为空,也就是next为空。Table[pucketIndex]就为当前对象。如果不为空,那么原先存在于这个下标下的元素就成为当前的next,这个列表可能会不停的加长,10个,20个….
如果你的HashMap效率很低,那么你要考虑是不是对象的HashCode()方法有问题.

  • 大小: 34.1 KB
分享到:
评论

相关推荐

    详解HashMap源码解析(下).doc

    HashMap的源码解析涉及到的数据结构主要包括数组和链表,以及在负载过大时升级为红黑树的优化策略。理解这些机制对于高效使用HashMap和解决相关问题至关重要。此外,HashMap是非线程安全的,如果在多线程环境下使用...

    HashMap源码深度剖析.md

    HashMap源码深度剖析,面试必备

    手写HashMap源码.rar

    《手写HashMap源码解析——深入理解数据结构与算法》 HashMap是Java编程语言中一个常用的集合类,它提供了一种高效、灵活的键值对存储方式。在面试过程中,尤其是2020年及以后的技术面试中,深入理解HashMap的实现...

    HashMap源码(上)

    HashMap的部分源码解析

    HashMap 源码分析

    《HashMap 源码解析——JDK11版本》 HashMap是Java中广泛使用的非同步散列表,其设计和实现是高效且灵活的。在JDK1.8之前,HashMap的底层数据结构采用的是数组+链表的方式,这种方式称为“拉链法”来解决哈希冲突。...

    HashMap源码流程图

    一图解析HashMap源码流程 // 默认的HashMap中数组的长度 16 static final int DEFAULT_INITIAL_CAPACITY = 1 ; // aka 16 // HashMap中的数组的最大容量 static final int MAXIMUM_CAPACITY = 1 ; // 默认的扩容的...

    补充资料(HashMap红黑树).rar

    5. HashMap源码解析 - put过程:涉及哈希计算、链表/红黑树的插入,以及扩容逻辑。 - get过程:通过哈希值快速定位到数组槽位,然后根据链表或红黑树结构进行查找。 - resize过程:当负载因子达到阈值时,HashMap...

    源码解析jdk8.0集合:HashMap的底层实现原理.pdf

    根据提供的文件信息,以下是对JDK 8.0中HashMap实现原理的详细知识点介绍: 1. HashMap概述 HashMap是Java集合框架的一部分,它实现了Map接口,用于存储键值对。其核心特点在于基于哈希表的映射机制,能够通过键...

    图灵Java高级互联网架构师第6期并发编程专题笔记.zip

    03-01-HashMap源码解析-monkey 03-并发List、Set、 ConcurrentHashMap底层原理剖析-monkey 04-Java并发线程池底层原理详解与源码分析-monkey 05-并发编程之深入理解Java线程-fox 06-并发编程之CAS&Atomic原子操作...

    并发编程之一 日常学习笔记

    另外,"HashMap源码解析"(文件"03-01")虽然不是直接关于并发的,但HashMap在并发环境下可能会出现线程安全问题。在并发编程中,不合适的并发操作HashMap可能导致数据不一致或者死循环。了解HashMap的内部实现有助...

    HashMap.txt

    该文件是拷贝的HashMap源码,在源码之上我对核心的代码都做了完整的注释,相信读者一定能够一看就懂,哈哈,HashMap是面试重灾区,我也是面试时才通读HashMap的。

    源码解析jdk7.0集合(3):HashMap的底层实现原理.pdf

    ### JDK 7.0集合系列解析之HashMap实现原理 #### 一、HashMap概述 HashMap是Java集合框架中Map接口的典型实现之一,主要用于存储键值对(Key-Value)数据。其特性如下: - 线程不安全,方法为非同步方法,因此多...

    源码解析及手写框架(Spring、Tomcat、Hashmap、Mybatis、SpringBoot)阿里面试官都被镇住了

    源码解析及手写框架(Spring、Tomcat、Hashmap、Mybatis、SpringBoot)阿里面试官都被镇住了

    Java源码解析HashMap简介

    Java源码解析HashMap简介 HashMap是Java开发中最常用的集合之一,它基于hash表的实现,提供了map的所有可选操作,并且允许null值和一个null的key。HashMap的实现提供了常量级的性能,假设hash函数把元素们比较好的...

    Java 中的HashMap详解和使用示例_动力节点Java学院整理

    HashMap源码解析 HashMap的put()方法是核心操作之一,它首先计算键的哈希值,然后根据哈希值定位到数组的索引位置。如果该位置没有元素,就直接插入;如果有元素,可能需要遍历链表找到插入位置。当元素数量达到...

    解析HashMap.md

    黑马程序员HashMap的笔记,面试必问,笔记很好,内容言简意赅,看完收获很多,希望能帮助大家的学习

    (003)HashMap中红黑树TreeNode的split方法源码解读.docx

    HashMap 中红黑树 TreeNode 的 split 方法源码解读 HashMap 中红黑树 TreeNode 的 split 方法是...通过对 split 方法的源码解析,我们可以更好地理解红黑树的实现机制和关键算法,从而更好地应用 Java 中的 HashMap。

Global site tag (gtag.js) - Google Analytics