HashMap

HashMap中的负载因子和容量

实际容量 = 负载因子 x 容量,也就是 12 = 0.75 x 16

//默认初始容量-必须为2的幂次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

//最大容量,如果传入的值大于下面的值,则使用下面定义的值
static final int MAXIMUM_CAPACITY = 1 << 30;

//在构造函数中未指定时使用的负载系数
//默认加载因子为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//当链表的长度 >= 8的时候,将链表转换成红黑树
static final int TREEIFY_THRESHOLD = 8;

//在resize()扩容的时候,HashMap的数据存储位置会重新进行计算
//在重新就散存储的位置后,当原有的红黑树数量 <= 6 的时候,则将红黑树转换为链表
static final int UNTREEIFY_THRESHOLD = 6;

//为了避免扩容/调整树化阀值之间的冲突,这个值不能 < 4 * TREEIFY_THRESHOLD
static final int MIN_TREEIFY_CAPACITY = 64;

构造函数

1.HashMap(int initialCapacity, float loadFactor)
2.HashMap(int initialCapacity)
3.HashMap()
4.HashMap(Map<? extends K, ? extends V> m)

//初始容量为32,负载因子0.75,容量必须为2的幂次方,调用tableSizeFor()实现
Map<String, String> map1 = new HashMap<String, String>(32,(float) 0.75);
//初始容量为18
Map<String, String> map2 = new HashMap<String, String>(18);
//默认容量为16
Map<String, String> map3 = new HashMap<String, String>();

tableSizeFor源码:使得容量初始化为2的幂次方

 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;
    }

resize()源码:

Hash冲突

  • 两个节点的key值相同,导致冲突
  • 两个节点的key值不同,由于hash函数的局限性导致hash值相同,导致冲突
  • 两个节点的key值不同,hash值不同,但hash值对数组长度取模后相同,导致冲突

解决方案:链地址法
HashMap 中数组的每一个元素不仅是一个 Entry 对象,还是一个链表的头节点。每一个 Entry 对象通过 next 指针指向它的下一个Entry 节点。当新来的Entry映射到与之冲突的数组位置时,只需要插入到对应的链表中即可

HashMap常用方法

get():

//调用getNode()方法,根据key获取hash值,有值就返回value,没有值返回null
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

put():

//调用putVal()方法,根据key获取hash值,找到该hash值对应的数组下标,插入到链表或红黑树中
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

jdk1.7与jdk1.8对比

jdk1.7jdk1.8
插入方式头插法尾插法
存储结构数组+链表数组+链表+红黑树
扩容后数据存储位置hash值和需要扩容的二进制数进行&扩容前的原始位置+扩容的大小值
resize时间复杂度nnlog(n)

线程安全问题

  • put()操作的时候导致的多线程数据不一致
    两个线程同时进行put()操作,导致其中一个值被覆盖

  • resize()而引起死循环
    这种情况发生在HashMap自动扩容时,当2个线程同时检测到元素个数超过 数组大小 × 负载因子。此时2个线程会在put()方法中调用了resize(),两个线程同时修改一个链表结构会产生一个循环链表

  • 如何实现线程安全

    HashMap<String,Integer> hashMap = new HashMap<String,Integer>();
    Map<String, Integer> map = Collections.synchronizedMap(hashMap);
    

性能

Entry的key最坏的情况下在Map中是一个链表,JDK8为优化这个问题在链表数目大于8的时候转化为红黑树,但是resize中,又必需拆解和重建红黑树

遍历方式

1.通过Iterator进行删除操作是线程安全的
2.通过map.keySet().removeIf(key -> key == 1)是线程安全的

Iterator+EntrySet()

Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> entry = iterator.next();
            System.out.print(entry.getKey());
            System.out.print(entry.getValue());
        }

Iterator+KeySet

Iterator<Integer> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Integer key = iterator.next();
            System.out.print(key);
            System.out.print(map.get(key));
        }

ForEach+EntrySet

for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.print(entry.getKey());
            System.out.print(entry.getValue());
        }

ForEach+KeySet

for (Integer key : map.keySet()) {
            System.out.print(key);
            System.out.print(map.get(key));
        }

ForEach+value

for (String value : map.value()) {
            System.out.print(value);
        }

Lambda

map.forEach((key, value) -> {
            System.out.print(key);
            System.out.print(value);
        });

Streams API 单线程

map.entrySet().stream().forEach((entry) -> {
            System.out.print(entry.getKey());
            System.out.print(entry.getValue());
        });

Streams API 多线程

map.entrySet().parallelStream().forEach((entry) -> {
            System.out.print(entry.getKey());
            System.out.print(entry.getValue());
        });
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值