https://pdai.tech/md/interview/x-interview.htm
Java全栈知识点问题汇总(上)
Java基础
语法基础
面向对象特性
封装,封装是利用抽象数据类型(就是类)将数据和基于数据的操作封装在一起,构成一个不可分割的独立实体,数据被保护在类中,隐藏内部细节,但暴露给外界一些方法完成相应操作,用户无需知道内部的细节。有利于提高项目的可维护性。
继承,是根据逻辑关系,实现两个有包含关系的抽象概念,子类有几乎全部的父类属性行为,而且可以在此基础上增加修改,这样可以减少大量重复代码,提高可维护性。
多态,多态需要有三个条件:继承、重写和引用,继承和重写才能让两个类有可替代的关系,引用一般是父类引用指向子类对象,可以说构建了一个父类,但实际细节还是子类,这样可以增加代码的灵活性,举个例子,向Java的poi中,我事先不确定是xls还是xlsx,我可以事先定义WorkBook,根据实际情况指向HSSWorkbook还是XSSWorkBook。
a=a+b与a+=b的区别
+=隐式的将加的操作结果转换为持有结果的类型,如果两个整型相加,如byte、short或者int,首先会将它们提升到int类型,然后再执行加法操作
3*0.1==0.3返回true还是false
返回false,因为浮点数不能精确的表示出来,要精确计算需要使用BigDecimal类
能在Switch中使用String吗
从Java7开始可以,实现机制是使用了字符串的hashcode
对equals()和hashCode()的理解
为什么在重写equals方法的时候需要重写hashCode方法?
因为有些容器类例如HashMap都有依赖于hashcode和equals
有没有可能两个不相等的对象有相同的hashcode?
有可能,hashcode的规定只是说了如果两个对象相等,必须有相同的hashcode
两个相同的对象会有不同的hashcode吗?
不能,hashcode的规定就是两个相同的对象有相同的hashcode
final、finalize和finally的不同之处
final是一个修饰符,可以修饰变量、方法和类,如果修饰变量,那标明这是一个常量,不能被修改,修饰方法则标明方法不能被重写,修饰类表示类不能被继承
finalize是Java中的一个方法,这个方法在垃圾回收器回收对象之前调用,用于释放对象占有的资源
finally是关键字,和try、catch一起用户异常的处理
String、StringBuffer与StringBuilder的区别
String是不可变的,底层是字符数组常量,每次修改都会重新生成一个String对象
StringBuffer和StringBuilder是可变的,区别在于StringBuffer是线程安全的,不考虑线程安全还是StringBuilder效率高
接口与抽象类的区别
一个类只能继承一个抽象类,但可以实现多个接口
抽象类本质上是一种特殊的类,在含义上指的是一个不完全的实体抽象概念,而接口只是一组行为规范
所以普通类有的抽象类大概率也可以有,而且可以有抽象方法
接口几乎只有未实现的方法
this()和super()在构造方法中的区别
在Java中,这两个都是用于在构造方法中调用其它构造方法的关键字
this()用于在当前类的构造方法中调用当前类的其它构造方法,可以避免重复代码
super()用于在子类的构造方法中调用父类的构造方法,用来初始化父类成员或执行父类的构造逻辑
这两个都需要作为构造方法的第一句出现,且不能同时出现在同一个构造方法中
Java移位运算符
<<:左移运算符,含义是二进制向左移位1,相当于乘以2
>>:右移运算符,向左移1位,相当于除以2
>>>:无符号右移,忽略符号位,空位都以0补齐
泛型
为什么需要泛型
泛型使用与多种数据类型执行相同的代码逻辑
泛型如何定义使用
一般使用T、K和V表示,例如List<T>、Map<K, V>
定义是设置变量和对应的getter、setter
泛型接口如何定义使用
在接口上定义泛型
interface Info<T>
泛型方法如何定义使用
public <T> T getObject(Class<T> c){}
必须在返回类型前增加<T>,来声明这是一个泛型方法
Object obj = c.getObject(Class.forName(“com.aa.bb.Cde”));
泛型的上限和下限
上限
class Info<T extends Number>{
下限
public static void fun(Info<? super String> temp){
如何理解Java中的泛型是伪泛型
Java只是在语法是哪个支持泛型,在编译阶段会进行类型擦除,将所有的泛型替换为具体的类型,就像没有泛型一样
注解
注解的作用
提供编译时的静态检查,例如@Override、@Deprecated等
自动生成代码,例如lombok的@Data、@NoArgsConstructor等
配置和参数传递,例如@Value
运行时动态处理,例如@Autowired
注解的常见分类
Java自带的标椎注解:@Override、@Deprecated
元注解:是用于定义注解的注解,@Retention
自定义注解:根基自己的需求定义注解
异常
Java异常类结构层次
Throwable是所有异常的超类,之下分为异常Exception和错误Error
异常又分为运行时异常RuntimeException和非运行时异常
运行时异常包括空指针异常、索引超出异常等
非运行时异常包括IO异常IOException
错误包括IOError等
可查的异常和不可查的异常区别
可查异常是编译器要求必须处理的异常,除了RuntimeException及其子类不是,其余Exception及其子类都是
不可查异常就是运行时异常和错误
throw和throws的区别
throw用于才程序中抛出异常
throws用于标注在方法上以声明可能抛出的异常
try-with-resource
使用这种结构可以自动关闭资源
反射
什么是反射
Java的反射机制是在运行状态中,对于任意一个类,都能获取到这个类的所有属性和方法,而且可以使用
SPI机制
什么是SPI机制
SPI,(服务提供者接口),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要用于框架
Java集合
Collection
集合有哪些类
Set:HashSet、TreeSet、CopyOnWriteArraySet
List:ArrayList、Vector、LinkedList、CopyOnWriteArrayList
Queue:LinkedList
ArrayList的底层
ArrayList的底层是通过数组实现的
Map
Map有哪些类
TreeMap、HashMap、HashTable、LinkedHashMap
JDK8中的HashMap如何实现
基于数组和链表实现,当链表的长度超过8时,会将链表转换为红黑树
HashSet是如何实现的
HashSet是对HashMap的简单包装
Java并发
并发基础
多线程的出现是要解决什么问题,本质是什么
CPU、内存、IO的速度差异极大,为了合理的利用CPU的性能,平衡三者的速度,为此操作系统增加了进程和线程,用来分时复用CPU
Java是怎么解决并发问题的
Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法,包括:volatile、synchornized、final
可见性、原子性、有序性
可见性是指一个线程修改了共享变量后,其它线程立即可以看到修改,通常使用volatile
原子性是指一组操作要么全部执行,要么全部不执行,没有中间状态,可以使用synchronized、Lock对象和原子类,来保证
有序性是指程序执行的结果按照一定的顺序呈现,多线程情况下线程执行顺序不确定,可能有指令重排序的问题,影响结果,可以使用volatile、synchronized、Lock对象等保证
线程安全有哪些实现思路
互斥同步:synchronized、ReentrantLock
如何理解并发和并行的区别
并发是指一个处理器同时处理多个任务
并行是指多个处理器或者处理器的多个核心同时处理多个任务
线程有哪几种状态,怎么转换
线程最初是新建状态,执行start方法后进入可运行状态,在此状态获取到CPU资源后进入运行状态,在等待锁或者io时会进入阻塞状态,调用了wait方法后进入等待状态,当运行结束或运行异常时进入终止状态
新建线程的方式
继承Thread类、实现Runnable接口、实现Callable接口
Callable接口可返回数据
线程的中断方式有哪些
InterruptedException,可以调用线程的interrupt方法中断该线程
线程的互斥同步方式有哪些
synchronized、ReentrantLock
线程之间有哪些协作方式
join方法,在线程中调用另一个线程的join方法,会将当前线程挂起,直到目标线程结束
wait()、notify()、notifyAll():调用wait方法使线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使这个条件满足时,其他线程会调用notify()或者notifyAll()唤醒挂起的线程
wait()和sleep()的区别
wait是Object的方法,sleep()是Thread的静态方法
wait方法会释放锁,sleep方法则不会
并发关键字
Synchronized可以作用在哪里
实例方法、静态方法、代码块、对象实例
Synchronized本质是通过什么保证线程安全的
本质是对代码块、实例方法、静态方法进行加锁,以保证同一时间只有一个线程执行被加锁的代码
synchronized是公平锁吗
不是
volatile关键字的作用是什么
实现可见性(内存屏障),防止重排序(happens-before等)
不能保证原子性,只能保证单词的读写操作具有原子性
Java IO
基础IO
如何从数据传输方式理解IO流
字节流(InputStream和OutputStream)和字符流(Reader和Writer)
如何从数据操作上理解IO流
文件、数组、缓冲、对象序列化反序列化转换
Java IO设计上使用了什么设计模式
装饰者模式,
JVM和调优
类加载机制
类加载的生命周期
加载,是查找并加载类的字节码数据、
验证,验证被加载类的正确性、
准备,为类的静态变量分配内存,并初始化默认值、
解析,吧类中的符号引用转换为直接引用、
初始化,为类的静态变量赋予正确的初始值
类加载器的层次
启动类加载器,扩展类加载器、应用程序类加载器、自定义类加载器
Class.forName()和ClassLoader.loadClass()区别
Class.forName()会将类加载到jvm中,并且执行类中的静态代码块
ClassLoader.loadClass()只是将类加载到jvm中,不会执行静态代码块
JVM有哪些类加载机制
双亲委派机制:当应用程序类加载器加载一个类时,首先会把加载请求发给扩展类加载器,而扩展类加载器收到请求后也不会自己加载,而是同样发给启动类加载器,若启动类加载器差找不到类,扩展类加载器才会尝试加载,若扩展类加载器还是加载不到,再让应用程序类加载器进行加载,再加载不到就抛出异常类未找到
内存结构
JVM内存的整体结构
程序计数器:存储当前线程执行的字节码指令的地址,每个线程都有自己的程序计数器
Java虚拟机栈:每个线程在运行时都会创建一个对应的虚拟机栈,存储Java方法的调用和执行信息
本地方法栈:用于存储本地方法的调用和执行信息
堆:用于存储对象实例
方法区:用于存储类的元数据信息,也叫元空间
方法区和堆是线程共享的,程序计数器、虚拟机栈和本地方法栈是线程私有的
JVM中对象在堆中的生命周期
在JVM内存模型的堆中,堆被划分为新生代和老年代,新生代又被进一步划分为Eden取和Survivor区,Survivor区由From Survivor和To Survivor组成
当创建一个对象时,对象会被优先分配到新生代的Eden区,此时jvm会给对象定义一个对象年轻计数器
当Eden区空间不足时,jvm将执行新生代的垃圾回收(Minor GC),JVM会把存活的对象转移到Survivor中,并且对象年龄+1,对象在Survivor中同样也会经理Minor GC,每经历一次Minor GC,对象年龄都会+1
如果分配的对象超过了一定限制(默认15次),对象会直接被分配到老年代
GC垃圾回收
如何判断一个对象是否可以回收
引用计数算法,引用为0就回收,但会出现两个对象相互引用导致循环引用的问题,所以Java虚拟机不使用引用计数算法
可达性分析算法,用于判断对象是否与程序的根节点(栈中引用的对象)相连,对不可达对象进行回收
对象有哪些引用类型
强引用:使用new一个对象来创建强引用,不会被回收
软引用:使用SoftReference类来创建软引用,只有内存不够的情况下才会被回收
弱引用:使用WeakReference来实现弱引用,被弱引用关联的对象一定会被回收
虚引用:PhantomReference来实现虚引用,对象被回收时会收到系统通知
有哪些基本的垃圾回收算法
标记清除:将存货的对象进行标记,然后清理掉未被标记的对象,缺点是标记和清除的效率都不高,容易产生大量内存碎片,导致无法给大对象分配内存
标记整理:让所有存活的对象向一端移动,然后清理掉边界外的内存
复制:将内存分为大小相等的两半,当一块用完后把存活的对象复制到另一半,清理这一半,缺点是只使用了内存的一半
分代收集:根据对象存活周期将内存划分为几块,不同块采用适当的收集算法,一半将堆分为新生代和老年代,新生代使用复制算法,老年代使用标记清除或者标记整理算法
什么是Minor GC、Major GC、Full GC
Minor GC是新生代垃圾收集
Major GC是老年代垃圾收集
Full GC是收集整个堆的垃圾
说说jvm内存分配策略
对象优先在Eden区分配
大对象直接进入老年代
长期存活的对象进入老年代
什么情况下会触发Full GC
调用System.gc()
老年代空间不足
空间分配担保失败
HotSpot中有哪些垃圾回收器
Serial收集器:串行收集器,简单高效
ParNew收集器:多线程收集器
Parallel Scavenge收集器:多线程,吞吐量优先
G1收集器:适用于大型应用
CMS收集器:停顿时间最短
问题排查
Linux常用工具
grep、awk、sed
tail、find
ifconfig、ip a、iptables、netstat -nplt
ps -ef | grep java、df -h、free -h
top
JDK自带的定位问题的工具
jps:查看当前java进程的小工具
jstack:线程堆栈分析工具
jinfo:查看正在运行的java程序的扩展参数
jmap:生成dump文件,分析堆内存的对象信息
jstat:监控和收集jvm的统计信息
Java新版本
Java8特性
什么是函数式编程
面向对象编程是对数据进行抽象,函数式编程是对行为进行抽象
核心思想:使用不可变值和函数对一个值进行处理,映射成另一个值
什么是Lambda表达式
用于在代码中表示匿名函数,使程序更加灵活简洁
Stream中常用方法
stream()、filter()、sort()、map()、distinct()、forEach()
Optional要解决什么问题
Optional是一个可以为null的容器对象,如果只存在则isPresent方法返回true,调用get方法返回该对象
什么是默认方法,为什么要有默认方法
就是接口可以有实现方法,不需要实现类去实现其方法,只需在方法名前加个default关键字即可
数据结构和算法
数据结构基础
如何理解基础的数据结构
红黑树是一种自平衡的二叉搜索树,用于在Java中实现有序集合等数据结构,通过在每个节点上添加额外的颜色标记,并遵循一些规则来保持树的平衡
查找、插入和删除的时间复杂度为O(log n)
所有的叶子节点都是黑色
算法思想
有哪些常见的算法思想
分治算法:分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解
动态规划算法:通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治算法类似,其基本思想都是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。和分治算法最大的区别:适用于动态规划算法求解的问题经过分解后得到的子问题往往不是相互独立的,而是下一个子阶段的求解是建立在上一个子阶段的解的基础上
贪心算法:保证每次操作都是局部最优解,并且最后得到的结果是全局最优的
二分法:二分查找也被称为折半查找,是一种效率较高的查找方法,但是要求数据有序并且顺序存储
常见排序算法
有哪些常见的排序算法
冒泡排序、快速排序、插入排序、选择排序
数据库
原理和SQL
什么是事务?事务基本特性ACID
事务指的是满足ACID特性的一组操作,可以通过commit提交一个事务,也可以使用rollback进行回滚
A:原子性,要么全部成功,要么全部失败,依靠日志保证,回滚时读取日志进行撤销
C:一致性,数据库从一个状态装换为另一个状态,保证数据的有效性,一般有代码层面保证
I:隔离性,事务在最终提交前,与其它事务隔离,互不影响,由MVCC保证
D:持久性,事务提交后就永久保存到数据库中,由内存和日志保证
数据库中并发一致性问题
丢失修改:两个事务对同一个数据进行了修改,一个修改覆盖了另一个修改
读脏数据:一个事务修改了数据,但又回滚了,另一个数据读取到了回滚前的数据
不可重复读:两次读取数据之间被其它事务修改了数据
幻影读:类似不可重复读
事务的隔离等级
未提交读、提交读、可重复读、可串行化
SQL优化实践经验
- 避免全表扫描,在条件和排序涉及的列上建立索引
- 避免在where中对字段进行null值判断,否则会引起全表扫描,可以给字段设置默认值
- 避免在where中使用!=或<>操作符,否则会全表扫描
- or连接的两个条件可以转化为union all,避免有字段没索引从而全表扫描
- 避免使用in和not in,否则会全表扫描
- 模糊查询也会引起全表扫描
- 使用合适的数据类型
- 定期优化数据库
MySQL
MySQL的索引有哪些
索引是存储引擎层实现的
B+Tree索引、哈希索引、全文索引
什么是B+树
B+树是基于B树和叶子节点顺序访问指针进行实现,具有B树的平衡性,并且通过顺序访问指针来提高区间查询的性能
优点:减少磁盘读取次数
什么是MVCC
MVCC是多版本并发控制,实现对数据库的并发访问
MySQL锁的类型有哪些
读锁:共享,能读不能写
写锁:排他,阻塞其它的写锁和读锁
表锁:锁定整张表,阻塞该表的所有读写操作,通常修改表结构会锁表
行锁:又分为乐观锁和悲观锁,乐观锁通过版本号实现,悲观锁通过for update实现
分表后的id怎么保证唯一性
使用雪花算法
使用其它字段如订单号等作为主键
MySQL主从复制
主要涉及三个线程:
binlog线程:负责将主服务器上的数据更改写入二进制日志中
IO线程:负责从主服务器上读取二进制日志并写入中继日志中
SQL线程:负责读取中继日志并重做其中的SQL语句
MySQL主从的延迟怎么解决
实时性要求较高的查询查主库
MySQL读写分离方案
主服务器处理写操作以及实时性要求较高的读操作,从服务器处理读操作
增加冗余,提高可用性
Redis
什么是redis,为什么用redis
redis是一种支持键值等多种数据结构的存储系统,可用于缓存、事件发布订阅、高速队列等场景。基于内存,可持久化
优点:
读写性能高、数据类型丰富、原子性、支持持久化
为什么redis是单线程的,为什么这么快
redis完全基于内存,相比磁盘速度特别快
数据结构简单,相关操作也简单
采用单线程避免了线程切换的资源消耗
采用了多路IO复用模型
Redis有哪些使用场景
热点数据的缓存
限时业务的运用
计数器相关问题
分布式锁
redis有哪些数据类型
5中基本类型
String字符串
List列表
Set集合
Hash散列
Zset有序集合
redis一个字符串类型的值能存储最大容量是多少
512M
redis的持久化机制是什么,各自的优缺点
一种是默认的RDB文件,是定时把数据库快照存储在磁盘,文件是二进制文件
另一种是AOF,redis吧执行的相关操作记录在日志中,这个日志文件就是AOF文件
新版的redis有混合这两种方式的持久化方案
rdb的方式优点是快速紧凑,缺点是会丢失最后一次快照后的数据,适合备份和恢复数据
AOF方式的优点是保障了数据的完整性,缺点是文件体积较大,恢复速度慢
rdb的触发方式
手动触发:save命令、bgsave命令
自动触发:配置文件中配置
在进行rdb快照操作的时期服务器崩溃怎么办
在没有完成一次rdb快照操作前,不影响上一次的备份数据,
AOF是写前日志还是后日志
AOF日志采用写后日志,即先写内存后写日志
什么是AOF重写
Redis通过创建一个新的AOF文件来替换现有的AOF,新旧两个文件数据相同,去除了冗余命令
AOF重写会阻塞吗
AOF重写构成是后台进程完成的,需要从主进程中fork出子进程,在fork子进程时会阻塞主进程
AOF日志何时会重写
配置文件中有两个配置项
redis过期键的删除策略有哪些
惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,过期就删除
定期删除:服务器执行定时任务删除过期数据,但考虑到CPU和内存的折中,删除的频率和执行时间有限制
redis内存淘汰算法有哪些
- 不淘汰
- noeviction
- 对设置了过期时间的数据中进行淘汰
- 随机:volatile-random
- ttl:volatile-ttl
- lru:volatile-lru
- lfu:volatile-lfu
- 全部数据进行淘汰
- 随机:allkeys-random
- lru:allkeys-lru
- lfu:allkeys-lfu
redis的内存用完了会发生什么
如果达到设置的上限,写命令会返回错误信息,读命令正常
redis如何做内存优化
缩减键值对象的长度
字符串优化
编码优化
控制key的数量
redis的key的过期时间和永久有效分别怎么设置
expire命令
persist命令
什么是redis事务
redis事务的本质是一组命令的集合
redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
redis事务相关命令
multi、exec、discard、watch
redis事务三个阶段
开启:以multi开始一个事务
入队,将多个命令入队到事务中,这些命令不会立即执行
执行:由exec执行事务
redis事务中watch是如何监视实现的
在multi之前使用watch监控某些键值对,然后使用multi开始事务,等exec执行事务时,会比对watch监视的键值对,如果发生了该改变,则事务回滚,否则正常执行事务
redis的客户端有哪些
Redission、Jedis、Lettuce
Redis缓存有哪些问题,如何解决
缓存穿透:请求到缓存和数据库都没没有的数据,解决方法是增加校验、缓存取不到的数据
缓存击穿:大量请求到缓存中没有但数据库中有的数据,解决方法是加互斥锁、设置热点数据永不过期
缓存雪崩:缓存大量到期,请求压力给到了服务器,解决办法是合理设置缓存的过期时间
缓存污染:缓存了许多不多用的数据,解决办法是合理设置缓存范围、适当淘汰数据
开发基础
常用类库
平时常用的开发工具库有哪些
Apache Common
Google Guava
Hutool
Spring常用工具类
Java常用的JSON库有哪些?有啥注意点
FastJson,漏洞多
Jackson
Lombok工具库用来决绝什么问题
通过注解的方式给实体类生成常用的方法,大量减少了简单代码
Lombok有什么缺点
一定程度上破坏了封装性
需要项目团队成员统一使用
MapStruct工具库
MapStruct是一款非常使用的Java工具,主要用于解决对象之间的拷贝问题,区别于BeanUtils这种通过反射,它是通过编译器生成常规方法,提升了效率
Lombok和MapStruct工具库的原理
Lombok是在编译时期通过注解处理器扫码源代码,并根据注解生成相应的代码,将生成的代码插入到编译后的字节码中
网络协议和工具
什么是754层网络模型
OSI的7层结构:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
TCP/IP的4层结构:网络接口层、网络层、传输层、应用层
5层协议结构:物理层、数据链路层、网络层、传输层、应用层
1455

被折叠的 条评论
为什么被折叠?



