Java 八股文-集合框架篇

news2024/11/28 11:46:18

Java 集合框架

一、常见集合

1.说说有哪些常见集合?

集合相关类和接口都在java.util中,主要分为3种:List(列表)、Map(映射)、Set(集)。
在这里插入图片描述

其中Collection是集合ListSet的父接口,它主要有两个子接口:

  • List:存储的元素有序,可重复。
  • Set:存储的元素不无序,不可重复。

Map是另外的接口,是键值对映射结构的集合。

二、List

List,也没啥好问的,但不排除面试官剑走偏锋,比如面试官也看了我这篇文章。

2.ArrayList和LinkedList有什么区别?

(1)数据结构不同

  • ArrayList基于数组实现

  • LinkedList基于双向链表实现
    在这里插入图片描述
    (2) 多数情况下,ArrayList更利于查找,LinkedList更利于增删

  • ArrayList基于数组实现,get(int index)可以直接通过数组下标获取,时间复杂度是O(1);LinkedList基于链表实现,get(int index)需要遍历链表,时间复杂度是O(n);当然,get(E element)这种查找,两种集合都需要遍历,时间复杂度都是O(n)。

  • ArrayList增删如果是数组末尾的位置,直接插入或者删除就可以了,但是如果插入中间的位置,就需要把插入位置后的元素都向前或者向后移动,甚至还有可能触发扩容;双向链表的插入和删除只需要改变前驱节点、后继节点和插入节点的指向就行了,不需要移动元素。
    在这里插入图片描述
    在这里插入图片描述

注意,这个地方可能会出陷阱,LinkedList更利于增删更多是体现在平均步长上,不是体现在时间复杂度上,二者增删的时间复杂度都是O(n)

(3)是否支持随机访问

  • ArrayList基于数组,所以它可以根据下标查找,支持随机访问,当然,它也实现了RandmoAccess 接口,这个接口只是用来标识是否支持随机访问。
  • LinkedList基于链表,所以它没法根据序号直接获取元素,它没有实现RandmoAccess 接口,标记不支持随机访问。
    (4)内存占用,ArrayList基于数组,是一块连续的内存空间,LinkedList基于链表,内存空间不连续,它们在空间占用上都有一些额外的消耗:
  • ArrayList是预先定义好的数组,可能会有空的内存空间,存在一定空间浪费
  • LinkedList每个节点,需要存储前驱和后继,所以每个节点会占用更多的空间

3.ArrayList的扩容机制了解吗?

ArrayList是基于数组的集合,数组的容量是在定义的时候确定的,如果数组满了,再插入,就会数组溢出。所以在插入时候,会先检查是否需要扩容,如果当前容量+1超过数组长度,就会进行扩容。

ArrayList的扩容是创建一个1.5倍的新数组,然后把原数组的值拷贝过去。

在这里插入图片描述

4.ArrayList怎么序列化的知道吗?为什么用transient修饰数组?

ArrayList的序列化不太一样,它使用transient修饰存储元素的elementData的数组,transient关键字的作用是让被修饰的成员属性不被序列化。

为什么最ArrayList不直接序列化元素数组呢?

出于效率的考虑,数组可能长度100,但实际只用了50,剩下的50不用其实不用序列化,这样可以提高序列化和反序列化的效率,还可以节省内存空间。

那ArrayList怎么序列化呢?

ArrayList通过两个方法readObjectwriteObject自定义序列化和反序列化策略,实际直接使用两个流ObjectOutputStreamObjectInputStream来进行序列化和反序列化。
在这里插入图片描述

5.快速失败(fail-fast)和安全失败(fail-safe)了解吗?

快速失败(fail—fast):快速失败是Java集合的一种错误检测机制

  • 在用迭代器遍历一个集合对象时,如果线程A遍历过程中,线程B对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
  • 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
  • 注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
  • 场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改),比如ArrayList 类。

安全失败(fail—safe)

  • 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
  • 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
  • 缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
  • 场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改,比如CopyOnWriteArrayList类。

6.有哪几种实现ArrayList线程安全的方法?

