2024年java面试--集合篇

news2024/11/16 11:43:02

文章目录

  • 前言
    • List
    • Set
    • Map
    • Collection
      • List
      • Set
      • Map
      • JDK1.7 HashMap:
      • JDK1.8 HashMap:
  • 一、ArrayList和LinkedList的区别
  • 二、HashSet的实现原理?
  • 三、List接口和Set接口的区别
  • 四、hashmap底层实现
  • 五、HashTable与HashMap的区别
  • 六、线程不安全体现
  • 七、想要线程安全的HashMap怎么办?
  • 八、put操作步骤
  • 九、Map的put方法的是怎么实现的?
  • 十、Map如何遍历
  • 十一、ConcurrentHashMap 是如何保证线程安全的?
  • 十二、ConcurrentHashMap 的扩容机制是怎样的?
  • 十三、ConcurrentHashMap 的 get() 方法是否需要加锁?
  • 十四、ConcurrentHashMap 与 Hashtable 有什么区别?
  • 十五、解决哈希冲突的四种方式
  • 十六、Java集合的快速失败机制 “fail-fast”?


前言

Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了两个接口,就是Set和List。Set中不能包含重复的元素。List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。

Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。

Iterator,所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法: 1.hasNext()是否还有下一个元素。 2.next()返回下一个元素。 3.remove()删除当前元素。

List

有索引,有序可重复ArrayListVector底层是数组,查询快增删慢。LinkedList底层是双向链表,查询慢增删快。ArrayList,LinkedList都是线程不安全,Vector线程安全。

List遍历
普通for循环遍历List删除指定元素

for(int i=0; i < list.size(); i++){
   if(list.get(i) == 5) 
       list.remove(i);
}

迭代遍历,用list.remove(i)方法删除元素

Iterator<Integer> it = list.iterator();
while(it.hasNext()){
    Integer value = it.next();
    if(value == 5){
        list.remove(value);
    }
}

foreach遍历List删除元素

for(Integer i:list){
    if(i==3) list.remove(i);
}

Set

无索引。无序不重复,Set实质上使用的是Map的Key存储,如果要将自定义的类存储到Set中,需要重写equals和hashCode方法。HashSet底层是通过Hashmap来实现的,HashSet是无序不重复的,且不能排序,集合元素可以是null,但只能放入一个null

LinkedHashSet底层是链表(保证有序)+哈希表(保证集合的唯一性),查询慢增删快,它是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的顺序所以遍历的时候会按照添加时的顺序来访问。

TreeSet底层是红黑树,一般用于排序,可以使用compareTo进行排序方法来比较元素之间大小关系,然后将元素按照升序排列,有序。

Map

Map: Key无序不重复,Value可重复。

HashMap底层是数组+链表,它根据键的HashCode值存储数据,根据键可以直接获取它的值,访问速度很快。所以在Map中插入、删除和定位元素比较适合用hashMap。

LinkedHashMap底层是链表+哈希表,它是HashMap的一个子类,如果需要读取的顺序和插入的相同,可以用LinkedHashMap来实现。

TreeMap底层是红黑树,与TreeSet类似,取出来的是排序后的键值对。但如果是要按自然顺序或自定义顺序遍历键,那么TreeMap会更好,有序。

Collection

List

  • Arraylist: Object数组
  • Vector: Object数组
  • LinkedList: 双向循环链表

Set

  • HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
  • LinkedHashSet(有序): LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

Map

  • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是 主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较 大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间

JDK1.7 HashMap:

底层是 数组和链表 结合在⼀起使⽤也就是链表散列。如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。扩容翻转时顺序不一致使用头插法会产生死循环,导致cpu100%

JDK1.8 HashMap:

底层数据结构上采用了数组+链表+红黑树;当链表⻓度⼤于阈值(默认为 8-泊松分布),数组的⻓度大于 64时,链表将转化为红⿊树,以减少搜索时间。(解决了tomcat臭名昭著的url参数dos攻击问题)

  • LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散 列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加 了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的 操作,实现了访问顺序相关逻辑。
  • HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突 而存在的
  • TreeMap: 红黑树(自平衡的排序二叉树)

一、ArrayList和LinkedList的区别

1.首先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的

2.由于底层数据结构不同,他们所适用的场景也不同,ArrayList更适合查找,LinkedList更适合删除和添加

3.另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以LinkedList还可以当做队列来使用

4.ArratList的底层使用动态数组,默认容量为10,当元素数量到达容量时,生成一个新的数组,大小为前一次的1.5倍,然后将原来的数组copy过来;


二、HashSet的实现原理?

HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,value统一为present,因此 HashSet 的实现比

较简单,相关 HashSet 的操作,基本上都是直接调用底层HashMap 的相关方法来完成,HashSet 不允许重复的值。


