`
zy19982004
  • 浏览: 661797 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
博客专栏
F6f66edc-1c1a-3859-b76b-a22e740b7aa7
Hadoop学习
浏览量:251950
社区版块
存档分类
最新评论

容器学习一:HashMap源码分析

 
阅读更多

一.HashMap的存储结构

hashmap结构

 

二.HashMap成员变量

    //默认初始容量,总为2的次方值
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    //最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;

    //默认加载因子  
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //Entry数组,每一个Entry是一个键值对实体
    transient Entry[] table;

    //实际存的Entry个数  
    transient int size;

    //数组扩容的阀值,当size+1 > threshold时,扩充到以前容量的两倍
    //threshold = table.length * loadFactor
    int threshold;

    //负载比率
    final float loadFactor;

    //Map结构一旦变化,如put remove clear等操作的时候,modCount随之变化
    transient volatile int modCount;

 

三.Entry对象

//很简单的一个键值对实体而已
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;          //key
        V value;              //value
        Entry<K,V> next;  //next Entry
        final int hash;       //计算出key的hash值

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        .....
}

 

四.构造函数

	// 构造函数
	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);

		// 将传入的initialCapacity值,转变成2的次方值capacity去实例化hashmap的属性
		// 比喻传入initialCapacity = 100,则算出来capacity = 2 << 7 = 128,
		// 最终threshold = 128 * 0.75 = 96,table = new Entry[128]
		int capacity = 1;
		while (capacity < initialCapacity)
			capacity <<= 1;

		this.loadFactor = loadFactor;
		threshold = (int) (capacity * loadFactor);
		table = new Entry[capacity];
		// 模板方法模式,子类想在init里面做点什么重写init就好了
		init();
	}

 

五.hash算法和index算法

    /**
     * 让hashMap里面的元素尽量分布均需,方便查找
     * @param h entry中key的hash值
     * @return 打散后的hash值
     */
    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /** 
     * 类似求模运算, 将hash值同length-1(必定是01111...1)相与,运算的结果处于1到length-1间,0刚好用来保存key为null和0的元素 
     * @param h 打散后的hash值 
     * @param length 数组的长度 
     * @return 数组下标 1到lenght-1  
     */ 
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

 

六.取数据  

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        //key != null时
        //1.根据key计算出hash和index
        int hash = hash(key.hashCode());
        //2.从index处的链表头Entry e开始遍历链表,
        //如果e满足hash(key.hashCode()) == e.hash && (key == e.key || key.equals(e.key)),就是我们要找的entry
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;        
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))    //注释3里的逻辑更好理解
                return e.value;
        }
        return null;
    }

    //如果key为空,直接从table[0]链表里面遍历寻找value
    private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

 

七.存数据,扩容

public V put(K key, V value) {
    	//1.key为空时候,调用putForNullKey方法
        if (key == null)
            return putForNullKey(value);
        //2.key不为空
        //2-1.计算hash和index
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        //2-2.根据index得到当前链表头e=table[i]不为空
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //2-3.如果e满足hash(key.hashCode())=e.hash && key==e.key || key.equals(e.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;
            }
        }
        //2-4.table[i]为空,直接插入entry
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

    private V putForNullKey(V value) {
    	//1.从0位置的链表头开始,遍历
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        	//1-1.e.key==null时,直接替代value
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        //2.头Entry不存在时,直接插入entry
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
    
    /** 
     * @param hash 计算后key的hash值
     * @param key 
     * @param value
     * @param bucketIndex 应该插入到Entry[]的哪个位置
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
    	//1.当前bucketIndex位置的头Entry e
    	Entry<K,V> e = table[bucketIndex];
    	//2.在bucketIndex位置新建一个Entry,新建Entry.next=原头Entry,也就是addEntry的Entry都被加到了链表头
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        //hashmap在每次addEntry完后检查是否扩容,而list是在每次加入之前检查:ensureCapacity(size + 1);elementData[size++] = e;
        if (size++ >= threshold)
            resize(2 * table.length);
    }
    
    //扩容,把hashmap的容量设置为新容量newCapacity
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //每次扩到到原来的两倍,总有一个时候到MAXIMUM_CAPACITY
        if (oldCapacity == MAXIMUM_CAPACITY) {
        	//到了MAXIMUM_CAPACITY设置threshold后直接return
            threshold = Integer.MAX_VALUE;
            return;
        }

        //1.新建newCapacity大小的Entry数组
        Entry[] newTable = new Entry[newCapacity];
        //2.把当前Entry的数据全部移到新Entry数组中
        transfer(newTable);
        //3.用已经就绪的新Entry数组重新给table赋值
        table = newTable;
        //4.设置新的threshold
        threshold = (int)(newCapacity * loadFactor);
    }

    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        //1.从0开始遍历原Entry数组
        for (int j = 0; j < src.length; j++) {
        	//2.拿到下标为j的链表头Entry e
            Entry<K,V> e = src[j];
            //3.遍历链表
            if (e != null) {
                src[j] = null;
                do {
                	//3-1.e的下一个Entry,为了继续遍历做准备
                    Entry<K,V> next = e.next;
                    //3-2.计算e应该存放在新Entry数组的位置下标
                    int i = indexFor(e.hash, newCapacity);
                    //3-3.将e插入到新Entry[i]链表的表头
                    e.next = newTable[i];
                    //3-4.将Entry[i]表头的Entry重新定位为e,这样就完成了一个元素的重新hash 
                    newTable[i] = e;
                    //3-5.继续链表的下一个Entry
                    e = next;
                } while (e != null);
            }
        }
    }

 

八.删数据

 

public V remove(Object key) {
		Entry<K, V> e = removeEntryForKey(key);
		return (e == null ? null : e.value);
	}

	/**
	 * Removes and returns the entry associated with the specified key in the
	 * HashMap. Returns null if the HashMap contains no mapping for this key.
	 */
	final Entry<K, V> removeEntryForKey(Object key) {
		// 1.计算hash和index
		int hash = (key == null) ? 0 : hash(key.hashCode());
		int i = indexFor(hash, table.length);
		Entry<K, V> prev = table[i];
		Entry<K, V> e = prev;
		// 2.遍历i位置的Entry链表
		while (e != null) {
			Entry<K, V> next = e.next;
			Object k;
			// 2-1.e满足hash(key.hashCode()) == e.hash && (key == e.key ||
			// key.equals(e.key)),找到了要移除的Entry
			if (e.hash == hash
					&& ((k = e.key) == key || (key != null && key.equals(k)))) {
				// 2-2.移除操作modCount++, size--
				modCount++;
				size--;
				// 2-3-1.如果prev和e相等,说明要删除的Entry是链表头,直接将table[i]位置的Entry设置为next,就删除了e
				if (prev == e)
					table[i] = next;
				else
					// 2-3-2.如果不相等e =
					// prev.next,说明要删除的Entry是除链表头外的其他Entry,将prev.next设置为next,就删除了e
					prev.next = next;
				e.recordRemoval(this);
				return e;
			}
			// 把prev和e到移到下一个
			prev = e;
			e = next;
		}

		return e;
	}

	/**
	 * 还是将o的key拿到,然后和removeEntryForKey(Object key)一样了
	 * 
	 * @param o
	 *            must be Map.Entry
	 * @return
	 */
	final Entry<K, V> removeMapping(Object o) {
		if (!(o instanceof Map.Entry))
			return null;

		Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
		Object key = entry.getKey();
		......
	}

 

九.对table[0]位置存放数据的理解

package com.zzy.collection2;

import java.util.Map;

public class TestHashMapEntry0 {
	public static void main(String[] args) {
		Map<Integer, Integer> map = new HashMap<Integer, Integer>();
		map.put(null, 1);
		map.put(null, 2);
		
		map.put(0, 3);
		map.put(0, 4);
	}

}

 

  1. put(null, value)其本质是addEntry(0, null, value, 0);
  2. put(0, value)其本质是addEntry(0, 0, value, 0);
  3. put(null, value1)后put(0, value2)都是向table[0]链表中处添加Entry,但 0 != null,所有value2不会覆盖value1。
  4. put(null, value1)后再次put(null, value2),value2覆盖value1。

 

分享到:
评论

相关推荐

    HashMap源码分析

    ### HashMap源码分析 #### 一、概述 `HashMap`是Java编程语言中非常重要的一个数据结构,它属于`java.util`包的一部分,是`Map`接口的一个具体实现类。`HashMap`允许存储键值对,并且支持使用`null`作为键或值,这...

    JavaHashSet和HashMap源码剖析编程开发技术

    在源码分析中,我们可以关注以下几个关键点: 1. **哈希函数**:HashMap使用`hash()`方法计算键的哈希值,这个过程决定了元素在内部数组中的分布。 2. **数组与链表**:当哈希冲突发生时,HashMap会将冲突的键值对...

    20-集合框架020-HashMap-1080P 高清-AVC20

    在Java编程语言中,集合框架是一个非常重要的组成部分,它提供了数据结构和算法的实现,使得在处理各种数据存储...这个高清视频可能包含了HashMap的实例演示、源码分析以及常见问题解答,有助于深入理解和应用HashMap。

    hashmap.zip

    - "资料"可能包括HashMap的源码分析,揭示了HashMap如何处理哈希冲突,以及扩容的细节。 - "讲义"可能系统地介绍了HashMap的基础知识,如插入、查找和删除的步骤,以及HashMap与其他数据结构(如TreeMap)的区别。 ...

    ConcurrentHashmap源码

    源码分析见我博文:http://blog.csdn.net/wabiaozia/article/details/50684556

    Java集合框架源码剖析:HashSet 和 HashMap

    允许放入null元素,除该类未实现同步外,其余跟Hashtable大致相同,跟TreeMap不同,该容器不保证元素顺序,根据需要该容器可能会对元素重新哈希,元素的顺序也会被重新打散,因此不同时间迭代同一个HashMap的顺序...

    免费开源-【Java学习+面试指南】部分内容大部分是Java程序员所需要掌握的核心知识

    Unsafe 详细解Java SPI 机制详解Java语法糖详解集合知识点/面试题总结:Java集合常见知识点&面试题总结(上)(必看)Java集合常见知识点&面试题总结(下)(必看)Java容器使用注意事项总结源码分析:ArrayList核心...

    Java源码分析:集合-容器.pdf

    HashSet是基于HashMap实现的,其元素存储在HashMap的key上,而value使用一个静态的默认对象。为了保证元素的唯一性,存储在HashSet中的对象必须正确地覆写hashCode和equals方法。TreeSet利用二叉树的原理对元素进行...

    Java容器总结

    源码分析有助于理解其设计模式和事件驱动机制。 3. **EJB(Enterprise JavaBeans)**:在企业级应用中,EJB容器负责管理事务、安全性和资源,提供服务器端组件的生命周期管理。EJB有三种类型:session beans、...

    【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    源码分析: ArrayList 核心源码+扩容机制分析 LinkedList 核心源码分析 HashMap 核心源码+底层数据结构分析 ConcurrentHashMap 核心源码+底层数据结构分析 LinkedHashMap 核心源码分析 CopyOnWriteArrayList 核心...

    易语言HashMap类源码-易语言

    通过分析源码,我们可以学习如何在易语言环境中实现高效的哈希表,这对于优化程序性能、解决实际问题有着重要的价值。此外,理解源码也有助于我们自定义和扩展数据结构,以适应特定的应用场景。

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识

    源码分析: ArrayList 源码+扩容机制分析 HashMap(JDK1.8)源码+底层数据结构分析 ConcurrentHashMap 源码+底层数据结构分析 IO IO 基础知识总结 IO 设计模式总结 IO 模型详解 并发 知识点/面试题总结 : (必看 ) ...

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识 准备 Java 面试,首选.zip

    源码分析 : ArrayList 源码+扩容机制分析 HashMap(JDK1.8)源码+底层数据结构分析 ConcurrentHashMap 源码+底层数据结构分析 IO IO 基础知识总结 IO 设计模式总结 IO 模型详解 并发 知识点/面试题总结 : (必看 ) ...

    11.集合框架001-Collection接口21-23

    在21号章节中,源码分析可能涉及了HashMap的put、get和resize等核心方法,以及如何通过哈希函数和链地址法处理哈希冲突。 在22号章节,HashMap的内部结构总结可能涵盖了以下内容:HashMap由数组(Entry[] table)和...

    java7hashmap源码-to-be-architect:成为Java架构师,你应该学习这些

    hashmap源码 to-be-architect to be a Java architect,you should learn these.This page is updated irregularly. Java基础 深入分析 Java SPI 机制和原理 并发编程专题 Executors线程池 线程池ThreadPoolExecutor...

    java源码解读-java-src:java源码解读

    源码分析能揭示这些数据结构的工作方式,例如ArrayList的动态扩容策略,HashSet的哈希函数设计,以及HashMap的冲突解决方法。理解这些细节有助于我们在实际编程中选择合适的集合类型,提高数据操作效率。 并发编程...

    javaSourceLearn:jdk源码构建

    List、Set、Map三大接口以及其实现类(ArrayList、LinkedList、HashSet、TreeSet、HashMap、LinkedHashMap等)的源码分析能帮助我们更高效地利用数据结构,优化程序性能。 标签“系统开源”表明这个项目鼓励开放和...

    Java容器之Hashtable源码分析

    【Java容器之Hashtable源码分析】 在Java编程中,`Hashtable`是一个古老的容器类,它继承自`Dictionary`接口,并实现了`Map`接口,同时也实现了`Cloneable`和`Serializable`接口。`Hashtable`与`HashMap`类似,都是...

Global site tag (gtag.js) - Google Analytics