Java类集高频面试题以及详解(必问)

Java类集高频面试题以及详解(必问)

类集在面试中被问到的概率非常高,下面是我整理的有关类集方面的高频面试点。

1.ArrayList、Vector、LinkedList的关系与区别

  • ArrayList、Vector、LinkedList都属于List接口的常用子类,其中ArrayList、Vector底层基于数组实现,LinkedList基于链表实现
  • Vector出现版本JDK1.0;ArrayList和LinkedList出现版本JDK1.0
  • Vector支持较老的迭代器Enumeration,而ArrayList不支持
  • ArrayList采用懒加载策略,第一次向集合中添加元素时,初始化对象数组容量为10;扩容策略为原先数组的1.5倍;线程不安全,性能较高;适合在频繁查找以及尾部的插入场景下使用ArrayList
  • Vector在产生对象时就初始化内部数组(默认大小为10);扩容策略为原先数组的2倍;采用synchronized同步方法,线程安全,性能很低(读读互斥)
  • LinkedList采用异步处理,线程不安全;适用于频繁在任意位置进行元素的插入与删除

2.jcl中fail-fast机制以及fail-safe机制?

  • 什么是快速失败策略?

    优先考虑出现异常的情况,当异常产生时,直接抛出异常(ConcurrentModificationException),程序终止。

  • 如何抛出ConcurrentModificationException异常?

    modCount记录当前集合修改的次数,exceptedModCount记录获取当前集合迭代器的修改次数,当modCount != expectedModCount时,就会抛出该异常。但是在一开始的时候, expectedModCount初始值默认等于modCount,并且expectedModCount在整个迭代过程除了一开始赋予初始值modCount外,并没有再发生改变,发生改变的只有modCount,当另一个线程(并发修改)或者同一个线程遍历过程中,调用相关方法使集合的个数发生改变,就会使modCount(modCount ++)发生变化,这样就会抛出ConcurrentModificationException异常。

  • 如何解决fail-fast?
    1)遍历不要修改集合内容;
    2)使用迭代器内部的删除方法;
    3)使用fail-safe集合

  • fail-safe集合?
    juc包下的(ConcyrrentHashMap、CopyOnWriteArrayList)都属于fail-safe

  • fail-fast意义?
    避免多线程场景下数据‘脏读’的问题,也就是说现在拿到的数据不是最新的所以禁止作!

3.Set与Map的关系?

  • Set接口相当于穿了马甲的Map接口,本质上Set接口的子类都是使用Map来存储元素的,都是将元素存储到Map接口的key中,Set中的value都是用一个空的Object对象

4.hashCode()与equals()的区别?

  • hashCode返回相等,equlas不一定相等(hash碰撞)
  • equals返回相等,hashCode一定相等

5.Java中实现一个类的两个对象大小比较的方式(内部排序与外部排序的区别)?

  • 在Java中,若想实现自定义类的比较,需要实现内部比较器Comparable接口或者外部比较器Comparator接口;
  • Comparable是排序接口,若一个类实现了Comparable接口,意味着该类支持排序,是一个内部比较器接口(自身去和别人比);
  • Comparator接口是比较器接口,类的本身不支持排序(类本身没有实现Comparable接口),专门有若干个第三方的比较器(实现了Comparator接口的类)来进行类的排序,是一个外部比较器(策略模式)
  • 实现Comparator接口进行第三方排序,这种方式更灵活,可以轻松改变策略进行第三方的排序算法

6.HashMap、TreeMap、Hashtable的关系与区别?

  • HashMap、TreeMap、Hashtable都是Map的常用子类,HashMap底层基于哈希表+红黑树(JDK1.8之后),Hashtable基于哈希表,TreeMap基于红黑树;
  • HashMap采用懒加载策略,采用异步处理,线程不安全,性能较高
  • Hashtable产生对象时初始化内部哈希表(默认大小为16),采用synchronized同步方法,线程安全,性能很低(读读互斥)
  • HashMap中键值对都允许为null
    TreeMap中键不能为null,值可以为null
    Hashtable中键与值都不能为null

7.HashMap内部源码分析(负载因子、树化策略、扩容策略、内部哈希算法)

  • 负载因子:float DEFAULT_LOAD_FACTOR = 0.75f
    树化阈值:int TREEIFY_THRESHOLD = 8
    解树化阈值:int UNTREEIFY_THRESHOLD = 6

  • 树化逻辑:当前桶中链表的长度大于等于8并且哈希表长度大于等于64开始树化,否则只是简单的扩容处理

  • 解树化逻辑:当红黑树个数在扩容或者删除时个数小于等于6,在下一次resize过程中会将红黑树退化成链表来节省空间

  • 为何引入红黑树?
    为了防止链表过长而导致查找性能急剧降低,引入红黑树后时间复杂度从O(n)优化成O(log2^n)

  • 为什么负载因子为0.75?
    根据科学的统计计算得出0.75是最合适的,如果负载因子>0.75,会增加了哈希表的利用率,哈希冲突概率明显增加;如果负载因子<0.75,会降低哈希表的利用率,导致扩容频繁。

  • 为何不直接使用HashCode方法?
    返回值普遍较大,需要开辟大量空间,浪费资源

  • 为何哈希表长度必须为2^n?
    保证哈希表中所有索引位置都有可能被访问到

  • 为何源码中h>>>16?
    取出高16位参与运算是因为有些数据计算出的哈希值主要的差异是在高位,而HashMap中哈希寻址是忽略高位的,这种策略可以有效避免哈希碰撞

8.ConcurrentHashMap如何高效实现线程安全?

  • 在ConcurrentHashMap没有出现以前,jdk使用hashtable来实现线程安全,但是Hashtable是将整个hash表锁住,所以效率很低下。

  • ConcurrentHashMap将数据分别放到多个Segment中,默认16个,每一个Segment中又包含了多个HashEntry列表数组,

  • 对于一个key,需要经过三次hash操作,才能最终定位这个元素的位置,这三次hash分别为:
    先进行一次hash操作,得到hash值h1,也即h1 = hash1(key);
    将得到的h1的高几位进行第二次hash,得到hash值h2,也即h2 =hash2(h1高几位),通过h2能够确定该元素的放在哪个Segment;
    将得到的h1进行第三次hash,得到hash值h3,也即h3 = hash3(h1),通过h3能够确定该元素放置在哪个HashEntry。

  • 每一个Segment都拥有一个锁,当 进行写操作时,只需要锁定当前操作的Segment,而其它Segment中的数据是可以访问的。

9.JDK1.7与JDK1.8ConcurrentHashMap的设计区别?

  • JDK1.7中Segment是ReentrantLock的子类,将HashTable整张表加锁,一把锁优化为16个Segment,锁的是当前的Segment,不同的Segment之间还是异步,Segment初始化为16后不可再次扩容
  • JDK1.8中ConcurrentHashMap锁进一步细化,结构类似于HashMap,锁的是当前桶的头节点,锁的个数进一步提升,也就是说,锁会随着哈希表扩容而增加,支持并发线程的数量进一步提升,内部使用CAS和synchronized来保证线程安全
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值