经过半个多月的海投和疯狂笔试与学习,终于在本周陆续迎来了两场面试,喜忧参半。两场面试自己录音之后复盘了好几天,有些地方达到企业要求,但同时也发现自己有太多的不足,长期与机器打交道好像自己丧失了一些交流沟通的能力,有些地方表达的还不够自信,举止不够大方得体,总之两场面试下来感觉很舒服,真的很感谢这些企业在这种环境下还可以给我这么宝贵的面试机会,也同时希望将自己在面试中的不足给大家分享一下,大家自己真实面对的时候就可以更加游刃有余。
你好,我叫xxx,目前就读于xxx,是一名xx专业大四学生,本专业开设了高等数学,线性代数,计算机基础,信息检索,多媒体技术及其应用等课程,后来自己也尝试去听我们学校计算机专业的课程,后来经过各种尝试发现线上课师资更好,加上自我控制能力还不错,就坚持学了下去,中间一直学习把计算机科班的课基本上都学了一遍并且后续不断深入进行体系化的学习,现在掌握Java的基本知识和企业开发的一些像Springboot、Vue、Mybatis、MyBatisPlus、Hibernate等技术栈;用过Mysql、Oracle等数据库;有像Redis、RabbitMQ等中间件的使用经验,并且有相关实际项目操作经验,上一段在xx的实习过程中能完成技术组长和项目经理布置的开发任务。相信我也是具备基本的开发能力能满足企业的要求,也很感谢贵公司给我这次面试机会
- 在 JDK 7 或更早版本中,接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实 现接⼝的类实现
- JDK 8 的时候接⼝可以有默认⽅法和静态⽅法功能
- JDK 9 在接⼝中引⼊了私有⽅法和私有静态⽅法
HashMap | HashTable | |
---|---|---|
线程安全 | 否 | 是 |
效率 | 高 | 低,基本被淘汰 |
Null键和Null值的支持 | 唯一Null键,多个Null值 | NPE |
初始容量大小和每次扩充容量大小 | 16,2的幂次倍 | 11(也可以指定),2n+1, |
底层数据结构 | 数组+链表或者红黑树 | 数组+链表 |
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文件替换到服务器文件夹位置中
它是一款半自动的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低很多
我说了一下oracle之后他没让我展开说,这里我详细放在下一个面试里面展开,内容太多了
我用docker把自己的项目部署在本地Linux
大型网站都要面对庞大的用户量,高并发,海量数据等挑战。为了提升系统整体的性能,可以采用垂直扩展和水平扩展两种方式。
垂直扩展:在网站发展早期,可以从单机的角度通过增加硬件处理能力,比如CPU处理能力,内存扩容,磁盘等方面,实现服务器处理能力的提升。但是,单机是有性能瓶颈的,一旦触及瓶颈,再想提升,付出的成本和代价会极高。这显然不能满足大型分布式系统(网站)所有应对的大流量,高并发,海量数据等挑战
水平扩展:通过集群来分担大型网站的流量。集群中的应用服务器(节点)通常被设计成无状态,用户可以请求任何一个节点,这些节点共同分担访问压力。水平扩展有两个要点
- 应用集群:将同一应用部署到多台机器上,组成处理集群,接收负载均衡设备分发的请求,进行处理,并返回相应数据。
- 负载均衡:将用户访问请求,通过某种算法,分发到集群中的节点
负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目标是 尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性
负载均衡的主要作用如下:
高并发:负载均衡通过算法调整负载,尽力均匀的分配应用集群中各节点的工作量,以此提高应用集群的并发处理能力(吞吐量)
伸缩性:添加或减少服务器数量,然后由负载均衡进行分发控制。这使得应用集群具备伸缩性。
高可用:负载均衡器可以监控候选服务器,当服务器不可用时,自动跳过,将请求分发给可用的服务器。这使得应用集群具备高可用的特性。
安全防护:有些负载均衡软件或硬件提供了安全性功能,如:黑白名单处理、防火墙,防 DDos 攻击等
负载均衡的分类:从载体维度可分为:硬件负载均衡、软件负载均衡
硬件负载均衡:一般是在定制处理器上运行的独立负载均衡服务器,价格昂贵,土豪专属。优点:
缺点:
**软件负载均衡:**软件负载均衡,应用最广泛,无论大公司还是小公司都会使用。软件负载均衡从软件层面实现负载均衡,一般可以在任何标准物理设备上运行,主流产品有:Nginx、HAProxy、LVS
软件负载均衡的优点:
缺点就是相比于硬件负载均衡,软件负载均衡的性能就要略低一点
网络通信分类:从通信层次上来看,又可以分为四层和七层负载均衡
小结负载均衡的类型:
Serial、ParNew、Paraller Scavenge收集器、CMS收集器、G1
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。虽然我们对各个收集器进⾏⽐较,但并⾮要挑选出⼀个最好的收集器。因为直到现在为⽌还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应⽤场景选择适合⾃⼰的垃圾收集器。试想⼀下:如果有⼀种四海之内、任何场景下都适⽤的完美收集器存在, 那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了
Serial: Serial(串⾏)收集器是最基本、历史最悠久的垃圾收集器了。⼤家看名字就知道这个收集器是 ⼀个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使⽤⼀条垃圾收集线程去完成 垃圾收集⼯作,更重要的是它在进⾏垃圾收集⼯作的时候必须暂停其他所有的⼯作线程( “STW” ),直到它收集结束。 新⽣代采⽤复制算法,⽼年代采⽤标记-整理算法 。 虚拟机的设计者们当然知道 STW 带来的不良⽤户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 但是 Serial 收集器有没有优于其他垃圾收集器的地⽅呢?当然有,它简单⽽⾼效(与其他收集器的单线程相⽐)。Serial 收集器由于没有线程交互的开销,⾃然可以获得很⾼的单线程收集效率。Serial 收集器对于运⾏在 Client 模式下的虚拟机来说是个不错的选择
ParNew: ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使⽤多线程进⾏垃圾收集外,其余⾏为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全⼀样 。 它是许多运⾏在 Server 模式下的虚拟机的⾸要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后⾯会介绍到)配合⼯作。
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 来禁⽤该功能
CMS: CMS(Concurrent Mark Sweep)收集器是⼀种以获取最短回收停顿时间为⽬标的收集器。它⾮常符合在注重⽤户体验的应⽤上使⽤。 CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第⼀款真正意义上的并发收集器, 它第⼀次实现了让垃圾收集线程与⽤户线程(基本上)同时⼯作。
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是⼀种 “标记-清除”算法实现的,它的 运作过程相⽐于前⾯⼏种垃圾收集器来说更加复杂⼀些。整个过程分为四个步骤 :
G1: G1 (Garbage-First) 是⼀款⾯向服务器的垃圾收集器,主要针对配备多颗处理器及⼤容量内存的机器. 以极⾼概率满⾜ GC 停顿时间要求的同时,还具备⾼吞吐量性能特征。 被视为 JDK1.7 中 HotSpot 虚拟机的⼀个重要进化特征。它具备以下特点 :
ZGC收集器: 与 CMS 中的 ParNew 和 G1 类似,ZGC 也采⽤标记-复制算法,不过 ZGC 对该算法做了重⼤改 进。 在 ZGC 中出现 Stop The World 的情况会更少! 详情可以看 : ZGC
基本函数接口: 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 函数式接口可以被隐式转换为 lambda 表达式。 基本的函数式接口主要有四个
Lambda表达式:Lambda它其实是匿名函数,通过约定好怎么传入参数,怎么返回参数,由编译器负责参数类型的猜测并执行结果,Lambda表达式的基本语法
Optional类的使用:Optional类的作用主要是为了解决空指针的问题,通过对结果的包装,并使用方法来代替if判断,为流式编程打下了良好的基础
Stream流式编程:Stream API借助Lambda表达式,提供串行和并行两种模式进行汇聚操作,并行模式能够充分利用多核处理器的优势,使用 fork/join 来拆分任务和加速处理过程
方法引用(::双冒号操作符):简单来说就是一个Lambda表达式,方法引用提供了一种引用而不执行方法的方式,运行时方法引用会创建一个函数式接口的实例
public class funRefTest{
@Test
//使用Lambda表达式
Consumer<String> consumer1 = x -> System.out.println(x);
consumer1.accept("Lambda表达式");
//使用方法引用
Consumer<String> consumer2 = System.out::println;
consumer2.accept("方法引用::");
}
池化技术相⽐⼤家已经屡⻅不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应⽤。池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率 。线程池提供了⼀种限制和管理资源(包括执⾏⼀个任务)。 每个线程池还维护⼀些基本统计信息,例如已完成任务的数量
使用线程池的好处
线程池核心参数:
corePoolSize
的时候,如果这时没有新的任务提交,核心线程数不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁keepAliveTime
参数的时间单位线程池有哪些工作队列
Integer.MAX_VALUE
,非连续性内存空间饱和策略定义: 如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor
定义一些策略:
ThreadPoolExecutor.AbortPolicy
: 抛出RejectedExcutionException
来拒绝新任务的处理ThreadPoolExecutor.CallerRunsPolicy
: 调用执行自己的线程运行任务,你不会任务请求.但是这种策略会降低对于新任务提交速度,影响程序的整体性能.另外,这个策略喜欢增加队列容量.如果你的应用程序可以承受此延迟并且不能丢弃任何一个任务请求的话,你可以选择这个策略ThreadPoolExecutor.DiscardPolicy
: 不处理新任务,直接丢弃掉ThreadPoolExecutor.DiscardOldestPolicy
: 此策略将丢弃最早的未处理的任务请求MySQL中left join的优化
类似提问方式:如果线上环境出现问题比如网站卡顿重则瘫痪 如何是好?
—>linux—>mysql/redis/nacos/sentinel/sluth—>可以从以上提到的技术点中选择一个自己熟悉单技术点进行分析
以mysql为例
前提: 由于慢查询日志记录默认是关闭的,所以开启数据库mysql的慢查询记录的功能 从慢查询日志中去获取哪些sql语句时慢查询 默认10S ,从中获取到sql语句进行分析
explain 分析一条sql
还可能这样去提问:sql语句中哪些位置适合建索引/索引建立在哪个位置?
Select id,name,age from user where id=1 and name=”xxx” order by age
总结: 查询字段 查询条件(最常用) 排序/分组字段
补充: 如何判断是数据库的问题? 可以借助于top命令
在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。慢查询的优化首先要搞明白慢的原因是什么? **是查询条件没有命中索引?是加载了不需要的数据列?还是数据量太大? **所以优化也是针对这三个方向来的
另一种说法SQL执行慢的情况分析
深入一点来了解慢查询
慢查询,顾名思义,执行很慢的查询。有多慢?超过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秒就记录】
慢查询的配置文件 my.cnf
在MySQL的配置文件my.cnf中写上:
long_query_time = 10
log-slow-queries = /var/lib/mysql/mysql-slow.loglong_query_time是指执行超过多久的SQL会被日志记录下来,这里是10 秒。
log-slow-queries设置把日志写在哪里。为空的时候,系统会给慢查询日志赋予主机名,并加上slow.log。如果设置了参数log-long-format,那么所有没有使用索引的查询也将被记录。
这是一个非常有用的日志。它对于性能的影响不大(假设所有查询都很快),并且强调了那些最需要注意的查询(丢失了索引或索引没有得到最佳应用)
如何优化下面的语句?
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索引的缺点
MySQL的BTree索引使⽤的是B树中的B+Tree,但对于主要的两种存储引擎的实现⽅式是不同 的。
B树 & B+树的异同
MyBatis:
Mybatis Plus:
MyBatis的优点:
- MyBatis封装了JDBC底层访问数据库的细节,是我们不需要与JDBC API打交道,就可以访问数据库
- MyBatis简单易学, 我们直接编写SQL语句, 适合对于SQL要求比较高的项目
- SQL语句封装在配置文件中,便于统一管理与维护, 降低了程序的耦合度
- SQL代码从程序代码中彻底分离出来,可重用
- 提供了动态SQL标签,支持编写动态SQL
- 提供映射标签,支持对象与数据库的ORM字段关系映射
缺点:
- 过于依赖数据库SQL语句,导致数据库移植性差,更换数据库,如果SQL语句有差异,SQL语句工作量较大
- 由于XML里标签Id必须唯一,导致DAO中方法不支持方法重载
1. <insert id="insertBatch" >
2. insert into tbl_employee(last_name,email,gender,d_id) values
3. <foreach collection="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注解, 做统一事务的处理
缓存在同⼀时间⼤⾯积的失效,后⾯的请求 都直接落到了数据库上,造成数据库短时间内承受⼤量请求
解决办法:
最近返校之后感觉学习效率也有相应的提升,也希望自己和大家都能找到一份还不错的工作,一起加油,自身足够强大才能不受这份寒气的影响,总之一直在路上就行了