2021 Java面试题精心整理(持续更新)

1、String类能不能被继承?为什么?

不能
因为string类是被final修饰的类,final修饰过的类不能被继承、final修饰过的变量不能被修
在这里插入图片描述
2、实现单例设计模式(懒汉、饿汉)

//懒汉,顾名思义比较懒,在用的时候才实例化
public class Singleton {
//创建实例,注意,此时没有new
private static Singleton instance = null;
//构造方法私有化
private Singleton() {}
//公有的静态方法,返回实例对象
public static Singleton getInstance() {
if (instance == null) {
//这里才new
instance = new Singleton();
}
return instance;
}
}

//饿汉,顾名思义很饥饿,创建对象的时候就直接new
public class Singleton {
//创建实例的时候就new
private static Singleton instance = new Singleton();
// 私有化构造方法,外部不能new
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
3、简述Java的反射机制和使用场景

反射是Java的一种机制,可以让我们在运行时获取类的信息
通过反射我们可以获取到类的所有信息,比如它的属性、构造器、方法、注解等
适用于需要动态创建对象的场景

关于反射能说的太多,已单独出一篇博客来记录,请出门右转至信不信十分钟让你彻底搞懂java反射

4、什么是内存泄漏,怎么确定内存泄漏?

概念:内存泄漏就是指jvm内存没有及时释放,用人话说就是使用完的对象没有被回收,一般造成原因都是编码不规范,new了很多值为null的对象,然后又不调用
怎么确认:linux有个工具叫valgrind,一两句话说不清楚,单独拎出来讲,移步使用valgrind来检查内存泄漏

5、简述动态代理和静态代理

静态代理:

由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了
静态代理通常只代理一个类
静态代理事先知道要代理的是什么

动态代理:

在程序运行时,运用反射机制动态创建而成
动态代理是代理一个接口下的多个实现类
动态代理不知道要代理什么东西,只有在运行时才知道

6、手写生产者消费者模型

仓库:Warehouse
public class Warehouse {
private volatile int apple = 0;
public synchronized void increace() {
while (apple == 5) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
apple++;
System.out.println(“苹果生产成功!”);
notify();
}
public synchronized void decreace() {
while (apple == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
apple–;
System.out.println(“苹果消费成功!”);
notify();
}
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
Consumer con = new Consumer(warehouse);
Producer pro = new Producer(warehouse);
Thread t1 = new Thread(con);
Thread t2 = new Thread(pro);
t1.start();
t2.start();
}
}
生产者:Producer
public class Producer implements Runnable {
private Warehouse warehouse;
public Producer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for( int i=0;i<10;i++)
{
try {
System. out .println(“pro i:” +i);
Thread. sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
warehouse.increace();
}
}
}
消费者:Consumer

public class Consumer implements Runnable {
private Warehouse warehouse;
public Consumer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for( int i=0;i<10;i++)
{
try {
System. out .println("Con: i " +i);
// 这里设置跟上面30不同是为了 仓库中的苹果能够增加,不会生产一个马上被消费
Thread. sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
warehouse.decreace();
}
}
}
7、Java中接口和抽象类的区别?

1、抽象类只能继承一次,但是可以实现多个接口
2、接口和抽象类必须实现其中所有的方法,抽象类中如果有未实现的抽象方法,那么子类也需要定义为抽象类。抽象类中可以有非抽象的方法
3、接口中的变量必须用 public static final 修饰,并且需要给出初始值。所以实现类不能重新定义,也不能改变其值。
4、接口中的方法默认是 public abstract,也只能是这个类型。不能是 static,接口中的方法也不允许子类覆写,抽象类中允许有static 的方法

8、Java中sleep和wait的区别?

1、sleep是Thread的方法,wait是Object的方法
2、sleep方法没有释放锁,而wait方法释放了锁
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

9、Java如何进行高效的数组拷贝?

Arrays.copyOf或 System.arraycopy,是自己new数组, 然后for循环复制效率的两倍左右
为什么快,因为它们是native方法;

10、工厂模式使用场景

设计模式共23种,按功能可以分为创建型、结构型、行为型,工厂模式属于创建型模式,主要用于创建对象;
比方说造一辆车
如果不使用工厂模式,我需要造宝马的时候,就写一个造宝马的方法,需要造奔驰的时候就写一个造奔驰的方法,缺陷很明显,后期不易维护、代码冗余、不符合面向对象的思想;
使用工厂模式时,我只需要写一个造汽车的方法,在内部做一些判断,如果参数是宝马我就造宝马,如果是奔驰我就造奔驰,这样后期如果造汽车,只需要调用它的方法传入参数就行;

工厂模式能细分为简单工厂,工厂方法和抽象工厂三种
常见的有简单工厂和工厂方法两种。 简单工厂中包含工厂、产品和具体产品三个角色。其中工厂是整个模式的核心,这个类当中包含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的对象。而客户端则可以免除直接创建产品对象的责任。从这个角度上说简单工厂实现了对责任的分割。另外,由于客户端所使用的对象都由工厂生成,并统一转型为产品类型,所以客户端无需关心自己得到的是哪一个具体产品,这样在添加新的具体产品时,就不需要修改客户端的代码,从某种程度上也实现了开放-封闭法则。但是每当需要添加新的具体产品时,就需要修改工厂类。 工厂方法使用了面向对象的多态性,保留了简单工厂的优点,而且克服了它的缺点。首先,在工厂模式当中,核心的工厂类不再负责所有产品的创建,而是将具体的产品创建交给子类去做。这个核心类成了抽象工厂角色,仅仅负责给出具体工厂子类必须实现的接口。这种进一步抽象的结果是,可以允许系统在不修改具体工厂角色的情况下引进新的产品。 在现实的开发当中,典型的例子就是在servcie层中需要得到DAO对象,通常就会抽象出DAO接口作为产品,接口的实现类作为具体产品,然后提供工厂供service使用。这样就可以分离service和dao的耦合,当DAO添加新的实现类是,service不需要修改。提升系统的扩展性和维护性。

11、成员变量和方法的区别?

成员变量有两种:实例变量和类变量(也称静态变量,静态域)。
成员方法有三种:实例方法,类方法(也称静态方法),构造方法(无返回值,方法名和类名一致)。
public class Person {
public static final int defaultAge = 18;//常量,类编译时放到常量池
public static int age = 18;//类变量,在类加载的准备阶段,分配到方法区
private String like;//实例变量,在类被实例化时,分配到堆中

//静态模块
static {
    System.out.println("唱 跳 rap 篮球");
}

//类方法,分配到方法区
static int getAge() {
    return age;
}

public Person() {
    //类的构造方法
}

//实例方法,分配到方法区
public String getLike() {
    return like;
}

//实例方法
public void setLike(String like) {
    this.like = like;
}

}
类变量的特点:
它是该类所有实例共享的属性,在内存中只有一个地方存储这个变量(在方法区)。在类加载的准备阶段,分配到方法区,初始化阶段正式赋值。
所有实例都可以修改这个类变量的值。(前提是没有被final修饰)
访问类变量不用实例化对象,直接通过类可以使用。
生命周期取决于类的生命周期。

类方法的特点:
直接通过类就可以调用。
类方法可以直接调用类变量和类方法。
类方法不可以直接调用实例变量和实例方法。
类方法没有this,因为没有实例。

实例变量和实例方法的特点:
都必须通过实例对象才可以访问。(实例变量位于堆中,生命周期取决于实例的生命周期)

12、Java编译后的.class文件包含了哪些内容?

编译后的.class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在.class文件之中,中间没有添加任何分隔符;
根据Java虚拟机规范的规定,.class文件格式采用一种类似于C语言的伪结构来存储数据,包含无符号数和表:
无符号数:以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值;
表:由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以”_info“结尾。
Class文件的结构没有分隔符,无论你是数量还是顺序,都是严格规定的,哪个字节代表什么含义、长度多少、先后顺序如何,都不允许改变。

详见.class文件剖析

13、简述zset实现原理

一两句话说不清楚,就是一种数据结构,打个比方
1、2、3、4、5、6、7、8、9
要找到8,我得遍历8次
如果我把1、3、5、7、9拎出来作为一个类似索引的东西
找8的时候,先在索引里面找,8在7和9中间,找到7以后再遍历1次就找到了8,总共遍历5次
而这个1、3、5、7、9的索引还可以拎个1、5、9出来,以此类推
回到zset,它维护了两个元素,一个是 dict,用来维护数据到分数的关系,一个是zskiplist,用来维护分数所在链表的关系

14、简述协程的优缺点

协程是一种轻量级线程

优点:
跨平台
跨体系架构
无需线程上下文切换的开销
无需原子操作锁定及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:
无法利用多核资源,协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序,这一点和事件驱动一样,可以使用异步IO操作来解决

15、如何判断一个hash函数好不好?

1、计算性能
2、离散性
hash算法一定要把数据哈希均匀,不能全部挤在一坨,对hash算法不太清楚的必须看看这篇文章,我以前花了整整一天时间写的,举了很多例,真正搞懂hashCode和hash算法

16、http中get和post的区别?

GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark,而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中。
(本标准答案参考自w3schools)

然而实际上,它们的本质都是 TCP 连接,并无区别。上面的答案纯粹是为了应付面试官。真正导致产生区别的原因是 HTTP 的规定以及浏览器/服务器的限制,这才导致它们在应用过程中可能会有所不同。

17、解决hash冲突的方式?

拉链法
代表作:hashMap
原理:把所有hash值相同的元素放到链表中
再哈希法
原理:产生冲突时,对结果再进行一次hash,直到没有hash冲突,一般没人用,太鲁莽了,而且治标不治本,理论上永远不能彻底解决hash冲突
开放地址法
1、线性探测法
2、线性补偿探测法
3、伪随机探测

18、简述装饰者模式和适配器模式

装饰者模式
概括:在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能),它属于结构型模式,mybatis-plus的QueryWrapper就是装饰者模式的体现

