前言:本文只是记录博主遇到的或者博主认为比较常见的面试题,问题和答案仅代表博主个人观点。如果正在看这篇博文的你有你认为比较常见而我没有记录的,欢迎评论,我会挑选记录,方便大家一起面试造火箭。(不定时更新)

1.HashMap的底层原理

HashMap可以接受null的键值对,是线程不安全的,它是基于hashing的原理,jdk8后采用数组+链表+红黑树的数据结构。通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时,它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。

2.Redis持久化的几种方式

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。过程比较简单,也不需要过多配置。

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。可以说几乎是用户的每一个操作,都会记录。RDB比AOF恢复数据较快,性能较高,但是数据安全性较低。

实际操作中,可以采用RDB与AOF混合持久化的方式进行持久化。

3.悲观锁与乐观锁

悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,用于多写的情况;乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。用于多读的情况。

4.数据库数据太多跑不动怎么办

垂直分库和垂直分表

垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。

垂直分表就是把一些字段长度大的单独出来用一个字段关联

水平切分 库内分表(比如按月份数据分表) 分库分表(先按业务分库,再按月份分表)

5.#与$的区别

#会对SQL进行预编译,一般传字段参数值使用,如id = #{id},$不会进行预编译,传进来是什么就是什么,一般用于传表名或者字段,如 order by ${id},但需要注意防SQL注入,比如传表名的时候传入user;--则会导致后面的SQL被注释掉从而导致SQL注入问题,一般能用#尽量用#,如果使用$要对传入值进行判断。

6.String与StringBuffer与StringBuilder的区别

String是不可变的,每次对String的操作都会生成新的String对象,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象;StringBuffer是线程安全的,效率较低,StringBuilder是线程不安全的,效率较高;效率StringBuilder >  StringBuffer  >  String,如果要操作少量的数据用String,单线程操作字符串缓冲区下操作大量数据用StringBuilder,多线程操作字符串缓冲区下操作大量数据 用StringBuffer

7.spring的IOC和AOP底层原理

IOC为控制反转,将创建对象交给spring去做,spring通过扫描注解,或者解析xml配置文件,获取class的属性值,然后通过反射创建对象保存在IOC容器中。

 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。JDK 动态代理机制只能对接口进行代理,其原理是动态生成一个代理类,这个代理类实现了目标对象的接口,目标对象和代理类都实现了接口。CGLIB 是对目标对象本身进行代理,其原理是使用字节码生成工具在内存生成一个继承目标对象的代理类,然后创建代理对象实例。

8.简单说说集合

集合主要分为List,Set,Map,其中List和Set继承Collection接口;

List主要分为ArrayList,Vector,LinkedList,

ArrayList的底层数据结构是数组,查询快,增删慢,线程不安全,效率高

Vector的底层数据结构是数组,查询快,增删慢,线程安全,效率低

LinkedList的底层数据结构是链表,查询慢,增删快,线程不安全,效率高

需要线程安全使用Vector,查询较多使用ArrayList,增删较多使用LinkedList

Set主要分为HashSet,LinkedHashSet,TreeSet

HashSet底层数据结构是Hash表,无序且唯一,通过hashCode()和equals()方法保证数据的唯一性。

LinkedHashSet的底层数据结构是链表和哈希表,有序且唯一,通过链表保证元素有序,通过哈希表保证数据唯一。

TreeSet底层数据结构是红黑树,有序且唯一,通过自然排序或者比较器排序保证元素排序,根据比较的返回值是否是0来决定保证数据唯一。

需要排序则使用TreeSet,需要有序则使用LinkedHashSet,不需要排序则使用HashSet

Map主要分为HashMap,TreeMap,HashTable,ConcurrentHashMap

HashMap存储数据采用的哈希表结构,无序,线程不安全,效率高,key和value都允许有空值null。

HashTable是线程安全的,效率低,所有public方法声明中都有synchronized关键字,不允许有空值null。

ConcurrentHashMap是线程安全的,jdk1.8以前采用Segment分段锁,1.8以后采用CAS + synchronized保证线程安全。

9.Redis的三大问题(雪崩,击穿,穿透)

雪崩:指大量的缓存由于过期时间都设置成一样,导致在同一时间大量的缓存同时失效,或者redis宕机了,导致大量的请求同时访问到数据库,雪崩的发生可能会导致数据库挂掉,并使服务器瘫痪。

解决方案,对于第一种来说,尽量将redis缓存的过期时间错开,避免同一时间大量的缓存同时失效。对于第二种,首先尽量使用高可用集群(主从架构+Sentinel 或者Redis Cluster),避免redis挂掉,

其次当redis挂掉以后要限流或者熔断,避免整个服务挂掉