fail-fast是一种可能触发的机制,实际上,ArrayList的线程安全仍然没有保证,一般,保证ArrayList的线程安全可以通过这些方案:

  • 使用 Vector 代替 ArrayList。(不推荐,Vector是一个历史遗留类)
  • 使用 Collections.synchronizedList 包装 ArrayList,然后操作包装后的 list。
  • 使用 CopyOnWriteArrayList 代替 ArrayList。
  • 在使用 ArrayList 时,应用程序通过同步机制去控制 ArrayList 的读写。

7.CopyOnWriteArrayList了解多少?

CopyOnWriteArrayList就是线程安全版本的ArrayList。

它的名字叫CopyOnWrite——写时复制,已经明示了它的原理。

CopyOnWriteArrayList采用了一种读写分离的并发策略。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。
在这里插入图片描述

三、Map

Map中,毫无疑问,最重要的就是HashMap,面试基本被盘出包浆了,各种问法,一定要好好准备。

8.能说一下HashMap的数据结构吗?

JDK1.7的数据结构是数组+链表,JDK1.7还有人在用?不会吧……

说一下JDK1.8的数据结构吧:

JDK1.8的数据结构是数组+链表+红黑树

数据结构示意图如下:
在这里插入图片描述
其中,桶数组是用来存储数据元素,链表是用来解决冲突,红黑树是为了提高查询的效率。

  • 数据元素通过映射关系,也就是散列函数,映射到桶数组对应索引的位置
  • 如果发生冲突,从冲突的位置拉一个链表,插入冲突的元素
  • 如果链表长度>8&数组大小>=64,链表转为红黑树
  • 如果红黑树节点个数<6 ,转为链表

9.你对红黑树了解多少?为什么不用二叉树/平衡树呢?

红黑树本质上是一种二叉查找树,为了保持平衡,它又在二叉查找树的基础上增加了一些规则:

  1. 每个节点要么是红色,要么是黑色;
  2. 根节点永远是黑色的;
  3. 所有的叶子节点都是是黑色的(注意这里说叶子节点其实是图中的 NULL 节点);
  4. 每个红色节点的两个子节点一定都是黑色;
  5. 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;
    在这里插入图片描述

所以不用二叉树:

红黑树是一种平衡的二叉树,插入、删除、查找的最坏时间复杂度都为 O(logn),避免了二叉树最坏情况下的O(n)时间复杂度。

之所以不用平衡二叉树:

平衡二叉树是比红黑树更严格的平衡树,为了保持保持平衡,需要旋转的次数更多,也就是说平衡二叉树保持平衡的效率更低,所以平衡二叉树插入和删除的效率比红黑树要低。

10.红黑树怎么保持平衡的知道吗?

红黑树有两种方式保持平衡:旋转染色

  • 旋转:旋转分为两种,左旋和右旋
    在这里插入图片描述
    在这里插入图片描述
  • 染⾊:
    在这里插入图片描述

11.HashMap的put流程知道吗?

先上个流程图吧:
在这里插入图片描述

  1. 首先进行哈希值的扰动,获取一个新的哈希值。(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  2. 判断tab是否位空或者长度为0,如果是则进行扩容操作。
if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
  1. 根据哈希值计算下标,如果对应小标正好没有存放数据,则直接插入即可否则需要覆盖。tab[i = (n - 1) & hash])
  2. 判断tab[i]是否为树节点,否则向链表中插入数据,是则向树中插入节点。
  3. 如果链表中插入节点的时候,链表长度大于等于8,则需要把链表转换为红黑树。treeifyBin(tab, hash);
  4. 最后所有元素处理完成后,判断是否超过阈值;threshold,超过则扩容。

12.HashMap怎么查找元素的呢?

先看流程图:
在这里插入图片描述
HashMap的查找就简单很多:

  1. 使用扰动函数,获取新的哈希值
  2. 计算数组下标,获取节点
  3. 当前节点和key匹配,直接返回
  4. 否则,当前节点是否为树节点,查找红黑树
  5. 否则,遍历链表查找

13.HashMap的哈希/扰动函数是怎么设计的?

HashMap的哈希函数是先拿到 key 的hashcode,是一个32位的int类型的数值,然后让hashcode的高16位和低16位进行异或操作。

