java面试八股文学习笔记

文章目录

简历注意事项

简历的结构

基本信息\教育背景\求职意向\工作经历*职业技能 **项目经历*\个人优势荣誉

职业技能

  • 放到简历的黄金位置
  • 职业技能 = 必要技术+第三方技术
  • 针对性准备,引导面试官针对性提问
  • 写在简历上的必须能聊,不然就别写

项目描述

  • 项目体现业务深度或技术深度
  • 主导设计过XXX模块(0-1,研发或1-2,改进),不要说太大,逮着模块说
  • 尽可能展示指标数据

如何找到适合的练手项目

gitee或github搜索开源项目,b站黑马项目课程

深入学习项目

  • 技术选型,通用模块,可嵌套到大部分项目中
  • 学习方式,多方位深入挖掘业务和技术
  • 功能实现->常见问题->系统设计

笔试面试可能就是发过来一个牛客网的链接

不仅要投校招,也要投社招

游戏策划

还要重点学的东西,MySQL,JUC(多线程),JVM,jdbc,rabbitMQ,kafka,直接刷题也行

数据经常问,hashmap,数组什么的

黑马的java虚拟机,多线程,mysql,redis,八股文,javaguide

redis黑马的课程只看实战篇就可以了

csdn上两个最火的java八股文要看

简历二月开始就要投了,找实习
https://heuqqdmbyk.feishu.cn/wiki/RymLwLLWfieibHkjf17cKhY4nlf(问答文稿地址)

Redis篇

缓存穿透

方案一,缓存空数据

方案二,布隆过滤器

检索一个元素是否在一个集合中

一个元素对应多个hash也是为了减小hash数组的大小

bitmap,只存0或1

SQL篇

如何定位慢查询

  • 开源工具,调试工具Arthas,运维工具,prometheus\Skywalking
  • mysql的配置文件中配置慢查询日志

语句执行很慢,如何分析

框架篇

spring中的单例bean是线程安全的吗

不是线程安全的

spring中的bean默认都是单例的

因为在spring中的bean中注入的一般都是无状态(不能被修改)的对象,没有线程安全问题

如果在bean中定义了可修改的成员变量,要考虑线程安全问题,可以使用多例或者加锁来解决

AOP相关

什么是AOP

面向切面编程,用于将哪些与业务无关,但对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合
你的项目有没有用到AOP(哪里可以用到AOP)

  • 记录操作日志
  • 缓存
  • spring事务
    spring中的事务如何实现
    通过AOP功能,对方法前后进行拦截,在执行方法签开启事务,在执行方法后根据执行情况提交或回滚事务

spring事务失效的场景

  • 异常捕获处理,自己处理了异常,没有抛出->手动抛出
  • 抛出检查异常(编译时异常),配置rollbackFor属性为Exception
  • 非public方法导致的事务失效,改为public

spring的bean的生命周期

构造函数->依赖注入->Aware接口->BeanPostProcessor#before->初始化方法->BeanPostProcessor#after->销毁bean

bean的循环依赖

什么是循环依赖

循环依赖,两个bean互相引用,形成闭环
如何解决

用三级缓存解决问题

  • 一级缓存,单例池
  • 二级缓存,缓存早期的bean对象(没有走完生命周期的bean对象)
  • 三级缓存,缓存的是ObjectFactory,表示对象工厂,用来创建对象
    构造方法出现循环依赖怎么办
    用@Lazy进行懒加载,什么时候需要对象再进行bean对象的创建

SpringMVC的执行流程

SpringMVC中重要的组件

  • DispatcherServlet(前端控制器)
  • HandlerMapping(处理器映射器)
  • HandleAdaptor(处理器适配器)
  • ViewResolver(视图解析器)
    SpringMVC的执行流程知道吗
    视图版本(JSP)的前后端不分离的流程比较复杂
    这里介绍前后端开发,接口开发版本
  1. 用户发送出请求到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
  3. HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet
  4. DispatcherServlet调用HandleAdapter(处理器适配器)
  5. HandleAdaptor经过适配调用具体的处理器(Handler/Controller)
  6. 方法上添加了@ResponseBody (一般包含在@restController中)
  7. 通过HttpMessageConverter来返回结果转换为JSON并响应

