HashMap源码分析二

jdk1.2中HashMap的源码和jdk1.3中HashMap的源码基本上没变。在上篇中,我纠结的那个11和101的问题,在这边中找到答案了。

jdk1.2

public HashMap() {
    this(101, 0.75f);
    }

    public HashMap(Map t) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
    }

jdk1.3

 public HashMap() {
        this(11, 0.75f);
    }

    public HashMap(Map t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

原来jdk1.2中的101是开发者的一个误会 ??

HashMap源码在jdk1.4中,相比于jdk1.2、jdk1.3,有些优化性的改动,更成熟,考虑更完善了,一些java的编程风格也慢慢形成了。

static final int DEFAULT_INITIAL_CAPACITY = 16;

static final int MAXIMUM_CAPACITY = 1 << 30;

static final float DEFAULT_LOAD_FACTOR = 0.75f;

定义了三个静态常量。之前这些都是直接分散到代码中或者没有做考虑的。

DEFAULT_INITIAL_CAPACITY 是HashMap内数组的默认初始长度,不是以前的11了,现在确定为16。也不知道以前的11,现在的16是怎么定下来的。

MAXIMUM_CAPACITY 是HashMap内数组的最大长度,1 << 30 , 往下能看到数组的增长是每次加2倍,1 << 30 是int里成倍加的最大值,在加就超过int最大值了。

DEFAULT_LOAD_FACTOR 数组增长提示阀值的计算系数,之前是分散在代码里的

构造方法也有做修改

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);

        // 数组的初始长度,必须是大于1 << 4 小于 1 << 30 中的一个数,并且是通过1左移多少得来的,左移的位数在4到30之间。
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init(); // 钩子函数
    }
 

并且在全部的构造方法里,都藏有钩子函数 init() ; 内部实现为空实现。

/**
     * Initialization hook for subclasses. This method is called
     * in all constructors and pseudo-constructors (clone, readObject)
     * after HashMap has been initialized but before any entries have
     * been inserted.  (In the absence of this method, readObject would
     * require explicit knowledge of subclasses.)
     */
    void init() {
    }

这里对key和value为null的情况,做了些装饰

static final Object NULL_KEY = new Object();

    static Object maskNull(Object key) {
        return (key == null ? NULL_KEY : key);
    }

    static Object unmaskNull(Object key) {
        return (key == NULL_KEY ? null : key);
    }

一些工具方法

// 在jdk1.4中,计算对象hash值,抽离出一个公用方法了,同时,计算hash值的方式也有改变,后面会专门分析这个

static int hash(Object x) {
        int h = x.hashCode();

        h += ~(h << 9);
        h ^=  (h >>> 14);
        h +=  (h << 4);
        h ^=  (h >>> 10);
        return h;
    }

    // 比较两个对象的是否相同,也是一个抽离相同代码的过程
    static boolean eq(Object x, Object y) {
        return x == y || x.equals(y);
    }

    // 通过hash值,计算对应的数组下标,也是抽离相同代码的过程。
    // 之前构造中确定了length为 1 << 4 到 1 << 30 之间的数,则 h & (length -1),相当于截取h值的后多少位的值
    // 如果length为 1 << n  ,n为4到30,这相当于截取h的后n位的值
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