static final int hash(Object key) {
	int h;
	// key的hashCode和key的hashCode右移16位做异或运算
	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这么设计是为了降低哈希碰撞的概率。

14.为什么哈希/扰动函数能降hash碰撞?

因为 key.hashCode() 函数调用的是 key 键值类型自带的哈希函数,返回 int 型散列值。int 值范围为 -2147483648~2147483647,加起来大概 40 亿的映射空间。

只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。

假如 HashMap 数组的初始大小才 16,就需要用之前需要对数组的长度取模运算,得到的余数才能用来访问数组下标。

源码中模运算就是把散列值和数组长度 - 1 做一个 “与&” 操作,位运算比取余 % 运算要快。

bucketIndex = indexFor(hash, table.length);

static int indexFor(int h, int length) {
     return h & (length-1);
}

顺便说一下,这也正好解释了为什么 HashMap 的数组长度要取 2 的整数幂。因为这样(数组长度 - 1)正好相当于一个 “低位掩码”。 操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问。以初始长度 16 为例,16-1=15。2 进制表示是 0000 0000 0000 0000 0000 0000 0000 1111。和某个散列值做 与 操作如下,结果就是截取了最低的四位值。
在这里插入图片描述
这样是要快捷一些,但是新的问题来了,就算散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。如果散列本身做得不好,分布上成等差数列的漏洞,如果正好让最后几个低位呈现规律性重复,那就更难搞了。

这时候 扰动函数 的价值就体现出来了,看一下扰动函数的示意图:
在这里插入图片描述
右移 16 位,正好是 32bit 的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

15.为什么HashMap的容量是2的倍数呢?

  • 第一个原因是为了方便哈希取余:

将元素放在table数组上面,是用hash值%数组大小定位位置,而HashMap是用hash值&(数组大小-1),却能和前面达到一样的效果,这就得益于HashMap的大小是2的倍数,2的倍数意味着该数的二进制位只有一位为1,而该数-1就可以得到二进制位上1变成0,后面的0变成1,再通过&运算,就可以得到和%一样的效果,并且位运算比%的效率高得多

HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞。

  • 第二个方面是在扩容时,利用扩容后的大小也是2的倍数,将已经产生hash碰撞的元素完美的转移到新的table中去

我们可以简单看看HashMap的扩容机制,HashMap中的元素在超过负载因子*HashMap大小时就会产生扩容。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ...
        ++modCount;
        if (++size > threshold)		// put时,当大小超过threshold,就会扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }

16.如果初始化HashMap,传一个17的值new HashMap<>,它会怎么处理?

简单来说,就是初始化时,传的不是2的倍数时,HashMap会向上寻找离得最近的2的倍数,所以传入17,但HashMap的实际容量是32。

我们来看看详情,在HashMap的初始化中,有这样⼀段⽅法;

public HashMap(int initialCapacity, float loadFactor) {
 ...
 this.loadFactor = loadFactor;
 this.threshold = tableSizeFor(initialCapacity);
}
  • 阀值 threshold ,通过⽅法 tableSizeFor 进⾏计算,是根据初始化传的参数来计算的。
  • 同时,这个⽅法也要要寻找⽐初始值⼤的,最⼩的那个2进制数值。⽐如传了17,我应该找到的是32。
static final int tableSizeFor(int cap) {
 int n = cap - 1;
 n |= n >>> 1;
 n |= n >>> 2;
 n |= n >>> 4;
 n |= n >>> 8;
 n |= n >>> 16;
 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
  • MAXIMUM_CAPACITY = 1 << 30,这个是临界范围,也就是最⼤的Map集合。
  • 计算过程是向右移位1、2、4、8、16,和原来的数做|运算,这主要是为了把⼆进制的各个位置都填上1,当⼆进制的各个位置都是1以后,就是⼀个标准的2的倍数减1了,最后把结果加1再返回即可。

以17为例,看一下初始化计算table容量的过程:
在这里插入图片描述

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

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

相关文章

AI热度降温?揭秘加德纳技术成熟度曲线与AI发展阶段

一文解决你所有对 AI 的焦虑 近期&#xff0c;我们发现ChatGPT的热度似乎不如前几个月。许多人尝试了这个技术&#xff0c;觉得它非常强大&#xff0c;但似乎与自己的日常生活和工作关系不大&#xff0c;因此逐渐失去了兴趣。 然而&#xff0c;这实际上是技术发展的正常周期&…

学术界用ChatGPT写论文,真的靠谱吗?

写论文时&#xff0c;赵铭用ChatGPT查询了国内外云计算技术的研究进展&#xff0c;并请它一一详细介绍。他用搜索引擎简单核实了真实性&#xff0c;润色后就放进了论文中&#xff0c;“我感觉它说的东西都是一些现状&#xff0c;也没什么好调整的&#xff0c;就直接用了&#x…

传感器-陀螺仪芯片

https://www.cnblogs.com/tomatokely/p/16392997.html 陀螺仪芯片厂家&#xff1a; ST ICM42605, MPU 6050, Murata SCL3300/3400 陀螺仪可选量程&#xff1a; 15.6/31.2/62.5/125/250/500/1000/2000 dps 加速度可选量程&#xff1a; 2/4/8/16 g 计算单位&#xff1a; 陀…

vue安裝及配置 nodejs安装配置

vue安装及配置 vue安装步骤 nodejs安装 安装nodejs环境&#xff1a;https://nodejs.org/en/ 查看node版本&#xff1a;node-v vue3.0需要使用node 8版本以上 npm镜像配置 npm是nodejs内置的资源管理器 npm两个镜像&#xff1a; 淘宝镜像&#xff1a;https://registry.npm.…

Mars3d的PolygonEntity的边框宽度outlineWidth只能是1

1.Mars3d的PolygonEntity的边框宽度只能是1 2.问题来源&#xff1a; 尝试在Mars3d官网的面的示例中修改高亮样式是&#xff0c;发现修改边框宽度为3或者是10&#xff0c;效果一致 function addDemoGraphic4(graphicLayer) { const graphic new mars3d.graphic.PolygonEntit…

车载以太网网络管理之UDPNM

前言 首先&#xff0c;请问大家几个小小问题&#xff0c;你清楚&#xff1a; 你知道UdpNm模块的主要作用是什么吗&#xff1f;UdpNm模块与其他AUTOSAR基础软件模块交互关系&#xff1b;UdpNm模块的网络管理算法&#xff0c;状态机如何运转&#xff1f;UdpNm模块的PNC功能如何…

TLE4250-2G-ASEMI代理英飞凌汽车芯片TLE4250-2G

编辑&#xff1a;ll TLE4250-2G-ASEMI代理英飞凌汽车芯片TLE4250-2G 型号&#xff1a;TLE4250-2G 品牌&#xff1a;Infineon(英飞凌) 封装&#xff1a;SCT-595-5 特性&#xff1a;驱动芯片、汽车芯片 温度范围-40C~150C 最大输入电压&#xff1a;-42 V~45 V TLE4250-2G…

跨境电商app系统开发

近年来&#xff0c;随着跨境电商行业的发展&#xff0c;越来越多的企业开始关注跨境电商app系统的开发。这些系统可以帮助企业更加高效地管理跨境电商业务&#xff0c;提高产品销售的效率&#xff0c;并且为消费者提供更加方便快捷的购物体验。 跨境电商app系统的开发需要考虑…

品牌618如何宣传,才能为业绩加油助力?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体 胡老师。 随着618年中消费季的临近&#xff0c;许多企业和品牌都已经卯足了马力&#xff0c;争取在年终狂欢中多多增加公司业绩&#xff0c;现在的618 不仅仅涉及我们的吃穿用行&#xff0c;而且各…

计算机网络开荒2.2-Socket编程

文章目录 一、Socket概述二、Socket API 概述三、WinSock常用API3.1 常用API3.1.1 WSAStartUP3.1.2 WSACleanup3.1.3 Socket3.1.4 Closesocket3.1.5 bind3.1.6 listen3.1.7 connect3.1.8 accept3.1.9 send, sendto3.1.10 recv, recvfrom3.1.11 etsockopt, getsockopt 3.2 网络字…

H3C 交换机的VXLAN二层转发配置

H3C 交换机的VXLAN二层转发配置 本篇介绍H3C交换机的VXLAN二层转发配置。 基本概念: 首先了解VXLAN&#xff08;Virtual eXtensible LAN&#xff0c;可扩展虚拟局域网络&#xff09;的基本概念。VXLAN是基于IP网络、采用“MAC in UDP”封装形式的二层VPN技术。VXLAN可以基于…

Vue.js 中的 v-bind 指令详解

Vue.js 中的 v-bind 指令 介绍 Vue.js中的v-bind指令是一种将组件的属性绑定到Vue实例的数据的方式。v-bind指令可以用于将任何组件属性绑定到Vue实例的数据上&#xff0c;例如class、style、属性等。v-bind指令允许我们动态地设置组件的属性&#xff0c;从而使组件更加灵活和…

案例精述丨Fortinet SASE 护航跨国公司中国区网络安全升级

在全球数字化转型大潮下&#xff0c;跨国公司的机构、设施、人员等全球分布式特性&#xff0c;不但带来了广域网建设的网络复杂性&#xff0c;也带来了更加严峻的安全挑战。某全球知名跨国公司&#xff0c;在中国区进行网络安全升级改造的过程中&#xff0c;采用国内某IDC运营商…

阿里巴巴序列模型梳理

SIM&#xff1a;基于搜索的用户终身行为序列建模 论文&#xff1a;《Search-based User Interest Modeling with Lifelong Sequential Behavior Data for Click-Through Rate Prediction》 下载地址&#xff1a;https://arxiv.org/abs/2006.05639 1、用户行为序列建模回顾 1…

JDK8-1-Lambda表达式(3)-函数式接口

JDK8-1-Lambda表达式&#xff08;3&#xff09;-函数式接口 有且仅有一个抽象方法的接口称为函数式接口&#xff0c;上文 中 java.util.function.Predicate 接口就是一个函数式接口&#xff0c;Java 8中引入的函数式接口定义在 java.util.function 包下 java.util.function.P…

【PWN · ret2text 格式化字符串漏洞 | NX | Canary | PIE】[深育杯 2021]find_flag

这一题最终的攻击手段可以是简单的ret2text&#xff08;后门函数给出&#xff09;&#xff0c;然而保护全开则确实让人汗颜。。。 更重要的是&#xff01;docker的程序偏移和本地不一样&#xff01;&#xff01;NSSCTF题目有问题&#xff01;&#xff01; 目录 前言 一、题目…

openEuler22+GreatSQL+dbops玩转MGR

芬达&#xff0c;《芬达的数据库学习笔记》公众号作者&#xff0c;开源爱好者&#xff0c;擅长 MySQL、ansible。 背景 openEuler 是什么 openEuler22.03 LTS 是 openEuler 社区于 2022 年 3 月发布的开源操作系统&#xff08;从系统版本的命名不难发现吧&#xff09;。openE…

apktool for mac

安装步骤 1、Apktool下载 安装apktool Apktool下载 macOS: Download Mac wrapper script (Right click, Save Link As apktool)Download apktool-2 (find newest here)Rename downloaded jar to apktool.jarMove both files (apktool.jar & apktool) to /usr/local/bin …

sqoop系列:sqoop(离线数据同步)开发案例

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;Mysql(RDBMS)与Hive/HDFS数据传输 1.1&#xff1a;列出MySQL数据有哪些数据库&#xff1a; 1.2&#xff1a;根据mysql表结构创建hive表 1.3: RDBMS导入到hdfs &#xff08;1&#xff09;条件导入 &#xff08;2&…

cisp证书含金量如何网络安全渗透测试工程师主要工作是什么?前景如何?

sp&#xff0c;国家注册信息安全专业工作员&#xff0c;由中国信息安全测评中心认证&#xff0c;作为我国目前网络安全认证之一&#xff01;cisp属于国家测评中心授予&#xff0c;目前遭到企业认可。 CISP在大部分网络安全行业变成了应聘求职的必考的证书。在信息安全行业&…