深入底层:HashMap与HashSet源码理解

一、HashMap

1.JDK7之前,HashMap底层是一个table数组+链表实现的哈希表存储结构
JDK1.7之前的HashMap存储结构
2.链表的每个节点就是一个Entry,其中包括:键key、值value、键的哈希码hash、执行下一个节点的引用next

static class Entry<K, V> implements Map.Entry<K, V> {
    final K key; //key
    V value;//value
    Entry<K, V> next; //指向下一个节点的指针
    int hash;//哈希码
}

3.JDK1.7中HashMap的主要成员变量及其含义

public class HashMap<K, V> implements Map<K, V> {
	//默认长度
    static final int DEFAULT_INITIAL_CAPACITY = 16; 
	//默认装填因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f; 
    transient Entry<K, V>[] table; 
    int threshold;//阈值 默认长度*默认装填因子
    final float loadFactor;//装填因子
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(int initialCapacity, float loadFactor) {
        this.loadFactor = loadFactor;
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
      ....
    }
}

4.调用put方法添加键值对
注意:

  1. 第一步计算哈希码时,不仅调用了key的hashCode(),还进行了更复杂处理,目的是尽量保证不同的key尽量得到不同的哈希码
  2. 第二步根据哈希码计算存储位置时,使用了位运算提高效率。同时也要求主数组长度必须是2的幂
  3. JDK7采用的是头插法,添加Entry时添加到链表的第一个位置
  4. 第三步添加Entry是发现了相同的key已经存在,就使用新的value替代旧的value,并且返回旧的value
public class HashMap {
    public V put(K key, V value) {
        //如果key是null,特殊处理
        if (key == null) return putForNullKey(value);
        //1.计算key的哈希码hash 
        int hash = hash(key);
        //2.计算出存储位置
        int i = indexFor(hash, table.length);
        //如果已经存在链表,判断是否存在该key
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如存在,使用新value覆盖旧的value,返回旧value
        	if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
                V oldValue = e.value;// the United States
                e.value = value;//America
                e.recordAccess(this);
                return oldValue;
            }
        }
        //添加结点
        addEntry(hash, key, value, i);
        return null;
    }
	//对哈希码进行更复杂处理
	final int hash(Object k) {
	    int h = 0;
	    h ^= k.hashCode();
	    h ^= (h >>> 20) ^ (h >>> 12);
	    return h ^ (h >>> 7) ^ (h >>> 4);
	}
	static int indexFor(int h, int length) {
		//采用位运算,效率更高
	    return h & (length-1);
	}
}

5.调用get方法根据key获取value
根据key找Entry,再从Entry中获取value即可

public V get(Object key) {
    //根据key找到Entry
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}

6.添加元素时,如达到了阈值,需扩容为原来主数组容量的2倍并且将所有已存的数据重新hash、重新存

void addEntry(int hash, K key, V value, int bucketIndex) {
    //如果达到了门槛值,就扩容,容量为原来容量的2倍
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    //添加节点
    createEntry(hash, key, value, bucketIndex);
}

7.在JDK8中有一些新的变化,当链表中存储数据的个数>=8,自动将链表转为红黑树。时间复杂度上,链表为O(n),而红黑树一直是O(logn)。当长度小于6时自动转化为链表。
JDK8中HashMap的结构

二、HashSet

1.HashSet的底层使用的是HashMap,所以底层结构也是哈希表
2.HashSet的元素是存到HashMap的key中,其中HashMap的value则统一是同一个Object对象

public class HashSet<E> implements Set<E> {
    private transient HashMap<E, Object> map;
    private static final Object PRESENT = new Object();//value都是这一个对象
    public HashSet() {
        map = new HashMap<>();
    }
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }
    public int size() {
        return map.size();
    }
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值