2021 Java知识点精心整理(持续更新)

本文大多是各大厂真实面试题目,针对中高级java开发,会持续更新,如果有优质面试题,欢迎大家加我好友投稿


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

不能
因为string类是被final修饰的类,final修饰过的类不能被继承、final修饰过的变量不能被修改
在这里插入图片描述

2、实现单例设计模式(懒汉、饿汉)

//懒汉,顾名思义比较懒,在用的时候才实例化
public class Singleton {
	//创建实例,注意,此时没有new
	private static volatile Singleton instance = null;
	//构造方法私有化,无法在外部获取实例,只能通过下方的公有静态方法
	private Singleton() {}
	//公有的静态方法,返回实例对象
	public static synchronized Singleton getInstance() {
		//先看下是否存在实例,有的话就不再new了
		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() {
		//直接将事先new好的实例返回
		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、都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,不提供具体的实现(jdk1.8允许接口有一个default的实现方法)
2、接口是对事物行为的抽象,而抽象类是对事务本质的抽象;
3、接口中的变量必须给出初始值,抽象类可以不给;
4、一个类只能继承一个抽象类,但可以实现多个接口;
5、抽象类中可以写非抽象的方法,从而避免在子类中重复书写它们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法;

针对第二点和第四点举个例帮助理解:

对于抽象类:比方说有公鸡、母鸡、公狗、母狗,我们可以抽象出两个更高级的类,鸡类和狗类,因为你不能又是鸡又是狗,所以你只能继承其中一个,这就是为什么抽象类只能单继承

对于接口:众所周知,鸡都会唱、跳、rap,于是,我们可以把这些鸡的基本操作抽象成接口A,而有的鸡通过练习两年半可能学会打篮球,那么对于这种鸡的高端操作我们可以再抽象出一个接口B,重点来了,对于常规鸡,这种鸡只会唱跳rap,所以只需要实现接口A;那对于一些高端鸡,这种鸡既会唱跳rap又会打篮球,我们就同时实现接口A和接口B,这就是为什么接口可以多实现,skr~

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有几种基本数据类型?分别占用多少字节?

数据类型占用字节
byte1
short2
int4
long8
char2(c语言是1字节,可以存储一个汉字)
float4
double8
boolean1/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

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

42、int和Integer有什么区别?

int是基本数据类型,默认值为0,integer是其包装类型,默认值为null

  • 原始类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

java实体类尽量都用integer,假设一个学生类,有个考试成绩属性,如果学生缺考,该属性不应该有值,应该为null,你用int的话默认值为0,考试成绩为0和缺考是两码事

43、String和StringBuilder、StringBuffer的区别?

Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。

44、常见的运行时异常

  • ArithmeticException(算术异常)
  • ClassCastException (类转换异常)
  • IllegalArgumentException (非法参数异常)
  • IndexOutOfBoundsException (下标越界异常)
  • NullPointerException (空指针异常)
  • SecurityException (安全异常)

45、事务的ACID是指什么?

  • 原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;
  • 一致性(Consistent):事务结束后系统状态是一致的;
  • 隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
  • 持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败,通过日志和同步备份可以在故障发生后重建数据。

46、事务隔离级别

1、READ_UNCOMMITTED

读未提交,即能够读取到没有被提交的数据,无法解决脏读、不可重复读、幻读

2、READ_COMMITED

读已提交,即能够读到那些已经提交的数据,能够防止脏读,但是无法限制不可重复读和幻读

3、REPEATABLE_READ

可重复读,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决

4、SERLALIZABLE

串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,解决了脏读、不可重复读和幻读

表格总结
在这里插入图片描述

47、悲观锁和乐观锁的原理以及应用场景

悲观锁:

顾名思义,比较悲观,每次去拿数据都认为别人会修改,所有每次在操作前都会加锁,如:读写锁、行锁、表锁等,synchronized的原理也是悲观锁;适用于多写操作

乐观锁:

每次拿数据都认为别人不会修改,所以不会加锁,但是在更新的时候,会先判断在此期间有没有人更新该数据,如果有,返回冲突报错信息,让用户决定怎么操作;适用于多读操作

48、对spring IOC和AOP的理解

IOC(控制反转)

也叫DI(依赖注入),是一种思想,不是一种技术,IOC主张把对象的控制权交由spring,底层实现是反射+工厂方法模式,IOC容器实际上就是个Map,存放各种对象;

AOP

面向切面编程,把一些能共用、冗余、繁琐的功能提取出来,AOP能在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复;常见使用场景有事务管理、日志、全局异常处理、用户鉴权;

49、简述java内存模型

注意和本文23点的jvm内存模型区分开,这是两种截然不同的内存模型
以下是本人总结的概念,别看字多,都是精华,我想过删些,一句都没找到,阅读一遍应该理解的差不多了

  • java内存模型的由来:这和cpu和内存技术发展有关,当代cpu运算速度太快,以至于被内存拉低了程序运行的整体效率

  • 应对措施:为了不被内存影响性能,cpu厂商给cpu加了一级二级甚至三级高速缓存,cpu读取数据时先从一级缓存中找,没有的话找二级,再没有就找三级或者主存;java内存模型规定了变量都是存储在主存中,程序运行时操作的是高速缓存中的数据,操作完之后再同步到主存;

  • 后遗症及规范:在cpu和主存之间增加了一个高速缓存固然提升了程序运行效率,但是在多线程并发操作同一个变量时,就可能造成数据不一致的问题,所以就有了JMM(java内存模型,也可叫JMM规范),它保证了并发编程中数据的正确性(正确性可细分为原子性、可见性、有序性),底层具体实现方式比较复杂,好在JMM为我们日常编程提供了一些用于保证数据正确性的关键字,如synchronized(原子性、有序性)、volatile(可见性、有序性);

50、什么是泛型擦除?

泛型只是为了在编码过程中,我的理解是泛型存在的意义有两个:一是为了让我们更快地发现错误,比如你把User放进了ArrayList< Dog >中,编译器立马会报错;二是避免类型检查,从而避免在运行时抛出 classCastException;泛型擦除就是指泛型会在编译时被消除

        ArrayList<User> users = new ArrayList<>();
        ArrayList<Dog> dogs = new ArrayList<>();
        System.out.println(users.getClass() == dogs.getClass());
		//true

如上,运行结果表面两个list是相等的,因为经过编译后,泛型被擦除,两个list当然也就相等;

51、你能想到几种方法实现两数交换?

	1、第三变量
    public void swapOne(){
        int a = 4;
        int b = 5;
        int c = a;
        a = b;
        b = c;
        System.out.println("a:"+a); //a:5
        System.out.println("b:"+b); //a:4
    }
	2、数学计算
    public void swapTwo(){
        int a = 4;
        int b = 5;
        a=a+b;
        b=a-b;
        a=a-b;
        System.out.println("a:"+a); //a:5
        System.out.println("b:"+b); //a:4
    }
	3、异或运算
    public void swapThree(){
        int a = 4;
        int b = 5;
        a = a ^ b; // 0101 ^ 0100 ===> 0001,此时a的值为1
        b = a ^ b; // 0001 ^ 0100 ===> 0101,此时b的值为5
        a = a ^ b; // 0001 ^ 0101 ===> 0100,此时a的值为4
        System.out.println("a:"+a); //a:5
        System.out.println("b:"+b); //a:4
    }

52、什么是CAS操作?什么ABA问题?如何解决?

CAS全称compare and swap(比较并交换),作用是保证原子性

CAS操作包含三个操作数 —— 内存位置、预期原值、新值。 如果内存位置的值和预期原值相等,就把该值更新为新值,如果不相等,则什么都不做;

ABA问题:CAS操作存在的一个并发问题,打个比方,有两个线程A、B同时操作变量x,A读取到的预期原值是1,此时线程B先将x设置为2,再设置为1,等线程A再来操作的时候,x变量的预期原值和当前值相等,但是x在整个过程中的值是发生过变化的,这在某些业务场景下是不允许的;
解决:利用版本号,给变量x增加版本号,每次操作增加对本版好的判断和修改;

53、什么是回表查询?如何避免?

对于mysql数据库的InnoDB引擎,它的非主键索引是非聚簇索引,索引文件和数据文件分开存储,索引文件B+树的叶节点只保存主键,在进行查询时,需要先去索引文件找到id,再根据id去数据文件查找具体数据,这种现象就叫回表查询

如何避免:索引覆盖

将查询sql中的字段添加到联合索引里面,只要保证查询语句里面的字段都在索引文件中,就无需进行回表查询;

更多索引知识点请移步mysql索引总结

54、什么是CAP理论?

CAP理论是分布式系统架构设计中的一种猜想,或者说是一种理论

  • 一致性(Consistence) : 所有节点访问的数据都是一致的;
  • 可用性(Availability): 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应);
  • 分区容错性(Partition tolerance) : 分布式系统出现网络分区的时候,仍然能够对外提供服务。网络分区指的是网络设备出现的丢包、阻塞、超时等问题。

误区:我发现很多人说到CAP时,都知道分布式系统中这三者不能同时兼顾,只能满足其二,这种说法不严谨,并不是简单的三选二,而是在一致性和可用性二者中只能选其一,分区容错性是我们必须保证的,不能因为出现网络分区导致系统瘫痪,而一致性和可用性可以根据我们的业务来舍弃其一,即我们只能实现AP方案或者CP方案;

55、什么是BASE理论?

BASE理论由CAP理论演化而来,因为CAP对系统设计的要求过高,实现CAP的代价太大;
BASE实质上是对CAP 中 AP 方案的一个补充。

BA:Basically Available(基本可用)
S:Soft-state(软状态) E:Eventually
Consistent(最终一致性)

BASE理论核心思想

牺牲强一致性,通过某些逻辑来达到数据的最终一致性

因为大多数系统对强一致性的依赖没那么强,抛开分区容错性不谈,一致性和可用性这两者,可用性才是我们应该保证的,而对于一致性,我们往往只需要保证最终的数据一致即可;

56、@Autowire和@Resource区别?

都是用来装配java bean

  • @Autowired:按类型注入,这是spring的注解,可以搭配@Qualifie实现按名称注入;
  • @Resource:默认情况下是按照名称进行匹配,如果没有找到相同名称的Bean,则会按照类型进行匹配,这是java自己的注解;

注意事项:

@Autowired有个弊端,打个比方,有个userService,然后它有两个实现类userServiceA和userServiceB,这时候用@Autowired就行不通了,因为它不知道找谁,但是你也不能因为这个一上来就直接用@Resource,这玩意儿性能没@Autowired好,因为@Resource要匹配两次

57、什么是可重入锁?实现原理?

可重入锁:不是一种锁,它是锁的一种性质,代表该锁是否可重入,可重入意思就是**任意线程在获取到锁后能够再次获取该锁,不会被锁阻塞**,这个锁知道它属于谁,其它线程来了就会阻塞等待;
原理:锁的内部维护了线程计数器state和当前持有锁的线程对象,当线程A获取锁资源后,锁会记录下A线程,并且state+1,此时如果有其它线程来获取锁,会被封装成node节点插到队列尾部并且阻塞;而线程A再来获取锁资源时,会成功拿到锁,并且state+1;当线程退出同步代码块时,state-1,如果计数器为0,则释放锁;

58、synchronized可重入吗?ReentrantLock呢?

都是可重入锁,java的锁都可重入

59、RPC协议与HTTP协议的区别?

RPC:远程过程调用,一般用于一台计算机调用另一个计算机上的服务,rpc能让我们像调用本地方法一样调用远程方法;
HTTP:超文本传输协议,一般用于浏览器和服务器之间的通讯;
RPC效率更高,但是实现起来较复杂;
HTTP一般使用json传输数据,RPC一般采用二进制;

60、String有没有长度限制?

既然都这么问了那肯定是有的,分编译器和运行期,长度限制不一样
编译器2 ^ 16 - 1(65535)
运行期2 ^ 32 - 1(2147483647)

不管是编译器还是运行期,如果超出长度都会抛异常:常量字符串过长

61、算法 - 字符串压缩

两种压缩方式

  • 只统计字符出现次数,比如aabcccccaaa,压缩成a5b1c5,利用hashMap键的唯一性;
  • 统计相邻字符串出现次数,比如aabcccccaaa,压缩成a2b1c5a3,需要维护一个当前对比字符,一个字符出现次数,用第n个字符去和第n+1个字符对比

详细实现demo请移步Java字符串压缩

62、简述一下二叉树,有什么特点,以及它的三种遍历方式

一个递归的树形数据结构,每个节点最多有两个子节点;二叉树一般都是二分查找树,每个节点的值大于它左子节点的值,小于它右子节点的值

递归遍历:

前序遍历:先访问根节点,再访问左子节点,最后访问右子节点
上图中前序遍历结果:30、20、5、28、50、38、58

中序遍历:先访问左子节点,再访问根节点,最后访问右子节点
上图中中序遍历结果:5、20、28、30、38、50、58

后序遍历:先访问左子节点,再访问右子节点,最后访问根节点
上图中后序遍历结果:5、28、20、38、58、50、30

非递归遍历:

常用的是利用栈的先进后出特性,不断地将节点入栈,然后再出栈
非递归前序遍历和非递归中序遍历两种方式很好理解,控制遍历时机即可,而非递归后序遍历较为复杂,需要额外维护一个最后访问节点

专门给对二叉树感兴趣的同学写了个二叉树demo,包含常用方法、遍历方式,注释中有很详细的实现原理,请移步八旬老人彻夜难眠,竟是为了学会二叉树

63、什么websocket?它有什么特点?

作用:
用来和服务端保持长连接,使服务端能主动推送消息给客户端

对比http:
websocket是一种类似http的协议,可以理解成http协议的加强版;http每次请求都需要建立tcp连接,而且一个request只能对应一个response,每次请求必须由客户端发起,由服务端响应;而websocket能使客户端在和服务端建立好tcp连接后,与服务端保持会话连接,服务端可以自由向客户端推送消息

注意:如果服务端同时维护了很多websocket连接,会对服务端造成很大压力,需要我们对websocket做一些优化(有个互联网独角兽公司面试官这么问我的)
我目前能想到的优化就是:

1、合并推送:将一些能合并的消息整合到一次推送,以此减少websocket连接
2、横向扩展:增加服务器 0.0

64、springboot自动装配原理?

在springboot的启动类上有个SpringBootApplication注解,这是个组合注解,这个注解里面有个注解叫EnableAutoConfiguration注解,@EnableAutoConfigration 注解会导入一个自动配置选择器去扫描每个jar包的META-INF/xxxx.factories 这个文件,这个文件是一个key-value形式的配置文件,里面存放了这个jar包依赖的具体依赖的自动配置类。这些自动配置类又通过@EnableConfigurationProperties 注解支持通过xxxxProperties 读取application.properties/application.yml属性文件中我们配置的值。如果我们没有配置值,就使用默认值,这就是所谓约定大于配置的具体落地点。

65、Eureka和Zookeeper的区别?

都能用来做服务注册与发现,但是两者侧重的点不一样,这跟本文的54点相关,zk是CP架构,而eureka是AP架构

zk集群的master节点挂了以后,会从slave节点中选举一个出来作为master,在选举这段时间内,zk服务不可用,为了保证一致性,牺牲了高可用
而eureka的节点之间没有主仆关系,当有节点挂了以后,其它节点依然能提供服务,只不过数据可能不一致,为了保证高可用,牺牲了一致性

66、公平锁和非公平锁的区别?如何实现?

拿ReentrantLock来说,众所周知,没拿到锁对象的线程会被放入同步队列,当持有锁的线程释放锁后

公平锁:队列中先来的线程先获取锁,不会造成锁竞争;
非公平锁:队列中所有线程一起竞争锁资源;

如何实现:

当前持锁线程把锁释放掉后,如果是公平锁,它只会唤醒队列头部第一个等待线程,如果是非公平锁,就会唤醒队列中所有等待线程

67、mysql如何实现可重复读?

字有点多,但是保证你看完后对可重复读有新的理解

基于mvcc(Multi-Version Concurrency Control,多版本并发控制)
mysql默认隔离级别就是可重复读,这个隔离级别解决了不可重复读和脏读,所谓不可重复读就是在一个事务内多次查询的结果不一样,其原因就是期间数据被另一个事务修改了;脏读就是一个事务读取到了另一个事务未提交的数据,然而该数据回滚了,实际上并未提交;
实现原理前置知识:
InnoDB是通过维护两个隐藏列来实现mvcc,隐藏列记录了数据行创建版本号删除版本号,每开始一个事务,版本号就会递增;事务开始时刻的版本号就是事务的版本号,用来和查询到的数据行的版本号进行比较;
mvcc在可重复读级别下的具体实现:
查询:需满足两个条件,1、创建版本号小于或等于当前事务版本,这样可以确保查出来的数据都是在本次事务开始前就已经存在,或者由本次事务创建或修改的;2、删除版本号大于当前事务版本号或者未定义(未定义即未删除),这样可以保证在此次事务之前就存在的行没有被删除;
插入:插入的每条数据都需要将创建版本号保存为当前事务版本号;
删除:删除的每条数据都需要将删除版本号保存为当前事务版本号;
修改:插入一条新数据,将创建版本号保存为当前事务版本号,并将原数据的删除版本号保存为当前事务版本号;

68、为什么finally里的代码一定会执行?

编译器在编译的时候,会把finally里面的代码复制多份,分别放在try和catch内所有能够正常执行以及异常执行逻辑的出口处,最直观的就是我们可以在字节码文件里看到很多份finally内部代码;

69、分别说下ConcurrentHashMap1.7和1.8的实现?

1.7

基于Segment数组和HashEntry,Segment继承自ReentrantLock,懂了吧,它自然就有了锁的基本功能;每个Segment数组中都有多个HashEntry,我们的数据都存在HashEntry里面,每次需要修改数据时,先对HashEntry所在的Segment加锁,其它Segment不受影响,分段锁就是这么来的;

1.8

整体实现很像HashMap,在它基础上引入了synchronized,和大量的CAS操作,以及大量的volatile关键字,所以1.8的ConcurrentHashMap中锁的粒度更小;

70、什么是mysql最左匹配原则?

测试数据太多了,直接说结论吧

比如有个表建了a、b、c三个字段的联合索引,那么哪些情况下会触发索引呢
只要查询条件中有a字段就会走索引,不管是bac、cba、ac、ca,都可以走索引,想不到吧,因为mysql有个sql优化器,会自己去优化顺序,但是如果你是bc或者b就不行



在这里插入图片描述
嘤~
这是博主的帅哥码,加载不出来的输暗号 chuanAhaojige8,需要内推的同学请主动加我好友,备注csdn,校招社招都可,别害羞

要想面试不失败,加我好友就现在

最后!!!

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

ok我话说完,skr~

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页