三、List接口和Set接口的区别

List:有序、可重复集合。按照对象插入的顺寻保存数据,允许多个Null元素对象,可以使用iterator迭代器遍历,也可以使用get(int index)方法获取指定下标元素。
Set:无序、不可重复集合只允许有一个Null元素对象,取元素时,只能使用iterator迭代器逐一遍历。
Map : key-value键值对形式的集合,添加或获取元素时,需要通过key来检索到value。


四、hashmap底层实现

HashMap 基于 Hash 算法实现的:

1.当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标

2.存储时,如果出现hash值相同的key,此时有两种情况。
(1)如果key相同,则覆盖原始值;
(2)如果key不同(出现冲突),则将当前的key-value放入链表中

3.获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

4.理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

数组加链表(1.8以前),1.8之后添加了红黑树,基于hash表的map接口实现
阈值(边界值)>8并且桶位数(数组长度)大于64,才将链表转换为红黑树,变为红黑树的目的是为了高效的查询。


五、HashTable与HashMap的区别

(1)HashTable的每个方法都用synchronized修饰,因此是线程安全的,但同时读写效率很低
(2)HashTable的Key不允许为null
(3)HashTable只对key进行一次hash,HashMap进行了两次Hash
(4)HashTable底层使用的数组加链表
(5)HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。


六、线程不安全体现

在HashMap扩容的是时候会调用resize()方法中的transfer()方法,在这里由于是头插法所以在多线程情况下可能出现循环链表,所以后面的数据定位到这条链表的时候会造成数据丢失。和读取的可能导致死循环。

1.并发修改导致数据不一致

HashMap的数据结构是基于数组和链表实现的。在进行插入或删除操作时,如果不同线程同时修改同一个位置的元素,就会导致数据不一致的情况。具体来说,当两个线程同时进行插入操作时,假设它们都要插入到同一个数组位置,并且该位置没有元素,那么它们都会认为该位置可以插入元素,最终就会导致其中一个线程的元素被覆盖掉。此外,在进行删除操作时,如果两个线程同时删除同一个元素,也会导致数据不一致的情况。

2.并发扩容导致死循环或数据丢失

当HashMap的元素数量达到一定阈值时,它会触发扩容操作,即重新分配更大的数组并将原来的元素重新映射到新的数组上。然而,在进行扩容操作时,如果不加锁或者加锁不正确,就可能导致死循环或者数据丢失的情况。具体来说,当两个线程同时进行扩容操作时,它们可能会同时将某个元素映射到新的数组上,从而导致该元素被覆盖掉。此外,在进行扩容操作时,如果线程不安全地修改了next指针,就可能会导致死循环的情况。


七、想要线程安全的HashMap怎么办?

(1)使用ConcurrentHashMap

(2)使用HashTable

(3)Collections.synchronizedHashMap()方法


八、put操作步骤

在这里插入图片描述

1、判断数组是否为空,为空进行初始化;

2、不为空,则计算 key 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;

3、查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;

4、存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据;

5、若不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;

6、若不是红黑树,创建普通Node加入链表中;判断链表长度是否大于 8,大于则将链表转换为红黑树;

7、插入完成之后判断当前节点数是否大于阈值,若大于,则扩容为原数组的二倍


九、Map的put方法的是怎么实现的?

通过调用key的hashCode方法获取哈希值找到存放的数组下标,通过遍历此位置的key与插入的key通过equals比较,如果已存在则替换

值,不存在则插入进来。


十、Map如何遍历

Map实现类调用entrySet方法获得一个Entry类型的Set,通过遍历这个Set集合获取Entry调用getKey或者getValue获取值


十一、ConcurrentHashMap 是如何保证线程安全的?

ConcurrentHashMap 使用分段锁的方式来实现线程安全,它将一个大的哈希表分成多个小的哈希表(段),每个小的哈希表都有自己的锁。这样,不同的线程可以同时访问不同的小哈希表,从而避免了多个线程同时竞争同一个锁的情况,提高了并发性能。


十二、ConcurrentHashMap 的扩容机制是怎样的?

ConcurrentHashMap 的扩容机制与 HashMap 类似,它会在哈希表的负载因子达到阈值时进行扩容。扩容的过程中,ConcurrentHashMap 会将原来的小哈希表逐一复制到新的大哈希表中,这个过程中仍然可以保证线程安全。扩容后,ConcurrentHashMap 会继续使用分段锁的方式来维护新的小哈希表。


十三、ConcurrentHashMap 的 get() 方法是否需要加锁?

ConcurrentHashMap 的 get() 方法不需要加锁,因为它是线程安全的。在并发访问时,ConcurrentHashMap 使用了 volatile 和 CAS 等机制来保证数据的一致性和可见性,所以可以保证多个线程同时访问时不会出现数据竞争和不一致的情况。


