HashMap源码分析

简介: HashMap源码分析

HashMap简介:


  HashMap实现了Map接口,同时是基于哈希表的,允许存放null键和null值。HashMap和Hashtable相差无几,唯一的几点区别就是:HashMap是非线程安全的,而Hashtable是线程安全的(方法都用synchronized关键字进行修饰)。Hashtable不允许存放null键和null值。HashMap不能保证元素的存放顺序,是无序的。

当多个线程并发地访问HashMap时,就算只有一个线程修改了其结构(增加或删除键值对才算,改变键对应的值不算),必须对HashMap进行同步处理,否则会引发线程安全问题。


  如果没有同步对象,最好在开始之前使用Collections.synchronizedMap方法进行同步处理,防止非线程安全情况发生,示例如下:


Map m = Collections.synchronizedMap(new HashMap(…));


  当多个线程同时访问迭代器iterator时,若一个正在修改iterator,一个正在使用iteartor,将抛出ConcurrentModificationException。


HashMap定义:


public class HashMap<K,V> extends AbstractMap<K,V>

implements Map<K,V>, Cloneable, Serializable {


HashMap底层结构示意图(数组+链表结构,针对jdk1.7)


 

重要属性:


默认加载因子:

static final float DEFAULT_LOAD_FACTOR = 0.75f;

默认初始容量(必须是2的n次方,后面会解释):

static final int DEFAULT_INITIAL_CAPACITY = 16;

最大容量:

static final int MAXIMUM_CAPACITY = 1 << 30;

阈值(计算公式:threshold = capacity * load factor):

int threshold;


重要方法:(针对jdk1.7)


带参的构造函数:

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)//初始容量若小于0,则抛出非法的初始容量异常
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)//若初始容量超过最大容量,默认赋值最大容量
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))//加载因子若不大于0或者非浮点数,则抛出非法加载因子异常
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);//阈值计算
        table = new Entry[capacity];
        init();
    }


计算哈希值所在的数组索引:


该方法是为了计算哈希值对应的数组索引位置,本来是可以用h%length来实现的,但是&的效率要比%稍高一些,所以采用&来实现。


而只有length是2的n次方时,才满足h%length = h&(length-1),所以HahsMap的长度都是2的n次方倍,这个也解决了上面提到的容量为什么必须得2的n次方问题。


static int indexFor(int h, int length) {
        return h & (length-1);
    }

get方法(返回key对应的value):


public V get(Object key) {
        if (key == null)//key为null处理
            return getForNullKey();     //先计算key对应的哈希值,然后再对该值进行哈希操作,得到的结果用于查找key对应的table数组位置
        int hash = hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];//遍历key对应的table数组索引处的Entry链表
             e != null;
             e = e.next) {
            Object k;       //若key和某个Entry的key相同,则返回该Entry的value值
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;//无对应值,返回null
    }


put方法(添加键值对):


public V put(K key, V value) {
        if (key == null)//key是null处理
            return putForNullKey(value);
        int hash = hash(key.hashCode());//先计算key对应的哈希值,然后再对该值进行哈希操作,
        int i = indexFor(hash, table.length);//得到的结果用于计算对应键值对存放的位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {//遍历table数组该索引处的Entry链表
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//若存在key相同的,则用新值替换旧值
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;//修改计算+1
        addEntry(hash, key, value, i);//添加Entry
        return null;
    }


addEntry方法(将包含特定key、value、hash值的新Entry添加到特定的桶上):


void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        if (size++ >= threshold)//若键值对长度超过阈值,则将HashMap扩容2倍
            resize(2 * table.length);
    }


程序示例:


package com.abc;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
 * @date: 2018/09/19
 * @author: alan
 * 
 */
public class HashMapTest {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();//实例化HashMap
        map.put(1, "apple");//添加元素
        map.put(2, "banana");
        map.put(3, "watermelon");
        System.out.println("HashMap's size is : " + map.size());
        //foreach遍历map
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
        for (Map.Entry<Integer, String> entry : entrySet) {
            int key = entry.getKey();
            String value = entry.getValue();
            System.out.println("key is " + key + ", value is " + value);
        }
        System.out.println("============================");
        //使用迭代器Iterator遍历map
        Iterator iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> entry = (Map.Entry)iterator.next();
            int key = entry.getKey();
            String value = entry.getValue();
            System.out.println("key is " + key + ", value is " + value);
        }
    }
}


控制台输出:


HashMap's size is : 3
key is 1, value is apple
key is 2, value is banana
key is 3, value is watermelon
============================
key is 1, value is apple
key is 2, value is banana
key is 3, value is watermelon
相关文章
|
18小时前
|
存储 缓存 算法
LinkedHashMap源码分析
LinkedHashMap源码分析
4 0
|
1月前
|
存储 算法
HashMap源码分析
HashMap源码分析
|
3月前
|
存储 算法
HashMap的实现原理
HashMap的实现原理
|
8月前
|
存储 Java
HashMap 的 源码分析
HashMap 的 源码分析
60 0
|
10月前
|
存储 算法 Java
【Java集合框架 二】HashMap源码分析
【Java集合框架 二】HashMap源码分析
68 0
|
11月前
通过对HashMap的源码分析解决部分关于HashMap的问题
通过对HashMap的源码分析解决部分关于HashMap的问题
|
存储 缓存 Java
30. 说一下HashMap的实现原理?上
30. 说一下HashMap的实现原理?上
96 0
30. 说一下HashMap的实现原理?上
|
索引
30. 说一下HashMap的实现原理?下
30. 说一下HashMap的实现原理?下
79 0
|
存储 安全 Java
HashMap实现原理及源码分析
在java中,HashMap是很常用的一种数据结构,最近重新温习了一下,这里以源码层面来分析总结一下HashMap,如有不合理或疑问的地方,欢迎沟通交流。
HashMap实现原理及源码分析
|
存储 Java
HashMap源码解读
带你走进HashMap,了解HashMap的特性和HashMap源码解读(jdk7)。
140 0
HashMap源码解读