2021年12月1日30道面试题
1.ThreadLocal的作用和原理以及使用场景?
threadlocal是线程本地变量,它为每一个线程创建一个变量副本,来解决并发冲突问题
在jdk1.8以之前,每个threadlocal实例维护一个map,每个线程作为key,value保存的就是变量副本
在jdk1.8,改为每个Thread中维护一个threadlocalMap,threadlocal作为key,value保存变量副本
这样设计,threadLocalMap中的键值对数量就取决于threadlocal实例的数量,提高了访问效率。当线程被销毁,threadlocalMap也会随之销毁,减少了内存的消耗
使用场景:
spring security中,我们使用SecurityContextHolder来获取SecurityContext,
比如在springMVC中,我们通过RequestContextHolder来获取当前请求,
比如在 zuul中,我们通过ContextHolder来获取当前请求
2.内存溢出和内存泄漏
内存溢出:没有足够的内存供申请者使用
内存泄漏:内存中动态分配的内存空间由于某种原因未释放或无法释放,导致内存浪费。比如:使用完的连接没有关闭,使用完的对象没有被回收
3.线程池作用、java中有哪些线程池
线程池可以控制并发数量,实现线程的复用,也可以管理线程的生命周期
java中有四种线程池
1.cachedThreadPool:可缓存的线程池
corePoolSize的为0,maximumPoolSize为无限大,keepAliveTime为60s,超过这个线程的最大空闲时间,线程就会被销毁,线程最大数量为Integer的最大值
2.FixedThreadPool:固定线程数量的线程池
corePoolSize和maximumSize都为用户设置的值,keepAlivetime为0,最大线程数等于核心线程数
3.SingleThreadPool:单个线程的线程池
只会创建一个线程来处理任务,串行化执行任务
4.scheduledThreadPool:可调度的线程池
最大线程数是Integer的最大值,可以延迟和周期性执行任务
4.线程池的执行流程
当一个任务来了,如果有空闲的核心线程,直接使用
如果核心线程都不空闲,就将任务放入队列
如果队列满了,就会新建非核心线程来处理任务,
如果新建的非核心线程和核心线程等于最大线程数,就会触发线程池的拒绝策略
5.解释一下线程构造器的7个参数
CorePoolSize:核心线程数
MaximunPoolSize:最大线程数:核心线程+非核心线程的,只有当所有核心线程都不空闲且队列也满了,才会创建非核心线程
KeepAliveTime:非核心线程的最大存活时间,到了这个时间,非核心线程没有被使用,就会被摧毁
Unit:空闲时间单位,是枚举类型(HOURS,MINUTES)
WorkQueue:是一个BlockingQueue阻塞队列,超过核心线程的任务会进入队列排队
ThreadFactory:创建线程的工厂,我们可以自己实现线程的创建
Handler:饱和处理机制,拒绝策略,当任务数超过最大线程数+队列中任务数,则执行饱和处理机制
6.为什么要使用线程池
1.可以减少线程的创建和销毁,每个线程可以复用,提高效率
2.可以根据系统的承受能力,调整线程池中工作的线程数量,减轻服务器压力
7.数据结构有哪些?
根据逻辑结构分:
集合:一群没有关系的数据
线性结构:数据之间存在一对一的关系:如队列,栈、链表
树形结构:数据之间存在一对多的关系,如二叉树,多叉树
图形结构:数据之间存在多对多的关系
根据物理结构分:
顺序储存结构:用一组连续的内存空间来依次储存线性表的元素,如数组
链接存储结构:用任意的内存空间来储存线性表中的元素,不要求相邻的元素物理位置也相邻
索引储存结构:对节点建立索引,通过索引快速找到数据
散列存储结构:将元素的储存位置和关键字建立联系,又叫hash存储
8.数组和链表在存储结构上有什么区别
数组的存储需要连续的内存空间,而链表则不需要连续的内存空间,它的存储空间可以是任意位置,因为链表中的每一个元素都保存了相邻元素的指针,但链表容易造成内存碎片化
9.为什么链表容易造成内存碎片化?
因为链表的元素储存位置在物理位置上不是连续的,它是动态的分配内存空间,不是像数组一样会提前申请一块连续的内存空间
10.什么是散列储存(Hash储存),什么是哈希冲突,怎么解决
散列储存就是通过将数据的关键字key通过一个函数计算出数据存放的位置,后面要找这个数据,还是通过该函数计算出数据存放的位置来寻找
哈希冲突就是两个不相同的数据计算出了在同一张hash表中相同的位置(hash值相同,或者下标相同),解决方案可以将两个数据指向同一个链表中,也可以把冲突的hash值再进行计算,直到不冲突为止
还可以换一种哈希算法重新计算hash值
11.时间复杂度
时间复杂度是用来衡量算法执行时间的长短
在哈希表中找一个元素就是O(1)
访问数组的第n个元素是O(1)
访问链表的n个元素就是O(n)
二分搜索的最好情况是O(1),最坏情况是O(lgn)
一次for循环是O(n)
两次是O(n2) 三次是O(n3)
12.java中有哪些是线性结构?
数组、链表、栈(先进后出)(数组栈和链栈)、队列(先进先出)(数组队列和链式队列)、特殊的线性结构String Stringbuffer Stringbuilder
13.树形结构和线性结构的优势?
树形结构中链表的插入删除效率高,树形结构中的数组的随机访问效率高
树形结构查找效率高,而删除和插入相对较慢,例如插入到平衡二叉树中,平衡二叉树为保持平横,会左旋或右旋
14.说一下树的分类以及优缺点
分为二叉树和多叉树
二叉树的任意节点最多允许有两个子节点,二叉树可以采用顺序结构(数组)和链式结构(链表)存储数据,分为:二叉查找树(二叉排序树、二叉搜索树),平衡二叉树、红黑树(自平衡二叉查找树)、赫夫曼树
多叉树分为:b树、b+树等等
二分排序树:是有序的二叉树,任意节点左边的子节点小于右边的子节点,二分排序树查找数据就相当于二分查找法,它查找性能很高,但是有可能会倾斜而变成链表结构
平衡二叉树:左右两颗子树高度的绝对值不超过1,是为了解决二分排序树倾斜变成链表,每次进行添加或者删除操作后,都会进行重排序,因此添加和删除性能较低
红黑树:是自平衡二叉查找树,它是根据查找路径上黑色节点的个数和红黑色节点之间的联系来保证树的平衡,红黑树和平衡二叉树在添加和删除操作中,为了保证平衡,都会进行左旋和右旋,不同的是红黑树还会改变节点的颜色,红变黑或者黑变红。在删除和添加操作中,红黑树的旋转次数要少于平衡二叉树,所以一般会优先选用红黑树
多叉树是为了解决二叉树在存储大量数据时出现的树深度过大的问题,树的深度过大会导致IO次数变多,效率低下
B树:是一种自平衡的多叉树,主要用于数据量较大的场景,如文件系统、数据库索引等
B+树:可以看作是B+树的升级版,b+树的内部节点不存储数据,只存储key,叶子节点存放key和数据,在mysql中,一个节点的大小会设置成一个磁盘页的大小,这样每个内部节点就可以存储更多的key,从而使树的高度的降低,查询更快。并且所有的叶子节点会组成一个有序的链表,方便区间查询
15. 为什么会有多叉树
因为在数据量很大的情况下,使用二叉树进行存储的话,会导致树的高度很大,从而增加与磁盘IO的次数,造成查询效率低下。而多叉树每层可以存放更多的数据,从而降低树的高度,提升查询速度
16.b树和b+树的区别
b树的所有节点均可存放key以及数据,而b+树只有叶子节点才会存储key和数据,其他节点只存放key
m阶的b树节点中可以存放的元素个数为m-1,b+树中每个节点可以存放的元素个数为m个
b+树的叶子节点会构成链表结构,方便区间查找和排序
17.平衡二叉树在什么时候左旋,什么时候右旋?
左右两颗子树高度差绝对值大于1且二叉树左倾,就会进行右旋,
左右两颗子树高度差绝对值大于1且二叉树右倾,就会进行左旋
红黑树的左旋和右旋的次数要小于平衡二叉树,因为红黑树会改变节点的颜色来减少旋转次数
18.二叉排序树存储数据是通过键值对的形式存储的,会通过算法对key进行排序,还储存了子节点的引用
19.平衡二叉树是在二叉排序树的基础上做了平衡处理,使二叉树变得平衡,降低了树的高度
20.Hashmap底层用了哪些数据结构
jdk1.7 数组+链表
jdk1.8 数组+链表+红黑树
21.为什么hashmap中要使用链表结构
因为在执行hashmap的put方法时,首先会通过key计算出哈希值,再根据哈希值计算出下标,再将元素放入数组对应的下标出。但是不同的数据可能会计算出相同的哈希值,从而造成哈希冲突,hashmap使用的是拉链法来解决哈希冲突,将产生哈希碰撞的元素,挂载到链表。挂载到链表的操作就是将发生哈希冲突的下标处的元素向下移位,将新的元素插入下标出,并保存对方的指针。
22.为什么hashmap中要用到红黑树
因为当同一个下标处产生哈希碰撞的元素增多后,使用链表进行查询的速度就会变慢,因此在链表中的元素大于8时且数组长度大于64时,链表就会转换为红黑树
扩展:为什么是红黑树?平衡二叉树的查询效率高,红黑树的查询效率略差于平衡二叉树,红黑树对平衡的要求没有那么高,在插入和删除操作时,为了保持平衡,平衡二叉树会旋转,而红黑树会着色和旋转,红黑树的旋转次数小于平衡二叉树,而且着色操作也很快,综合性能红黑树优于平衡二叉树,所以选择了红黑树
23.hashmap是如何计算哈希值,如何计算下标
在调用hashmap的put方法时,会调用hash()方法来计算key的hashcode,如果key为null,那么哈希值为0,如果key不为null,则调用key的hashcode方法然后异或哈希值右移16位后的二进制值得到哈希值
将数组的长度-1和哈希值进行与运算得到下标
右移16位,左边补16个0
24.链表和红黑树在什么时候互相转换
当链表中元素个数大于8,且数组长度大于64时,链表会转换成红黑树,若链表中元素个数大于8,但是数组长度小于64,则会对数组进行扩容
当红黑树中的节点个数小于6个时,就会将红黑树转换为链表
为什么是treeify的阈值是8,而不是7或者9 泊松分布
为什么untreeify的阈值是6
25.hashmap如何进行扩容
hashmap中的数组容量是16,负载因子是0.75,当数组中的元素个数大于数组长度*负载因子时,就会进行成倍扩容
为什么负载因子是0.75,因为负载因子过小会导致资源浪费,负载因子过大会增加哈希碰撞的概率,所以综合考虑,使用0.75作为负载因子
26.为什么扩容是成倍扩容?
27.hashmap是如何put一个元素的?
28.hashmap是如何get一个元素?
首先计算出key的哈希值,再根据哈希值计算出元素所在数组的下标,判断数组的下标处是否有元素存在,如果不存在就返回null,如果存在,就判断该数据的key是否为查询的key,
如果是就返回value,如果下标处的第一个元素的key不匹配,则判断是红黑树还是链表,如果是红黑树,按照红黑树的查找方式进行查找并返回value,如果是链表,则进行遍历查找匹配key,然后返回value
29.hashmap是如何解决哈希冲突的
hashmap采用的是拉链法,将发生哈希冲突的元素挂载到链表中
在jdk8中,当链表中的元素个数大于8且数组长度大于64时,会将链表转换成红黑树提高查询效率
30.hashmap的死循环问题

浙公网安备 33010602011771号