Springboot自动配置原理

SpringBoot项目中的引导类上有一个注解@SpringBootApplication,此注解对三个注解进行了封装:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan
    @EnableAutoConfiguration是实现自动化配置的核心注解,该注解通过@Import注解导入对应的配置选择器
    内部是读取了该项目和该项目引用的Jar包的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名
    在这些配置类中所定义Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中
    条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用

Spring常见的注解

spring的常见注解有哪些

用于依赖注入,bean放入IOC容器中,IOC,DI

  • @Component@Controller@Service@Respository,实例化bean
  • @Autowired
  • @Qualifier,结合@Autowired用于根据名称进行依赖注入
  • @Scope,标注bean的作用范围
  • @Configuration,指定当前类是一个spring配置类,当创建容器时会从该类上加载注解
  • @ComponentScan,用于指定Spring在初始化容器时要扫描的包
  • @Bean,用在方法上,将返回值放入容器
  • @import,用于将其他配置类或组件类导入到当前的 Spring 配置类 中,通常是手动引入 Bean 的一种方式。
  • @Aspect@Before@After@Around@Pointcut,用于AOP切面编程
    springMVC常见的注解有哪些
    用于请求响应
  • @RequestMapping,映射请求路径
  • @RequestParam,指定请求参数的名称
  • @PathVariable,从请求路径中获取参数
  • @RequestBody,将请求json转为Java对象
  • @ResponesBody,将Controller返回值转化为json对象响应
  • @RestController,@Controller+@ResponesBody
  • RequestHeader,获取指定的请求头数据
    Springboot常见注解有哪些
  • @SpringBootConfiguration,组合了@Configuration注解,实现配置文件的功能
  • @EnableAutoConfiguration,自动配置
  • @ComponentScan,spring组件扫描,扫描那些带@Component及延伸注解的类

mybatis的执行流程

配置文件->构建会话工厂->创建会话->Executor执行器->MappedStatement对象->数据库

输入参数->MappedStatement对象->输出结果

mybatis的延迟加载

嵌套查询的时候可能用到,但一般不会用嵌套查询
mybatis是否支持延迟加载

mapper映射文件中启用延迟加载lazyloadingEnabled=true|false
延迟加载的底层原理知道吗

用CGLIB创建目标对象的代理对象,用反射invoke方法,检查目标方法是null值,执行sql查询

一级\二级缓存

mybatis的一级二级缓存用过吗
mybatis的二级缓存什么时候会清理缓存中的数据

集合篇

面试中常被问集合,ArrayList,LinkedList,HashMap,ConcurrentHashMap

ArrayList

介绍一下什么是数组array

用连续的内存空间存储相同数据类型 数据的线性数据结构
数组如何获取其他元素的地址值

寻址公式:baseAddress+idataTypeSize
为什么数组索引从0开始:,假如从1开始不行吗
从0开始:baseAddress+i
dataTypeSize

从1开始:baseAddress+(i-1)*dataTypeSize

从1开始多了一次减法指令运算,从0开始计算效率较高
查找的时间复杂度

通过下标,o(1)

未知下标,o(n)

已排序,二分查找,o(logn)
插入和删除时间复杂度

插入和删除,为了保证数组的内存连续性,连续挪动,时间复杂度都是o(n)

源码分析

源码基于JDK8

底层是依据数组实现的
这里建议去看C++的理论分析,源码分析还是不好理解,主要内容就是初始构造,添加和扩容的关系

空集合没有容量

