Skip to content

面试(2025年6月24日)

1. String、StringBuffer、StringBuilder区别,以及各自的底层

可变性

String 对象是不可变的。StringBufferStringBuilder 对象是可变的。

线程安全性

String 对象由于是不可变的,可以理解为常量,线程安全。StringBuffer 对方法加了同步锁,线程安全。StringBuilder 没对方法加同步锁,线程不安全。

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%-15% 的性能提升。

底层实现

String:底层是使用private final的byte数组

StringBuffer:底层是使用byte数组,但是他的方法都是加锁实现的

StringBuilder:底层是使用byte数组

总结

  • String:不可变,线程安全,适用于字符串内容不经常变化的场景。
  • StringBuffer:可变,线程安全,适用于多线程环境下多线程环境下频繁修改字符串的场景。
  • StringBuilder:可变,非线程安全,适用于单线程环境下频繁修改字符串的场景。

2. ArrayList与LinkedList区别

内部实现

ArrayList

  • 底层数据结构:基于动态数组实现。
  • 存储方式:元素存储在连续的内存空间中。
  • 初始化:默认初始容量为 10,当列表容量不足时,会自动扩容(通常是当前容量的 1.5 倍)。

LinkedList

  • 底层数据结构:基于双向链表实现。
  • 存储方式:每个元素都有一个指向前一个元素和后一个元素的引用。
  • 初始化:默认初始容量为 0,随着元素的增加,链表会动态增长。

性能特性

添加元素

  • ArrayList
    • 末尾添加:O(1) 时间复杂度,因为直接在数组末尾添加元素。
    • 中间插入:O(n) 时间复杂度,因为需要移动后续元素以腾出空间。
    • 扩容:当数组容量不足时,需要进行扩容操作,这是一个 O(n) 的操作。
  • LinkedList
    • 任意位置添加:O(1) 时间复杂度,因为只需要修改相邻节点的引用。
    • 末尾添加:O(1) 时间复杂度,因为 LinkedList 维护了一个指向最后一个节点的引用。

删除元素

  • ArrayList
    • 末尾删除:O(1) 时间复杂度,因为直接移除最后一个元素。
    • 中间删除:O(n) 时间复杂度,因为需要移动后续元素以填补空缺。
  • LinkedList
    • 任意位置删除:O(1) 时间复杂度,因为只需要修改相邻节点的引用。
    • 末尾删除:O(1) 时间复杂度,因为 LinkedList 维护了一个指向最后一个节点的引用。

访问元素

  • ArrayList
    • 随机访问:O(1) 时间复杂度,因为可以直接通过索引访问数组中的元素。
  • LinkedList
    • 随机访问:O(n) 时间复杂度,因为需要从头或尾遍历链表以找到指定索引的元素。

内存占用

  • ArrayList
    • 内存占用:相对较低,因为只需要为数组分配连续的内存空间。
    • 内存碎片:可能会产生内存碎片,特别是在频繁扩容和缩减的情况下。
  • LinkedList
    • 内存占用:相对较高,因为每个节点除了存储元素外,还需要存储前后节点的引用。
    • 内存碎片:较少产生内存碎片,因为链表节点是分散存储的。

使用场景

  • ArrayList
    • 适用场景:需要频繁随机访问元素的场景,例如需要频繁通过索引获取或修改元素。
    • 不适用场景:需要频繁在列表中间插入或删除元素的场景。
  • LinkedList
    • 适用场景:需要频繁在列表中间插入或删除元素的场景,例如实现队列或栈。
    • 不适用场景:需要频繁随机访问元素的场景。

总结

  • ArrayList:基于动态数组实现,适用于需要频繁随机访问元素的场景。插入和删除操作在中间位置性能较差。
  • LinkedList:基于双向链表实现,适用于需要频繁在列表中间插入或删除元素的场景。随机访问性能较差。

3. Java中常见的集合类

列表:ArrayList、LinkedList、Vector

集合:HashSet、LinkedHashSet、TreeSet

队列:PriorityQueue、Deque

映射:HashMap、LinkedHashMap、TreeMap、HashTable

4. 什么是序列化和反序列化

序列化:将内存的对象转换为可存储或传输的格式(如二进制流、JSON、XML)

反序列化:将序列化后的数据转换为内存中的对象

5. 接口与抽象类区别

接口可以多实现,规范一个类能做什么。需要规范行为、需要定义常量使用接口。

