我是靠谱客的博主 甜美台灯,最近开发中收集的这篇文章主要介绍2022年个人Java面试总结   1.你们公司的关系型数据库用的是什么?,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

   

        今天面试了深圳一家1000+规模的房地产互联网公司,由于准备不充分惨遭毒打,不过整个过程还是比较轻松愉快的。

        惨遭毒打之后,将面试官的问题全部记录下来了,并正在寻找答案中QAQ。以下答案仅代表个人观点,回答的深度不一定符合所有人,读者应该关注的是面试官抛出的问题,而不是作者给出的答案,当然,如果觉得作者答案写的有问题的地方也欢迎指正。

        工作经验1.5年,Java开发工程师

1.你们公司的关系型数据库用的是什么?

答:我们公司主要用的关系型数据库用的是MySQL,oracle也有在用,但是主要接触的是mysql。

2.知道mysql有哪些索引吗?

答:

主键索引:是一种唯一索引,但是必须指定primary key,每个表只能有一个主键,可以设置联合主键。

唯一索引:索引列所有值都只能出现一次,即必须唯一,值可以为空

普通索引:基本的索引类型,值可以为空,没有唯一性的限制

全文索引:全文索引的索引类型为FULLTEXT。全文索引可以在varchar、char、text类型的列上创建。可以通过alter tablecreate index命令创建。FULLTEXT 用于搜索很长一篇文章的时候,效果最好。alter table 表名 add FULLTEXT(‘字段名’)

联合索引:指多个字段上创建的索引,只有符合最左匹配原则时索引才会被用到。

3.知道怎么创建索引吗?项目中有没有用到索引?在什么样的业务场景下如何创建索引的?

        答:知道,create index 索引名 on table(字段名,字段名····),

4.说一说mysql的隔离级别,mysql默认的隔离级别是什么?Oracle默认的隔离级别是什么?

        答:mysql的隔离级别有读未提交,读已提交,可重复读,串行化,读未提交可以避免脏写,脏写就是一个事务回滚了另一个事务已经提交的数据,读已提交避免了脏读,脏读就是一个事务读取到了另一个事务还没有提交的修改数据,可重复读避免了不可重复读,不可重复读就是一个事务读取到了另一个事务已经提交的修改数据,串行化避免了幻读,幻读就是一个事务读取到了另一个事务重新提交的数据

5.有了解过MySQL的缓冲池吗?说一说它的工作原理

        答:MySQL缓冲池的作用就是为了提高MySQL的SQL执行效率的,当我们执行一条查询语句的SQL时,它会先去MySQL的缓冲池里面查找,如果找到数据则直接返回,如果没有找到,则会从磁盘里面加载数据到缓冲池之后再返回。当执行update语句时,也会先更新缓存里面的缓存页,然后根据刷盘策略将缓存页刷入磁盘。(这里面看你对缓冲池的理解程度,如果面试官感兴趣,你可以深入跟面试官探讨一下,数据描述,free链表,flush链表,lru链表实现冷热数据区分等等)

5.有没有优化过sql语句?怎么优化的?

答:不知道怎么说,原话:sql优化涉及到复杂sql,复杂sql主要在报表系统那边,由于我没怎么接触过报表系统,所以不是很了解。

6.知道处理字符串的类有哪些吗?说一下他们之间的区别

        答:String,StringBuffer,StringBuilder,String的底层是由char数据组成的,并且由final关键字修饰,所以当我们做字符串拼接时,底层是new的一个String来拼接两个字符串,然后将这变量的引用指向这个新new出来的String,StringBuilder底层也是char数组组成的,但是没有final关键字修饰,所以对于字符串的拼接是直接在这个对象上面操作的,但是StringBuilder不是线程安全的。StringBuffer的原理和StringBuilder原理相似,但是StringBuffer对于字符串的操作都使用了synchronized关键字修饰,所以它是线程安全的。

7.知道hashmap吗?说一下它的实现原理

答:HashMap底层是由数组+链表组成的,jdk1.8之后链表改为红黑树,数组是HashMap的主体,链表主要是为了解决hash冲突的,对于查找和添加等操作很快。

添加:当往HashMap添加一个Entry时,首先对Entry的key做hash运算,然后用计算出来的hash值跟HashMap 的长度进行取模获得到一个数组的下标,如果该位置没有数据则直接放入,如果有值,则说明已经产生了hash冲突,此时就会将该Entry中的key跟该位置链表中Entry的key一一进行equals比较,如果找到相等的key,则进行value覆盖,如果没有找到则将该Entry添加到链表的最后。HashMap的初始大小为16,它的负载因子为0.75即当HashMap的长度达到16*0.75=12时,HashMap就会自动扩容为原来的2倍,当HashMap的长度大于64且链表的长度大于8时,链表就会转变为红黑树,目的就是为了加快HashMap的查询效率。