十四、ConcurrentHashMap 与 Hashtable 有什么区别?

ConcurrentHashMap 和 Hashtable 都是线程安全的哈希表,但是它们有很大的区别。ConcurrentHashMap 使用了分段锁的方式来提高并发性能,而 Hashtable 使用了一个全局锁来保证线程安全,所以并发性能比 ConcurrentHashMap 差很多。此外,ConcurrentHashMap 允许空键和空值,而 Hashtable 不允许。另外,ConcurrentHashMap 支持更多的操作,比如 ConcurrentHashMap 支持的批量操作和原子操作等,Hashtable 不支持。


十五、解决哈希冲突的四种方式

1.开放定址法

当关键字key的哈希地址p =H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,若p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。

即:Hi=(H(key)+di)% m (i=1,2,…,n)

开放定址法有下边三种方式:

线性探测再散列 顺序查看下一个单元,直到找出一个空单元或查遍全表 di=1,2,3,…,m-1 二次(平方)探测再散列 在表的左右进行跳跃式探测,直到找出一个空单元或查遍全表 di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 ) 伪随机探测再散列 建立一个伪随机数发生器,并给一个随机数作为起点 di=伪随机数序列。具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。

优点

容易序列化 若可预知数据总数,可以创建完美哈希数列

缺点

占空间很大。(开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间) 删除节点很麻烦。不能简单地将被删结点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

2.再哈希法

提供多个哈希函数,如果第一个哈希函数计算出来的key的哈希值冲突了,则使用第二个哈希函数计算key的哈希值。

优点

不易产生聚集

缺点

增加了计算时间

3.链地址法(hashmap使用此法)

对于相同的哈希值,使用链表进行连接

优点

处理冲突简单,无堆积现象。即非同义词决不会发生冲突,因此平均查找长度较短; 适合总数经常变化的情况。(因为拉链法中各链表上的结点空间是动态申请的) 占空间小。装填因子可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计 删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。

缺点

查询时效率较低。(存储是动态的,查询时跳转需要更多的时间) 在key-value可以预知,以及没有后续增改操作时候,开放定址法性能优于链地址法。 不容易序列化

4.建立公共溢出区

将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。


十六、Java集合的快速失败机制 “fail-fast”?

是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。 例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时 候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这 个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。 原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集 合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next() 遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍 历;否则抛出异常,终止遍历。 解决办法: 在遍历过程中,所有涉及到改变modCount值的地方全部加上synchronized。 使用CopyOnWriteArrayList来替换ArrayList

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/947579.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java+SpringBoot+Vue前后端分离医药管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

无涯教程-Android - List fragments函数

框架的ListFragment的静态库支持版本&#xff0c;用于编写在Android 3.0之前的平台上运行的应用程序&#xff0c;在Android 3.0或更高版本上运行时,仍使用此实现。 List fragment 的基本实现是用于创建fragment中的项目列表 List in Fragments 示例 本示例将向您说明如何基于…

基于饥饿游戏算法优化的BP神经网络(预测应用) - 附代码

基于饥饿游戏算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于饥饿游戏算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.饥饿游戏优化BP神经网络2.1 BP神经网络参数设置2.2 饥饿游戏算法应用 4.测试结果&#xff1a;5…

基于Java+SpringBoot+Vue前后端分离文理医院预约挂号系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

AD域控统一设置桌面壁纸

这里的UNC路径是在C盘建立的共享文件夹Desktop 最后更新一下策略就可以了。

yolo增加RFEM

论文地址&#xff1a;https://arxiv.org/pdf/2208.02019.pdf 代码地址&#xff1a;GitHub - Krasjet-Yu/YOLO-FaceV2: YOLO-FaceV2: A Scale and Occlusion Aware Face Detector 总的来说就是RFEM利用了感受野在特征图中的优势&#xff0c;通过使用不同膨胀卷积率的分支来捕捉多…

新能源发展趋安科瑞助力风力发电场集中监控系统解决方案

安科瑞 崔丽洁 作为清洁能源之一&#xff0c;风力发电场近几年装机容量快速增长。2023年8月17日&#xff0c;国家能源局发布1-7月份全国电力工业统计数据。截至7月底&#xff0c;全国累计发电装机容量约27.4亿千瓦&#xff0c;同比增长11.5%。其中&#xff0c;太阳能发电装机容…

OA项目之我的审批(查询会议签字审批)

目录 会议查询 会议签字 会议审批 讲解思路 我的审批查询功能手写签批插件及工具类介绍手写签批插件集成手写签批功能实现 会议查询 MeetingInfoDao.java // 我的审批public List<Map<String,Object>> myAudit(MeetingInfo info,PageBean pageBean) throws E…

