集合容器面试题

news2024/10/7 12:21:42

Java 容器都有哪些?

Java 容器分为 Collection 和 Map 两大类

Collection 和 Collections 有什么区别?

Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。
Collections 是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供的排序方法:Collections. sort(list)。

如何决定使用 HashMap 还是 TreeMap?

对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,
但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。

说一下 HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

ArrayList 和 LinkedList 的区别是什么?

相同点:
ArrayList 和 LinkedList:有序(存取元素顺序一致)、有索引、可重复
不同点:
数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
随机访问效率:ArrayList 比 LinkedList 在随机访问(查询)的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
增加和删除效率:LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

如何实现数组和 List 之间的转换?

数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。

// list to array
List<String> list = new ArrayList<String>();
list. add("ben");
list. add("的博客");
Object[] objects = list.toArray();
for (int i = 0; i < objects.length; i++) {
    System.out.println(objects[i]);//ben 的博客
}
// array to list
String[] array = new String[]{"ben","的博客"};
System.out.println(Arrays.asList(array));//[ben, 的博客]

Array 和 ArrayList 有何区别?

Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。

哪些集合类是线程安全的?

Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现,它们也有了自己对应的线程安全类,比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。

迭代器 Iterator 是什么?

Iterator是Collection下的,所以list,set都可以拿来遍历。在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。

ConcurrentModificationException并发修改异常

ConcurrentModificationException并发修改异常

怎么确保一个集合不能被修改?

可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

示例代码如下:

List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错UnsupportedOperationException
System. out. println(list. size());

HashMap的扩容机制?

HashMap初始容量:
HashMap默认容量大小是16
当我们设置HashMap初始化容量时,实际上HashMap会采用第一个>=该数字的2幂作为初始容量(比如设置初始容量为7,那么HashMap会采用2三次幂8作为初始容量)
HashMap默认容量大小是16,装载因子是0.75,所以扩容的边界值就是0.75*16=12,当HashMap大小超过12 的时候,HashMap就会自动增加一倍,变成32
HashMap 的初始化容量大小 = 需要存储的元素个数 / 负载因子0.75 + 1.0F
比如:需要存储11个元素,通过按位与运算,11/0.75+1=15.66,比这个数大的2的几次幂是16
HashMap的扩容机制
1.7 数组+链表 数组先扩容,遍历老数组每个位置链表的每个元素,取每个元素的key,基于数组下标,将元素添加到新数组中
1.8 数组+链表+红黑树,数组先扩容,遍历老数组中每个位置上的链表或红黑树,
当链表长度>=8时,且数组长度>64时,链表数据将树化,以红黑树形式存储。
当红黑树个数<6时,红黑树将退化为链表

HashMap put()?

1,根据hash算法与取余得出数组下标,
2,如果数组下标位置元素为空,1.7 将key,value封装成entry对象 。1.8 将key,value封装成node对象
3,如果数组下标位置不为空,1.7判断数组是否需要扩容,如果需要就先扩容,如果不需要,就头插法将entry对象插入到链表中。1.8先判断当前位置node类型,链表node,还是红黑树node,如果是红黑树node,就需要将key,value封装成红黑树node,添加到红黑树中。
4,也会判断是否已经存在这个key,如果存在只需要更新value
5,如果该位置是个链表node,就需要将key、value封装成链表node,通过尾插法添加到链表最后。
6,插入后如果链表的长度超过8的话看是否需要树化(如果数组容量小于64的话,就只会进行扩容,大于64才会转换为红黑树)
7,再插入链表或者红黑树之后,在判断是否需要扩容,如果需要扩容就扩容,如果不需要就结束put方法
总之,1.7是先扩容,再插入节点;1.8是先插入节点,再扩容

