HashMap

HashMap

基本特性

特性:

hashmap 是一个散列表,存储的内容是以键值对(key-value)进行存储的,key仅允许一条为null的的数据,无序,根据key的hashcode存储数据,不支持线程同步。

数据结构:

JDK1.7:Table数组+Entry链表

JDK1.8:Table数组+Entry链表/红黑树

常用方法

  • 初始化

    //不给定初始化容量
    Map<String, String> hashMap1 = new HashMap<>();
    //给定初始容量(最好给定合适初始容量,避免扩容消耗资源)
    Map<String, String> hashMap2 = new HashMap<>(2);
    //给定集合
    Map<String, String> hashMap3 = new HashMap<>(hashMap1);
    //给定初始容量和负载因子(当容量百分比超过因子的进行扩容)
    Map<String, String> hashMap4 = new HashMap<>(16,0.8f);
    
  • 添加数据

    HashMap<String, String> map = new HashMap<>(2);
    map.put("B","456");
    map.put("C","789");
    Map<String, String> hashMap1 = new HashMap<>(3);
    //添加键值对,存在修改value值
    hashMap1.put("A","123");
    //将map中的键值对插入hashmap1中
    hashMap1.putAll(map);
    //如果 hashMap1中不存在指定的键,则将指定的键/值对插入到hashMap1中。
    hashMap1.putIfAbsent("A","666");
    
  • 删除元素

    //删除 hashMap1 中指定键 key 的映射关系
    hashMap1.remove("A");
    //删除 hashMap1 中指定键值对 key-value,不存在该键值对则不删除
    hashMap1.remove("B","4567"
    
  • 获取元素

    //获取key=B的value值
    String bValue = hashMap1.get("B");
    //获取key=A的value值,若不存在返回默认值"555"
    String aValue = hashMap1.getOrDefault("A","555");
    
  • 遍历

    //lambda遍历
    hashMap1.forEach((key,value)->{
        System.out.println(key+"-"+value);
    });
    //keySet遍历
    for (String key : hashMap1.keySet()) {
        System.out.println(key+"-"+hashMap1.get(key));
    }
    //entrySet 迭代器遍历
    Iterator<Map.Entry<String, String>> iterator = hashMap1.entrySet().iterator();
    while (iterator.hasNext()) {
        Map.Entry<String, String> entry = iterator.next();
        System.out.println(entry.getKey());
        System.out.println(entry.getValue());
    }
    //KeySet 迭代器遍历
    Iterator<String> iterator2 = map.keySet().iterator();
    while (iterator2.hasNext()) {
        String key = iterator2.next();
        System.out.println(key);
        System.out.println(map.get(key));
    }
    //stream单线程遍历
    map.entrySet().stream().forEach((entry) -> {
        System.out.println(entry.getKey());
        System.out.println(entry.getValue());
    });
    //stream多线程遍历
    map.entrySet().parallelStream().forEach((entry) -> {
        System.out.println(entry.getKey());
        System.out.println(entry.getValue());
    });
    

其他常用的方法

方法描述
size()计算 hashMap 中键/值对的数量
containsKey()检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue()检查 hashMap 中是否存在指定的 value 对应的映射关系。
values()返回 hashMap 中存在的所有 value 值。
replace()替换 hashMap 中是指定的 key 对应的 value。
replaceAll()将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。

源码分析

public class HashMap<K,V> extends AbstractMap implements Map{
//Hash数组初始化容量 -- 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//Hash数组最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//转红黑树阈值
static final int TREEIFY_THRESHOLD = 8
//红黑树元素节点小于等于6转链表
static final int UNTREEIFY_THRESHOLD = 6;
//操作次数
transient int modCount;
//默认初始容量
int threshold
    
public V put(K key, V 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;
    //判断数组是否为空
    if ((tab = table) == null || (n = tab.length) == 0)
        //初始化
        n = (tab = resize()).length;
    //判断下标位置是否有值
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {//有值
        Node<K,V> e; K k;
        //判断key是否已经存在存在直接替换
        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);
                    //判断链表长度是否大于8
                    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;
            }
        }
        //判断key是否存在,存在即替换
        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;
    }

}

核心在于put方法

hashmap的put过程:

1.计算key的hashcode值(hashcode是object基类的方法,如果是String类型,他已经重写了hashcode方法,所以保证了同样的值,hashcode值相等;如果是其他类型,需要自己重写hashcode方法,因为,object的hashcode方法只是返回对象的内存地址,相同值的对象,由于在堆中存放的位置不同,hashcode也不同,这就违反了hashmap的key唯一性的定义),

2.根据hashcode计算key的hash值。(使用hash散列算法,目前采用的是:key==null?0:(h=key.hashCode())^(h>>>16);)

3.根据hash值计算数组index。(Entry[]数组的长度在初始化的时候会被指定,index需要尽可能的分布均匀;两种算法:取模运算(index=hash%length)和位运算(index=hash&(length-1);但是位运算有个前置条件:length的值必须是2的n次幂,因为只有这样,hash%length=hash&(length-1)才成立,实践证明,这样可以降低hash碰撞,就减少存放在链表的可能性,相对的,查询的时候就不用遍历某个 位置上的链表,这样查询效率也就较高了。 );两者都能保证key分布在Entry[0,length-1]上,但是取模运算效率低,所以选用位运算进行映射。)

4.插入,得到index之后,判断Entry[index]当前数组位置上是否已经有值,如果没有,直接插入;如果有值,需要根据equals方法判断key是否已经存在,存在的话直接覆盖;不存在的话,判断Entry[index]是否是treeNode,是的话直接红黑树插入value;如果不是的话,开始遍历链表准备插入,插入后判断此时链表长度是否大于8,大于的话转换为红黑树,插入,不大于的话,根据equals方法判断key是否已经存在,存在的话直接覆盖,不存在的话,直接插入到最前面,其他value后移。 插入之后判断此时size是否大于阈值,大于的话需要resize进行扩容,重新映射。

总结:

1.底层存储数据的数组,在第一次put元素的时候初始化,同时发生第一次扩容;

2.相比较JDK 1.8之前的版本,JDK 1.8在链表长度大于8的时候,会转化为红黑树处理,主要是基于效率的考量;

常见问题

  • HashMap容量为什么必须是2的幂?

    因为计算数组中的下标是由key的hash值和数组长度-1进行位运算(&),如果数组长度不是2的幂,数组长度的二进制有可能出现0,导致数组中的某些位置有可能永远存储不到值

  • HashMap默认的负载因子为什么是0.75?

    通过统计,取得一个时间和空间的平衡

  • 什么是Hash碰撞?

    两个对象的hash值一样,也就是key的计算出来的下标相同

  • HasHMap存储null键的位置?

    在table数组第0个下标的位置

  • HashMap再次扩容的数量?

    原来数组的两倍

  • JDK1.7版本和JDK1.8版本的HashMap有什么区别?

    JDK1.7:数组+链表

    JDK.18:数组+链表+红黑树,链表中的元素个数大于8并且数组大于64转换为红黑树,提高了查询的效率

  • 为什么链表大于8才转换为红黑树?

    泊松分布(了解即可)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值