Linux中的基础IO

目录 1、关于C语言中的文件操作符 1.1 C语言中写文件 1.2 C语言读文件 1.3 往显示器上输出信息 1.4 stdin & stdout & stderr 1.5 打开文件的方式 2、系统文件IO 2.1 写操作文件 2.2 读操作文件、 2.3 open open函数的返回值 2.4 文件描述符 0 & 1 &a…

MybatisPlus-插件篇

文章目录 一、前言二、插件1、分页插件2.1.1、引入依赖2.1.1、配置分页插件2.1.3、使用分页方法 2、乐观锁插件2.1、引入依赖2.2、添加版本字段2.3、配置乐观锁插件2.4、执行更新操作 三、总结 一、前言 本文将详细介绍mybatisplus中常用插件的使用。 二、插件 1、分页插件 …

双向A*算法

前面看最佳路径优先搜索算法的时候顺便研究了一下它的改进算法&#xff1a;双向最佳路径优先搜索算法。那既然有双向最佳路径优先搜索算法自然也可以有双向A* 算法。这篇文章简单看一下双向A*算法的基本原理以及代码实现。 基本原理 双向A* 算法是一种用于解决图搜索问题的启…

供水营业收费管理系统:智慧水务的得力助手

随着我国经济的快速发展&#xff0c;城市化进程不断加快&#xff0c;供水行业的需求也不断增长。为满足人们日益增长的用水需求&#xff0c;提高供水企业的管理水平和服务质量&#xff0c;供水营业收费管理系统应运而生&#xff0c;成为智慧水务的得力助手。 一、供水营业收费管…

算法通关村-----哈希和队列的基本知识

哈希概念 哈希也称为散列&#xff0c;就是把任意长度的输入&#xff0c;通过散列算法&#xff0c;变成固定长度的输出&#xff0c;这个输出值就是散列值。 哈希存储 现在有1&#xff0c;2&#xff0c;3…15&#xff0c;要将其存储到大小为7的哈希表中&#xff0c;应该如何存…

Android studio实现水平进度条

原文 ProgressBar 用于显示某个耗时操作完成的百分比的组件称为进度条。ProgressBar默认产生圆形进度条。 实现效果图&#xff1a; MainActivity import android.os.Bundle; import android.view.View; import android.app.Activity; import android.widget.Button; import…

算法 稀疏数组 数组优化 数组压缩 二维数组转稀疏数组 算法合集(二)

1. 五子棋游戏&#xff0c;玩家对战一半停战休息&#xff0c;此时需要存储当前对战双方棋子信息 a. 采用二维数组存储&#xff1a; 0为空&#xff0c; 1代表黑棋 2代表蓝色棋子 b. 棋盘为11行&#xff0c;11列 > int [][] chessArray new int [11][11]; c. 出现的问题&am…

RT_Thread内核机制学习(五)邮箱

之所以引入线程间通信&#xff0c;是为了实现互斥&#xff0c;休眠-唤醒。 队列可以指定消息的大小、个数&#xff0c;存放消息&#xff0c;取出消息时都是由rt_memcpy()实现。 邮箱 保存数据的核心在于数组&#xff0c;只能存放unsigned long类型数据&#xff0c;数据存取、…

Acwing798.差分矩阵

前缀和与差分 图文并茂 超详细整理&#xff08;全网最通俗易懂&#xff09;_前缀和差分_林小鹿的博客-CSDN博客 代码展示&#xff1a; #include<iostream> #include<cstdio> using namespace std; const int N 1e3 10; int a[N][N], b[N][N]; void insert(int x…

在iPhone 15发布之前,iPhone在智能手机出货量上占据主导地位,这对安卓来说是个坏消息

可以说这是一记重拳&#xff0c;但似乎没有一个有价值的竞争者能与苹果今年迄今为止的智能手机出货量相媲美。 事实上&#xff0c;根据Omdia智能手机型号市场跟踪机构收集的数据&#xff0c;苹果的iPhone占据了前四名。位居榜首的是iPhone 14 Pro Max&#xff0c;2023年上半年…

Python Qt学习(五)Checkbox

源码 # -*- coding: utf-8 -*-# Form implementation generated from reading ui file qt_checkbox.ui # # Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this fil…

ATF(TF-A)安全通告 TFV-3 (CVE-2017-7563)

安全之安全(security)博客目录导读 ATF(TF-A)安全通告汇总 目录 一、ATF(TF-A)安全通告 TFV-3 (CVE-2017-7563) 二、CVE-2017-7563 一、ATF(TF-A)安全通告 TFV-3 (CVE-2017-7563) Title RO内存始终在AArch64 Secure EL1下可执行 CVE ID CVE-2017-7563 Date 06 Apr 2017 …