查找:hashMap.get(key),此时会先对key做一个hash运算,然后对HashMap的长度进行取模可以获取到一个数组下标,当数组的下标对应的位置没有值时就会返回null,当数组的下标有值时,就会将这个key 跟该链表中entry的key逐一进行equals比较,如果发现有相等的,则返回该entry的value,如果没有与之相等的,则说明没有找到,返回null。

8.知道hashmap的遍历方法有哪几种吗?

答:

1)使用for-each迭代entries(使用之前需要判断是否为null,否则会报空指针异常)

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
    System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue())

}

2)使用for-each迭代keys和values

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Integer key : map.keySet()) {
    System.out.println("Key = " + key);
}

for (Integer value : map.values()) {
    System.out.println("Value = " + value);
}

3)使用iterator迭代

使用泛型

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry<Integer, Integer> entry = entries.next();
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

不使用泛型

Map map = new HashMap();
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry entry = (Map.Entry) entries.next();
    Integer key = (Integer)entry.getKey();
    Integer value = (Integer)entry.getValue();
    System.out.println("Key = " + key + ", Value = " + value);
}

4)迭代keys搜索values (效率很低)

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Integer key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println("Key = " + key + ", Value = " + value);
}

9.说一下接口与抽象类的区别

1)一个类可以同时实现多个接口,但是只能继承一个抽象类   (继承角度)

2)抽象类中可以有实例成员,类变量,抽象方法;接口中只能有抽象方法和常量,jdk1.8之后可以有static和default方法。   (成员角度)

3)子类使用extend继承抽象类,使用implement实现接口。 (实现角度)

4)子类实现抽象方法时不允许缩小访问权限;子类在实现接口方法时只能使用public修饰。(子类实现的角度)

5)抽象类中可以有构造方法,接口中不能有构造方法  (构造方法的角度)

10.了解过线程池吗?说一下它的工作原理

        答:了解过,

1.当我们创建线程池时,首先会根据核心线程的参数corePoolSize(假设是5个)去创建5个核心线程,等待线程任务的到来。

2.线程任务到来,会先由核心线程去处理线程任务。

3.当核心线程处理线程任务的速度跟不上线程任务产生的速度时,多余的线程任务会先进入队列workQueue进行等待。

4.随着任务队列里面的任务越来越多,队列已经放满了时,就会根据最大线程数maxNumPoolSize这个参数去创建一些临时线程,假设maxNumPoolSize设置为8,那么就会创建3个临时线程去处理线程任务。

5.如果此时8个线程处理线程的速度还是跟不上任务产生的速度,而且workQueue也已经满了的情况下,就会执行设置好的拒绝策略。

6.当任务产生的速度降下来时,那么任务的数量就会慢慢的减少,直到有的线程获取不到任务时,根据设定的空闲时间keepAliveTime(假设是3秒),即一个线程的空闲时间超过了3s。那么这个线程将会被回收掉,直到线程数等于核心线程数时,线程无论是否空闲都不会被回收。

11.你刚刚说到了拒绝策略,那你知道有哪几种拒绝策略吗?他们的特点是什么?

        CallerRunsPolicy :当线程池没有调用shutDown()方法时,假设是用main函数开启的线程池,那么此时直接由main线程完成该被拒绝的任务。如果调用shutDown(),则该任务会被丢弃。

        AbortPolicy:丢弃任务,并抛出异常。

        DiscardPolicy :直接丢弃,不会抛出异常。

        DiscardOldestPolicy :只要线程池没有执行shutDown(),会丢弃workQueue中最老的一个任务,并将新的任务放入workQueue。

12.多线程自带的阻塞队列有哪几种知道吗?他们有什么区别

        ArrayBlockingQueue:基于数组的先进先出队列,有界,即需要指定初始队列的大小。

        LinkedBlockingQueue:基于链表的先进先出队列,无界,默认大小是Interger.MAX_VALUE,但是也可以指定大小。

        SynchronousQueue:无缓冲的等待队列,无界,一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等待队列中添加的元素被消费后才能够继续添加新的元素,否则将会阻塞,直到队列中的消息被消费。

        

13.那你的项目中有用到线程池吗,说一说是什么场景下使用到的?

