面试总结1

47 阅读8分钟

1. ==和 equals() 的区别?**

  • ==:比较两个对象的内存地址是否相同(对于基本类型比较值,引用类型比较地址)。
  • equals() :默认行为与 == 相同(比较地址)。但通常会被类(如 StringInteger重写,用于比较两个对象的逻辑内容是否相等。

2. String, StringBuilder,和 StringBuffer的区别?

  • String:不可变字符序列。任何修改都会产生新对象。线程安全。
  • StringBuffer:可变字符序列。修改发生在原对象上。方法用 synchronized修饰,线程安全,但效率较低。
  • StringBuilder:可变字符序列。StringBuffer 的非线程安全版本,效率更高。单线程环境下首选。

3. 重写(Override)和重载(Overload)的区别?

  • 重写:子类重新定义父类中已有方法的方法体。要求方法名、参数列表、返回类型都相同。遵循“两同两小一大”原则(运行时多态)。
  • 重载:在同一个类中,有多个方法名相同参数列表不同(类型、个数、顺序)的方法。与返回类型无关(编译时多态)。

4. Java 中基本数据类型有哪些?它们对应的包装类是什么?

  • byte -> Byte
  • short -> Short
  • int -> Integer
  • long -> Long
  • float -> Float
  • double -> Double
  • char -> Character
  • boolean -> Boolean

5. finalfinallyfinalize 的区别?

  • final修饰符。修饰类(不可继承)、方法(不可重写)、变量(常量,值不可改变)。
  • finally异常处理关键字。与 try-catch 一起使用,定义一段无论是否发生异常都会执行的代码,常用于释放资源。
  • finalize()Object 类的一个方法。垃圾回收器在回收对象之前会调用此方法,但不推荐依赖它来释放资源,因为调用时机不确定。

6. ArrayList 和 LinkedList 的区别?

  • ArrayList:基于动态数组查询快(通过索引随机访问,O(1)),增删慢(需要移动元素,O(n))。
  • LinkedList:基于双向链表增删快(只需要修改指针,O(1)),查询慢(需要遍历链表,O(n))。

7. HashMap 的工作原理是什么?

  1. 存储结构:数组 + 链表/红黑树(JDK1.8+)。
  2. put() 过程:计算 key 的 hashCode() -> 通过哈希函数得到数组下标 -> 如果该位置为空,直接放入 -> 如果不为空,比较 key(equals())-> 如果相同则覆盖,不同则以链表形式存放(链表长度超过8且数组长度超过64时,链表转红黑树)。
  3. get() 过程:类似 put(),找到对应位置后,遍历链表或树来查找 key。

8. HashSet 是如何保证元素不重复的?
HashSet 内部使用 HashMap 来存储元素。添加元素时,将元素作为 HashMap 的 key 存储,而 value 是一个固定的 Object 常量。由于 HashMap 的 key 是唯一的,所以 HashSet 的元素自然就是唯一的。

9. ConcurrentHashMap 是如何保证线程安全的?

  • JDK 1.7:使用分段锁(Segment),将数据分成一段一段存储,每段配一把锁,允许多个线程访问不同段的数据,从而提高并发度。
  • JDK 1.8及以后:摒弃了分段锁,采用 synchronized + CAS 操作来保证线程安全。锁的粒度更细,只锁住数组的单个桶(链表或树的头节点),并发性能更高。

10. 创建线程有哪几种方式?

  1. 继承 Thread 类,重写 run() 方法。
  2. 实现 Runnable 接口,实现 run() 方法(更推荐,因为避免了单继承的局限性)。
  3. 实现 Callable 接口,实现 call() 方法(可以有返回值,可以抛出异常)。
  4. 使用线程池(如 ExecutorService),这是最推荐的方式,利于资源复用和管理。

11. sleep() 和 wait() 的区别?

  • 所属类sleep() 是 Thread 的静态方法;wait() 是 Object 的实例方法。
  • 锁释放sleep() 不释放当前持有的锁;wait() 会释放锁,以便其他线程可以进入同步代码块。
  • 使用范围sleep() 可以在任何地方使用;wait() 必须在同步方法或同步块synchronized)中使用。
  • 唤醒机制sleep() 时间到自动唤醒;wait() 需要被 notify()/notifyAll() 唤醒。

12. synchronized 和 ReentrantLock 的区别?

  • 本质synchronized 是 JVM 层面的关键字;ReentrantLock 是 JDK 层面的类。
  • 使用synchronized 不需要手动释放锁;ReentrantLock 必须手动 lock() 和 unlock(),通常在 finally 块中解锁。
  • 功能ReentrantLock 功能更丰富,支持公平锁、可轮询的锁等待tryLock())、条件变量Condition)等。

