10k模拟面试

HeJin大约 14 分钟

10k模拟面试

1.java中面向对象的理解

Java是一门面向对象的语言,在Java里一切皆对象。面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成类(类实例化后才是对象),让对象去解决对应的问题。而java中的面向对象,主要表现在三个方面:封装继承多态

  • 封装:把一类事物的行为和属性抽象出来,封装成一个类。然后根据这个类去构建具体的事物(实例对象)。这样就可以对一类事物进行统一定义和管理。
  • 继承:一个类可以继承一个类,被继承的类我们称之为【父类】或者【超类】,另一个类称之为【子类】也叫【派生类】。继承可以解决代码复用的问题。因为某些类具有相同或者相似的行为,就可以使用继承。所有类的父类是Object。被final修饰的类不能被继承。
  • 多态:同一个行为具有多个不同表现形式或者说形态的能力。
    • (1)形成条件有三个:继承重写父类引用指向子类对象
    • (2)在Java中有编译类型运行时类型,前者在编译的时候就能确定具体调用哪个版本的方法,字节码指令执行时直接调用即可,而动态类型必须等待运行时才能确定类型。
    • (3)常量池是我们的资源仓库,里边保存了大量的符号引用(就是的你给类、方法、变量的名字),这些符号引用有一部分会在类加载阶段或者第一次使用的时候就被转化为【直接引用】,这种转化叫做静态解析,另一部分会在运行期间转化为直接引用,这一部分称之为动态链接
    • (4)抽象类和接口:
      • 抽象类是为了约定,存在的目的就是为了让子类去继承。本类需要实现的功能就在本类实现,需要让子类实现的功能就直接定义好方法即可。抽象类必须有子类,抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理。
      • 接口是比抽象类更高级的抽象,接口中只能有方法的定义,接口是契约、是约定子类必须具备的某些能力,是需要子类去实现的。接口是多实现的,一个类可以实现多个接口,但是只能继承一个类。接口之间也可以相互继承。继承是 is-a 的关系, dog is an animal。 man is a human。实现是 can-do的关系, 实现更体现一个类的能力,通过实现多个接口是可以聚合多个能力的。
      • 抽象类是模板式的设计,而接口是契约式设计。抽象类设计时往往就是将相同实现方法抽象在父类,由子类独立实现那些实现各自不同的实现。
    • (5)好处:提高了代码的维护性(继承保证);提高了代码的扩展性(由多态保证)。弊端:不能使用子类的特有功能。

2.创建线程的方式

  • 继承Thread类重写run方法
  • 实现Runnable接口
  • 实现Callable接口

3.线程池的七个参数

为什么要使用线程池?

(1) 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

(2) 提高响应速度。 当任务到达时,任务可以不需要等到线程创建就能立即执行。

(3) 提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。

ThreadPoolExecutor

public ThreadPoolExecutor
    (int corePoolSize,
     int maximumPoolSize,
     long keepAliveTime,
     TimeUnit unit,
     BlockingQueue<Runnable> workQueue,
     ThreadFactory threadFactory,
     RejectedExecutionHandler handler)
corePoolSize指定了线程池里的线程数量,核心线程池大小
maximumPoolSize指定了线程池里的最大线程数量
keepAliveTime当线程池线程数量大于corePoolSize时候,多出来的空闲线程,多长时间会被销毁
unit时间单位,TimeUnit
workQueue任务队列,用于存放提交但是尚未被执行的任务
threadFactory线程工厂,用于创建线程,线程工厂就是给我们new线程的
handler所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略

常见的工作队列我们有如下选择,这些都是阻塞队列,阻塞队列的意思是,当队列中没有值的时候,取值操作会阻塞,一直等队列中产生值。

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。
  • LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。

线程池提供了四种拒绝策略:

  • AbortPolicy:直接抛出异常,默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务;

自定义线程池

  1. 拒绝策略其实很简单,ExecutorService构造时可以不传递拒绝策略,默认使用异常抛出的方式。
  2. 阻塞队列我们搞一个定长的队列就好了,ArrayBlockingQueue<>(DEFAULT_SIZE)
  3. 线程工厂的获取我们可以使用以下的方法:
    • 第一种办法,看看原生的怎么搞一个线程工厂。
    • Google guava 工具类 提供的 ThreadFactoryBuilder 。
    • Apache commons-lang3 提供的 BasicThreadFactory。

4.hashmap实现原理

https://blog.csdn.net/androidstarjack/article/details/124507171open in new window

1.HashMap的底层数据结构

2.hash的计算规则

3.默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?

4.HashMap的主要参数都有哪些

5.哈希冲突及解决方法

6.HashMap如何有效减少碰撞?

