0


本周面试经验总结

文章目录

前言

经过半个多月的海投和疯狂笔试与学习,终于在本周陆续迎来了两场面试,喜忧参半。两场面试自己录音之后复盘了好几天,有些地方达到企业要求,但同时也发现自己有太多的不足,长期与机器打交道好像自己丧失了一些交流沟通的能力,有些地方表达的还不够自信,举止不够大方得体,总之两场面试下来感觉很舒服,真的很感谢这些企业在这种环境下还可以给我这么宝贵的面试机会,也同时希望将自己在面试中的不足给大家分享一下,大家自己真实面对的时候就可以更加游刃有余。

面试的问题

自我介绍太过简单

你好,我叫xxx,目前就读于xxx,是一名xx专业大四学生,本专业开设了高等数学,线性代数,计算机基础,信息检索,多媒体技术及其应用等课程,后来自己也尝试去听我们学校计算机专业的课程,后来经过各种尝试发现线上课师资更好,加上自我控制能力还不错,就坚持学了下去,中间一直学习把计算机科班的课基本上都学了一遍并且后续不断深入进行体系化的学习,现在掌握Java的基本知识和企业开发的一些像Springboot、Vue、Mybatis、MyBatisPlus、Hibernate等技术栈;用过Mysql、Oracle等数据库;有像Redis、RabbitMQ等中间件的使用经验,并且有相关实际项目操作经验,上一段在xx的实习过程中能完成技术组长和项目经理布置的开发任务。相信我也是具备基本的开发能力能满足企业的要求,也很感谢贵公司给我这次面试机会

接口和抽象类的区别

  1. 接口的方法默认是public,所有方法在接口中不能有实现(Java8开始接口方法可以有默认实现),而抽象类可以有非抽象的方法
  2. 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定
  3. 一个类可以实现多个接口,但只能实现一个抽象类。接口本身可以通过extends关键字扩展多个接口
  4. 接口方法默认是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)
  5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范
  1. 在 JDK 7 或更早版本中,接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实 现接⼝的类实现
  2. JDK 8 的时候接⼝可以有默认⽅法和静态⽅法功能
  3. JDK 9 在接⼝中引⼊了私有⽅法和私有静态⽅法

HashMap和HashTable区别

HashMapHashTable线程安全否是效率高低,基本被淘汰Null键和Null值的支持唯一Null键,多个Null值NPE初始容量大小和每次扩充容量大小16,2的幂次倍11(也可以指定),2n+1,底层数据结构数组+链表或者红黑树数组+链表

讲讲HashMap底层原理

JDK1.8 之前 HashMap 底层是 数组和链表 结合在⼀起使⽤也就是 链表散列。HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这⾥的 n 指的是数组的⻓度),如果当前位置存在元素的话,就判断该元素与要存⼊的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突

所谓扰动函数指的就是 HashMap 的 hash ⽅法。使⽤ hash ⽅法也就是扰动函数是为了防⽌⼀ 些实现⽐较差的 hashCode() ⽅法 换句话说使⽤扰动函数之后可以减少碰撞 。 所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建⼀个链表数组,数组中每⼀格就是⼀个链表。若遇到哈希冲突,则将冲突的值加到链表中即可

HashMap的长度为什么是2的幂次方

为了能让 HashMap 存取⾼效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上⾯也讲到 了过了,Hash 值的范围值-2147483648到2147483647,前后加起来⼤概40亿的映射空间,只要 哈希函数映射得⽐较均匀松散,⼀般应⽤是很难出现碰撞的。但问题是⼀个40亿⻓度的数组,内存是放不下的。所以这个散列值是不能直接拿来⽤的。⽤之前还要先做对数组的⻓度取模运算, 得到的余数才能⽤来要存放的位置也就是对应的数组下标。这个数组下标的计算⽅法是“ (n - 1) & hash ”。(n代表数组⻓度)。这也就解释了 HashMap 的⻓度为什么是2的幂次⽅

这个算法应该如何设计?

我们⾸先可能会想到采⽤%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2 的幂次则等价于与其除数减⼀的与(&)操作(也就是说 hash%length==hash&(length-1)的前提 是 length 是2的 n 次⽅;)。” 并且 采⽤⼆进制位操作 &,相对于%能够提⾼运算效率,这就解 释了 HashMap 的⻓度为什么是2的幂次⽅

如果需要的话继续把下面的源码说给他听

简单描述你实习的这个项目

前端项目你是自己搭建的还是用现成的

前端你怎么打包