适配器模式
概况:将一个接口转换成用户需要的另一个接口,使接口不兼容的那些类可以一起工作,也属于结构型模式,SpringMvc中的HandlerAdapter就是适配器模式的体现

19、hashCode和equals方法的联系

java规定:
如果两个对象的hashCode()相等,那么他们的equals()不一定相等。
如果两个对象的equals()相等,那么他们的hashCode()必定相等。

对hash算法不太清楚的同学必须看看这篇文章,我以前花了整整一天时间写的,举了很多例,真正搞懂hashCode和hash算法

20、什么是重写和重载?

1、重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Override)
2、重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overload)
3、重载是一个类的多态性表现,而重写是子类与父类的一种多态性表现

21、Java有几种基本数据类型?分别占用多少字节?

数据类型 占用字节
byte 1
short 2
int 4
long 8
char 2(c语言是1字节,可以存储一个汉字)
float 4
double 8
boolean 1/8
22、Java异常有哪些类型?

Java所有的异常都继承至Throwable,分为Error和Exception两大类,其中:
Error是系统级错误,在代码层无法处理,常见的有StackOverflowError、OutOfMemoryError等;
Exception是异常,通常可以在代码层处理,常见的有NullpointerException、ClassCaseException、IndexOutOfBoundsException等

23、简述jvm内存模型?

程序计数器:线程私有,各线程之间独立储存,互不影响,若当前执行的是Java方法,则记录的就是当前执行指令的地址,若是native方法,则为空;

java虚拟机栈:线程私有,每个方法在执行时都会创建一个栈帧,方法执行过程就是栈帧在虚拟机栈中从入栈到出栈的过程,入栈表示方法开始被调用,出栈表示方法执行完毕,栈帧用于保存方法内部局部变量、操作数、方法返回值、动态链接;我们平时说的栈其实一般就是指局部变量区:用于存放方法参数、方法内定义的局部变量,还有已知的八大基本数据类型、对象引用、返回值地址;

本地方法栈:线程私有,和虚拟机栈相似,区别在于虚拟机栈的服务对象是java方法,而本地方法栈是本地方法;

堆:线程共享,在虚拟机启动的时候创建,用于存放对象实例,堆是GC管理的主要区域;

方法区:线程共享,其实方法区也是堆的物理组成部分,用于存放常量、静态变量 、 类信息(构造方法/接口定义) 、运行时常量池;注意, 实例变量在堆内存中,和方法区无关。
(jdk1.8之前,方法区的实现是永久代,从1.8开始,用元空间代替了永久代,注意一点,方法区还是那个方法区)

这一坨内容请酌情观看,我怕把你绕晕~
再细分的话还有运行时常量池和字符串常量池:jdk1.6的时候,两者都是属于方法区,1.7开始,字符串常量池被移到了堆内存;运行时常量池用于存放编译期生成的各种常量(“abc”,123等)和符号引用;而字符串常量池是为了提高jvm效率单独用来存放字符串的,因为字符串不同于其他数据类型,它可以很长很长很长很长很长很长很长很长很长很长很长很长~;

