JAVA-HashMap的具体实现

HashMap具体实现

HashMap默认的初始容积大小为16,加载因子默认0.75,threshold阈值为【容积*加载因子】

HashMap采用的是链表法解决哈希冲突问题,同时引入红黑树可以避免单个链表长度过长的问题

默认8将单向链表转换为红黑树,注意这里还有一个条件,默认64,只有集合中的结点数大于64时才可能进行树化处理

默认6将红黑树退化成链表

hash函数的涉及需要考虑简单高效和分布均匀两个方面,所以首先获取对象的hashCode值,然后要将hash值的高位和低位进行与运算后再针对数组长度进行求余。

HashMap线程不安全,进行多线程操作时可能会出现扩容时执行rehash操作的死循环问题、脏读问题和size值不精确的问题

类定义

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable, Serializable{

具体内部存储方式

transient Node<K,V>[] table; //哈希表的本质就是一个数组,数组中每个元素称为一个桶,桶中存放的是键值对构成的单向链表或者红黑树

transient定义数据的存储,HashMap类中提供了序列化与反序列化操作的方法writeObject和readObject,以提高序列化处理的执行效率

private void writeObject(java.io.ObjectOutputStream s)throws IOException{}
        private void readObject(java.io.ObjectInputStream s) throws IOException,ClassNotFoundException{}

重要的阈值

static final int TREEIFY_THRESHOLD = 8;//树化阈值,就是链表转换为红黑树的阈值,当一个链表中存储的元素个数大于8时,会自动将链表转换为红黑树
        static final int UNTREEIFY_THRESHOLD = 6;//桶的红黑树蜕化为链表的还原阈值。当红黑树中结点数小于6时会自动将红黑树转换为链表。注意:如果结点数较小时,红黑树的总体性能不一定优于单向链表
        static final int MIN_TREEIFY_CAPACITY = 64;//最小树化处理的容积阈值。当哈希表中的容量>64时才允许进行树化处理

内部存储的实现

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash; //缓存的hash值
        final K key; //具体存放的key值
        V value; //具体存放数据的value值
        Node<K,V> next; //单向链

构造器

HashMap采用的是懒加载lazy-load机制,也就是在构建HashMap对象时,并不直接构建数组,而是第一次添加元素时才进行数组的初始操作。HashMap的无参构造器采用所有的默认配置值,容积默认值为16,加载因子默认0.75,扩容阈值threshold此时实际上是12。

public HashMap() {

        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

        }

允许在构建hashMap对象时,手工设置其中的初始化容积值initialCapacity

public HashMap(int initialCapacity) {

        this(initialCapacity, DEFAULT_LOAD_FACTOR);

        }

tableSizeFor方法用于获取合理的数组的初始化容积值,返回的是一个大于等于初始化容积值的2的n次方值。

输入10返回的是16,返回值必须是2的多少次方

输入7返回的是8

static final int tableSizeFor(int cap) {

        int n = cap - 1;

        n |= n >>> 1;

        n |= n >>> 2;

        n |= n >>> 4;

        n |= n >>> 8;

        n |= n >>> 16;

        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n +1;

}

HashMap的存储结构

每个Node[]数组中的元素被称为桶bucket,一个桶对应一个hash映射的值,例如0、1、2、3..,注意相同的hash映射值可能会出现不同的key值【鸽巢理论】,所以针对桶采用链表结构存储hash映射值相同的所有数据【JDK1.8在单个链表长度大于阈值8时会自动转换位红黑树;当删除结点使某个红黑树中节点数小于阈值6时会自动从红黑树蜕化成链表结构】

相关参数

capacity容积值标识当前输入的容量,始终保持是2的n次方,可以扩容,扩容后的数组大小为原始数组大小的2倍。

loadFactor负载因子,默认是0.75,注意这个值在使用链表法的实现中可以大于1。

threshold扩容阈值,等于capacity*loadFactor。

JDK1.7版本:

hashmap底层采用的是Entry数组和链表实现的。

向一个链表中添加数据时采用的是头插法,在多线程并发操作中会有环形链的问题。

JDK1.8版本

hashmap使用Node类型的数组和链表以及红黑树实现的,Node用于链表,红黑树使用的是

TreeNode。

树化处理

向一个链表中插入数据采用的是尾插法,可以避免环形链问题,但是并不是解决了线程安全问题,仍旧会有扩容处理的rehash时的死循环问题、脏读丢失数据问题、size不准确问题

HashMap中存储的是key-value,其中key不允许重复(keySet():Set),但是value可以重复

(values():Collection),允许null的key和value,但是key只能有一个,value没有限制。HashMap线程不安全,如果多线程使用时可以考虑使用ConcurrentHashMap或者使用

Collections.synchronizedMap(map)将HashMap转换为线程安全的map

注意:作为key值出现的类型必须实现equals和hashCode两个方法,要求equals为true时则hashCode码值必须相等。建议可以考虑使用IDE工具自动生成equals和hashCode两个方法

put方法的具体实现流程

public V put(K key, V value) { // 向hashmap集合中添加一个key/value对数据
	return putVal(hash(key), key, value, false, true);
	}

	final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
		Node<K, V>[] tab;
		Node<K, V> p;
		int n, i;
		// 1、如果table为空或者长度为0,那么调用resize方法扩容数组
		if ((tab = table) == null || (n = tab.length) == 0)
			n = (tab = resize()).length;
		// 2、计算插入数据存储到数组的对应索引值,如果数组为空则不存在hash冲突,则直接插入
		if ((p = tab[i = (n - 1) & hash]) == null)
			tab[i] = newNode(hash, key, value, null);
		else {
			Node<K, V> e;
			K k;
			if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
				e = p;
			else if (p instanceof TreeNode)
				e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
			else {
				for (int binCount = 0;; ++binCount) {
					if ((e = p.next) == null) {
						p.next = newNode(hash, key, value, null);
						if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
							treeifyBin(tab, hash);
						break;
					}
					if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
						break;
					p = e;
				}
			}
			if (e != null) { // existing mapping for key
				V oldValue = e.value;
				if (!onlyIfAbsent || oldValue == null)
					e.value = value;
				afterNodeAccess(e);
				return oldValue;
			}
		}
		++modCount;
		if (++size > threshold)
			resize();
		afterNodeInsertion(evict);
		return null;
	}

resize()方法的说明:将table大小初始化或加倍。如果为null,则按照默认值初始数组(16、0.75)。否则,使用2倍扩容处理,因为每个容器中的元素必须保持在相同的索引中,或者移动在新表中具有二次方偏移

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值