HashMap中的put方法工作原理
1:当传入一个k-v的时候,首先会根据hash()方法计算一个hash值(这个值不决定在数组的位置)
2:在put的时候才会进行数组的初始化,判断数组是否存在,如果不存在就调用resize()方法创建默认数组容量为16的数组
3:通过key的hash值与(数组最大索引-1)进行位运算,确定在数组中的位置
4:获取该位置是否有元素,如果没有元素就创建一个新的Node节点元素存放在该数组中
5:如果该位置有元素的话就判断put进来的key与当前数组Node节点的key是否相同,如果相同的话就就进行值的替换。
6:当前的条件没有成立的话,判断该位置是红黑树还是链表
7:如果是红黑树,那么就把当前节点放在红黑树上面
8:如果是链表的话,就遍历该链表,把Node追加在链表中
9:这个时候也会去判断链表的长度,如果链表的长度超过8的话看是否需要树化(
会调用treeifyBin方法,如果数组容量小于64的话,就只会进行扩容,大于64才会转换为红黑树
)
10:返回覆盖的值
在这里插入图片描述

HashMap 中 hash 函数是怎么实现的?还有哪些hash函数的实现方式?

对于key的hashCode方法的值,结合数组长度进行无符号右移然后做异或运算、与运算计算出索引。还有平方取中法,伪随机数法或取余数法。这三种效率都比较低。而位运算效率是最高的。

如果两个键的 hashCode 相同,如何存储键值对?

会产生哈希冲突。
此时结合equals()方法比较key值内容是否相同
如果相同(比如key都是"重地")则替换旧的 value;
key值不相等(比如key是"重地",“通话”)继续向下和其他的数据的key进行比较,如果都不相等,则划出一个结点存储数据,这种方式称为拉链法。链表长度超过8并且数组长度超过64,就转换为红黑树存储。

什么是哈希碰撞,如何解决哈希碰撞?

只要两个元素的 key计算的哈希码值相同就会发生哈希碰撞。jdk8 之前使用链表解决哈希碰撞。jdk8之后使用链表 + 红黑树解决哈希碰撞。

为什么数组长度必须是 2 的 n 次幂?如果输入值不是 2 的幂比如 10 会怎么样?

当向 HashMap 中添加一个元素的时候,需要根据 key 的 hash 值,去确定其在数组中的具体位置。HashMap 为了存取高效,减少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现的关键就在把数据存到哪个链表中的算法。

这个算法实际就是取模,hash % length,计算机中直接求余效率不如位移运算。所以源码中做了优化,使用 hash & (length - 1)按位与运算,而实际上 hash % length 等于 hash & ( length - 1) 的前提是 length 是 2 的 n 次幂

例如长度为 8 的时候,3 & (8 - 1) = 3,2 & (8 - 1) = 2,不同位置上,不碰撞。
按位与运算:相同的二进制数位上,都是1的时候,结果为1,;否则为0。
按位或运算:相同的二进制数位上,都是0的时候,结果为0;否则为1。

如果输入值不是 2 的幂比如 10,底层会通过右移运算、按位或运算将10转化为2的4次幂=16。数组长度最大是2的30次幂。

为什么 Map 桶中结点个数超过 8 才转为红黑树?

TreeNodes 占用空间是普通 Nodes两倍,所以只有当桶(数组上)包含足够多的结点时才会转成 TreeNodes,而是否足够多就是由 TREEIFY_THRESH〇LD的值决定的。当 bin 中结点数变少时,又会转成普通的 bin。并且我们查看源码的时候发现,链表长度达到 8 就转成红黑树,当长度降到 6 就转成普通 bin

这样就解释了为什么不是一开始就将其转换为 TreeNodes,而是需要一定结点数才转为 TreeNodes,说白了就是权衡空间和时间

红黑树的平均查找长度是 log(n),如果长度为 8,平均查找长度为 log(8) = 3链表的平均查找长度为 n/2,当长度为 8 时,平均查找长虔为 8/2 = 4,这才有转换成树的必要;链表长度如果是小于等于 6, 6/2 = 3,而 log(6) = 2.6,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

// 当桶(bucket)上的结点数大于这个值时会转为红黑树
static final int TREEIFY_THRESHOLD = 8;

// 当桶(bucket)上的结点数小于这个值,树转为链表
static final int UNTREEIFY_THRESHOLD = 6;

// 桶中结构转化为红黑树对应的数组长度最小的值
static final int MIN_TREEIFY_CAPACITY = 64;

HashMap实现逻辑?