这里在分析下jdk1.4中的添改查

 // 判断HashMap中value是否存在
    public boolean containsValue(Object value) {
        // 当value为null,判断是否存在
        // 这里把判断是否存在value=null抽出了一个公用方法;特地全文搜索了下,现在只有这个地方用到了,可能当时有其他考虑
        if (value == null)
            return containsNullValue();

        Entry tab[] = table;
        // 遍历数组
        // 这种遍历方式,相比以前的”for (int i = tab.length ; i-- > 0 ;)“,更习惯些
        for (int i = 0; i < tab.length ; i++)
            // 遍历链表
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
        return false;
    }

    // 判断是否存在value=null的实体
    private boolean containsNullValue() {
        Entry tab[] = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (e.value == null)
                    return true;
        return false;
    }

    // 判断key是否存在
    public boolean containsKey(Object key) {
        // 装饰key
        Object k = maskNull(key);
        // 计算key的hash值
        int hash = hash(k);
        // 计算key对应的数组下标值,这里把hash值的计算和数组下标的计算分别单独出一个方法,看着很清晰
        int i = indexFor(hash, table.length);
        Entry e = table[i];
        // 循环遍历链表
        while (e != null) {
            if (e.hash == hash && eq(k, e.key))
                return true;
            e = e.next;
        }
        return false;
    }

    // 获得key对应的值
    public Object get(Object key) {
        // 装饰key
        Object k = maskNull(key);
        // 计算key的hash值
        int hash = hash(k);
        // 计算key对应的数组下标值
        int i = indexFor(hash, table.length);
        Entry e = table[i];
        // 循环遍历链表
        while (true) {
            if (e == null)
                return e;
            if (e.hash == hash && eq(k, e.key))
                return e.value;
            e = e.next;
        }
    }

    // 获得key对应的实体,包括key和value等信息,同get方法类似
    Entry getEntry(Object key) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry e = table[i];
        while (e != null && !(e.hash == hash && eq(k, e.key)))
            e = e.next;
        return e;
    }

    // 存储key和value值
    public Object put(Object key, Object value) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        // 这里把key做了装饰,就不需要像以前的版本一样,key分为null和不为null两种情况来处理
        // key对应的value值存在时的添加替换处理
        for (Entry e = table[i]; e != null; e = e.next) {
            if (e.hash == hash && eq(k, e.key)) {
                Object oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        // key对应的value值不存在时的添加处理
        addEntry(hash, k, value, i);
        return null;
    }

    // 添加实体
    void addEntry(int hash, Object key, Object value, int bucketIndex) {
        table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]);
        // 如果已经超过阀值,这扩展数组,这里是在原基础2倍的扩展
        if (size++ >= threshold)
            resize(2 * table.length);
    }

    // 只是添加key和value,不考虑数组的扩展和老value的返回
    private void putForCreate(Object key, Object value) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);

        for (Entry e = table[i]; e != null; e = e.next) {
            if (e.hash == hash && eq(k, e.key)) {
                e.value = value;
                return;
            }
        }

        createEntry(hash, k, value, i);
    }

    // 只是添加key和value,不考虑数组的扩展和老value的返回
    void createEntry(int hash, Object key, Object value, int bucketIndex) {
        table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]);
        size++;
    }

    // 批量只是添加key和value,不考虑数组的扩展和老value的返回
    // 这里的使用场景在构造传入一个map时
    void putAllForCreate(Map m) {
        for (Iterator i = m.entrySet().iterator(); i.hasNext(); ) {
            Map.Entry e = (Map.Entry) i.next();
            putForCreate(e.getKey(), e.getValue());
        }
    }

    // 扩展数组
    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);
        // 在jdk1.4里,有个改动,老表到新表,迁移完了之后在赋给HashMap使用
        // 解决了之前在迁移过程中有可能取不到数据的bug
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

    // 数组从老表到新表
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        // 遍历数组
        for (int j = 0; j < src.length; j++) {
            Entry e = src[j];
            // 遍历链表,这版本中,很多地方把for改用成了while(while  或者 do while)
            // 其实后面jdk用推崇 for(;;),这都是向着效率越来越高,代码越来越简洁上优化
            if (e != null) {
                src[j] = null;
                do {
                    Entry next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

idk中还有个小优化点,以前HashMap中获取不同类型的HashIterator,需要用户在构造中传入类型,现在只要调用不同的方法,他们分别做重载,挺有意思的,对开发者来说,更友好了,可以学习下

private class ValueIterator extends HashIterator {
        public Object next() {
            return nextEntry().value;
        }
    }

    private class KeyIterator extends HashIterator {
        public Object next() {
            return nextEntry().getKey();
        }
    }

    private class EntryIterator extends HashIterator {
        public Object next() {
            return nextEntry();
        }
    }

    // Subclass overrides these to alter behavior of views‘ iterator() method
    Iterator newKeyIterator()   {
        return new KeyIterator();
    }
    Iterator newValueIterator()   {
        return new ValueIterator();
    }
    Iterator newEntryIterator()   {
        return new EntryIterator();
    }
时间: 2024-10-29 19:10:56

HashMap源码分析二的相关文章

【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 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

[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 之

netty 源码分析二

以服务端启动,接收客户端连接整个过程为例分析, 简略分为 五个过程: 1.NioServerSocketChannel 管道生成, 2.NioServerSocketChannel 管道完成初始化, 3.NioServerSocketChannel注册至Selector选择器, 4.NioServerSocketChannel管道绑定到指定端口,启动服务 5.NioServerSocketChannel接受客户端的连接,进行相应IO操作 Ps:netty内部过程远比这复杂,简略记录下方便以后回忆

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte