Java核心知识快速复习:30分钟搞定高频面试题

Java核心知识快速复习:30分钟搞定高频面试题

分类:review
摘要:面试前快速复习Java核心知识点,掌握高频面试题对提升面试通过率至关重要


引言

在Java工程师的面试中,无论是校招还是社招,核心知识点的考察永远是重中之重。很多开发者有着丰富的项目经验,但在面对基础原理性问题时往往因为平时“只顾低头拉车,忘了抬头看路”而痛失良机。所谓的“高频面试题”,实际上是对Java语言本质特征的提炼。

本文将跳出死记硬背的模式,用30分钟的时间,带你深入理解Java集合、并发编程、JVM内存管理这三大核心领域的底层原理。我们将通过源码分析、实际代码演示和场景落地,帮你构建起坚固的知识体系。

一、 集合框架:HashMap的底层奥秘

HashMap是面试中“当之无愧”的王者。理解HashMap不仅仅是为了回答“线程不安全”,更是为了展示对数据结构与哈希算法的深刻理解。

1.1 核心数据结构演变

在JDK 1.8之前,HashMap的实现是数组 + 链表。而在JDK 1.8中,为了解决哈希冲突导致链表过长从而降低查询效率的问题,引入了红黑树

底层结构Node<K,V>[] table

当链表长度超过8且数组长度超过64时,链表会转化为红黑树。这将查询操作的时间复杂度从 $O(n)$ 降低到了 $O(\log n)$。

1.2 扰动函数与哈希冲突

面试常问:为什么HashMap的容量必须是2的n次幂?

这涉及到哈希计算的均匀分布。HashMap在计算索引时,使用 (n - 1) & hash。如果 n 是2的幂,那么 n-1 的二进制表示全是1(例如16-1=15,即1111)。这与hash进行与运算,等价于取模运算 hash % n,但位运算效率远高于取模。

此外,HashMap并非直接使用key的hashCode,而是进行了“扰动”处理:

// JDK 1.8 HashMap hash方法源码逻辑
static final int hash(Object key) {
    int h;
    // 高16位异或低16位,目的是让高位的特征参与到低位运算中,减少冲突
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

1.3 实战代码:重写HashCode与Equals

在实际开发中,使用自定义对象作为Key时,必须重写 hashCodeequals 方法,否则会导致内存泄漏或查询失败。

import java.util.HashMap;
import java.util.Objects;

/**
 * 演示自定义对象作为HashMap的Key时,正确重写hashCode和equals的重要性。
 */
class User {
    private String id;
    private String name;

    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // 必须重写equals:比较对象内容而非内存地址
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) && Objects.equals(name, user.name);
    }

    // 必须重写hashCode:保证equals相等的对象hashCode必须相等
    // 假如只重写equals不重写hashCode,两个逻辑相同的对象会计算出不同的hash值,
    // 导致存入Map时存了两个,取的时候却取不到。
    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

public class HashMapDemo {
    public static void main(String[] args) {
        HashMap<User, String> map = new HashMap<>();
        User u1 = new User("001", "Alice");

        // 存入对象
        map.put(u1, "Admin");

        // 此时如果u1的属性被修改,hashCode会发生变化,导致无法从map中移除或查询该对象
        // u1.name = "Bob"; // 这是一个危险操作,会导致内存泄漏

        // 正常查询
        User queryUser = new User("001", "Alice");
        System.out.println("查询结果: " + map.get(queryUser)); // 输出: Admin
    }
}

二、 并发编程:线程池与锁机制

并发编程是区分初中级工程师与高级工程师的分水岭。重点在于理解线程池的参数配置以及锁的底层实现。

2.1 线程池的拒绝策略与参数配置

阿里巴巴Java开发手册强制规定:禁止使用Executors创建线程池。因为 FixedThreadPoolCachedThreadPool 允许请求的队列长度为 Integer.MAX_VALUE,可能导致OOM。

我们需要手动通过 ThreadPoolExecutor 创建,重点理解以下参数:
1. corePoolSize:核心线程数(常驻)。
2. maximumPoolSize:最大线程数(包含临时线程)。
3. workQueue:工作队列(存储待执行任务)。

任务提交流程
任务来了 -> 核心线程数未满?创建线程 -> 满了?

posted @ 2026-03-03 13:01  寒人病酒  阅读(0)  评论(0)    收藏  举报