答:有用到,我们的项目是和个小贷业务有关的,在申请贷款的时候我们都需要实名,其中涉及到人脸识别,身份证和银行卡照片的上传,我们后台将这类东西称为影像资料,影像资料上传的时候就是使用的多线程,核心线程数为2个,最大线程数为10个,空闲线程存活时间为3s,阻塞队列用大小为40的LinkedBlockingQueue,拒绝策略使用的CallerRunsPolicy来保证线程尽量不丢失。

14.说一下eureka注册中心的原理

答: Eureka工作原理_代码忘烦恼的博客-CSDN博客_eureka

1.Eureka Server启动,等待服务端的注册,如果配置了集群,Eureka Server之间定时通过Replicate 来同步服务注册表,集群的每个Eureka Server都存在一个完整的服务注册表。

2.Eureka Client根据配置的Eureka Server地址去注册中心注册服务。

3.Eureka Client每隔30会想Eureka Server发送一次心跳,证明自己还活着

4.当Eureka Server 经过90s还没有收到某个Eureka Client的心跳时,注册中心会认为该节点失效,从而注销该节点。

5.默认在15分钟内有超过85%的Eureka Client没有想Eureka Server发送心跳时,则此时会被认为是网络原因导致无法通信,就会进入自我保护机制,不在剔除没有发送心跳的服务。

6.当Eureka Client心跳请求恢复正常之后,Eureka Server会退出自我保护模式

7.Eureka Client每隔30秒会从注册中心增量或全量获取注册表,并将注册表的信息缓存到本地。

8.服务调用时,Eureka Client会先从本地缓存中获取调度的服务信息,找不到,先从服务中心刷新注册表,再将注册表缓存到本地。

9.Eureka Client获取到目标服务器信息,发起调用

10.Eureka Client程序关闭时向Eureka Server发送取消请求,Eureka Server将实例从注册表中删除。

15.当Eureka Server进入自我保护模式时会发生什么?

1)Eureka Server不在从注册表中删除因长时间没有发送心跳而已经过期的实例

2)Eureka Server仍然能够接受新服务的注册和查询请求,但是不会同步到其他节点上去(既保证当前节点一定可用)

3)当恢复稳定时,当前Eureka Server 新的注册信息会被同步到其他节点上

16.说一下Spring ioc和Spring AOP的原理

答:

        IOC:IOC控制反转是一种思想,就是将本程序中手动创建对象的控制权交由Spring来管理,并且当一个对象依赖于其他对象时,Spring IOC容器也会动态的将依赖的对象注入进去(DI依赖注入),它实际上就是一个Map(key, value),Map中存放的是各种对象。

        AOP:AOP是面向切面编程,使用AOP可以实现不需要修改代码,只需要修改配置或者加上注解,即可实现功能的扩展。AOP横向扩展的机制代替了传统的纵向继承。Spring实现AOP是通过动态代理实现的,当需要代理的对象实现了某个接口,Spring AOP就会自动使用JDK Proxy去创建代理对象,对于没有实现接口的对象,Spring AOP就会使用cglib生成一个被代理对象的子类来作为代理。

17.redis有哪几种数据结构?

答:string,map,list,set,zset

18.redis的键过期键删除策略知道吗?

        定时删除:在设置键的过期时间的同时,创建一个定时器( timer ). 让定时器在键的过期时间来临时,立即执行对键的删除操作。

        优点:对内存友好

        缺点:对CPU不友好,非常耗费CPU资源

        惰性删除:放任建过期不过,但是当你使用到这个键的时候,就会先判断这个键是否过期,如果过期则删除这个键并返回null

        优点:对CPU友好

        缺点:对内存不友好,可能导致大量的已过期的key没有被删除

        定期删除:每隔一段时间执行一次删除键的操作。

        优点:可以控制过期删除的执行频率

        坏处:服务器必须合理设置执行删除操作的间隔时间以及执行频率

19.redis的数据淘汰策略了解吗?

        答:redis的淘汰策略就是当redis的内存不足时就会触发redis 的淘汰策略,保证写入的成功,当无淘汰策略或没有找到能够删除的key时,reids就会报out of memory,redis的淘汰策略主要有六种。

        noeviction:不删除策略,即当达到最大内存限制时,如果需要更多的内存,直接返回错误信息(读取数据正常)。

        allkeys-lru:所有key通用,优先删除最近最少使用的key

        volatile-lru:针对设置了过期时间的key,优先删除最近最少使用的key

        allkeys-random:所有key通用,随机删除一部分key

        volatile-random:针对设置了过期时间的key,优先删除最近最少使用的key

        volatile-ttl:针对设置了过期时间的key,优先删除快过期的key