7.HashMap可以实现同步吗?

8.为啥我们重写equals方法的时候需要重写hashCode方法呢?

9.HashMap什么时候进行扩容?它是怎么扩容的呢?

10.JDK1.7扩容的时候为什么要重新Hash呢,为什么不直接复制过去?

11.和Hashtable的区别是什么?

12.什么是Java集合中的快速失败(fast-fail)机制?

13.HashTable一定是线程安全吗?它会有快速失败的时候吗?

14.为什么String, Interger这样的wrapper类适合作为键?

15.HashMap的数据结构?

16.HashMap的工作原理?

17.当两个对象的hashCode相同会发生什么?

18.你知道hash的实现吗?为什么要这样实现?

19.为什么要用异或运算符?

20.HashMap的table的容量如何确定?loadFactor是什么?该容量如何变化?这种变化会带来什么问题?

21.HashMap中put方法的过程?

22.数组扩容的过程?

23.拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

24.说说你对红黑树的见解?

25.jdk8中对HashMap做了哪些改变?

26.HashMap,LinkedHashMap,TreeMap有什么区别?

27.HashMap&TreeMap&LinkedHashMap使用场景?

28.HashMap和HashTable有什么区别?

29.HashMap 的底层数组长度为何总是2的n次方

30.jdk1.8中做了哪些优化优化?

31.HashMap线程安全方面会出现什么问题

32.为什么HashMap的底层数组长度为何总是2的n次方

33.那么为什么默认是16呢?怎么不是4?不是8?

34.HashMap的不安全体现在哪里?

35.为什么JDK1.8使用红黑树?

36.1.8中的扩容为什么逻辑判断更简单

37.HashMap中容量的初始化

38.HashMap的put方法的具体流程?

39.HashMap是怎么解决哈希冲突的?

40.HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?

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

42.HashMap 和 ConcurrentHashMap 的区别

43.ConcurrentHashMap 和 Hashtable 的区别?

44.ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

45.Java中的另一个线程安全的与HashMap极其类似的类是什么?同样是线程安全,它与HashTable在线程同步上有什么不同?

46.HashMap&ConcurrentHashMap的区别?

47.为什么ConcurrentHashMap比HashTable效率要高?

48.针对ConcurrentHashMap锁机制具体分析(JDK1.7VSJDK1.8)?

49.ConcurrentHashMap在JDK1.8中,为什么要使用内置锁synchronized来代替重入锁ReentrantLock?

50.ConcurrentHashMap简单介绍?

51.ConcurrentHashMap的并发度是什么?

5.cs和aqs

6.nio

7.数据库索引

需求:按照a,b,c三个字段查询,如何去建索引

建立复合索引

create index idx_a_b_c user(a, b, c);

8.数据库索引的种类,为什么建索引

聚簇索引和非聚簇索引:MySQL的底层是B+树,数据是存在叶子节点的。叶子结点同时存储索引和数据,就是聚簇索引(一般是主键索引)。叶子结点只存储索引,不存储真正的数据,存储主键ID,叫做非聚簇索引。非聚簇索引找到确定的索引之后,需要回表(主键索引)查询最后的数据。

普通索引:

唯一索引:

复合索引(联合索引):

非聚簇索引:

哈希索引:

空间索引:

创建索引可以大大提高系统的性能。

9.mysql更新语句会上锁吗?什么锁

如果更新条件不是索引字段,会上表锁。

更新条件不是索引字段,会上行锁。

10.Spring框架IOC容器启动过程

Spring IOC的理解

  • Spring IOC解决的是对象管理对象依赖的问题。之前是我们自己手动new对象,现在则把对象交给Spring的IOC容器管理
  • IOC容器可以理解为一个对象工厂,工厂管理这些对象的创建和依赖关系。我们需要对象的时候,从工厂里面获取就行。
  • 说起IOC,一般会说到控制反转依赖注入。控制反转就是把原本自己掌控的事交给别人来管理。本来是我们自己new对象,现在把对象的控制权交给Spring容器了。依赖注入是控制反转的实现方式。对象无需自行创建或者管理自己的依赖关系,依赖关系被自动注入到需要它们的对象当中去。

Spring IOC的好处

  • 对象集中进行统一管理,降低耦合度。与工厂模式类似,我们只需要知道怎么从工厂里面获取对象,而不用关系工厂内部是怎么实现的。
  • 如果项目里面的对象都是new就可以创建的话,没有多个实现类,那么不使用Spring也可以。但是基本上项目里面的实现类是很多的。
  • Spring不仅仅是管理创建对象,还有一整套的Bean生命周期管理。这样可以很方便的实现功能扩展,比如AOP实现对象增强。