24、简述GC机制,新生代和老年代的区别?

垃圾回收是java管理内存的一种机制,作用是清理无用对象避免内存泄漏,gc主要发生在java堆上,而堆可以细分为新生代和老年代(分代是为了提高gc效率,其实不分代也可以完成gc,只不过gc机制会对堆的所有区域进行扫描,浪费资源),新生代还可以细分为三个虚拟的区,Eden区、FromSurvivor区、ToSurvivor区,一开始对象都在Eden区,Eden区的对象经过一次新生代gc(复制算法)后若还能存活,就会移动到survivor区(ToSurvivor区),在此次新生代gc时,在survivor发生的改变就是,From区中的对象会根据年龄来决定去留,达到阈值,会移动到老年代,没达到就移动到To区,经过此次新生代gc,Eden和From区都已被清空,From区和To区会互换;

新生代gc(Minor GC)触发时机:当Eden区没有足够空间进行分配时;
老年代gc(Major GC/Full GC)触发时机:当老年代中没有足够的内存空间来存放对象时,虚拟机会发起一次Major GC/Full GC。只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。

25、如何优化JVM频繁Minor GC?

适当扩大堆内存太小

26、简述类加载机制,什么是双亲委派?

1、加载:将源文件(代码)编译成.class文件,传递给类加载器
2、验证:类加载器拿到这些字节码文件后开始执行检查(通过本地类库),确保加载进来的字节流格式符合java虚拟机要求
3、准备:对类变量(不是实例变量)分配内存,并且给一个默认初始化的值(0或者null)
4、解析:将常量池中的符号引用加载成直接引用(如string str = new string(“123”),str就是符号引用,123是直接引用)
5、初始化:对类的静态变量初始化为指定的值,执行静态代码块

双亲委派机制:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。
因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

27、synchronized底层实现原理?它与lock相比有什么优缺点?

首先那些说看过synchronized源码的基本都是大聪明,synchronized根本点不进去,想弄懂它的实现原理,我们只能通过看编译好的字节码文件

原理:

基于对象的监视器(ObjectMonitor),我们在字节码文件里面可以看到,在同步方法执行前后,有两个指令,方法前monitorenter,方法后monitorexit;

关于字节码一两句话说不清楚,我单独写了篇synchronized底层实现原理,保证你五分钟看懂

与lock对比:

1、synchronized不需要手动释放锁,lock需要在锁用完后进行unlock;
2、synchronized只能是默认的非公平锁,lock可以指定使用公平锁或者非公平锁;
3、lock提供的Condition(条件)可以指定唤醒哪些线程,而synchronized只能随机唤醒一个或者全部唤醒;

28、jvm指令重排原因?怎么避免?

原因:计算机内存操作速度远慢于CPU运行速度,所以就造成CPU空置,为了将提高CPU利用率,虚拟机会按照自己的一些规则会跳过执行慢的代码,去执行快的代码(即对代码重新排序),从而提升jvm的整体性能。
怎么避免:给关键的代码加上volatile关键字,所谓关键,就是会被执行顺序影响结果。

