2023年Java知识总结

本篇只是记录本人在工作或面试过程中遇到的一些问题及总结,如有错误,欢迎指正!

Java基础

1.Java普通类和抽象类的区别

普通类:

  • 可以直接实例化为对象。
  • 可以包含成员变量、成员方法和构造方法。
  • 可以被其他类继承。
  • 可以被其他类实现(如果实现了接口)。
  • 可以重写父类的方法。

抽象类:

  • 不能直接实例化为对象,只能作为基类供其他类继承。
  • 可以包含成员变量、成员方法和构造方法。
  • 可以包含抽象方法,抽象方法没有具体的实现,需要在子类中被实现。
  • 子类必须实现抽象类中的所有抽象方法。
  • 可以被其他类继承。
  • 可以实现接口。

集合

1.List、Set、Map区别

List集合:有序、可重复

Set集合:无序、不重复

Map集合:无序,键值对形式。键不可重复,值可重复

2.List

2.1 ArryList 

动态数组。延迟加载(懒加载),默认大小为10,每次扩容1.5倍,线程不安全,查询速度相对较快。

为什么说是懒加载?

因为当new一个ArryList集合时,并没有分配具体的容量,只有当第一次添加进集合里面时,才会分配容量。

2.2 LinkedList

使用双向链表方式存储数据。插入删除速度较快,线程不安全。

3.Map

3.1 HashMap

JDK1.8后,数组+链表+红黑树。当链表长度大于8时,链表会转换为红黑树(前提条件:当前数组长度大于了64),反之小于6时会还原成链表结构。

扩容条件:默认容量16,负载因子0.75(时间和空间复杂度最佳),所以第一次扩容阈值为12,每次扩容的容量为原来的2倍。

3.2 HashMap和HashTable

HashMap:键值可以为空,线程不安全。

HashTable:键值不可以为空,线程安全(过时弃用)

多线程

1. 线程和进程的区别?

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

2. 并行和并发的区别?

   现在都是多核CPU,在多核CPU下

  • 并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU
  • 并行是同一时间动手处理多件事情的能力,例如:4核CPU同时执行4个线程

3. 创建线程的方式有那些?

共有四种:继承Thread类、实现Runable接口、实现Callable接口、线程池创建线程

3.1 继承Thread类

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("MyThread...run..");
    }
    public static void main(String[] args) {
        // 创建MyThread对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        // 调用start方法启动线程
        t1.start();
        t2.start();
    }
}

3.2 实现Runable接口

public class MyRunable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunable...run...");
    }

    public static void main(String[] args) {
        // 创建MyRunable对象
        MyRunable mr = new MyRunable();
        // 创建Thread对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        // 调用start方法启动线程
        t1.start();
        t2.start();
    }
}

3.3 实现Callable接口

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return "OK";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建MyCallable对象
        MyCallable mc = new MyCallable();
        // 创建FutureTast
        FutureTask<String> ft = new FutureTask<>(mc);
        // 创建Thread对象
        Thread t1 = new Thread(ft);
        Thread t2 = new Thread(ft);
        // 调用start方法启动线程
        t1.start();
        // 调用ft的get方法获取执行结果
        String result = ft.get();
        System.out.println(result);
    }
}

3.4 线程池创建线程

public class MyExecutors implements Runnable{
    @Override
    public void run() {
        System.out.println("MyExecutors...run...");
    }
    public static void main(String[] args) {
        // 创建线程池对象
        ExecutorService threadPoll = Executors.newFixedThreadPool(3);
        threadPoll.submit(new MyExecutors());
        
        // 关闭线程池
        threadPoll.shutdown();
    }
}

3.5 线程的run()和start()有什么区别?

start():用来启动线程,通过该线程调用 run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。

run():封装了要被线程执行的代码(相对于是普通方法),可以被多次调用。 

4. 线程包括哪些状态?

框架

Spring

1. AOP

面向切面编程,作用:减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。(底层:动态代理)

JDK动态代理只能代理实现接口的类或者直接代理接口;对于没有实现接口的类,可以使用CGLIB代理进行代理。Sring框架中默认使用JDK动态代理。

常见的AOP使用场景:

1.记录操作日志

        核心:使用AOP中的环绕通知+切点表达式。

2.缓存处理

3.spring中的内置事务

2. Spring中的事务

编程式事务:使用TransactionTemplate来进行实现,对代码的有侵入性,项目中很少用。

声明式事务:其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始前加入一个事务,在执行目标方法之后根据执行情况提交或者事务回滚。

3. Spring中事务失效的场景

3.1 异常捕获处理

原因:事务通知只有捕获到目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉了一场,事务通知无法知晓。

解决:在catch块中添加throw new RuntimeException(e)抛出。

3.2 抛出检查异常 

原因:Spring默认只会回滚非检查异常。

解决:配置rollbackFor属性

@Transactional(rollbackFor=Exception.class)//这里也可以写抛出具体的异常
public void update(....)throws FileNotFoundException{
....
}

3.3 非public方法

原因:Spring为方法创建代理、添加事务管理、前提条件都是改方法是public的。

4.  Spring的bean的生命周期

5.Spring中的循环引用

三级缓存解决循环依赖