Spring框架IOC容器启动过程

11.Spring框架bean的生命周期

  • 普通Java对象和Spring所管理Bean实例化的过程是有区别的。
    • 普通Java环境下创建对象:(1)java源码被编译为class文件。(2)等到类被初始化:new、反射等。(3)class文件被虚拟机通过类加载器加载的JVM。(4)初始化对象。简单来说,是通过Class对象作为模板进而创建出具体的实例。
    • Spring所管理的bean:除了Class对象,还会使用BeanDefinition的实例来描述对象的信息。Class对象描述了类的信息,BeanDefinition描述了对象的信息。
  • 过程:
    1. Spring在启动的时候需要扫描在XML/注解/JavaConfig中需要被Spring管理的Bean信息
    2. 然后,把这些信息封装成BeanDefinition。最后会把这些信息放到一个beanDefinitionMap中。key是beanName,value是BeanDefinition对象。这一步就是将定义的元数据加载起来,目前真实对象还没有被实例化
    3. 接着会遍历这个beanDefinitionMap,执行BeanFactoryPostProcessor这个Bean工厂后置处理器的逻辑。在这里,我们也可以自定义BeanFactoryPostProcessor来对我们定义好的Bean元数据进行获取或者修改。
    4. BeanFactoryPostProcessor后置处理器执行完之后,就到了实例化对象。Spring里面是通过反射来实现的,一般情况下会通过反射选择合适的构造器来把对象实例化。但这里把对象实例化,只是把对象给创建出来,而对象具体的属性还没注入。接下来就是对象的相关属性注入(循环依赖问题),属性注入完之后,就是初始化的工作。
    5. Bean的初始化:首先判断该Bean是否实现了Aware相关的接口,如果存在则填充相关的资源。Aware相关的接口处理完之后,就会到BeanPostProcessor后置处理器了。BeanPostProcessor有两个方法,一个是before,一个是after。这个BeanPostProcessor后置处理器是AOP实现的关键
    6. BeanPostProcessor相关子类的before方法执行完,则执行init相关的方法。在对象实例化之后,我们如果需要初始化工作,就可以在init方法执行(比如把数据库的配置信息同步到redis等)。等init方法执行完之后,就会执行BeanPostProcessor的after方法。到这里,我们就可以获取到对象进行使用了。销毁的时候就看有没有配置相关的destroy方法,执行即可。
  • 循环依赖问题:三级缓存。三级缓存就是3个Map。singletonObjects(一级,日常实际获取Bean的地方);earlySingletonObjects(二级,还没进行属性注入,由三级缓存放进来);singletonFactories(三级,Value是一个对象工厂)。
    1. A对象实例化之后,属性注入之前,其实会把A对象放入三级缓存中。key是BeanName,Value是ObjectFactory。
    2. 等到A对象属性注入时,发现依赖B,又去实例化B时。
    3. B属性注入需要去获取A对象,这里就是从三级缓存里拿出ObjectFactory,从ObjectFactory得到对应的Bean(就是对象A)。把三级缓存的A记录给干掉,然后放到二级缓存中。显然,二级缓存存储的key是BeanName,value就是Bean(这里的Bean还没做完属性注入相关的工作)。
    4. 等到完全初始化之后,就会把二级缓存给remove掉,塞到一级缓存中。我们自己去getBean的时候,实际上拿到的是一级缓存的。
    5. 为什么是三级缓存?我们的对象是单例的,有可能A对象依赖的B对象是有AOP的(B对象需要代理)。假设没有第三级缓存,只有第二级缓存(Value存对象,而不是工厂对象)。那如果有AOP的情况下,岂不是在存入第二级缓存之前都需要先去做AOP代理?这不合适。这里肯定是需要考虑代理的情况的,比如A对象是一个被AOP增量的对象,B依赖A时,得到的A肯定是代理对象的。所以,三级缓存的Value是ObjectFactory,可以从里边拿到代理对象。二级缓存存在的必要就是为了性能,从三级缓存的工厂里创建出对象,再扔到二级缓存(这样就不用每次都要从工厂里拿)。

12.Spring框架AOP

  • AOP主要解决的是非业务代码的抽取,底层实现技术是动态代理。在Spring中实现依赖的BeanPostProcessor。所谓的面向切面编程就是在方法前后增加非业务代码。
  • 注解 + AOP实现日志。

13.Spring事务中,什么情况下导致事务失效

14.Spring框架提供几种事务的传播行为

15.springboot相比于SSM的优势在哪劣势在哪

16.手写springboot的starter

17.微服务项目的分布式事务怎么实现

18.分布式事务和传统的事务相同点和不同点

19.电商项目退单流程,退一部分