20.Redis持久化的原理?

答:

redis提供RDB和AOF两种持久化机制:

RDB持久化方式:

是指用数据集快照的方式(半持久化模式)记录redis数据库的所有键值对,在某个时间点将数据写入一个临时文件。持续化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。

优点:

1.只有一个dump.rdb文件,方便持久化。

2.容灾性好,一个文件可以保存到安全的磁盘上。

3.性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以时IO最大化。使用单独的子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能。

4.相对于数据集大时,比AOF的启动效率高。

缺点:

1.数据安全性低。RDB是间隔一段时间进行持久化,如果持久化的时候redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候。

AOF持久化方式:

AOF机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集。

优点:

1.数据安全,aof持久化可以配置appendfsync属性,有always,每进行一次命令操作就记录到aof文件中一次。

2.通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。

3.AOF机制的rewrite模式。AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中某些命令(比如误操作的flushall)

缺点:

1.AOF文件比RDB文件大,且恢复速度慢。

2.数据集大的时候,比rdb启动效率低

21.说一下rocketmq有哪些组件组成?

答:由Broker,NameServer,生产者,消费者,Broker中又含有Topic与MessageQueue

Broker:Broker的主要作用就是用来接收与存储消息的。

NameServer:所有的Broker,生产者,消费者都会把自己的信息注册到NameServer上,这样就可以知道有哪些Broker,生产者与消费者。

生产者:消息的提供方

消费者:消息的消费方

Topic:只有生产者与消费者订阅了同一个Topic,消费者才可以消费到生产者产生的消息

MessageQueue:生产者写入消息时最终就是通过MessageQueue写入的,一个Topic可以包含多个MessageQueue。

22.说一下rocketmq接收消息的原理(生产者发送消息的原理)

答:在生产者发送消息到RocketMQ之前,会先验证生产者是否正常运行,然后判断group,topic等等是否为空,合法之后,会获取topic的MessageQueue列表。当消息第一次过来的时候,会通过Random随机选择一个MessageQueue,进行发送,如果发送失败,会将该MessageQueue隔离起来,然后重试消息发送,且不会给这个发送失败的MessageQueue投递消息。选择出来MessageQueue之后,(因为MessageQueue是属于Topic的,而Topic又存在于Broker上,所以知道 了要发送消息的MessageQueue,肯定可以获取到对应的BrokerName)发送消息首先会根据BrokerName获取Broker的地址,封装请求头,根据发送消息的不同,选择对应的请求命令发送消息。

23.说一下MQ消息丢失的原理

答:RocketMQ消息丢失的场景主要有三个方面可能存在消息丢失的问题,分别是生产者,MQ,消费者这三个地方。

        生产者:生产者发送消息到RocketMQ的时候,由于网络波动或者网络故障导致消息发送失败。

        MQ:RocketMQ默认的刷盘策略就是异步刷盘,即当收到消息之后,不会立刻将消息保存到磁盘上,而是先将数据写入os cache,过一会再由后台线程将os cache中的数据刷入磁盘,当消息写入os cache,但是还没有刷入磁盘时,MQ所在的服务器宕机了,就会造成消息丢失。

        消费者:当消费者接收到消息后开始消费这条消息,当消费者后面处理逻辑时产生异常或者宕机。

24.RocketMQ是怎么避免消息丢失的?简单说一下原理

答:RocketMQ避免消息丢失的方法是逻辑优化+事务机制。

逻辑优化:假设订单系统下单成功,在本地数据库插入一条订单信息,需要调用库存系统减库存,需要发送下单成功的消息到优惠券系统生成一张优惠券给用户,先执行生成订单,调用库存服务扣减库存,然后再发送订单支付成功的消息到消息队列。

生产者端避免:然后当生产者发送消息到RocketMQ时,会先发送一条half消息,这个half消息跟原来的消息差不多,我把它理解为half状态的消息,当RocketMQ接收到这条half消息的时候,会给生产者返回一个接收成功的响应。简单理解发送这个half消息的目的就是确保生产者与MQ之间的网络通信是正常的(这个时候还看不出来half消息的妙用)。

        假设half消息发送失败了,说明生产者与MQ无法进行正常的通信了,此时直接回滚之前的操作就行,或者执行异常的逻辑,具体根据自己的需求来。

        此时MQ成功接收到half消息后,生产者接收到响应成功的信息后,就会给RocketMQ返回一个commit操作

        RocketMQ接收到half状态的消息时,消费者是看不到这条消息的,需要等生产者返回commit之后这条消息才会被消费者看见