npm run build ——> 生成dist的文件夹 ——> 用Nginx(启动:cd/usr/local/nginx/sbin ./nginx) ——>将dist文件传过去 ——> 服务器中 /usr/local/nginx/conf/nginx.conf
——>重启Nginx:nginx -s reload ——> 后续需要更新再次生成dist,将新的dist文件替换到服务器文件夹位置中

简单说说你用的git操作

  1. git init 初始化本地仓库 git clone + 地址 把远程仓库的代码拉下来
  2. git pull 本地与服务器端同步 git fetch 相当于是从远程获取最新版本到本地,不会自动merge
  3. git branch 查看本地所有分支 git status 查看当前状态
  4. git push 将本地库代码传给服务器库 git checkout -b dev 建立一个新的本地分支dev
  5. git commit -a 提交当前仓库所有的改变

你对SpringMVC了解多少,讲讲执行流程

  1. 用户发送请求至前端控制器,DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器
  3. 处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter处理器适配器
  5. HanderAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
  6. Controller执行完成后返回ModelAndView
  7. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewResolver视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
  11. DispatcherServlet响应用户

Mybatis你了解多少

它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低

为什么mybatis是半自动的ORM框架?

用mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。由于mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。并且它的学习成本也比hibernate低很多

垃圾收集有哪些算法?

  1. 标记-清除:该算法分为"标记"和"清除"阶段;首先标记出所有不需要回收的对象,在标记完成后统一回收掉所没有被标记的对象。会产生效率问题和大量不连续的碎片
  2. 复制:为了解决效率问题,"复制算法"出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收
  3. 标记整理:根据老年代的特点提出的一种标记算法,标记过程仍然与"标记-清除"算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象像一端移动,然后直接清理掉端边界以外的内存
  4. 分代收集:当前虚拟机的垃圾回收算法都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将Java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法;
  5. 比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除"或者"标记-整理”算法进行垃圾收集

你对MySQL了解多少?

我说了一下oracle之后他没让我展开说,这里我详细放在下一个面试里面展开,内容太多了

你有把自己的项目部署到其他地方吗

我用docker把自己的项目部署在本地Linux

能说说负载均衡吗?

大型网站都要面对庞大的用户量,高并发,海量数据等挑战。为了提升系统整体的性能,可以采用垂直扩展和水平扩展两种方式。

垂直扩展:在网站发展早期,可以从单机的角度通过增加硬件处理能力,比如CPU处理能力,内存扩容,磁盘等方面,实现服务器处理能力的提升。但是,单机是有性能瓶颈的,一旦触及瓶颈,再想提升,付出的成本和代价会极高。这显然不能满足大型分布式系统(网站)所有应对的大流量,高并发,海量数据等挑战

水平扩展:通过集群来分担大型网站的流量。集群中的应用服务器(节点)通常被设计成无状态,用户可以请求任何一个节点,这些节点共同分担访问压力。水平扩展有两个要点

  1. 应用集群:将同一应用部署到多台机器上,组成处理集群,接收负载均衡设备分发的请求,进行处理,并返回相应数据。
  2. 负载均衡:将用户访问请求,通过某种算法,分发到集群中的节点

负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目标是 尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性

负载均衡的主要作用如下:

高并发:负载均衡通过算法调整负载,尽力均匀的分配应用集群中各节点的工作量,以此提高应用集群的并发处理能力(吞吐量

伸缩性添加或减少服务器数量,然后由负载均衡进行分发控制。这使得应用集群具备伸缩性。

高可用:负载均衡器可以监控候选服务器,当服务器不可用时,自动跳过,将请求分发给可用的服务器。这使得应用集群具备高可用的特性。

安全防护:有些负载均衡软件或硬件提供了安全性功能,如:黑白名单处理、防火墙,防 DDos 攻击等

负载均衡的分类:从载体维度可分为:硬件负载均衡、软件负载均衡

硬件负载均衡:一般是在定制处理器上运行的独立负载均衡服务器,价格昂贵,土豪专属。优点:

  1. 功能强大:支持全局负载均衡并提供较全面的、复杂的负载均衡算法
  2. 性能强悍:硬件负载均衡由于是在专用处理器上运行,因此吞吐量巨大,可支持单机百万以上的并发
  3. 安全性高:往往具备防火墙,防DDos攻击等安全功能

缺点

  1. 成本昂贵:购买和维护硬件负载均衡的成本都很高
  2. 扩展性差:当访问量突增时,超过限度不能动态扩容

软件负载均衡:软件负载均衡,应用最广泛,无论大公司还是小公司都会使用。软件负载均衡从软件层面实现负载均衡,一般可以在任何标准物理设备上运行,主流产品有:Nginx、HAProxy、LVS

  1. LVS可以作为四层负载均衡容器。其负载均衡的性能要优于Nginx
  2. HAProxy可以作为HTTP和TCP负载均衡器
  3. Nginx、HAProxy可以作为四层或七层负载均衡器

软件负载均衡的优点:

  1. 扩展性好:适应动态变化,可以通过添加软件负载均衡实例,动态扩展到超出初始容量的能力
  2. 成本低廉:软件负载均衡可以在任何标准物理设备上运行,降低了购买和运维的成本

缺点就是相比于硬件负载均衡,软件负载均衡的性能就要略低一点

网络通信分类:从通信层次上来看,又可以分为四层和七层负载均衡

  1. 七层负载均衡:就是可以根据访问用户的HTTP请求头、URL信息将请求转发到特定的主机 1. DNS重定向 HTTP重定向 反向代理
  2. 四层负载均衡:基于IP地址和端口进行请求的转发 1. 修改IP地址 修改MAC地址

小结负载均衡的类型

  1. DNS方式实现负载均衡
  2. 硬件负载均衡:F5和F10
  3. 软件负载均衡:Nginx、HAproxy、LVS。其中的区别: 1. Nginx:七层(应用层:根据HTTP的URL一定规则来进行路由,不同的URL路由到不同的Server中去,并且还需要跟客户端和服务端建立和维护长连接,要关注Http请求报文中的部分报文,接收请求的Body,把它从客户端传到服务端去,相对来说效率就没有那么高)负载均衡,支持Http、E-mail协议,同时也支持4层负载均衡2. HAproxy:支持七层规则的,性能也很不错。OpenStack默认使用的负载均衡软件就是HAproxy3. LVS:运行在内核态,性能是软件负载均衡中最高的,严格来说工作在三层(传输层:请求目标IP指向LVS,LVS只需把目标IP改成代理的Server端IP,也不需要关心里面有什么参数),所以更通用一些,适用各种应用服务

简单讲讲你的酒店项目

除了Java你还学习了哪些其他语言吗

专业以及职业规划问题

第二家

常见的垃圾回收器有哪些?

Serial、ParNew、Paraller Scavenge收集器、CMS收集器、G1

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。虽然我们对各个收集器进⾏⽐较,但并⾮要挑选出⼀个最好的收集器。因为直到现在为⽌还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应⽤场景选择适合⾃⼰的垃圾收集器。试想⼀下:如果有⼀种四海之内、任何场景下都适⽤的完美收集器存在, 那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了

  1. Serial: Serial(串⾏)收集器是最基本、历史最悠久的垃圾收集器了。⼤家看名字就知道这个收集器是 ⼀个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使⽤⼀条垃圾收集线程去完成 垃圾收集⼯作,更重要的是它在进⾏垃圾收集⼯作的时候必须暂停其他所有的⼯作线程( “STW” ),直到它收集结束。 新⽣代采⽤复制算法,⽼年代采⽤标记-整理算法 。 虚拟机的设计者们当然知道 STW 带来的不良⽤户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 但是 Serial 收集器有没有优于其他垃圾收集器的地⽅呢?当然有,它简单⽽⾼效(与其他收集器的单线程相⽐)。Serial 收集器由于没有线程交互的开销,⾃然可以获得很⾼的单线程收集效率。Serial 收集器对于运⾏在 Client 模式下的虚拟机来说是个不错的选择
  2. ParNew: ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使⽤多线程进⾏垃圾收集外,其余⾏为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全⼀样 。 它是许多运⾏在 Server 模式下的虚拟机的⾸要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后⾯会介绍到)配合⼯作。1. 并⾏和并发概念补充: 1. 并⾏(Parallel) :指多条垃圾收集线程并⾏⼯作,但此时⽤户线程仍然处于等待状态2. 并发(Concurrent):指⽤户线程与垃圾收集线程同时执⾏(但不⼀定是并⾏,可能会交替执⾏),⽤户程序在继续运⾏,⽽垃圾收集器运⾏在另⼀个 CPU 上
  3. Paraller Scavenge: Parallel Scavenge 收集器也是使⽤复制算法的多线程收集器,它看上去⼏乎和 ParNew 都⼀ 样。 那么它有什么特别之处呢? 在这里插入图片描述 Parallel Scavenge 收集器关注点是吞吐量(⾼效率的利⽤ CPU)。CMS 等垃圾收集器的关注 点更多的是⽤户线程的停顿时间(提⾼⽤户体验)。所谓吞吐量就是 CPU 中⽤于运⾏⽤户代码的时间与 CPU 总消耗时间的⽐值。 Parallel Scavenge 收集器提供了很多参数供⽤户找到最合适的停顿时间或最⼤吞吐量,如果对于收集器运作不太了解,⼿⼯优化存在困难的时候,使⽤ Parallel Scavenge 收集器配合⾃适应调节策略,把内存管理优化交给虚拟机去完成也是⼀个不错的选择,这是JDK8默认收集器 使⽤ java -XX:+PrintCommandLineFlags -version 命令查看 在这里插入图片描述JDK1.8 默认使⽤的是 Parallel Scavenge + Parallel Old,如果指定了-XX:+UseParallelGC 参 数,则默认指定了-XX:+UseParallelOldGC,可以使⽤-XX:-UseParallelOldGC 来禁⽤该功能1. Serial Old收集器 : Serial 收集器的⽼年代版本,它同样是⼀个单线程收集器。它主要有两⼤⽤途:⼀种⽤途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使⽤,另⼀种⽤途是作为 CMS 收集 器的后备⽅案。2. Parallel Old收集器: Parallel Scavenge 收集器的⽼年代版本。使⽤多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器
  4. CMS: CMS(Concurrent Mark Sweep)收集器是⼀种以获取最短回收停顿时间为⽬标的收集器。它⾮常符合在注重⽤户体验的应⽤上使⽤。 CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第⼀款真正意义上的并发收集器, 它第⼀次实现了让垃圾收集线程与⽤户线程(基本上)同时⼯作。 从名字中的Mark Sweep这两个词可以看出,CMS 收集器是⼀种 “标记-清除”算法实现的,它的 运作过程相⽐于前⾯⼏种垃圾收集器来说更加复杂⼀些。整个过程分为四个步骤 :1. 初始标记暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;2. 并发标记同时开启 GC 和⽤户线程,⽤⼀个闭包结构去记录可达对象。但在这个阶段结 束,这个闭包结构并不能保证包含当前所有的可达对象。因为⽤户线程可能会不断的更新引⽤域,所以 GC 线程⽆法保证可达性分析的实时性。所以这个算法⾥会跟踪记录这些发⽣引⽤更新的地⽅3. 重新标记: 重新标记阶段就是为了修正并发标记期间因为⽤户程序继续运⾏⽽导致标记产⽣变动的那⼀部分对象的标记记录,这个阶段的停顿时间⼀般会⽐初始标记阶段的时间稍⻓, 远远⽐并发标记阶段时间短4. 并发清除: 开启⽤户线程,同时 GC 线程开始对未标记的区域做清扫 在这里插入图片描述 从它的名字就可以看出它是⼀款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下 ⾯三个明显的缺点: 1. 对 CPU 资源敏感2. ⽆法处理浮动垃圾3. 它使⽤的回收算法-“标记-清除”算法会导致收集结束时会有⼤量空间碎⽚产⽣
  5. G1: G1 (Garbage-First) 是⼀款⾯向服务器的垃圾收集器,主要针对配备多颗处理器及⼤容量内存的机器. 以极⾼概率满⾜ GC 停顿时间要求的同时,还具备⾼吞吐量性能特征。 被视为 JDK1.7 中 HotSpot 虚拟机的⼀个重要进化特征。它具备以下特点 :1. 并⾏与并发:G1 能充分利⽤ CPU、多核环境下的硬件优势,使⽤多个 CPU(CPU 或者 CPU 核⼼)来缩短 STW 停顿时间。部分其他收集器原本需要停顿 Java 线程执⾏的 GC 动作,G1收集器仍然可以通过并发的⽅式让 java 程序继续执⾏2. 分代收集:虽然 G1 可以不需要其他收集器配合就能独⽴管理整个 GC 堆,但是还是保留了 分代的概念。3. 空间整合:与 CMS 的“标记–清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的 收集器;从局部上来看是基于“复制”算法实现的4. 可预测的停顿:这是 G1 相对于 CMS 的另⼀个⼤优势,降低停顿时间是 G1 和 CMS 共同 的关注点,但 G1 除了追求低停顿外,还能建⽴可预测的停顿时间模型,能让使⽤者明确指 定在⼀个⻓度为 M 毫秒的时间⽚段内5. G1 收集器的运作⼤致分为以下⼏个步骤: 初始标记 并发标记 最终标记 筛选回收6. **G1 收集器在后台维护了⼀个优先列表,每次根据允许的收集时间,优先选择回收价值最⼤的 Region(这也就是它的名字 Garbage-First 的由来)**。这种使⽤ Region 划分内存空间以及有优先级的区域回收⽅式,保证了 G1 收集器在有限时间内可以尽可能⾼的收集效率(把内存化整为 零)。
  6. ZGC收集器: 与 CMS 中的 ParNew 和 G1 类似,ZGC 也采⽤标记-复制算法,不过 ZGC 对该算法做了重⼤改 进。 在 ZGC 中出现 Stop The World 的情况会更少! 详情可以看 : ZGC

