【暑期实习】【春招秋招】Java集合相关经典面试题2023

文章详细介绍了Java集合框架中的ArrayList和HashMap的实现原理,包括ArrayList的扩容机制和HashMap的哈希冲突解决策略。同时,对比了线程安全的Hashtable和非线程安全的HashMap,讨论了多线程环境下的集合选择,推荐使用ConcurrentHashMap。
摘要由CSDN通过智能技术生成

Java集合相关经典面试题


集合框架中的常见类和数据结构,以及它们的底层原理?
Java中的集合类分为4大类,分别由4个接口来代表,它们是Set、List、Queue、Map。其中,Set、List、Queue、都继承自Collection接口。 - Set代表无序的、元素不可重复的集合。 - List代表有序的、元素可以重复的集合。 - Queue代表先进先出(FIFO)的队列。 - Map代表具有映射关系(key-value)的集合。
Java提供了众多集合的实现类,它们都是这些接口的直接或间接的实现类,其中比较常用的有:HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap等。 加分回答 上面所说的集合类的接口或实现,都位于java.util包下,这些实现大多数都是非线程安全的。虽然非线程安全,但是这些类的性能较好。如果需要使用线程安全的集合类,则可以利用Collections工具类,该工具类提供的synchronizedXxx()方法,可以将这些集合类包装成线程安全的集合类。 java.util包下的集合类中,也有少数的线程安全的集合类,例如Vector、Hashtable,它们都是非常古老的API。虽然它们是线程安全的,但是性能很差,已经不推荐使用了。

ArrayList 的扩容机制是什么?
ArrayList是基于数组实现的,它的内部封装了一个Object[]数组。 通过默认构造器创建容器时,该数组先被初始化为空数组,之后在首次添加数据时再将其初始化成长度为10的数组。我们也可以使用有参构造器来创建容器,并通过参数来显式指定数组的容量,届时该数组被初始化为指定容量的数组。 如果向ArrayList中添加数据会造成超出数组长度限制,则会触发自动扩容,然后再添加数据。扩容就是数组拷贝,将旧数组中的数据拷贝到新数组里,而新数组的长度为原来长度的1.5倍。 ArrayList支持缩容,但不会自动缩容,即便是ArrayList中只剩下少量数据时也不会主动缩容。如果我们希望缩减ArrayList的容量,则需要自己调用它的trimToSize()方法,届时数组将按照元素的实际个数进行缩减。

HashMap 的底层原理是什么? 数组+链表+红黑树 2的N次方(位移运算)
在JDK8中,HashMap底层是采用“数组+链表+红黑树”来实现的。 HashMap是基于哈希算法来确定元素的位置(槽)的,当我们向集合中存入数据时,它会计算传入的Key的哈希值,并利用哈希值取余来确定槽的位置。如果元素发生碰撞,也就是这个槽已经存在其他的元素了,则HashMap会通过链表将这些元素组织起来。如果碰撞进一步加剧,某个链表的长度达到了8,则HashMap会创建红黑树来代替这个链表,从而提高对这个槽中数据的查找的速度。 HashMap中,数组的默认初始容量为16,这个容量会以2的指数进行扩容。具体来说,当数组中的元素达到一定比例的时候HashMap就会扩容,这个比例叫做负载因子,默认为0.75。自动扩容机制,是为了保证HashMap初始时不必占据太大的内存,而在使用期间又可以实时保证有足够大的空间。采用2的指数进行扩容,是为了利用位运算,提高扩容运算的效率。
put()流程 put()方法的执行过程中,主要包含四个步骤: 1. 判断数组,若发现数组为空,则进行首次扩容。 2. 判断头节点,若发现头节点为空,则新建链表节点,存入数组。 3. 判断头节点,若发现头节点非空,则将元素插入槽内。 4. 插入元素后,判断元素的个数,若发现超过阈值则再次扩容。 其中,第3步又可以细分为如下三个小步骤: 1. 若元素的key与头节点一致,则直接覆盖头节点。 2. 若元素为树型节点,则将元素追加到树中。 3. 若元素为链表节点,则将元素追加到链表中。追加后,需要判断链表长度以决定是否转为红黑树。若链表长度达到8、数组容量未达到64,则扩容。若链表长度达到8、数组容量达到64,则转为红黑树。 扩容机制 向HashMap中添加数据时,有三个条件会触发它的扩容行为: 1. 如果数组为空,则进行首次扩容。 2. 将元素接入链表后,如果链表长度达到8,并且数组长度小于64,则扩容。 3. 添加后,如果数组中元素超过阈值,即比例超出限制(默认为0.75),则扩容。 并且,每次扩容时都是将容量翻倍,即创建一个2倍大的新数组,然后再将旧数组中的数组迁移到新数组里。由于HashMap中数组的容量为2^N,所以可以用位移运算计算新容量,效率很高。
加分回答 HashMap是非线程安全的,在多线程环境下,多个线程同时触发HashMap的改变时,有可能会发生冲突。所以,在多线程环境下不建议使用HashMap,可以考虑使用Collections将HashMap转为线程安全的HashMap,更为推荐的方式则是使用ConcurrentHashMap。

HashMap 如何解决哈希冲突? 4种 数据无限结果范围有限
哈希冲突是由于哈希算法被计算的数据是无限的,而计算后的结果范围是有限的,所以总会存在不同的数据计算后得到的值是一样的,那将会存在同一个位置,就会出现哈希冲突。比如 key = “name”,和key = “hobby” 计算后的哈希值一样 。

  1. 开放地址法:也称线性探测法,就是从发生冲突的那个位置,按照一定次序从Hash表中找到一个空闲的位置,把发生冲突的元素存入到这个位置。而在java种ThreadLocal就用到了线性探测法,来解决Hash冲突。
  2. 链式寻址法:通过单向链表的方式来解决哈希冲突,Hashmap就是用了这个方法。(但会存在链表过长增加遍历时间)
  3. 再哈希法:key通过某个哈希函数计算得到冲突的时候,再次使用哈希函数的方法对key哈希一直运算直到不产生冲突为止 (耗时间,性能会有影响)
  4. 建立公共溢出区:就是把Hash表分为基本表和溢出表两个部分,凡是存在冲突的元素,一律放到溢出表中

Hashtable 与 HashMap 有何不同?
HashMap和Hashtable都是典型的Map实现,它们的区别在于是否线程安全,是否可以存入null值。

  1. Hashtable在实现Map接口时保证了线程安全性,而HashMap则是非线程安全的。所以,Hashtable的性能不如HashMap,因为为了保证线程安全它牺牲了一些性能。
  2. Hashtable不允许存入null,无论是以null作为key或value,都会引发异常。而HashMap是允许存入null的,无论是以null作为key或value,都是可以的。 加分回答 虽然Hashtable是线程安全的,但仍然不建议在多线程环境下使用Hashtable。因为它是一个古老的API,从Java 1.0开始就出现了,它的同步方案还不成熟,性能不好。如果要在多线程环下使用HashMap,建议使用ConcurrentHashMap。它不但保证了线程安全,也通过降低锁的粒度提高了并发访问时的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值