数组加链表实现的,
数组中每个元素都是链表,链表中每个元素又是一个entry对象
entry对象用来存储真正的K-V
里面有两个比较重要的方法get(),put()
put()在存储K-V时,首先会调用hash(),可以得到一个key的hash值,hash值与(length - 1)按位与运算,得到数组下标
找到相应下标下插入的每一个key进行equals()比较,如果相等就把value进行更新。如果不相等就把新的K-V值put到链表中去
put过程中如果存放的K-V超过了,数组元素16*负载因子0.75时,就进行扩容
如果链表上存储的K-V超过阈值8时,并且数组长度超过64时,就会自动转化为红黑树
get()方法和put()比较类似,同样也会先去调用hash()与(length - 1)按位与运算,得到数组下标
遍历下标对应的元素,进行equals()比较,如果key相同就把value取出返回给用户

红黑树?

红黑树是二叉查找树的一种,查找算法相当于二分查找
红黑树时间复杂度O(log n),如果长度为 8,平均查找长度为 log(8) = 3,在数据比较多时,会比链表的时间复杂度O(n),链表的平均查找长度为 n/2,当长度为 8 时,平均查找长虔为 8/2 = 4要好很多
红黑树结构是“小中大,左中右”,某个节点上数据,比它小的都在左边,比它大的都在右边
所以红黑树查找快,但是插入时因为需要维护红黑树结构,所以相对慢

HashMap可不可以不使用链表?

HashMap之所以没有一开始就使用红黑树,应该是时间和空间的折中考虑
在hash冲突比较小时,即使转化为红黑树,在时间复杂中所产生的效果,也并不是特别大
put时效率会降低,因为每次都要进行比较复杂的红黑树这种旋转算法和旋转操作,
空间上每个节点需要来维护更多的指针。
HashMap之所以选择红黑树而不是二叉搜索树,二叉树在一些极端情况下,会变成倾斜结构,查找效率就退化成链表差不多
红黑树是一种平衡树,可以防止这种退化
红黑树又不像其他完全的平衡二叉树有着严格的平衡条件,
所以,红黑树插入效率要比完全的平衡二叉树要高。
所以说,HashMap选择红黑树,既可以避免极端情况下的退化,也可以兼顾查询和插入的效率

HashMap是线程安全的么?

HashMap是针对单线程设计的,所以是线程不安全
多线程并发环境下,可以使用ConcurrentHashMap

ConcurrentHashMap?

Java7Java8
数据结构采用Segment分段锁来实现数组+链表+红黑树
并发度每个Segment独立加锁,最大并发个数就是Segment的个数数组长度
并发原理Segment分段锁来保证现成安全,Segment继承自ReenreantLock采用Node+CAS+synchronized保证线程安全
hash碰撞链表链表+红黑树
查询时间复杂度O(n),n为链表长度,比如链表长度为8,那么时间复杂度为4O(log(n)),n为树的节点个数,比如节点个数=8,那么时间复杂度为3

JDK1.7底层使用数组+链表实现的,使用了分段锁来保证线程安全,
将数组分成了16段,给每个Segment来配一把锁,在读每个Segment时,就要先获取对应的锁
它是最多能有16个线程去并发操作
JDK1.8和HashMap一样引入了红黑树,在并发处理方面采用CAS+synchronized关键字来实现一种更加细粒度的锁

JDK1.7中ConcurrentHashMap中利用ReenreantLock tryLock()去尝试加锁,加锁过程中,同时遍历链表,创建node对象,如果一切准备工作都做完了,tryLock()还没有加到锁,就再重试64次(一半再来查看链表头部、尾部是否有新的元素插入),还没加到就使用lock()阻塞加锁,如果没有拿到锁,后边的代码不会执行。

Segment继承了ReentrantLock,
1,先根据key,算出对应的Segment数组的下标index
2,获取index位置上的锁,segments[index].lock();
3,segments[index].put(key,value)–entry–数组或链表
4,释放index位置上的锁,segments[index].unlock();

创建Segment线程中,用到了乐观锁,seg = UNSAFE.getObjectValitie()

ConcurrentHashMap扩容是Segment内部的数组的扩容,Segment本身不扩容