RocketMQ端避免:将一部刷盘策略改成同步刷盘策略,即消息写入os cache之后强制将数据刷入磁盘,然后再返回给生产者消息接收成功的响应。

消费者端避免消息的丢失:消费者接收到消息之后,先处理一些业务逻辑,处理完成之后再返回消息的offset给RocketMQ,只有当RocketMQ成功接收到这个offset之后,才会认为消费者已经成功消费完了这一条消息。

25.RocketMQ重复消费的原理(自己加的,感觉问的比较频繁)

        系统处理超时导致重复调用:订单系统还是序号18中的订单系统,此时用户支付完之后,支付系统需要通过RPC调用订单系统的生成订单方法,但是由于网络原因或者其他原因,导致调用超时,就会导致请求重试,即调用了两次生成订单的方法推送了两条消息。

        生产者接收commit响应成功超时导致消息重发:当RocketMQ接收到half的commit请求时,将half改为消费者可见后就会给生产者返回接收成功,但此时由于网络抖动或什么原因,总之就是生产者没有收到这个响应成功的消息。然后导致产生了超时异常,而且刚好生产者的异常处理就是超时重新发送消息,此时就会重新给RocketMQ重新发送消息。

26.你有什么比较好的解决方案吗?(自己加的,感觉问的比较频繁)

        解决方案:引入幂等性避免重复发送和重复消费

                什么是幂等性:就是用户对一个接口发起一次或多次请求的结果是一致的。

                生产者端避免:

                        1.生产者发送消息之前先去RocketMQ中查询是否这个订单id所对应的消息,没有就说明可以发送,有则不需要重复发送消息(直接去RocketMQ查数据效率很低)。

                        2.使用redis记录已经丢到RocketMQ中的订单消息,生产者发消息前只需要去redis中找是否有这个id的消息就行。

               消费者端避免:

                        消费者在消费消息之前先去查一下优惠券系统是否有这个订单所对应的优惠券,有则说明已经生成优惠券,没有则消费该消息。

27.RocketMQ是如何做到支持海量高并发的?(自己加的,感觉问的比较频繁)

        答:文件顺序写入+OS PageCache写入+OS异步刷入磁盘来支持海量并发。

原理:当生产者发送一条消息到Broker的时候,首先会先将这条消息顺序写入磁盘上面一个叫CommitLog的日志文件,这个CommitLog不止一个文件,每个文件限定1GB大小,消息会追加顺序写入这个文件的末尾,如果一个文件写满了,就会创建一个新的CommitLog文件。当消息写入CommitLog之后,就会将这条消息在磁盘的物理位置,也就是偏移量offset,还有消息的长度,Tag,HashCode等一些信息写入一个叫ConsumerQueue的文件中。ConsumerQueue相当于是消息的一个索引文件。当然Broker不是将消息直接写入磁盘文件的,而是先将消息写入os的PageCache内存缓存中,然后后续由os的后台线程选一个时间将缓存中的数据刷入磁盘。就是在磁盘采用文件顺序写+OS PageCache写入+OS异步后台刷盘的策略,基本可以让消息写入CommitLog的性能跟写内存差不多。

28说一下OpenFeign是如何RPC远程调用的?

29.看你项目中有用到sentinel,你知道sentinel是干什么用的吗?

        答:知道,是用来实现服务的降级、限流、熔断的。

                降级:系统将某些不重要的业务或者接口的功能降低,可以只提供部分功能,也可以完全停掉所有不重要的功能。降级的思想是弃车保帅。

                熔断:降级是应对系统的自身故障,而熔断的目的是应对外部的故障。比如服务A的X功能依赖B服务的某个接口,当B服务接口响应很慢时,A服务的X功能响应也会被拖慢,进一步导致了A服务的线程都卡在了X功能上,A服务的其他功能也会被卡住或拖慢。此时就需要熔断机制,即A服务不在请求B这个接口,A服务内部发现B接口就直接返回错误,从而避免整个A服务被拖慢。

                限流:只允许系统能够承受的访问量进来,超出的会被丢弃。降级从系统功能优先考虑如何应对故障,而限流则从用户访问压力的角度来考虑如何应对故障。

30.那你说一声sentinel实现服务降级,限流,熔断的原理

31.你项目中还用到了nacos,nacos你们一般是用来干什么?

32.nacos是怎么实现动态配置以及环境隔离的

最后

以上就是甜美台灯为你收集整理的2022年个人Java面试总结   1.你们公司的关系型数据库用的是什么?的全部内容,希望文章能够帮你解决2022年个人Java面试总结   1.你们公司的关系型数据库用的是什么?所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部