Springboot

1. Springboot自动配置原理

Spring、SpringMVC、Springboot常见注解

1. Spring常见注解

2. SpringMVC常见注解

3. Springboot常见注解

Spring Cloud

1.SpringCloud常见的5大组件

①Eureka:注册中心(阿里巴巴的组件:注册中心/配置中心--Nacos)

②Ribbon:负载均衡

③Feign:远程调用

④Hystrix:服务熔断(阿里巴巴的组件:服务保护--sentinel)

⑤Zuul/Gateway:网关

2.服务注册和发现,SpringCloud是如何实现服务注册发现?

  • 以eureka注册中心
  • 服务注册:服务提供者把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等
  • 服务发现:消费者向eureka拉取服务信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用
  • 服务监控:服务提供者会每隔30s向eureka发送心跳,报告健康状况,如果eureka90s没有接收到心跳,则从eureka中剔除

3.Nacos与Eureka的区别?

  • Nacos与Eureka的共同点(注册中心)

            ① 都支持服务注册和服务的拉取

            ② 都支持服务提供者心跳方式做健康检测

  • Nacos与Eureka的区别(注册中心)

            ①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

            ② 临时实例心跳不正常会被剔除,非临时实例则不会被剔除

            ③ Nacos支持服务列表变更的消息推送模式,服务列表更新更及时

            ④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

  • Nacos还支持了配置中心,eureka则只有注册中心,也是选择使用nacos的一个重要原因

MyBatis

1. MyBatis执行流程

①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件

②构建会话工厂SqlSessionFactory

③会话工厂创建SqlSession对象(包含了执行SQL语句的所以方法)

④操作数据库的接口,Executor执行器,同时负责查询缓存的维护

⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息

⑥输入参数映射

⑦输出参数映射

2. MyBatis是否支持延迟加载

MyBatis支持延迟加载,但默认没有开启。

局部延迟加载:可以在Mapper的映射文件中添加fetchType=‘lazy’就可以开启局部延迟加载了。

全局延迟加载:再MyBatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnable=ture|false,默认是关闭的。

延迟加载的原理:

①使用CGLIB创建目标对象的代理对象

②当调用目标方法时,进入拦截器invoke方法,发现目标方法时null值,执行sql查询

③获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

3. MyBatis的一级、二级缓存

3.1 一级缓存

是基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session进行flush或者close之后,该Session中的所有Cache就将清空,默认打开一级缓存。

3.2 二级缓存

是基于namespace和mapper的作用域起作用的,不依赖于SQL session,默认也是采用PerpetualCache,HashMap本地缓存。

二级缓存默认是关闭的,需要单独打开,开启方式:

①全局配置文件

<settings>
    <setting name="cacheEnable" value="true"/>
</settings>

②映射文件

使用<cache/>标签让当前mapper生效二级缓存。

3.3 二级缓存什么时候会清理缓存中的数据

当某一个作用域(一级缓存Session/二级缓存Namespaces)进行了新增、修改、删除操作后,默认该作用域下select中的缓存都将被clear。

其他

1.#{}和${}的区别

#{}是预编译,可防止SQL注入
${}是直接拼接在SQL语句中。

Redis

1.缓存

1.1 缓存穿透

查询一个不存在的数据,mysql查询不到数据也不会写入缓存,就会导致每次请求都查询数据库。

解决方案一:缓存空数据,查询返回的数据为空,扔把这个空的结果进行缓存{key:1,value:null}

优点:简单。

确定:消耗内存,可能会发生数据不一致问题。

解决方案二:布隆过滤器

底层主要是先去初始化一个比较大的数组,里面存的二进制0或者1。当一个key来了之后会经过3次hash计算,模于数组长度 找到数据的下标后把数组中原来的0改为1,这样的话三个数组的位置就能标明一个key的存在。查找也是一样的。

优点:内存占用较少,没有多余的key

缺点:实现复杂,存在误判

bitmap(位图):相当于是一个以(bit)位为单位的数组,数组中的每个单元只能存储二进制数0或1。

布隆过滤器的作用:布隆过滤器可以用于检索一个元素是否存在一个集合中。

问题:不存在的数据(假设id3)通过使用相同的hash函数获取的hash值有一定的概率可能会有重复,从而导致误判(误判率)。

解决:数组越小误判率越高,数组越大误判率就越小,但同时带来了更多的内存消耗。设置误判率一般在5%以内。

//第一个参数:布隆过滤器存储的元素个数,第二个参数:误判率

 bloomfilter.tryInit(size,0.05);

1.2 缓存击穿

给某一个key设置了过期时间,当key过期时,恰好这个时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮。

解决方案一:互斥锁:强一致,性能差

解决方案二:逻辑过期:高可用,性能优        

1.3 缓存雪崩

指在同一时间段有大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大的压力。

解决方案:

1.给不同的key的TTL添加随机值

2.利用Redis集群提高服务的可用性(哨兵模式、集群模式)

3.给缓存业务添加降级限流策略(Nginx或spring cloud gateway)

降级可作为系统的保底策略,适用于穿透、击穿、雪崩

4.给业务添加多级缓存

消息中间件

RabbitMQ

1.如何保证消息不丢失

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值