volatile关键字的三个特征是:线程可见、不具备原子性、禁止指令重排,volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

29、ReentrantLock是什么?有什么用?怎么用?和synchronized的区别?

ReentrantLock是Lock的一个子类
在这里插入图片描述

作用:
用来给资源加锁,避免高并发造成的数据异常问题;

使用:
public void lockTest() {
//创建lock实例,可传参数true或者false,表示是否是公平锁,默认是非公平锁
ReentrantLock lock = new ReentrantLock();
//在需要保证同步的代码前lock
lock.lock();
int i = 0;
i++;
//代码后unlock
lock.unlock();
}
与synchronized主要区别,lock需要手动释放锁,可以指定公平锁或者非公平锁

30、volatile关键字解决了什么问题?实现原理是什么?

解决了什么问题:
1、保证了变量的可见性
2、禁止指令重排

比如i=i+1,单线程操作没问题,如果使用多线程,比如两个线程,执行这段代码后(i初始值为0),i应该等于2,但是如果不用volatile修饰变量i,结果会等于1,初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。

实现原理
基于内存屏障,关于内存屏障,搞java开发的同学在开发中不可能接触到,所以不用关心太多,知道内存屏障有什么作用,面试官问到你能唬住他就行了,因为面试官自己也不懂

(1)它确保指令重排序时不会把其后面的指令排到内存屏障前面,也不会把前面的指令排到内存屏障后面,总之一句话,他能保证指令按照我们希望的顺序执行;
(2)它会强制将对缓存的修改操作立即写入主存,使得其它线程能立马发现;

31、ThreadLocal实现原理?

ThreadLocal中文名叫线程变量,它底层维护了一个map,key就是当前的ThreadLocal对象(可以理解为当前执行该段代码的线程),value就是你set的值,这个map保证了各个线程的数据互不干扰;

想进一步了解ThreadLocal的同学请移步我凭ThreadLocal唬住了京东面试官

32、线程池实现原理?

要说实现原理,必须得把线程池的几个参数彻底搞懂,不要死记硬背

1、corePoolSize(必填):核心线程数。 2、maximumPoolSize(必填):最大线程数。
3、keepAliveTime(必填):线程空闲时长。如果超过该时长,非核心线程就会被回收。
4、unit(必填):指定keepAliveTime的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
5、workQueue(必填):任务队列。通过线程池的execute()方法提交的Runnable对象将存储在该队列中。
6、threadFactory(可选):线程工厂。一般就用默认的。
7、handler(可选):拒绝策略。当线程数达到最大线程数时就要执行饱和策略。

贴心的博主单独给你们写了篇讲线程池的,五分钟就能看懂的那种,请移步线程池实现原理吐血总结

33、java是如何实现线程安全的?哪些数据结构是线程安全的?

1、锁机制:用synchronize、lock给共享资源加锁;
2、使用java提供的安全类:java.util.concurrent包下的类自身就是线程安全的,在保证安全的同时还能保证性能;

线程安全的数据结构:
常见的有Atomicinteger、ConcurrentHashMap,太多了,截个图吧,大家可以自己去java.util.concurrent包下看
在这里插入图片描述
34、java线程间通信方式

1、基于synchronized+wait() 和 notify()
2、基于reentrantLock的Condition
3、基于volatile
4、基于CountDownLatch

一两句话说不清楚,单独写了篇,每个方式我都给了demo,复制就能运行,请移步java线程间通讯的几种方式

35、什么是公平锁?什么是非公平锁?

公平锁:当前获得锁的线程释放锁后,其它所有等待中的线程会按照来的顺序执行,不会造成锁竞争;
非公平锁:当前获得锁的线程释放锁后,其它所有等待中的线程会全部参与锁竞争;

36、什么是redis缓存穿透、缓存击穿、缓存雪崩?如何解决?

从事态严重性来讲:穿透 > 雪崩 > 击穿