CAS
保证多线程环境下,对于共享变量修改的一个原子性
Unsafe类中方法全称是compare and swap比较相同再交换
一种乐观锁的实现机制,预期值和希望更改之后的值
线程1,希望把0改成1,cpu会比较原本值是不是0,如果已经被别的线程改成1,线程1的修改操作就会失败,然后再自旋重新修改

CAS可以将比较和交换转换为原子操作,这个原子操作直接由CPU保证。CAS可以保证共
享变量赋值时的原子操作。CAS操作依赖3个值:内存中的值V,旧的预估值X,要修改的新值B,如果旧
的预估值X等于内存中的值V,就将新的值B保存到内存中。
适合竞争不激烈,多核cpu的场景下。1. 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一。
2. 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响。

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

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

相关文章

电商卖家如何从1688批量采集商品信息并铺货到自己店铺?

目前&#xff0c;各大电商平台严查无货源&#xff0c;不管是已经开店还是准备开店的卖家&#xff0c;想要店铺长久发展&#xff0c;都需要从1688找授权分销的厂家拿货源铺货。 然而很多新手卖家在1688找货源时&#xff0c;不知道如何找到物美价廉的源头厂货&#xff0c;选好货…

SpringBoot实现RabbitMQ的通配符交换机(SpringAMQP 实现Topic交换机)

文章目录 pomyml生产者消费者 Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符&#xff01; Routingkey 一般都是有一个或多个单词组成&#xff0c;多个单词…

关于Linux下的进程创建与终止(进程篇 - 涉及写时拷贝,fork函数)

目录 创建进程 写时拷贝 fork函数 进程终止 进程终止时&#xff0c;操作系统都做了什么&#xff1f; 进程终止的常见方式有哪些&#xff1f; 如何使用代码终止掉一个进程&#xff1f; 创建进程 写时拷贝 在了解下面的内容之前&#xff0c;我们需要先聊一聊写时拷贝这一…

MySQL-基本SQL语句编写:运算符练习

运算符练习 1.选择工资不在5000到12000的员工的姓名和工资 SELECT last_name,salary FROM employees #where salary not between 5000 and 12000; WHERE salary < 5000 OR salary > 12000;2.选择在20或50号部门工作的员工姓名和部门号 SELECT last_name,department_id…

ChatGPT 之联盟营销

原文&#xff1a;ChatGPT for Affiliate Marketing 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第二章 制定转化对话 制定转化对话是每个营销人员和企业所有者都应该掌握的关键技能。它涉及创建和传递引人入胜的信息&#xff0c;吸引您的受众并激励他们采取行动。…

【windows】--- nginx 超详细安装并配置教程

目录 一、下载 nginx二、安装三、查看是否安装成功四、配置五、关闭 nginx六 负载均衡七 配置静态资源1. 根目录下的子目录(root)2.完全匹配(alias) 刷新配置&#xff08;不必重启nginx&#xff09;八、后端鉴权 一、下载 nginx 打开 nginx 的官网&#xff1a;nginx.org/ &…

【操作系统】STM32-操作系统——持续更新

【操作系统】STM32-操作系统——持续更新 文章目录 前言一、ucosii二、freertos1.介绍2.移植 总结 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、ucosii UCOSII移植到STM32F103C8T6上之移植记录&#xff08;一&#xff09; UCOSII移植到ST…

GIS 如何帮助减少交通排放

GIS 可通过多种方式帮助减少交通排放。从智能路线和减少拥堵&#xff0c;到鼓励人们骑自行车或步行&#xff0c;以及帮助指导政府交通政策&#xff0c;GIS 正在帮助改善空气质量。 GIS 长期以来一直被用来提高各行业多种类型车辆行驶的效率&#xff0c;并将继续这样做。通过分…

CURL状态码60问题解决

问题说明 就是没法验证ssl证书是否合法 时间长了&#xff0c;系统自带的ca肯定不会有新的ca机构增加 解决方法 CentOS下 yum update ca-certificates 或者手动更新指定位置上的ca文件 手动下载 https://curl.se/docs/caextract.html 临时处理方案(不推荐) curl_setopt(…

网站统计中的数据收集原理及实现