13. 什么是死锁?如何避免死锁?

  • 死锁:两个或以上的线程互相持有对方所需的资源,导致它们都无法继续执行。

  • 必要条件:互斥、请求与保持、不剥夺、循环等待。

  • 避免方法

    • 破坏循环等待:按固定的顺序申请锁。
    • 破坏请求与保持:一次性申请所有需要的资源。
    • 使用 tryLock() :尝试获取锁,失败则释放已获得的锁。

14. Java 内存区域(运行时数据区)有哪些?

  • 线程私有:程序计数器、Java 虚拟机栈、本地方法栈。
  • 线程共享(存放对象实例)、方法区(存放类信息、常量、静态变量等。JDK8+中为元空间)。
  • 直接内存:不属于JVM运行时数据区,但也被频繁使用(如NIO)。

15. 简述 Java 的垃圾回收机制(GC)。
GC 主要针对堆内存。它自动回收不再被引用的对象(垃圾),释放内存。

  • 如何判断对象可回收?

    • 引用计数法(Java未采用):存在循环引用问题。
    • 可达性分析(Java采用):从一系列“GC Roots”对象开始向下搜索,走过的路径叫引用链。如果一个对象到GC Roots没有任何引用链相连,则证明此对象不可用。
  • 常见的垃圾收集器:Serial, Parallel, CMS, G1, ZGC。

16. 什么是类加载器?双亲委派模型是什么?

  • 类加载器:负责将 .class 文件加载到JVM中,并生成对应的 Class 对象。

  • 双亲委派模型

    1. 一个类加载器收到加载请求后,不会自己先加载,而是委派给父类加载器去完成。
    2. 只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
  • 好处:保证了Java核心库的类型安全(如无论哪个加载器加载 java.lang.Object,最终都是同一个核心类)。

17. String s = new String("abc"); 创建了几个对象?
1个或2个

  1. 如果字符串常量池中已存在 "abc",则只在上创建1个 new String 对象。
  2. 如果字符串常量池中不存在 "abc",则会先在常量池中创建1个 "abc" 对象,然后在上再创建1个 new String 对象,共2个。

18. 抽象类(Abstract Class)和接口(Interface)的区别?

  • 抽象类abstract 修饰。可以包含抽象方法具体实现有构造器。只能单继承。成员变量可以是各种类型。
  • 接口interface 修饰。JDK8前只能有抽象方法,JDK8+后可以有 default 和静态方法。没有构造器。可以多实现。成员变量默认是 public static final 的。

19. 什么是反射?有什么应用场景?

  • 反射:在运行时动态获取类的所有信息(属性、方法、构造器等)并可以动态操作对象的能力。
  • 核心APIClassFieldMethodConstructor
  • 应用场景:Spring框架的IoC容器、动态代理、IDE的代码提示、JDBC加载驱动等。

20. try-with-resources 是什么?
JDK7引入的语法糖,用于自动关闭资源。任何实现了 AutoCloseable 接口的资源(如 InputStreamConnection),都可以在 try 后的括号中声明,无需在 finally 中手动关闭。

java

// 传统方式,需要在finally中手动关闭
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // use the resource
} catch (IOException e) { // 资源会自动关闭
    e.printStackTrace();
}

数据库表是如何设计的?有没有遇到复杂的查询场景?你是怎么优化SQL或数据库结构的?

  • 复杂场景: “遇到过最复杂的场景是运营后台需要根据多条件(时间范围、商品名称、用户ID、订单状态)进行分页查询和汇总统计。”

  • 优化手段

    1. 索引优化:针对高频查询条件建立了联合索引(如 (status, create_time))。
    2. SQL优化:避免使用 SELECT *,只获取需要的字段;使用 EXPLAIN 分析执行计划,避免全表扫描和文件排序(Using filesort)。
    3. 归档和历史表:将超过3个月的订单数据迁移到历史表,保证主表的数据量级在一个可控范围内。

项目中是否使用了缓存(如Redis)?用在什么场景?如何保证缓存与数据库的数据一致性?

  • 应用场景: “广泛使用。主要用在:1. 用户会话(Session)存储;2. 热点商品信息和首页数据的缓存;3. 分布式锁(如防止重复下单);4. 秒杀库存的预扣减。”

  • 一致性策略(核心) : “采用最常用的 Cache-Aside (旁路缓存)模式。”

    • 1、什么是 Cache-Aside 模式?

      核心思想:应用程序直接与缓存和数据库对话。缓存不是作为数据库的透明代理,而是由应用程序代码显式地管理。

      • 旁路”**  的含义:缓存位于数据库的“旁边”,应用程序按需从缓存中获取数据,如果缓存没有,再去数据库找。

    1. 读流程:先读缓存,命中则返回;未命中则读数据库,然后将数据写入缓存。
    2. 写流程:先更新数据库,然后删除缓存(而非更新缓存)。
  • “为什么是删除而不是更新?主要是为了避免并发写操作导致的缓存数据覆盖和时序错乱问题。