缓存穿透:请求数据库中根本就不存在的数据,既然数据库中都没有,缓存中更没有,导致每次请求直接怼到数据库;
缓存雪崩:缓存大面积失效;
缓存击穿:请求了很多缓存中没有但是数据库中真实存在的数据,一般是缓存过期导致,也导致请求直接怼到数据库;

解决办法:

缓存穿透:最简单的就是利用布隆过滤器过滤非法key,我写了个 demo来分析具体原理,请移步布隆过滤器原理
缓存雪崩:设置key过期时间的时候加上一个随机数,关键点就在于让key错开时间失效;
缓存击穿:延长热点数据过期时间,或者直接设置永远不过期;

37、说说数据库设计三范式?

第一范式

属性(字段)的原子性约束,要求属性具有原子性,不可再分割; 比如个人信息,个人信息不能作为一个字段,它可以再分为姓名、name、age等;

第二范式

记录的惟一性约束,要求记录有惟一标识,每条记录需要有一个属性来做为实体的唯一标识;

第三范式

字段冗余性的约束,即任何字段不能由其他字段派生出来;主键没有直接关系的数据列必须消除,消除的办法就是再创建一个表来存放他们,当然外键除外;

误区:
并不是非得严格按照三范式来设计,好的数据库设计一定不是这样的,而是根据实际情况柔性处理;

38、简单阐述Java中的io、nio、bio

i/o即input/output,就是指读写操作

面试官经常问io和nio的区别,如果把io和nio放一起比较的话,那这里的io其实可以理解为bio,即blocking-io:

bio:同步阻塞

bio是java传统的io模型,他是同步阻塞io,一个线程触发io操作后,必须等待这个io操作执行完成,期间不能去做其他事情;

nio:同步非阻塞

nio(non-blocking-io)是同步非阻塞io,一个线程触发io操作后它可以立即返回,但是他需要不断地轮询去获取返回结果;

aio:异步非阻塞

aio(Asynchronous-io)是异步非阻塞io,一个线程触发io操作后她可以立马返回去做其他事情,内核系统将io操作执行完成后会通知线程;

多路复用io:异步阻塞

io多路复用:可以理解为异步阻塞io,但官方没这么叫,一个线程可以管理多个连接,不用来回切换;

39、String str = new String(“abc“)到底new了几个对象?

两个或者一个

1、两个:如果常量池里面没有“abc”这个字符串,那虚拟机就会在堆内存中new出一个String对象,还会在常量池中new一个abc字符串对象;

2、一个:如果常量池中已经有"abc"这个字符串,也就是说你在前面已经new过一个值为“abc”的字符串,那虚拟机就只会在堆内存中new一个String对象,并将常量池中“abc”的地址指向你刚刚new的String对象

40、spring如何解决循环依赖?

spring只能通过提前暴露bean来解决setter注入的循环依赖,无法解决构造器注入的循环依赖;

这个属于大厂高端面试题了,一般只要如上答出就ok了,因为这个问题装逼成分太高,想了解更多的同学请移步spring如何解决循环依赖(大白话)

41、mysql深度分页

分页大家都懂,如果某天数据库量达到100万条,而你需要的数据恰好在最后10条,常规的分页就会变得特别慢
此时就要用些技巧,思路就是先查id后查记录,直接看代码吧

/常规分页
SELECT * FROM table_name limit 7000,10 //0.868s

//先查id ,写法很多,看个人习惯
SELECT * FROM table_name a,(SELECT id FROM table_name limit 7000,10) b WHERE a.id = b.id //0.468

//如果你的表有自增id,就这么写,效率直接起飞,真的是项目经理看了感动,架构师看了落泪
SELECT * FROM table_name WHERE id>7000 LIMIT 10 //0.372

很生气,我现在数据库就只有这么多数据,体现不出太大的差别,数据量越大优势越明显

嘤~

我简单说两个事

1、文中所有的demo都是复制即可运行,不要只动眼睛不动手,家里有条件的都用idea跑一跑,没条件的用手抄

2、本文会持续更新,如果有优质面试题,也欢迎大家加我好友投稿
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_38127487/article/details/113746277