击穿:当高并发的时候,大量的请求同一个缓存,会导致缓存失效,导致这些请求都直接到数据库,可能会导致数据库挂掉。

解决方案:添加分布式锁或者互斥锁。

穿透:指的是大量请求缓存和数据库都不存在某一条数据,由于缓存和数据库都不存在,导致每次的请求都会访问数据库。

解决方案:缓存空值或者采用布隆过滤器先过滤掉非法数据

10.jvm的底层原理

jvm运行时数据区主要分为五块区域,堆,栈,方法区,本地方法区,程序计数器,其中堆和方法区是线程共享,也就是线程不安全;栈,本地方法区,程序计数器是线程私有的,也就是线程安全的。堆存放的对象的实例以及数组(所有new的对象),栈存放的局部变量以及基本数据类型,方法区存放的虚拟机加载的类信息,常量,静态变量,本地方法区存放的是Native方法,程序计数器的作用是当前线程所执行的字节码的行号指示器。堆内存空间主要分两块区域,新生代与老年代,内存比为1:2,新生代中又分为Eden区与两个S区(Survivor Spaces,S0,S1),内存比为8:1:1,Eden区主要存放新生的对象,S区存放的是每次垃圾回收(Minor GC)幸存下来的对象,当每次垃圾回收时,如果没有被回收的对象的大小超过S区的内存大小的50%就会直接进入老年区,否则则会进入S区,S0和S1会进行相互复制,S区满了以后也会进行一次垃圾回收,当在S区的对象还未被回收时那么他的岁数就会+1,当岁数等于15(默认值)的时候,该对象就会进入老年区,当老年区满了以后就会发生Full GC。

11.jvm的底层调优

12.MySQL的索引的底层原理(未完成)

MySQL的索引的底层实现是B+树。

13.Mybatis的缓存

Mybatis分为一级缓存和二级缓存,Mybatis默认开启一级缓存。

一级缓存的作用域只对同一个SqlSession有效,当执行某一条SQL的时候,sqlSession会先从一级缓存找有没有该数据,如果有则直接取缓存仲的数据,如果没有则从数据库查询并将结果放入一级缓存。当配置flushCache=true时则会清除缓存;当全局配置localCacheScope=Statement时,完成一次查询就会清除缓存;在执行commit,rollback,update方法时会清空一级缓存。

二级缓存的作用域是同一个命名空间。配置二级缓存有三个地方,第一个全局的mybatis配置cacheEnabled=true(默认开启),第二个在mapper映射文件配置缓存标签<cache/>或者<cache-ref namespace=""/>,第三如果只需要在某个查询中使用二级缓存,则在select查询语句标签上添加useCache="true"。需要注意的是,二级缓存只有当commit了以后才会生效,另外得在结果集的对象实现序列化接口Serializable。二级缓存适合在读多写少的时候开启,对关联表的查询,关联的所有表的操作都必须在同一个namespace,且在别的namespace中不要操作该namespace下的表,否则会出现数据脏读。

14.Spring中BeanFactory和ApplicationContext的区别

BeanFactory,当使用BeanFactory实例化对象时,配置的bean不会马上被实例化,而是等到你使用该bean的时候(getBean)才会被实例化。好处是节约内存,坏处是速度比较慢。

ApplicationContext,如果配置的bean是singleton,在加载xml的时候它都会被实例化。好处是可以预先加载,坏处是浪费内存。

15.spring中bean的生命周期和作用域

作用域:基本的有两个:

Singleton单例;Prototype原型

 Web 容器另外支持三个:

Session会话;请求Request;全局会话GlobalSession,仅在使用portlet context的时候有效

生命周期:

简单来说只有四个阶段:

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

展开来说详细流程:

      1、Spring对bean进行实例化

  2、Spring将值和bean的引用注入到bean对应的属性中

  3、如果bean实现了BeanNameAware接口, Spring将bean的ID传递给setBeanName()

  4、如果bean实现了BeanFactoryAware接口, Spring将调用setBeanFactory(),将BeanFactory容器实例传入

  5、如果bean实现了ApplicationContextAware接口, Spring将调用setApplicationContext(),将bean所在的应用上下文的引用传入进来

  6、如果bean实现了BeanPostProcessor接口, Spring将调用它们的postProcessBeforeInitialization()

  7、如果bean实现了InitializingBean接口, Spring将调用它们的afterPropertiesSet()。类似地,如果bean使用init-method声明了初始化方法,该方法也会被调用

  8、如果bean实现了BeanPostProcessor接口, Spring将调用它们的postProcessAfterInitialization()

  9、此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁

  10、如果bean实现了DisposableBean接口, Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用