JDK8新特性

  1. 基本函数接口: 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 函数式接口可以被隐式转换为 lambda 表达式。 基本的函数式接口主要有四个1. Supplier 生产者:无输入,生产一个 T 类型的值;接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据2. Consumer 消费者:输入一个 T 类型的值,无输出;对给定参数执行消费操作3. Function<T,R> 函数:输入一个 T 类型的值,返回一个 R 类型的值;4. Predicate 断言:输入一个 T 类型的值,返回 true/false
  2. Lambda表达式:Lambda它其实是匿名函数,通过约定好怎么传入参数,怎么返回参数,由编译器负责参数类型的猜测并执行结果,Lambda表达式的基本语法1. (parameters) ——> expression 即"->"操作符将Lambda表达式分为两个部分:左侧为参数列表,右侧为Lambda体。每一个Lambda表达式的返回体都是一个函数式编程的接口
  3. Optional类的使用:Optional类的作用主要是为了解决空指针的问题,通过对结果的包装,并使用方法来代替if判断,为流式编程打下了良好的基础
  4. Stream流式编程:Stream API借助Lambda表达式,提供串行和并行两种模式进行汇聚操作,并行模式能够充分利用多核处理器的优势,使用 fork/join 来拆分任务和加速处理过程
  5. 方法引用(::双冒号操作符):简单来说就是一个Lambda表达式,方法引用提供了一种引用而不执行方法的方式,运行时方法引用会创建一个函数式接口的实例publicclass funRefTest{@Test//使用Lambda表达式Consumer<String> consumer1 = x ->System.out.println(x); consumer1.accept("Lambda表达式");//使用方法引用Consumer<String> consumer2 =System.out::println; consumer2.accept("方法引用::");}

什么是集合,集合有哪些种类?

List想要线程安全应该怎么用?

  1. 使用java.util.Vector
  2. 使用java.util.Collections.synchronizedList(list)
  3. java.util.concurrent.CopyOnWriteArrayList 读写分离的思想,写上锁,读无锁。写入时,加锁(利用了java.util.concurrent.locks.ReentrantLock上锁),复制原数组(并且数组长度+1,赋值数组末尾元素为要新增的元素),再更新数组的引用,解锁

HashMap

Java的线程池

池化技术相⽐⼤家已经屡⻅不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应⽤。池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率 。线程池提供了⼀种限制和管理资源(包括执⾏⼀个任务)。 每个线程池还维护⼀些基本统计信息,例如已完成任务的数量

使用线程池的好处

  1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和消耗造成的消耗
  2. 提高响应速度:当任务到达时,任务可以不需要得等到线程创建就能立即执行
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

线程池核心参数

  1. corePoolSize:核心线程数线程数定义了最小可以同时运行的线程数量
  2. maximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
  3. workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中
  4. keepAliveTime:当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程数不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁
  5. unitkeepAliveTime参数的时间单位
  6. threadFactory:executor创建新线程的时候会用到
  7. handler:饱和策略

线程池有哪些工作队列

  1. ArrayBlockingQueue 数组型阻塞队列: 初始化一定容量的数组,使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥 是有界设计,如果容量满无法继续添加元素直至有元素被移除; 使用时开辟一段连续的内存,如果初始化容量过大容易造成资源浪费,过小易添加失败
  2. LinkedBlockingQueue 链表型阻塞队列: 内部使用节点关联,会产生多一点内存占用; 使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待; 有边界的,再默认构造方法中容量是Integer.MAX_VALUE,非连续性内存空间
  3. DelayQueue 延时队列: 无边界设计,添加(put)不阻塞,移除阻塞, 元素都有一个过期时间,取元素只有过期的才会被取出
  4. SynchroniousQueue 同步队列: 内部容量是0, 每次删除操作都要等待插入操作; 每次插入操作都要等待删除操作 一个元素,一旦有了插入线程和移除线程,那么很快由插入线程移交给移除线程,这个容器相当于通道,本身不存储元素, 在多任务队列,是最快的处理任务方式
  5. PriorityBlockingQueue 优先阻塞队列: 无边界设计,但容量实际是依靠系统资源影响; 添加元素,如果超过1,则进行优先级排序

饱和策略定义: 如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,

ThreadPoolTaskExecutor

定义一些策略:

  1. ThreadPoolExecutor.AbortPolicy: 抛出RejectedExcutionException来拒绝新任务的处理
  2. ThreadPoolExecutor.CallerRunsPolicy: 调用执行自己的线程运行任务,你不会任务请求.但是这种策略会降低对于新任务提交速度,影响程序的整体性能.另外,这个策略喜欢增加队列容量.如果你的应用程序可以承受此延迟并且不能丢弃任何一个任务请求的话,你可以选择这个策略
  3. ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉
  4. ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求

MySQL中left join的优化

  1. 小表驱动大表
  2. 子查询代替left join,依据join的原理,减少了驱动表的数据量,从而减少访问匹配表的次数.
  3. 不使用group by减少了cpu对分组数据的处理,分组的操作可以理解为数据库引擎查询数据出来的过程,当然是越少越好
  4. 因为group by的优先级是在limit之前的,所以之前是对查询的所有数据先进行分组,然后取十条.优化后,先取十条再进行sum()操作,优化力度还是挺大的

以MySQL为例Linux下如何排查问题?

类似提问方式:如果线上环境出现问题比如网站卡顿重则瘫痪 如何是好?

—>linux—>mysql/redis/nacos/sentinel/sluth—>可以从以上提到的技术点中选择一个自己熟悉单技术点进行分析

以mysql为例

  1. 架构层面 是否使用主从
  2. 表结构层面 是否满足常规的表设计规范(大量冗余字段会导致查询会变得很复杂)
  3. sql语句层面(⭐)

前提: 由于慢查询日志记录默认是关闭的,所以开启数据库mysql的慢查询记录的功能 从慢查询日志中去获取哪些sql语句时慢查询 默认10S ,从中获取到sql语句进行分析

explain 分析一条sql在这里插入图片描述

  1. Id: 执行顺序 如果单表的话,无参考价值,如果是关联查询,会据此判断主表 从表
  2. Type: All 未创建索引 const 常量 ref 其他索引 eq_ref 主键索引
  3. Key: 实际是到用到索引的字段
  4. Key_len: 索引字段数据结构所使用的长度与是否有默认值null以及对应字段到数据类型有关,有一个理论值,实际使用值也即key_len的值
  5. Rows: 检索的行数 与查询返回的行数无关
  6. Extra: 常见的值: usingfilesort 使用磁盘排序算法进行排序,事关排序分组的字段是否使用索引的核心参考值

还可能这样去提问:sql语句中哪些位置适合建索引/索引建立在哪个位置?

Select id,name,age from user where id=1 and name=”xxx” order by age

总结: 查询字段 查询条件(最常用) 排序/分组字段

补充: 如何判断是数据库的问题? 可以借助于top命令

如何处理慢查询?

在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。慢查询的优化首先要搞明白慢的原因是什么? **是查询条件没有命中索引?是加载了不需要的数据列?还是数据量太大? **所以优化也是针对这三个方向来的

  1. 首先分析语句,看看是否加载了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  2. 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
  3. 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表

另一种说法SQL执行慢的情况分析

  1. 大多数情况下很正常,偶尔很慢,则有如下原因 1. 数据库在刷新脏页, 例如 redo log 写满了需要同步到磁盘 内存不够用 MySQL认为系统"空闲"的时候 MySQL正常关闭的时候2. 执行的时候遇到, 如表锁 行锁 show processlist 命令查看当前状态(太重要了)
  2. 这条SQL语句一直执行的很慢, 则有如下原因 1. 没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法用索引2. 数据库选错了索引

深入一点来了解慢查询

  1. 慢查询,顾名思义,执行很慢的查询。有多慢?超过long_query_time参数设定的时间阈值(默认10s),就被认为是慢的,是需要优化的。慢查询被记录在慢查询日志里。慢查询日志默认是不开启的。如果需要优化SQL语句,就可以开启这个功能,它可以让你很容易地知道哪些语句是需要优化的。> 1️⃣> > show variables like 'slow_query_log'> > ;查询是否开启慢查询日志> 【开启慢查询sql:set global slow_query_log = 1/on;】> 【关闭慢查询sql:set global slow_query_log = 0/off;】> 2️⃣> > show variables like 'log_queries_not_using_indexes'> > ;查询未使用索引是否开启记录慢查询日志> 【开启记录未使用索引sql:set global log_queries_not_using_indexes=1/on】> 【关闭记录未使用索引sql:set global log_queries_not_using_indexes=1/off】> 3️⃣> > show variables like 'long_query_time'> > ;查询超过多少秒的记录到慢查询日志中> 【设置超1秒就记录慢查询sql:set global long_query_time= 1;设置超1秒就记录】
  2. 慢查询的配置文件 my.cnf 在MySQL的配置文件my.cnf中写上:> long_query_time = 10> log-slow-queries = /var/lib/mysql/mysql-slow.log> > long_query_time是指执行超过多久的SQL会被日志记录下来,这里是10 秒。> log-slow-queries设置把日志写在哪里。为空的时候,系统会给慢查询日志赋予主机名,并加上slow.log。如果设置了参数log-long-format,那么所有没有使用索引的查询也将被记录。

这是一个非常有用的日志。它对于性能的影响不大(假设所有查询都很快),并且强调了那些最需要注意的查询(丢失了索引或索引没有得到最佳应用)

  1. 慢查询解读1. 第一行:记录时间2. 第二行:用户名 、用户的IP信息、线程ID号3. 第三行:执行花费的时间【单位:毫秒】、执行获得锁的时间、获得的结果行数、扫描的数据行数4. 第四行:这SQL执行的时间戳5. 第五行:具体的SQL语句

MySQL优化

  1. 条件尽量选择较小的列
  2. 将where中用的比较频繁的字段建立索引
  3. 将select子句中避免使用 “*”
  4. 避免在索引列上使用计算 not in 和 < > 等操作
  5. 当只需要一行数据的时候用 limit 1
  6. 保证单表数据不超过200w, 适时分割表. 针对查询较慢的语句, 可以使用explain来分析该语句的具体执行情况
  7. 避免改变索引列的类型
  8. 选择最有效的表名顺序, from子句中写在最后的表是基础表,将被最先处理, 在from子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表
  9. 尽量缩小子查询的结果

如何优化下面的语句?

select * from admin left join log on admin.admin_id = log.admin_id where log.admin_id>10

优化为:select * from (select * from admin where admin_id>10) T1 lef join log on T1.admin_id = log.admin_id。

使用 JOIN 时候,应该用小的结果驱动大的结果(left join 左边表结果尽量小如果有条件应该放到左边先处理, right join 同理反向),同时尽量把牵涉到多表联合的查询拆分多个 query(多个连表查询效率低,容易到之后锁表和阻塞

MySQL高性能优化规范建议: MySQL高性能优化规范建议,速度收藏 (qq.com)

后端程序员必备:书写高质量SQL的30条建议 (qq.com)

索引有哪些数据结构及其优劣势

MySQL索引使⽤的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝⼤多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余⼤部分场景,建议选择BTree索引

Hash索引的缺点

  1. Hash冲突问题: 也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 链地址法。链地址法就是将哈希冲突数据存放在链表
  2. 不支持顺序和范围查询

MySQL的BTree索引使⽤的是B树中的B+Tree,但对于主要的两种存储引擎的实现⽅式是不同 的。

  1. MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,⾸先按照 B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的 值为地址读取相应的数据记录。这被称为“⾮聚簇索引”。
  2. InnoDB: 其数据⽂件本身就是索引⽂件。相⽐MyISAM,索引⽂件和数据⽂件是分离的,其 表数据⽂件本身就是按B+Tree组织的⼀个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据⽂件本身就是主索引。这被称为“聚 簇索引(或聚集索引)”。⽽其余的索引都作为辅助索引,辅助索引的data域存储相应记录主 键的值⽽不是地址,这也是和MyISAM不同的地⽅。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再⾛⼀遍主索引。 因此,在设计表的时候,不建议使⽤过⻓的字段作为主键,也不建议使⽤⾮单调的字段 作为主键,这样会造成主索引频繁分裂

B树 & B+树的异同

  • B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
  • B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
  • B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显

SQL在MySQL里是如何执行的

  • MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有
  • 引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
  • 查询语句的执行流程如下:权限校验(如果命中缓存)—>查询缓存—>分析器—>优化器—>权限校验—>执行器—>引擎
  • 更新语句执行流程如下:分析器---->权限校验---->执行器—>引擎—redo log(prepare 状态)—>binlog—>redo log(commit状态) 这里的redo log采用两阶段提交从而保证数据的一致性

MyBatis和MyBatisPlus在使用上有什么差异

MyBatis:

  1. 所有SQL语句全部自己写
  2. 手动解析实体关系映射转换为MyBatis内部对象注入容器
  3. 不支持Lambda形式调用

Mybatis Plus:

  1. 强大的条件构造器,满足各类使用需求
  2. 内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作
  3. 支持Lambda形式调用
  4. 提供了基本的CRUD功能,连SQL语句都不需要编写
  5. 自动解析实体关系映射转换为MyBatis内部对象注入容器

MyBatis的优点:

  1. MyBatis封装了JDBC底层访问数据库的细节,是我们不需要与JDBC API打交道,就可以访问数据库
  2. MyBatis简单易学, 我们直接编写SQL语句, 适合对于SQL要求比较高的项目
  3. SQL语句封装在配置文件中,便于统一管理与维护, 降低了程序的耦合度
  4. SQL代码从程序代码中彻底分离出来,可重用
  5. 提供了动态SQL标签,支持编写动态SQL
  6. 提供映射标签,支持对象与数据库的ORM字段关系映射

缺点:

  1. 过于依赖数据库SQL语句,导致数据库移植性差,更换数据库,如果SQL语句有差异,SQL语句工作量较大
  2. 由于XML里标签Id必须唯一,导致DAO中方法不支持方法重载

Mybatis批量操作

1.    <insertid="insertBatch">  
2.        insert into tbl_employee(last_name,email,gender,d_id) values  
3.        <foreachcollection="emps"item="curr_emp"separator=",">  
4.            (#{curr_emp.lastName},#{curr_emp.email},#{curr_emp.gender},#{curr_emp.dept.id})  
5.        </foreach>  
6.    </insert>

批量执行报错: mysql对语句的长度有限制,默认是 4M。遇到这种情况可以分批次进行插入,即将数据分成几个小批次,然后对每个小批次批量insert

当插入的时候需要回滚怎么处理: 在Spring那里用 @Transactional注解, 做统一事务的处理

关系型数据库和非关系型数据库一般都存什么数据

  1. 关系型: 做一些求和 求平均等运算的时候, 或者做关联查询
  2. 数据量比较多的时候
  3. 有动态的列, 频繁新增或减少字段, 表的结构
  4. 如果数据量太大可能关系型数据库和非关系型数据库要结合使用, 看具体业务

Redis有哪些数据类型

  1. String: 是简单的 key-value 类型。虽然 Redis 是⽤ C 语⾔写的,但是 Redis 并没有使⽤ C 的字符串表示,⽽是⾃⼰构建了⼀种 简单动态字符串(simple dynamic string,SDS)
  2. list: 链表。链表是⼀种⾮常常⻅的数据结构,特点是易于数据元素的插⼊和删除 并且且可以灵活调整链表⻓度,但是链表的随机访问困难。许多⾼级编程语⾔都内置了链表 的实现⽐如 Java 中的 LinkedList,但是 C 语⾔并没有实现链表,所以 Redis 实现了⾃⼰ 的链表数据结构。Redis 的 list 的实现为⼀个 双向链表,即可以⽀持反向查找和遍历,更⽅便操作,不过带来了部分额外的内存开销
  3. set: 类似于 Java 中的 HashSet 。Redis 中的 set 类型是⼀种⽆序集合,集合中的元素没有先后顺序。当你需要存储⼀个列表数据,⼜不希望出现重复数据时,set 是⼀个很好的选择,并且 set 提供了判断某个成员是否在⼀个 set 集合内的重要接⼝,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。⽐如:你可以将⼀个⽤户所有的关注⼈存在⼀个集合中,将其所有粉丝存在⼀个集合。Redis 可以⾮常⽅便的实现如共 同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程 . 常用命令 sadd, spop, smembers, sismember, scard, sinterstore, sunion
  4. zset: 和 set 相⽐,sorted set 增加了⼀个权重参数 score,使得集合中的元素能够按 score 进⾏有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体. 常⽤命令: zadd, zcard, zscore, zrange, zrevrange, zrem 等. 应用场景: 需要对数据根据某个权重进⾏排序的场景。⽐如在直播系统中,实时排⾏信息包 含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息(可以理解为按消息维度的消息排⾏榜)等信息
  5. hash: hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过, Redis 的 hash 做了更多优化。另外,hash 是⼀个 string 类型的 field 和 value 的映射表, 特别适合⽤于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的 值。 ⽐如我们可以 hash 数据结构来存储⽤户信息,商品信息等等

Redis缓存雪崩了解过吗

缓存在同⼀时间⼤⾯积的失效,后⾯的请求 都直接落到了数据库上,造成数据库短时间内承受⼤量请求

解决办法:

  1. 针对Redis服务不可用的情况: 1. 采用Redis集群, 避免单机出现问题整个缓存服务都没办法使用2. 限流,避免同时处理大量的请求
  2. 针对热点缓存失效的情况: 1. 设置不同的失效时间比如随机设置缓存的失效时间2. 缓存永不失效

后期的职业规划, 是打算吃苦耐劳走技术路线还是走管理

结语

最近返校之后感觉学习效率也有相应的提升,也希望自己和大家都能找到一份还不错的工作,一起加油,自身足够强大才能不受这份寒气的影响,总之一直在路上就行了


本文转载自: https://blog.csdn.net/qq_57469718/article/details/126924037
版权归原作者 慢慢敲吧 所有, 如有侵权,请联系我们删除。

“本周面试经验总结”的评论:

还没有评论