网站数据统计分析工具是网站站长和运营人员经常使用的一种工具&#xff0c;比较常用的有谷歌分析、百度统计和腾讯分析等等。所有这些统计分析工具的第一步都是网站访问数据的收集。目前主流的数据收集方式基本都是基于javascript的。本文将简要分析这种数据收集的原理&#xf…

C++ 静态库与动态库的生成和使用:基于 VS Studio 生成 newmat 矩阵库的静态库与动态库

文章目录 Part.I IntroductionChap.I 预备知识Chap.II 静态库与动态库区分 Part.II 静态库的生成与使用 (newmat)Chap.I 生成静态库Chap.II 使用静态库 Part.III 动态库的生成与使用 (newmat)Chap.I 生成动态库Chap.II 使用动态库 Part.IV 文件内容Chap.I test.cpp (静态库)Cha…

linux常用目录结构(目录命令)--6986字详谈

前面与大家讨论了linux的发展与由来&#xff08;这一块挺多的&#xff0c;小编还没有编写完成&#xff0c;希望大家理解&#xff09;&#xff0c;紧接着谈到了vmware安装及运行所存在的故障&#xff08;鉴定错误&#xff0c;虚拟机没有网&#xff0c;蓝屏等常见现象的总结及处理…

学透Spring Boot — 004. Spring Boot Starter机制和自动配置机制

如果你项目中一直用的是 Spring Boot&#xff0c;那么恭喜你没有经历过用 Spring 手动集成其它框架的痛苦。 都说 Spring Boot 大大简化了 Spring 框架开发 Web 应用的难度&#xff0c;这里我们通过配置 Hibernate 的两种方式来深刻体会这一点&#xff1a; 使用 Spring 框架集…

centos7.2系统部署ZooKeeper集群和Kafka集群(集群应用系统商城前置环境)

本次实验将使用centos7.2系统部署部署ZooKeeper集群因为Kafka依赖于ZooKeeper&#xff0c;所以我们一并进行部署。 实验所示的资源软件已上传至百度网盘&#xff0c;需要自取。 链接&#xff1a;https://pan.baidu.com/s/1a-7_iAIX0DBAMkF9bhiTcA?pwd2333 提取码&#xff1…

C++:stack类和queue类

stack的介绍和使用 1. stack 是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。 2. stack 是作为容器适配器被实现的&#xff0c;容器适配器即是对特定类封装作为其底层的容器&#xff0c;并…

壁纸小程序Vu3(预览页面:弹窗)

1.展示跳转后的分类列表图片 classlist.vue <template><view class"classlist"><view class"content"><navigator class"item" v-for"item in 10"><image src"../../common/images/64.png" mode…

DFS(排列数字、飞机降落、选数、自然数的拆分)

注&#xff1a;1.首先要知道退出条件 2.还原现场 典型&#xff1a;全排列 题目1&#xff1a; 代码&#xff1a; #include<bits/stdc.h> using namespace std; int a[1005],p[1005],v[1005]; int n; void dfs(int x) {//此次dfs结束条件,即搜到底 if(xn1){for(int i1;i&…

AcWing刷题-计算系数

计算系数 代码&#xff1a; MOD 10007 a, b, k, n, m [int(x) for x in input().split()]def pow(a, n):r 1 % MODwhile n:if n & 1: r r * a % MODa a * a % MODn >> 1return rdef C(n, m):r 1 % MODfor i, j in zip(range(n, -1, -1), range(1, m 1)):r …

CLR学习

视频链接&#xff1a;《CLR十分钟》系列之CLR运行模型_哔哩哔哩_bilibili 什么是 CLR 公共语言运行时&#xff08;Common Language Runtime CLR&#xff09; 是一个可有多种编程语言使用的 运行时&#xff0c;CLR 的核心功能&#xff08;比如 内存管理&#xff0c;程序集加载…

解锁网络安全新境界:雷池WAF社区版让网站防护变得轻而易举!

网站运营者的救星&#xff1a;雷池WAF社区版 ️ 嘿朋友们&#xff01;今天我超级激动要跟你们分享一个神器——雷池WAF社区版。这个宝贝对我们这帮网站运营者来说&#xff0c;简直就是保护伞&#xff01; 智能语义分析技术&#xff1a;超级侦探上线 先说说为啥我这么稀饭它。雷…