抽象类单继承,规范有一个类是什么。需要复用代码或部分实现、需要构造方法或实例变量使用抽象类

6. 深拷贝、浅拷贝、引用拷贝的区别

深拷贝:会完全复制整个对象,包括这个对象所包含的内部对象

浅拷贝:会在堆上创建一个新对象,如果原对象内部属性是引用类型,浅拷贝就会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用一个内部对象

引用拷贝:两个不同的引用指向了同一个对象

7. 线程池核心参数

corePoolSize、maximumPoolSize、workQueue

引申:常见参数

keepAliveTime、unit、threadFactory、handler

8. 线程池拒绝策略

  • ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException来拒绝新任务的处理
  • ThreadPoolExector.CallerRunsPolicy:调用执行者自己的线程运行任务,也就是直接在调用execute方法的线程中运行被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果应用程序可以承受此延迟并且你要求任何一个任务请求都熬被执行,可以选择这个策略。
  • ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。

9. MySQL常见的索引有哪些

B-Tree 索引、非空索引、唯一索引、主键索引、组合索引、全文索引

10.MySQL索引失效常见情况

①不满足最左前缀原则

②使用函数表达式

③隐式类型转换

④使用OR条件不当

⑤使用不等于

⑥使用LIKE,开始就模糊匹配

⑦索引列参与计算

⑧索引列数据分布不均匀

⑨使用IS NULL或IS NOT NULL

⑩全表扫描更快时

11.MySQL中怎么看SQL语句有没有使用索引

EXPLAIN是查看索引使用情况的主要工具,重点关注typekey

12.事务的隔离级别

读未提交、读已提交、可重复读、串行化

引申:并发事务出现的问题?

脏读、不可重复读、幻读

13.MySQL默认的事务隔离级别

可重复读,再加上MVCC机制保证MySQL的事务正常执行

14.MySQL锁有哪些

全局锁

表级锁:表锁(读锁、写锁)、意向锁(意向共享锁、意向排他锁)

行锁:记录锁、临键锁、间隙锁、插入意向锁

15.Spring的AOP常用在哪里?

日志记录、事务管理、权限验证、缓存管理、异常处理、性能监控、参数校验、分布式事务、资源管理、事件驱动

AOP 的核心价值在于将通用功能(横切关注点)与业务逻辑分离,避免代码重复,提高可维护性。

16.循环依赖是什么?如何解决循环依赖?

循环依赖:类A依赖类B,类B依赖类A

解决循环依赖:①引入中间层②延迟加载④采用事件驱动

17.Spring注入方式

构造器注入、Setter注入、属性注入、普通方法注入、接口注入

18.@Autowried与@Resource有什么区别

@Autowired来源于Spring框架,优先按类型查找,多Bean需要配合@Qualifier,支持构造器注入

@Resource来源于JSR-250,优先按名称查找,多Bean指定name即可,不支持构造器注入

19.什么RESTful类型的接口

基于HTTP协议的API设计风格

主要特征①资源导向②同一接口(GET/POST/PUT/DELETE)③无状态性④可缓存⑤分层系统

资源命名规范①统一使用名词②复数形式表示资源集合③层级关系表达

20.过滤器和拦截器的区别

过滤器属于Servlet规范。可以拦截所有请求,包括静态资源。无法捕获Controller层的异常。若要使用注解或xml文件配置即可

拦截器属于Spring框架组件。只拦截SpringMVC处理的请求(通过DispatchServlet请求)。能够捕获Controller层的异常。若要使用需实现WebMvcConfigurer接口。

21.创建线程的方式

①继承Thread类②实现Callable接口③实现Runnable接口④使用线程池的方式⑤使用CompletableFuture⑥基于ThreadGroup线程组⑦使用FutureTask类⑧使用匿名内部类或Lambda表达式⑨使用Timer定时器类⑩使用ForkJoin线程池或Stream并行流

虽然上述能说很多种,但是归根结底都是基于调用Thread.start()方法创建线程。

22.线程生命周期

  • NEW:初始状态,线程被创建出来但没有被调用start()
  • RUNNABLE:运行状态,线程被调用了start()等待运行的状态
  • BLOCKED:阻塞状态,需要等待锁释放
  • WAITING:等待状态,表示该线程需要等待其他线程做出一些特定的动作
  • TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像WAITING那样一致等待
  • TERMINATED:终止状态,表示该线程已经运行完毕

上次更新于: