线程面试题-1

news/2024/11/17 3:27:02/

看的博客里面总结的线程的八股文

1、线程安全的集合有哪些?线程不安全的呢?

线程安全的:

  • Hashtable:比HashMap多了个线程安全。

  • ConcurrentHashMap:是一种高效但是线程安全的集合。

  • Vector:比Arraylist多了个同步化机制。

  • Stack:栈,也是线程安全的,继承于Vector。

线性不安全的:

  • HashMap

  • Arraylist

  • LinkedList

  • HashSet

  • TreeSet

  • TreeMap

2、Arraylist与 LinkedList 异同点?

  • 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

  • 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构;

  • 插入和删除是否受元素位置的影响: ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行 add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话( add(int index, E element) )时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。

  • 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而ArrayList 实现了RandmoAccess 接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于 get(int index) 方法)。

  • 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。

3、ArrayList 与 Vector 区别?

  • Vector是线程安全的,ArrayList不是线程安全的。其中,Vector在关键性的方法前面都加了synchronized关键字,来保证线程的安全性。如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。

  • ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍,这样ArrayList就有利于节约内存空间。

4、说一说ArrayList 的扩容机制?

ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。

默认情况下,新的容量会是原容量的1.5倍

以JDK1.8为例说明:

public boolean add(E e) {//判断是否可以容纳e,若能,则直接添加在末尾;若不能,则进行扩容,然后再把e添加在末尾ensureCapacityInternal(size + 1); // Increments modCount!!//将e添加到数组末尾elementData[size++] = e;return true;}
/* 每次在add()一个元素时,arraylist都需要对这个list的容量进行一个判断。通过
ensureCapacityInternal()方法确保当前ArrayList维护的数组具有存储新元素的能力,经过处理之后
将元素存储在数组elementData的尾部*/
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {modCount++;
/* 若ArrayList已有的存储能力满足最低存储要求,则返回add直接添加元素;如果最低要求的
存储能力>ArrayList已有的存储能力,这就表示ArrayList的存储能力不足,因此需要调用 grow();方法
进行扩容*/if (minCapacity - elementData.length > 0)grow(minCapacity);
}
private void grow(int minCapacity) {// 获取elementData数组的内存空间长度int oldCapacity = elementData.length;// 扩容至原来的1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);//校验容量是否够if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//若预设值大于默认的最大值,检查是否溢出if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 调用Arrays.copyOf方法将elementData数组指向新的内存空间//并将elementData的数据复制到新的内存空间elementData = Arrays.copyOf(elementData, newCapacity);
}

5、Array 和 ArrayList 有什么区别?什么时候该应 Array而不是 ArrayList 呢?

  • Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。

  • Array 大小是固定的,ArrayList 的大小是动态变化的。

  • ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

6、HashMap的底层数据结构是什么?

在JDK1.7 和JDK1.8 中有所差别:

在JDK1.7 中,由“数组+链表”组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。

在JDK1.8 中,由“数组+链表+红黑树”组成。当链表过长,则会严重影响 HashMap 的性能,红黑树搜索时间复杂度是 O(logn),而链表是糟糕的 O(n)。因此,JDK1.8 对数据结构做了进一步的优化,引入了红黑树,链表和红黑树在达到一定条件会进行转换:

  • 当链表超过 8 且数据总量超过 64 才会转红黑树。

  • 将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树,以减少搜索时间。

在这里插入图片描述

7、解决hash冲突的办法有哪些?HashMap用的哪种?

解决Hash冲突方法有:开放定址法、再哈希法、链地址法(拉链法)、建立公共溢出区。HashMap中采用的是 链地址法

  • 开放定址法也称为 再散列法 ,基本思想就是,如果 p=H(key) 出现冲突时,则以 p 为基础,再次hash, p1=H§ ,如果p1再次出现冲突,则以p1为基础,以此类推,直到找到一个不冲突的哈希地址 pi 。 因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素,而且因为存在再次hash,所以 只能在删除的节点上做标记,而不能真正删除节点。

  • 再哈希法(双重散列,多重散列),提供多个不同的hash函数,当 R1=H1(key1) 发生冲突时,再计算 R2=H2(key1) ,直到没有冲突为止。 这样做虽然不易产生堆集,但增加了计算的时间。

  • 链地址法(拉链法),将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。

  • 建立公共溢出区,将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。

8、为什么在解决 hash 冲突的时候,不直接用红黑树?而选择先用链表,再转红黑树?

因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于 8 个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于 8 个的时候, 红黑树搜索时间复杂度是 O(logn),而链表是 O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。

因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。

9、HashMap默认加载因子是多少?为什么是 0.75,不是0.6 或者 0.8 ?

回答这个问题前,我们来先看下HashMap的默认构造函数:

int threshold; // 容纳键值对的最大值
final float loadFactor; // 负载因子
int modCount;
int size;

Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳键值对的最大值。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。

默认的loadFactor是0.75,0.75是对空间和时间效率的一个平衡选择,一般不要修改,除非在时间和空间比较特殊的情况下 :

  • 如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值 。

  • 相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。

追溯下作者在源码中的注释(JDK1.7):

As a general rule, the default load factor (.75) offers a good tradeoff between
time and space costs. Higher values decrease the space overhead but increase the
lookup cost (reflected in most of the operations of the HashMap class, including
get and put). The expected number of entries in the map and its load factor
should be taken into account when setting its initial capacity, so as to
minimize the number of rehash operations. If the initial capacity is greater
than the maximum number of entries divided by the load factor, no rehash
operations will ever occur.

翻译过来大概的意思是:作为一般规则,默认负载因子(0.75)在时间和空间成本上提供了很好的折衷。较高的值会降低空间开销,但提高查找成本(体现在大多数的HashMap类的操作,包括get和put)。设置初始大小时,应该考虑预计的entry数在map及其负载系数,并且尽量减少rehash操作的次数。如果初始容量大于最大条目数除以负载因子,rehash操作将不会发生。

10、HashMap 的put方法流程?

以JDK1.8为例,简要流程如下:

  1. 首先根据 key 的值计算 hash 值,找到该元素在数组中存储的下标;

  2. 如果数组是空的,则调用 resize 进行初始化;

  3. 如果没有哈希冲突直接放在对应的数组下标里;

  4. 如果冲突了,且 key 已经存在,就覆盖掉 value;

  5. 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;

  6. 如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,链表插入键值对,若 key 存在,就覆盖掉 value。

在这里插入图片描述


http://www.ppmy.cn/news/1051262.html

相关文章

ClickHouse(二十一):Clickhouse SQL DDL操作-临时表及视图

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术&#xff0c;IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &…

不负众望~历时4年修炼,这本册子终于成书了(文末赠书)

名字&#xff1a;阿玥的小东东 学习&#xff1a;Python、C/C 主页链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 精进Spring Boot首选读物 “小册”变“大书”&#xff0c;彻底弄懂Spring Boot 全方位配套资源…

07 mysql5.6.x docker 启动, 无 config 目录导致客户端连接认证需要 10s

前言 呵呵 最近再一次 环境部署的过程中碰到了这样的一个问题 我基于 docker 启动了一个 mysql 服务, 然后 挂载出了 数据目录 和 配置目录, 没有手动复制配置目录出来, 所以配置目录是空的 然后 我基于 docker 启动了一个 nacos, 配置数据库设置为上面的这个 mysql 然后 启…

uniapp 自定义手机顶部状态栏(适配状态栏高度)

开启页面自定义导航栏功能 uniapp 在 pages.json 页面设置了全局的 globalStyle 的 "navigationStyle": "custom" 或单页面的 style 的 "navigationStyle": "custom" 之后页面顶部就没有自带的导航栏了&#xff0c;这时用户可自定义该…

剑指Offer33.二叉搜索树的后序遍历序列 C++

1、题目描述 输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true&#xff0c;否则返回 false。假设输入的数组的任意两个数字都互不相同。 参考以下这颗二叉搜索树&#xff1a; 5 / 2 6 / 1 3 示例 1&#xff1a; 输入: [1,6,3,2,…

Python XML处理中级篇:深入探索lxml库

lxml库是Python中处理XML和HTML文档的强大库&#xff0c;提供了丰富的API以进行各种操作。在初级篇中&#xff0c;我们介绍了如何使用lxml库解析、访问和修改XML文档。在这篇中级篇中&#xff0c;我们将更深入地探讨如何使用lxml库&#xff0c;包括如何创建XML文档&#xff0c;…

如何区分线上支付和线下支付

线上支付和线下支付是根据支付场景和渠道进行区分的。以下是区分线上支付和线下支付的一些关键特征&#xff1a; 1. 场景和渠道&#xff1a; 线上支付&#xff1a;线上支付通常发生在互联网环境中&#xff0c;用户通过电子设备&#xff08;如电脑、手机、平板等&#xff09;进行…

Alibaba-Easyexcel 使用总结

简介 简介 EasyExcel 是一个基于 Java 的简单、省内存的读写 Excel 的开源项目&#xff0c;在尽可能节约内存的情况下支持读写百 M 的 Excel。 但注意&#xff0c;其不支持&#xff1a; 单个文件的并发写入、读取读取图片宏 常见问题 Excel 术语 Sheet&#xff0c;工作薄…