Java基础--collection容器

本文详细介绍了Java集合类的基本概念,包括List、Set、Map之间的区别及应用场景,并深入探讨了ArrayList与HashMap的工作原理,特别是HashMap的put过程及多线程环境下可能导致的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 集合类总结: 

  1. Map接口和Collection是平行关系,Map没有继承Collection接口
  2. List接口继承了Collection接口,List接口允许存放重复的对象,排序方式为按照对象的插入顺序。 
  3. Set接口继承了Collection接口,Set接口不允许存放重复的对象,排序方式为按照自身内部的排序规则。 
  4. Map接口以键值对(key—value)的形式存放对象,其中键(key)对象不可以重复,值(value)对象可以重复,排序方式为按照自身内部的规则。

ArrayList原理

ArrayList底层是基于数组实现的,创建的时候可以用有参构造指定大小,否则无参构造默认初始容量为10。ArrayList是支持自动扩容的,原理是他会创建一个新的大小为10+10/2的数组,然后把数组复制过去,然后把原地址指向新ArrayList。

删除也是同理,创建新数组,把删除位置前后的数据都复制过去。

需要注意的是,ArrayList a = new ArrayList(10); 这种有参构造方式创建ArrayList并不会初始化数组大小,个人理解其实这样创建跟无参构造区别不大。

  • HashMap

  • put() 新增一个(key,value)键值对的过程:
  1. hash(key)    计算key的hash值;
  2. indexFor(key)    把key和hashMap的大小做与操作,计算下标(桶的位置)
  3. if(key==e.key && e.hash== hash(key))    如果桶是一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖(比如put(1,a),put(1,aaa)-->get(1)=aaa),并返回原来的值。
  4. addEntry()    如果桶是空的,新增一个 Entry 。如果此时size达到了阈值。会先扩容,包含重新计算所有节点的下标,然后transfer转移(遍历旧数组,将旧数组元素通过头插法的方式,迁移到新数组)等操作。

  • hashMap多线程死循环原因:

​​​​​​​原因是多线程情况下对hashmap操作,当需要扩容时如果线程1执行到Entry<K,V> next = e.next; 也就是说当前线程已经给e和e.next赋值,然后时间片用完被挂起,线程2执行并扩容完成,注意jdk1.7中hashmap扩容用的是头插法,所以链表顺序颠倒。此时线程1继续执行就会导致循环链表,当我们get到这一个key就会死循环,引起CPU的100%问题

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next; // 假设线程1执行完这一句被挂起
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

 

å¨è¿éæå¥å¾çæè¿°
线程2也来执行transfer函数,并执行完成,此时的状态为

 å¨è¿éæå¥å¾çæè¿°

 

此时线程1接着执行余下的代码,将key3放到线程1的table[3]处

å¨è¿éæå¥å¾çæè¿°

 

接着将e指向key7,不为null,再次进入循环,将next指向key3如下图

å¨è¿éæå¥å¾çæè¿°
当跑完这次循环时key7被放入线程1的table中,e指向key3,next指向null

 å¨è¿éæå¥å¾çæè¿°

 

 

e不为null,还能再次执行循环,key3再次插入线程1中table[3]的头节点,此时e变为null,循环完毕。结构如下

å¨è¿éæå¥å¾çæè¿°

环形链表形成,此时无论将线程1还是线程2的table设置为newTable,当调用get方法执行到这条链上时,死循环形成

  • 为什么重写equals()同时需要重写hashcode()?

这个一般主要用于自定义对象里,一般自定义对象我们判断相等只需要对象equals或者每条属性相等即可,而hashcode是属于Object的方法,是根据内存地址值计算的。这时如果不重写hashcode,那么在使用hashmap存储对象时,会先根据hashcode计算散列值(key.hashcode()),这样两个相等的对象就没法落到同一个位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值