容量和集合长度(大小)不是一个概念
ArraysList底层的实现原理是什么

  • ArrayList底层是用动态的数组实现的
  • ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10
  • 在进行扩容时是原来容量的1.5倍,每次扩容都需要拷贝数组
  • 在添加数据时
    ArrayList的size加1后如果大于当前的数组长度(length),则调用grow方法扩容(1.5倍)
    确保新增的数据有地方存储后,将新元素添加到位于size的位置上
    ArrayList的size加1后如果小于等于当前的数组长度(length),则不需要扩容
    这里容量就是数组长度(length),ArrayList的size是位于数组的size索引位置
    ArrayList list=new ArrayList(10)中的list扩容几次
    只是声明和实例了一个ArrayList,指定了数组的长度为10,集合的容量和10,未扩容
    如何实现数组和list之间的转换
    数组转list,使用JDK中java.util.Arrays工具类的asList方法
    List转数组,使用List的toArray方法,无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象数组
    用Arrays.asList转List后,如果修改了数组内容,list受影响吗
    list会受影响,因为它的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,只是引用了原数组,指向的都是同一个内存地址
    list用toArray转数组后,如果修改了List内容,数组受影响吗
    数组不会受影响,调用了toArray以后,在底层进行了数组的拷贝,跟原来的元素没关系了,

LinkedList

单向链表和双向链表的区别是什么

单向链表只有一个方向,节点只有一个后继指针next

双向链表支持两个方向,每个节点不止有一个后继指针next,还有前驱指针prev指向前面

双向链表和单向链表都有frist和last分别指向头和尾
链表操作数据的时间复杂度是多少

无论是单向链表还是双向链表,增删的平均时间复杂度都是O(n),主要是要有查询的过程
ArrayList和LinkedList的区别是什么

四个层面,结构,时间效率,空间效率,安全

  • 底层数据结构
    ArrayList是动态数组的数据结构实现
    LinkedList是双向链表的数据结构实现
  • 操作数据效率(增删改查)
    ArrayList按照下标查询的时间复杂度O(1),linkedList不支持下标查询
    查找:ArrayList需要遍历,链表也需要链表时间复杂度都是O(n)
    新增和删除,ArrayList的平均时间复杂度是O(n)(准确来说查要O(n),删增也要O(n)),尾部是O(1)
    LinkedList平均是O(n),头尾增删为O(1)
  • 内存空间占用
    ArrayList底层是数组,内存连续,节省内存
    LinkedList是双向链表需要存储数据和两个指针,更占内存
  • 线程安全
    ArrayList和LinkedList都不是线程安全的(比如,两个线程同时 add() 时,一个线程的修改可能覆盖另一个线程的修改)
    如果要保证线程安全,两种方案:
    在方法内使用,局部变量则是线程安全的
    使用线程安全的ArrayList和LinkedList
    List objects = Collections.synchronizedList(new ArrayList<>());
    List objects1 = Collections.synchronizedList(new LinkedList<>());

HashMap

数据结构,数组+链表+红黑树,阿伟前面讲的不是这样的结构呀???

红黑树

红黑树的复杂度

二叉树->二叉搜索树(相比二叉树就是插入的时候排好了,找的时候方便)->红黑树(自平衡的二叉搜索树(BST))->B+树

遍历都可以使用前\中\后序遍历和层次遍历,但只有中序遍历是按排序遍历的

红黑树的规则都是为了让二叉树保证平衡,不形成极端的二叉树

查找,O(logn)

添加和删除,都要先查找(O(logn)),再旋转(O(1)),所以复杂度就是O(logn)

散列表HashTable

散列表又名哈希表
怎么解决哈希冲突

拉链法,数组的每个下标位置称为桶(bucket)或槽(slot),每个槽对应一条链表

hash冲突后的元素放到相同槽对应的链表或红黑树中(更好用的是红黑树,一是降低时间复杂度,二是防止恶意的Dos攻击,大量的访问攻击,将链表的时间复杂度O(n)降为O(logn))

HashMap的实现原理

HashMap的实现原理是什么

底层使用hash表数据结构,即数组+链表或红黑树

添加数据时,计算key的值确定元素在数组中的下标

  • key相同则替换
  • 不同则存入链表或红黑树中
    获取数据通过key的hash计算数组下标获取元素
    HashMap的jdk1.7和jdk1.8有什么区别
    JDK1.8之前采用的拉链法,数组+链表
    JDK1.8之后采用数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表转化为红黑树

HashMap的put方法的具体流程

