JAVA基础(一)HashMap

HashMap集合类是JAVA中常用的数据结构,与此相关的集合还有LinkHashMap和TreeMap,这里主要介绍HashMap
HashMap是一种基于线性存储结构(也就是数组)的数据结构,数据以Entry(K,V)的形式保存在数组table中。Entry本身是一个链表结构,如下所示

Entry{
	final K key;//键
	V value;//值
	Entry<K,V> next;//指向下一个实体
	int hash;//key对应的hash值
}

TIPS:看最新的jdk和sdk代码时发现已经用Node implements Entry<K,V>代替了原来的Entry,Entry成了接口,被Node实现。

在存储数据时,先获取key值对应的hashcode值,然后根据table数组长度对hashcode值进行indexFor()取模运算,将具有相同值的(key,value)作为链表头部保存在同一个数组元素中,其中next元素指向原来的Entry对象。
在Java1.7中,整个集合初始capacity容量(集合中Entry实体的数量)为2^4 = 16,Entry被保存在容器大小为16的bucket数组中。在添加数据过程中,如果bucket数组有超过75%的位置(12个)被填充数据时,此时数据容器需要进行扩容一倍。所以HashMap的扩容因子为0.75,每次扩容数组大小增加一倍

/*h为hashcode,length为数组cap
*/
indexFor(int h,int length){
	return h & (length-1);//从这儿可以得知,数组的bucket个数和数组大小capacity的关系近乎为bucket个数^2 = capacity大小
}

扩容阈值threshold = 数组大小 * loadFactor,loadFactor默认为0.75。
在每次添加键值对时,会先对数组容量进行判断,

void addEntry(int hash ,K key,V,value,int bucketIndex){
	if((size >= threshold) &&(null != table[bucketIndex])){
		resize(2*table*length);//达到阈值条件后,每次扩容两倍,将capacity变成原来的两倍
		...
	} 
	createEntry(hash,k,v,bucketIndex);
}

如果数组中元素的个数(非数组的实际长度或容量capacity)超过阈值,则对数组进行扩容。数组容量每次添加两倍,好处是,保持了原来的Entry的bucketIndex保持不变,具体算法可见这篇文章
下面分析几个常见的api接口:

public V put (K key,V value){
	...
	int hash = hash(key);//获取key所对应的hash值
	int i = indexFor(hash,table.length);//获取该hash值对应的Bucket
	for(Entry<K,V>e = table[i];e != null;e = e.next){//遍历链表
		Object k;
		//判断新添加的key是否存在,如果存在,则更新该key键所对应的value值,并返回原来的oldValue值。
		//判断的标准为:先判断该key是否有相同的hashCode,再判断该key是否和原来的key相等或相同(这儿是复写equals方法后需要复写hashCode的关键所在,因为如果hashCode都不相等,后面的equals等判断都不会执行)
		if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
			V oldValue = e.value;
			e.value = value;
			e.recordAccess(this);
			return oldValue;
		}
	}
	modCount++;//数组中存储的Entry个数加一
	addEntry(hash,key,value,i);//存储Entry,上面有讲解
	return null;
}
//内部方法
void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);//这两行表示,先将序号为bucketIndex的桶中的entry提取出来,然后作为next元素赋给新创建的Entry实体;也就是每次新添加的元素被放在链表头部
        size++;//每次新建一个Entry实体,size都会加1
    }
public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
        //通过O(1)次查找到key所在的桶,再进行链表的遍历操作
        for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            //key值的比较和put方法中的一样,先比较hashCode,再比较key
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

在JDK1.8中,HashMap新增了红黑树数据结构。当bucket数组中某个元素的链表数据Node个数超过8时,会使用红黑树数据结构代替链表。
另外,https://blog.csdn.net/suifeng629/article/details/82179996这篇文章总结也不错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值