HashMap的put方法的具体流程

  1. 判断键值对数组table是否为空或null,是的话,执行resize()进行扩容(初始化)
  2. 根据键值key计算hash值得到数组索引
  3. 判断table[i]==null,条件成立,直接新建节点添加
  4. 如果table[i]==null,不成立
  • 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value
  • 判断table[i]是否为treeNode(红黑树),即table[i]是否是红黑树,则遍历,有相同key覆盖,无则插入
  • 如果是链表,则遍历,有相同key覆盖,无则插入,判断链表长度是否大于8,大于8把链表转换为红黑树,在红黑树中执行插入操作
  1. 插入成功后,判断实际存在的键值对数量size是否超过了最大容量threshold(数组长度*0.75),如果超过,进行扩容
    我这里有个疑问hashcode是如何计算的,如果扩容之后,hashcode的计算方法是否会变,如果变了,之前插入的数据按现在的hashcode算法是否还是定位到那个数组索引

HashMap的扩容机制

讲一讲HashMap的扩容机制
注意在旧数组中在一个桶中的节点,在扩容后可能分配到不同桶

在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次扩容都是达到扩容阈值(数组长度*0.75)

每次扩容的时候,都是扩容之前容量的2倍(数组的长度必须也肯定是2的n次幂)

扩容之后,会创建一个数组,需要把老数组中的数据挪到新的数组中

没有hash冲突的桶的节点,直接使用e.hash&(newCap-1)计算新数组的索引位置

如果是链表,遍历链表,判断每个节点(e.hash&oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+旧数组大小的位置

如果是红黑树,遍历红黑树,和链表节点的放置类似,但是红黑树的节点在新位置的添加和旧位置删除很复杂,如果插入的桶里面是红黑树,在新的位置还要考虑左旋右旋那些东西
为什么不管是这个桶是一个节点还是链表还是红黑树不都用e.hash&(newCap-1)计算节点的索引位置??

我不知道,但应该是甲酸效率更高

hashMap的寻址算法

为什么计算索引位置不取模而是按位与

采用e.hash&(newCap-1)而不是(newCap-1)%e.hash是因为e.hash&(newCap-1)性能更好,计算资源消耗小
为什么可以用(e.hash&oldCap)是否为0这么判断

因为老数组和新数组的长度的二进制数差异就在oldCap二进制数为1的那一位

e.hash&(Cap-1)计算数组的索引位置

比如,长度为16时,Cap是00001111

32时,Cap是00011111

oldCap是00010000,位与判断第4位是不是1就可以知道放在新位置还是不动
hashMap的寻址算法

计算对象的hashCode()

再进行调用hash()方法进行二次哈希,hashcode值右移16位再异或运算,让哈希分布更为均匀(扰动算法,hash^(hash>>16))

最后(capacity-1)&hash得到索引
为何HashMap的数组长度一定是2的次幂

计算索引时效率更高,如果是2的n次幂可以使用位与运算代替取模

扩容时重新计算搜因效率更高,hash&oldCap==0的元素留在原来位置,否则新位置=旧位置+oldCap

hashMap在1.7情况下的多线程死循环问题

hashMap在1.7情况下的多线程死循环问题

在jdk1.7的hashmap中在数组进行扩容的时候,因为链表是头插法,在进行数据前移的过程中,有可能导致死循环

所以JDK8中采用尾插法

具体过程很绕,这里就不赘述了

并发编程篇

线程的基础知识

线程和进程的区别

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)

并行和并发

并行和并发的区别

在多核CPU下

  • 并发是同一时间应对多件事情的能力,多个线程轮流在不同时间片使用一个或多个CPU
  • 并行是同一时间动手做多件事情的能力,比如4核CPU同时执行4个线程

创建线程的方式

创建线程的方式有哪些

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口
  • 线程池创建线程(项目中使用方式)
    runnable和callable有什么区别
  • Runnable接口run方法没有返回值
  • Callable接口call方法有返回值,需要FutureTask获取结果
  • Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
    run()和start()有什么区别
    start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码,start方法只能被调用一次
    run():封装了要被线程执行的代码,可以被调用多次

线程包含的状态,状态之间的变化

线程包含了哪些状态

新建\可运行\阻塞\等待\时间等待\终止
线程之间是如何变化的

创建线程对象是新建状态

调用start()方法转变为可运行状态

线程获取到CPU的执行权,执行结束是终止状态

在可运行状态的过程中,如果没有获取CPU的执行权,可能会切换其他状态

  • 如果没有获取锁(synchronized或lock)进入阻塞状态,获取锁再切换为可执行状态
  • 如果线程调用了wait()方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
  • 如果线程调用了sleep()方法,进入计时等待状态,到时间后可切换为可执行状态

线程按顺序执行

新建T1\T2\T3三个线程,如何保证它们按顺序执行

使用线程中的join方法解决

比如,在T2中调用T1.join(),阻塞调用此方法的线程进入timed_waiting直到线程T1执行完成后,此线程再继续执行

notify()和notifyAll()的区别

notify()和notifyAll()有什么区别

notifyAll:唤醒所有wait的线程

notify:只随机唤醒一个wait线程

java中wait和sleep方法的不同

java中wait和sleep方法有什么不同
共同点

wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
不同点

方法归属不同

  • sleep(long)是Thread的静态方法
  • 而wait(),wait(long)都是Object的成员方法,每个对象都有
    醒来时机不同
  • 执行sleep(long)和wait(long)的线程都会在等待响应毫秒后醒来
  • wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒就一直等下去
  • 它们都可以被打断唤醒(参考下面一个面试题)
    锁特性不同(重点)
  • wait方法的调用必须先获取wait对象的锁,而sleep则无此限制
  • wait方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃CPU,但你们可以用)
  • 而sleep如果在synchronized代码块中执行,并不会释放对象锁(我放弃CPU,你们也用不了)

停止一个正在运行的线程

如何停止一个正在运行的线程

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止(不推荐,方法已作废)
  • 使用interrupt方法中断线程
    • 打断阻塞的线程(sleep\wait\join),线程会抛出interruptedException异常
    • 打断正常的线程,可以根据打断状态来标志是否退出线程

线程的并发安全

synchronized的底层原理

synchronized关键字的底层原理

synchronized底层是Monitor

  • synchronized对象锁采用互斥的方式让同一时刻至多只有一个线程能持有对象锁
  • 它的底层由monitor实现的,monitor是jvm级别的对象(C++实现),线程获得锁需要使用对象(锁)关联monitor
  • 在monitor内部有三个属性,分别是owner\entrylist\waitset
  • 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于waiting状态的线程

synchronized的底层原理进阶

轻量级锁\重量级锁

轻量级锁在线程间没有竞争时使用,都没竞争为什么要上锁 ,上锁是为了防止竞争,但是实际可能没竞争,这种情况用重量级锁性能低,一但竞争就会升级为重量级锁

Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。

  • 重量级锁:底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
  • 轻量级锁:线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性
  • 偏向锁:一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令
    一旦锁发生了竞争,都会升级为重量级锁
相关推荐
武子康7 分钟前
大数据-253 离线数仓 - Airflow 任务调度 核心概念与实际案例测试 Py脚本编写
java·大数据·数据仓库·hive·hadoop·springboot
奔跑的犀牛先生24 分钟前
概率论得学习和整理27:关于离散的数组 & 随机变量数组的均值,方差的求法3种公式,思考和细节。
学习·均值算法·概率论
Sigtuna29 分钟前
layui动态添加option
java·服务器·layui
文浩(楠搏万)34 分钟前
Java Spring Boot 项目中嵌入前端静态资源:完整教程与实战案例
java·服务器·前端·spring boot·后端·nginx·github
Q_19284999061 小时前
基于Spring Boot的校园部门资料管理系统
java·spring boot·后端
m0_749317521 小时前
偷懒算法第二天
java·学习·算法·蓝桥杯·动态规划
chengxuyuan1213_1 小时前
高级SQL技巧
java·数据库·sql
AI大模型训练家1 小时前
JAVA没有搞头了吗?
java·开发语言·python·面试·职场和发展·性能优化
小A1591 小时前
STM32完全学习——FSMC控制LCD的一些问题
stm32·嵌入式硬件·学习
终生成长者1 小时前
go 自己写序列化函数不转义
java·开发语言·golang