Java 算法从入门到进阶(2026版)

算法的核心是「解决问题的步骤化逻辑」,Java 算法的学习不是 "背代码",而是「理解思想→手动实现→性能优化→场景落地」的循序渐进过程。本文将按「入门→进阶→精通」三层阶梯,覆盖基础算法思想、经典算法实现、工程化优化、实战落地,形成完整的知识闭环。

一、学习路径总览

表格

阶段 核心目标 关键能力 核心算法 / 思想
入门 理解算法本质,掌握基础思想和简单算法 能实现排序 / 查找,分析时间复杂度 枚举、递归、贪心(入门);冒泡 / 选择 / 插入排序;顺序 / 二分查找
进阶 掌握四大经典算法思想,实现高级算法 能解决中等难度算法题,融合数据结构 分治、动态规划、回溯、贪心(进阶);快排 / 归并 / 堆排;DFS/BFS;KMP
精通 算法优化 + 工程化落地,适配高并发 / 高性能场景 能设计算法、优化性能、落地业务 算法剪枝 / 并行化;高并发算法;框架 / 中间件算法应用;业务场景算法选型

二、入门阶段:算法基础

1. 核心目标

  • 理解「算法 = 输入→处理步骤→输出」的本质;
  • 掌握时间复杂度 / 空间复杂度的分析方法(大 O 表示法);
  • 实现最基础的排序、查找算法,理解核心逻辑。

2. 前置知识

  • Java 基础:循环、分支、数组、方法、递归;
  • 数据结构基础:数组、链表(线性结构);
  • 核心概念:时间复杂度(O(1)、O(n)、O(n2)、O(logn))、空间复杂度。

3. 基础算法思想(入门)

(1)枚举(暴力破解):最直观的算法思想

原理 :遍历所有可能的解,验证是否满足条件,适合小规模数据场景。代码案例:枚举法找两数之和

java

运行

复制代码
/**
 * 枚举法:在数组中找到和为target的两个数,返回其索引
 * 时间复杂度:O(n²),空间复杂度:O(1)
 */
public class EnumerationDemo {
    public static int[] twoSum(int[] nums, int target) {
        // 枚举所有两两组合
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        throw new IllegalArgumentException("无满足条件的数");
    }

    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        int target = 9;
        int[] result = twoSum(nums, target);
        System.out.println("索引:" + result[0] + "," + result[1]); // 输出:0,1
    }
}

关键解析

  • 枚举法是 "笨办法",但逻辑简单、易实现,适合数据量小的场景;
  • 时间复杂度O(n2)(两层循环),空间复杂度O(1)(仅用常量空间)。
(2)递归:自顶向下的分解思想

原理 :将大问题分解为相同逻辑的小问题,直到触达终止条件,再回溯合并结果。核心三要素

  1. 递归终止条件(避免死循环);
  2. 递推公式(大问题→小问题);
  3. 回溯处理(小问题结果→大问题结果)。

代码案例:递归求阶乘

java

运行

复制代码
/**
 * 递归求n的阶乘(n! = n*(n-1)*...*1)
 * 时间复杂度:O(n)(递归n次),空间复杂度:O(n)(递归栈深度)
 */
public class RecursionDemo {
    public static int factorial(int n) {
        // 1. 终止条件
        if (n == 1) {
            return 1;
        }
        // 2. 递推公式:n! = n * (n-1)!
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        System.out.println("5! = " + factorial(5)); // 输出:120
    }
}

关键解析

  • 递归的核心是 "分解 + 回溯",但递归栈过深会导致StackOverflowError(如 n=10000);
  • 优化方向:尾递归(JVM 不优化尾递归,需手动改为循环)、记忆化缓存(避免重复计算)。

4. 基础查找算法

(1)顺序查找:遍历所有元素

原理 :从数组头部开始,逐个比较元素,找到目标值则返回索引,适合无序数组。代码案例

java

运行

复制代码
/**
 * 顺序查找:无序数组中查找目标值
 * 时间复杂度:O(n),空间复杂度:O(1)
 */
public class SequentialSearch {
    public static int search(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == target) {
                return i;
            }
        }
        return -1; // 未找到
    }

    public static void main(String[] args) {
        int[] nums = {3, 1, 4, 1, 5, 9};
        System.out.println("查找4的索引:" + search(nums, 4)); // 输出:2
        System.out.println("查找6的索引:" + search(nums, 6)); // 输出:-1
    }
}
(2)二分查找:有序数组的高效查找

原理 :将有序数组折半,每次排除一半元素,核心是 "缩小查找范围"。代码案例:非递归版二分查找

java

运行

复制代码
/**
 * 二分查找:有序数组中查找目标值(非递归版,推荐)
 * 时间复杂度:O(logn),空间复杂度:O(1)
 */
public class BinarySearch {
    public static int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        // 循环条件:left <= right(包含最后一个元素)
        while (left <= right) {
            // 计算中间索引(避免left+right溢出)
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid; // 找到目标,返回索引
            } else if (nums[mid] < target) {
                left = mid + 1; // 目标在右半区
            } else {
                right = mid - 1; // 目标在左半区
            }
        }
        return -1; // 未找到
    }

    public static void main(String[] args) {
        int[] nums = {1, 3, 5, 7, 9, 11};
        System.out.println("查找7的索引:" + search(nums, 7)); // 输出:3
        System.out.println("查找8的索引:" + search(nums, 8)); // 输出:-1
    }
}

关键解析

  • 二分查找的前提是「数组有序」,时间复杂度O(logn)(每次折半,效率远高于顺序查找);
  • 易错点:mid的计算(避免溢出)、循环条件(left <= right)、边界调整(mid±1)。

5. 基础排序算法

表格

排序算法 核心思想 时间复杂度 空间复杂度 稳定性
冒泡排序 相邻元素比较,大的后移(像气泡上浮) O(n2) O(1) 稳定
选择排序 找最小值,放到已排序区末尾 O(n2) O(1) 不稳定
插入排序 逐个将元素插入已排序区的正确位置 O(n2)(最坏)/O(n)(最好) O(1) 稳定
(1)冒泡排序(入门必学)

java

运行

复制代码
/**
 * 冒泡排序:相邻元素比较,大的后移
 * 优化:添加标志位,若某轮无交换则提前结束
 */
public class BubbleSort {
    public static void sort(int[] nums) {
        int n = nums.length;
        boolean swapped; // 标记是否发生交换
        for (int i = 0; i < n - 1; i++) {
            swapped = false;
            // 每轮冒泡后,最后i个元素已排序,无需比较
            for (int j = 0; j < n - 1 - i; j++) {
                if (nums[j] > nums[j + 1]) {
                    // 交换相邻元素
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                    swapped = true;
                }
            }
            if (!swapped) {
                break; // 无交换,数组已排序,提前退出
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = {6, 3, 8, 2, 9, 1};
        sort(nums);
        for (int num : nums) {
            System.out.print(num + " "); // 输出:1 2 3 6 8 9
        }
    }
}
(2)插入排序(实际场景更常用)

java

运行

复制代码
/**
 * 插入排序:将元素逐个插入已排序区的正确位置
 * 适合小规模/近乎有序的数组,效率高于冒泡/选择排序
 */
public class InsertionSort {
    public static void sort(int[] nums) {
        int n = nums.length;
        for (int i = 1; i < n; i++) {
            int temp = nums[i]; // 待插入元素
            int j = i - 1;      // 已排序区最后一个元素索引
            // 找到插入位置(已排序区元素>temp则后移)
            while (j >= 0 && nums[j] > temp) {
                nums[j + 1] = nums[j];
                j--;
            }
            nums[j + 1] = temp; // 插入元素
        }
    }

    public static void main(String[] args) {
        int[] nums = {6, 3, 8, 2, 9, 1};
        sort(nums);
        for (int num : nums) {
            System.out.print(num + " "); // 输出:1 2 3 6 8 9
        }
    }
}

三、进阶阶段:经典算法思想与高级算法

1. 核心目标

  • 掌握分治、动态规划、回溯、贪心四大经典算法思想;
  • 实现工程中常用的高级排序(快排 / 归并 / 堆排);
  • 融合数据结构(树 / 图 / 堆)实现复杂算法(DFS/BFS/KMP)。

2. 前置知识

  • 入门阶段的算法基础;
  • 数据结构:树、图、堆、哈希表(非线性结构);
  • 核心能力:问题拆解、状态定义、边界处理。

3. 四大经典算法思想(进阶核心)

(1)分治算法:分而治之

原理 :将大问题分解为多个独立的子问题,解决子问题后合并结果,核心是「递归 + 合并」。典型应用:快速排序、归并排序、二分查找。

代码案例:归并排序(分治的经典实现)

java

运行

复制代码
/**
 * 归并排序:分治思想,先拆分再合并
 * 时间复杂度:O(nlogn)(稳定),空间复杂度:O(n)(需要临时数组)
 */
public class MergeSort {
    // 主方法:排序数组
    public static void sort(int[] nums) {
        if (nums == null || nums.length <= 1) {
            return;
        }
        mergeSort(nums, 0, nums.length - 1);
    }

    // 递归拆分:将数组拆分为左右两部分
    private static void mergeSort(int[] nums, int left, int right) {
        if (left >= right) {
            return; // 终止条件:子数组长度为1
        }
        int mid = left + (right - left) / 2;
        mergeSort(nums, left, mid); // 左半区排序
        mergeSort(nums, mid + 1, right); // 右半区排序
        merge(nums, left, mid, right); // 合并左右有序数组
    }

    // 合并:将两个有序子数组合并为一个有序数组
    private static void merge(int[] nums, int left, int mid, int right) {
        int[] temp = new int[right - left + 1]; // 临时数组
        int i = left;    // 左半区指针
        int j = mid + 1; // 右半区指针
        int k = 0;       // 临时数组指针

        // 合并两个有序数组
        while (i <= mid && j <= right) {
            if (nums[i] <= nums[j]) {
                temp[k++] = nums[i++];
            } else {
                temp[k++] = nums[j++];
            }
        }

        // 处理左半区剩余元素
        while (i <= mid) {
            temp[k++] = nums[i++];
        }

        // 处理右半区剩余元素
        while (j <= right) {
            temp[k++] = nums[j++];
        }

        // 将临时数组复制回原数组
        System.arraycopy(temp, 0, nums, left, temp.length);
    }

    public static void main(String[] args) {
        int[] nums = {6, 3, 8, 2, 9, 1};
        sort(nums);
        for (int num : nums) {
            System.out.print(num + " "); // 输出:1 2 3 6 8 9
        }
    }
}

关键解析

  • 归并排序是「稳定排序」(相等元素相对位置不变),适合链表排序、大数据量外排序;
  • 时间复杂度O(nlogn)(拆分logn层,每层合并O(n)),空间复杂度O(n)(临时数组)。
(2)动态规划(DP):最优子结构 + 重叠子问题

核心思想 :将大问题分解为子问题,存储子问题的解(避免重复计算),逐步推导最优解。核心步骤

  1. 定义状态(子问题的解);
  2. 确定状态转移方程(子问题间的关系);
  3. 初始化边界条件;
  4. 推导最终解。

代码案例:斐波那契数列(DP 优化递归)

java

运行

复制代码
/**
 * 动态规划:求斐波那契数列第n项(F(n)=F(n-1)+F(n-2),F(0)=0,F(1)=1)
 * 递归版:时间O(2ⁿ)(大量重复计算),DP版:时间O(n),空间O(1)
 */
public class DynamicProgrammingDemo {
    // 优化版:滚动数组,空间O(1)
    public static int fibonacci(int n) {
        if (n <= 1) {
            return n;
        }
        int a = 0; // F(0)
        int b = 1; // F(1)
        int res = 0;
        for (int i = 2; i <= n; i++) {
            res = a + b; // F(i)=F(i-1)+F(i-2)
            a = b;       // 滚动更新
            b = res;
        }
        return res;
    }

    // 经典DP版:数组存储子问题解,空间O(n)
    public static int fibonacciDP(int n) {
        if (n <= 1) {
            return n;
        }
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2]; // 状态转移方程
        }
        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println("F(10) = " + fibonacci(10)); // 输出:55
    }
}

代码案例:0-1 背包问题(DP 经典应用)

java

运行

复制代码
/**
 * 0-1背包问题:给定物品重量w、价值v,背包容量C,求最大价值
 * 状态定义:dp[i][j] = 前i个物品,容量j的背包的最大价值
 * 状态转移:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])(选/不选第i个物品)
 */
public class KnapsackProblem {
    public static int maxValue(int[] weights, int[] values, int capacity) {
        int n = weights.length;
        // 优化:一维数组(逆序遍历避免重复选择)
        int[] dp = new int[capacity + 1];
        for (int i = 0; i < n; i++) {
            // 逆序遍历:保证每个物品只选一次
            for (int j = capacity; j >= weights[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
            }
        }
        return dp[capacity];
    }

    public static void main(String[] args) {
        int[] weights = {2, 3, 4, 5}; // 物品重量
        int[] values = {3, 4, 5, 6};  // 物品价值
        int capacity = 8;             // 背包容量
        System.out.println("最大价值:" + maxValue(weights, values, capacity)); // 输出:10
    }
}
(3)回溯算法:试错 + 回溯

原理 :通过递归尝试所有可能的解,若当前解不满足条件则 "回溯"(撤销选择),适合组合、排列、子集等问题。核心模板

java

运行

复制代码
void backtrack(路径, 选择列表) {
    if (终止条件) {
        保存结果;
        return;
    }
    for (选择 : 选择列表) {
        做出选择;
        backtrack(路径, 选择列表);
        撤销选择; // 回溯
    }
}

代码案例:全排列问题

java

运行

复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 回溯算法:求数组的全排列
 * 时间复杂度:O(n!)(n个元素的全排列数),空间复杂度:O(n)(递归栈+路径)
 */
public class Permutation {
    private List<List<Integer>> result = new ArrayList<>();

    public List<List<Integer>> permute(int[] nums) {
        List<Integer> path = new ArrayList<>();
        boolean[] used = new boolean[nums.length]; // 标记元素是否已使用
        backtrack(nums, path, used);
        return result;
    }

    private void backtrack(int[] nums, List<Integer> path, boolean[] used) {
        // 终止条件:路径长度等于数组长度
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path)); // 保存结果(需新建列表)
            return;
        }
        // 遍历选择列表
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue; // 跳过已使用的元素
            }
            // 做出选择
            path.add(nums[i]);
            used[i] = true;
            // 递归
            backtrack(nums, path, used);
            // 撤销选择(回溯)
            path.remove(path.size() - 1);
            used[i] = false;
        }
    }

    public static void main(String[] args) {
        Permutation permutation = new Permutation();
        int[] nums = {1, 2, 3};
        List<List<Integer>> result = permutation.permute(nums);
        System.out.println("全排列:" + result); // 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
    }
}
(4)贪心算法:局部最优→全局最优

原理 :每一步选择当前局部最优解,最终希望得到全局最优解(并非所有问题都适用,需满足 "贪心选择性质")。典型应用:活动选择、哈夫曼编码、最短路径(Dijkstra)。

代码案例:活动选择问题(最多不重叠活动)

java

运行

复制代码
import java.util.Arrays;
import java.util.Comparator;

/**
 * 贪心算法:活动选择问题,选择最多的不重叠活动
 * 策略:按结束时间排序,每次选结束最早的活动
 */
public class ActivitySelection {
    // 活动类:开始时间、结束时间
    static class Activity {
        int start;
        int end;

        Activity(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }

    public static int maxActivities(Activity[] activities) {
        // 按结束时间升序排序(贪心策略核心)
        Arrays.sort(activities, Comparator.comparingInt(a -> a.end));
        int count = 1; // 至少选第一个活动
        int lastEnd = activities[0].end; // 上一个活动的结束时间
        // 遍历剩余活动
        for (int i = 1; i < activities.length; i++) {
            if (activities[i].start >= lastEnd) { // 不重叠
                count++;
                lastEnd = activities[i].end;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        Activity[] activities = {
            new Activity(1, 2),
            new Activity(3, 4),
            new Activity(0, 6),
            new Activity(5, 7),
            new Activity(8, 9),
            new Activity(5, 9)
        };
        System.out.println("最多选" + maxActivities(activities) + "个活动"); // 输出:4
    }
}

4. 高级排序算法(工程核心)

表格

排序算法 核心思想 时间复杂度 空间复杂度 稳定性 适用场景
快速排序 分治,选基准值分区 O(nlogn)(平均)/O(n2)(最坏) O(logn)(递归栈) 不稳定 通用排序(工程首选,JDK Arrays.sort 底层)
归并排序 分治,拆分后合并 O(nlogn)(稳定) O(n) 稳定 链表排序、外排序、需要稳定排序的场景
堆排序 基于堆结构,选堆顶元素 O(nlogn) O(1) 不稳定 内存受限、需要原地排序的场景

代码案例:快速排序(工程首选)

java

运行

复制代码
/**
 * 快速排序:分治+基准值分区
 * 优化:随机选基准值,避免最坏情况(有序数组)
 */
public class QuickSort {
    public static void sort(int[] nums) {
        if (nums == null || nums.length <= 1) {
            return;
        }
        quickSort(nums, 0, nums.length - 1);
    }

    private static void quickSort(int[] nums, int left, int right) {
        if (left >= right) {
            return;
        }
        int pivotIndex = partition(nums, left, right); // 分区,返回基准值索引
        quickSort(nums, left, pivotIndex - 1); // 左半区排序
        quickSort(nums, pivotIndex + 1, right); // 右半区排序
    }

    // 分区:将数组分为<=基准值、基准值、>=基准值三部分
    private static int partition(int[] nums, int left, int right) {
        // 随机选基准值(避免有序数组导致的最坏情况)
        int randomIndex = left + (int) (Math.random() * (right - left + 1));
        swap(nums, left, randomIndex); // 基准值移到左边界

        int pivot = nums[left]; // 基准值
        int i = left;
        int j = right;
        while (i < j) {
            // 从右找第一个<=基准值的元素
            while (i < j && nums[j] > pivot) {
                j--;
            }
            // 从左找第一个>基准值的元素
            while (i < j && nums[i] <= pivot) {
                i++;
            }
            swap(nums, i, j); // 交换
        }
        swap(nums, left, i); // 基准值归位
        return i;
    }

    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    public static void main(String[] args) {
        int[] nums = {6, 3, 8, 2, 9, 1, 5, 7};
        sort(nums);
        for (int num : nums) {
            System.out.print(num + " "); // 输出:1 2 3 5 6 7 8 9
        }
    }
}

5. 图 / 树的核心算法

(1)DFS/BFS(图 / 树的遍历)

java

运行

复制代码
import java.util.*;

/**
 * 图的DFS(深度优先)和BFS(广度优先)遍历
 * 邻接表存储图,适用于稀疏图
 */
public class GraphTraversal {
    private Map<Integer, List<Integer>> adj; // 邻接表

    public GraphTraversal() {
        adj = new HashMap<>();
    }

    // 添加边(无向图)
    public void addEdge(int v1, int v2) {
        adj.computeIfAbsent(v1, k -> new ArrayList<>()).add(v2);
        adj.computeIfAbsent(v2, k -> new ArrayList<>()).add(v1);
    }

    // DFS:递归实现
    public void dfs(int start) {
        Set<Integer> visited = new HashSet<>();
        dfsRec(start, visited);
        System.out.println();
    }

    private void dfsRec(int v, Set<Integer> visited) {
        visited.add(v);
        System.out.print(v + " ");
        // 遍历邻接顶点
        for (int neighbor : adj.getOrDefault(v, new ArrayList<>())) {
            if (!visited.contains(neighbor)) {
                dfsRec(neighbor, visited);
            }
        }
    }

    // BFS:队列实现
    public void bfs(int start) {
        Set<Integer> visited = new HashSet<>();
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(start);
        visited.add(start);

        while (!queue.isEmpty()) {
            int v = queue.poll();
            System.out.print(v + " ");
            // 遍历邻接顶点
            for (int neighbor : adj.getOrDefault(v, new ArrayList<>())) {
                if (!visited.contains(neighbor)) {
                    visited.add(neighbor);
                    queue.offer(neighbor);
                }
            }
        }
        System.out.println();
    }

    public static void main(String[] args) {
        GraphTraversal graph = new GraphTraversal();
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(1, 3);
        graph.addEdge(2, 3);
        graph.addEdge(3, 4);

        System.out.println("DFS遍历:");
        graph.dfs(0); // 输出:0 1 3 2 4
        System.out.println("BFS遍历:");
        graph.bfs(0); // 输出:0 1 2 3 4
    }
}
(2)Dijkstra 算法(单源最短路径)

java

运行

复制代码
import java.util.*;

/**
 * Dijkstra算法:带权有向图的单源最短路径(权重非负)
 * 基于小顶堆优化,时间复杂度O(ElogV)
 */
public class Dijkstra {
    // 存储边:<目标顶点,权重>
    private Map<Integer, Map<Integer, Integer>> adj;

    public Dijkstra() {
        adj = new HashMap<>();
    }

    // 添加带权边(有向图)
    public void addEdge(int from, int to, int weight) {
        adj.computeIfAbsent(from, k -> new HashMap<>()).put(to, weight);
    }

    // 求start到所有顶点的最短路径
    public Map<Integer, Integer> shortestPath(int start) {
        // 存储最短路径长度:<顶点,距离>
        Map<Integer, Integer> dist = new HashMap<>();
        // 小顶堆:<距离,顶点>,按距离升序
        PriorityQueue<int[]> heap = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));

        // 初始化:start到自身距离为0,其他为无穷大
        for (int v : adj.keySet()) {
            dist.put(v, Integer.MAX_VALUE);
        }
        dist.put(start, 0);
        heap.offer(new int[]{0, start});

        while (!heap.isEmpty()) {
            int[] curr = heap.poll();
            int currDist = curr[0];
            int currV = curr[1];

            // 已找到更短路径,跳过
            if (currDist > dist.get(currV)) {
                continue;
            }

            // 遍历邻接顶点
            for (Map.Entry<Integer, Integer> entry : adj.getOrDefault(currV, new HashMap<>()).entrySet()) {
                int nextV = entry.getKey();
                int weight = entry.getValue();
                int newDist = currDist + weight;
                // 更新最短路径
                if (newDist < dist.get(nextV)) {
                    dist.put(nextV, newDist);
                    heap.offer(new int[]{newDist, nextV});
                }
            }
        }
        return dist;
    }

    public static void main(String[] args) {
        Dijkstra graph = new Dijkstra();
        graph.addEdge(0, 1, 2);
        graph.addEdge(0, 2, 5);
        graph.addEdge(1, 2, 1);
        graph.addEdge(1, 3, 4);
        graph.addEdge(2, 3, 1);

        Map<Integer, Integer> dist = graph.shortestPath(0);
        System.out.println("0到各顶点的最短路径:");
        for (Map.Entry<Integer, Integer> entry : dist.entrySet()) {
            System.out.println("0 → " + entry.getKey() + ":" + entry.getValue());
        }
        // 输出:
        // 0 → 0:0
        // 0 → 1:2
        // 0 → 2:3
        // 0 → 3:4
    }
}

四、精通阶段:算法优化与工程化落地

1. 核心目标

  • 掌握算法性能优化技巧(剪枝、缓存、并行化);
  • 适配高并发 / 高性能场景的算法设计;
  • 理解框架 / 中间件中的算法应用,落地业务场景。

2. 算法性能优化技巧

(1)剪枝:提前排除无效解
  • 原理:在递归 / 枚举过程中,提前判断当前路径不可能得到最优解,直接回溯 / 终止;
  • 应用:回溯算法(如 N 皇后、数独)、动态规划(如背包问题)。
(2)缓存 / 记忆化:避免重复计算
  • 原理:存储已计算的子问题解,下次直接使用;
  • 应用:递归→记忆化递归(如斐波那契、爬楼梯)、动态规划。
(3)并行化:利用多线程加速
  • 原理:将算法拆分为独立子任务,多线程并行执行;
  • 应用:归并排序(拆分后多线程排序子数组)、大数据量查找(分块并行查找)。

代码案例:并行归并排序

java

运行

复制代码
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

/**
 * 并行归并排序:基于Fork/Join框架,利用多线程加速
 * 适合大数据量排序,充分利用多核CPU
 */
public class ParallelMergeSort extends RecursiveAction {
    private static final int THRESHOLD = 1000; // 阈值:小于阈值则串行排序
    private int[] nums;
    private int left;
    private int right;

    public ParallelMergeSort(int[] nums, int left, int right) {
        this.nums = nums;
        this.left = left;
        this.right = right;
    }

    @Override
    protected void compute() {
        if (right - left <= THRESHOLD) {
            // 小数据量:串行插入排序(比归并更高效)
            InsertionSort.sort(nums, left, right);
            return;
        }
        // 大数据量:拆分任务
        int mid = left + (right - left) / 2;
        ParallelMergeSort leftTask = new ParallelMergeSort(nums, left, mid);
        ParallelMergeSort rightTask = new ParallelMergeSort(nums, mid + 1, right);
        // 并行执行子任务
        invokeAll(leftTask, rightTask);
        // 合并结果
        merge(nums, left, mid, right);
    }

    // 合并两个有序子数组
    private void merge(int[] nums, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = left, j = mid + 1, k = 0;
        while (i <= mid && j <= right) {
            temp[k++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];
        }
        while (i <= mid) temp[k++] = nums[i++];
        while (j <= right) temp[k++] = nums[j++];
        System.arraycopy(temp, 0, nums, left, temp.length);
    }

    // 对外暴露的排序方法
    public static void sort(int[] nums) {
        ForkJoinPool pool = new ForkJoinPool(); // 默认使用CPU核心数
        pool.invoke(new ParallelMergeSort(nums, 0, nums.length - 1));
        pool.shutdown();
    }

    public static void main(String[] args) {
        int[] nums = new int[1000000]; // 100万数据
        for (int i = 0; i < nums.length; i++) {
            nums[i] = (int) (Math.random() * 1000000);
        }
        long start = System.currentTimeMillis();
        sort(nums);
        long end = System.currentTimeMillis();
        System.out.println("并行排序耗时:" + (end - start) + "ms");
    }
}

// 辅助:插入排序(指定区间)
class InsertionSort {
    public static void sort(int[] nums, int left, int right) {
        for (int i = left + 1; i <= right; i++) {
            int temp = nums[i];
            int j = i - 1;
            while (j >= left && nums[j] > temp) {
                nums[j + 1] = nums[j];
                j--;
            }
            nums[j + 1] = temp;
        }
    }
}

3. 高并发场景的算法适配

(1)并发排序:避免线程安全问题
  • 禁止在多线程下直接修改共享数组(如快速排序的分区操作);
  • 推荐:分块排序→合并(如 ParallelMergeSort)、使用线程安全的集合(CopyOnWriteArrayList)。
(2)限流算法:高并发系统的核心算法

代码案例:令牌桶算法(限流)

java

运行

复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 令牌桶算法:高并发限流核心算法
 * 原理:定时生成令牌放入桶中,请求需获取令牌才能执行
 */
public class TokenBucketLimiter {
    private final BlockingQueue<Object> bucket; // 令牌桶
    private final ScheduledExecutorService scheduler; // 定时生成令牌

    // 构造方法:桶容量、令牌生成速率(个/秒)
    public TokenBucketLimiter(int capacity, int rate) {
        this.bucket = new ArrayBlockingQueue<>(capacity);
        this.scheduler = Executors.newSingleThreadScheduledExecutor();
        // 定时生成令牌
        scheduler.scheduleAtFixedRate(() -> {
            try {
                bucket.put(new Object()); // 放入令牌
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, 0, 1000 / rate, TimeUnit.MILLISECONDS);
    }

    // 获取令牌(阻塞)
    public boolean acquire() {
        try {
            bucket.take(); // 无令牌则阻塞
            return true;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    // 关闭调度器
    public void shutdown() {
        scheduler.shutdown();
    }

    public static void main(String[] args) {
        // 桶容量10,每秒生成5个令牌(限流QPS=5)
        TokenBucketLimiter limiter = new TokenBucketLimiter(10, 5);
        // 模拟10个请求
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                if (limiter.acquire()) {
                    System.out.println(Thread.currentThread().getName() + " 获取令牌,执行请求");
                }
            }).start();
        }
        // 延迟关闭
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        limiter.shutdown();
    }
}

4. 框架 / 中间件中的算法应用

表格

框架 / 中间件 核心算法 应用场景
JDK Arrays.sort 快速排序(基本类型)/ 归并排序(对象类型) 数组排序,兼顾性能与稳定性
Redis 跳表(ZSet)、哈希表(String/Hash)、布隆过滤器 有序集合、键值存储、缓存去重
Tomcat 线程池(阻塞队列 + 拒绝策略)、LRU 缓存 请求处理、连接池管理
MyBatis 动态 SQL 解析(正则表达式)、缓存(LRU) SQL 生成、减少数据库查询

五、精通阶段补充:高频高级算法(字符串 / 高级数据结构 / 分布式)

这部分是大厂面试核心考点工业级开发必备 ,也是从 "单机算法" 到 "分布式算法" 的跨越,涵盖字符串处理、连通性问题、单调优化、分布式架构四大类高频场景,是算法能力从 "中级" 到 "高级" 的关键。

5.1 经典字符串算法(面试 / 工程 TOP1 高频)

字符串处理是 Java 开发中最常见的场景(如解析、匹配、脱敏),核心算法围绕匹配、查找、回文、前缀匹配 展开,以下是工业级开发中最常用的 4 类算法,配套可运行代码 + 工程化优化

5.1.1 KMP 算法:高效字符串匹配(解决「暴力匹配」的 O (n*m) 痛点)

核心问题 :在主串S中查找模式串P的首次出现位置,暴力匹配的时间复杂度为O(n∗m),KMP 通过预处理模式串生成 next 数组 ,将时间复杂度优化至O(n+m)。核心原理 :利用模式串的最长相等前后缀 ,当匹配失败时,模式串无需回退到起点,仅回退到最长相等后缀的下一个位置,减少重复比较。代码案例:KMP 算法实现(工业级可直接复用)

java

运行

复制代码
/**
 * KMP算法:高效字符串匹配
 * 核心:预处理模式串生成next数组(最长相等前后缀长度)
 * 适用场景:大文本匹配、正则解析、关键词检索(工程高频)
 */
public class KMPAlgorithm {
    // 主方法:在主串s中查找模式串p,返回首次出现的起始索引,未找到返回-1
    public static int kmpSearch(String s, String p) {
        if (s == null || p == null || p.length() > s.length()) {
            return -1;
        }
        int[] next = getNextArray(p); // 预处理模式串生成next数组
        int i = 0; // 主串指针
        int j = 0; // 模式串指针
        while (i < s.length() && j < p.length()) {
            if (j == -1 || s.charAt(i) == p.charAt(j)) {
                i++;
                j++;
            } else {
                j = next[j]; // 模式串回退,主串不回退(KMP核心)
            }
        }
        return j == p.length() ? i - j : -1;
    }

    // 生成next数组:next[j]表示p[0..j-1]的最长相等前后缀长度,next[0]固定为-1
    private static int[] getNextArray(String p) {
        int[] next = new int[p.length()];
        next[0] = -1;
        int i = 0; // 前缀指针
        int j = -1; // 后缀指针
        while (i < p.length() - 1) {
            if (j == -1 || p.charAt(i) == p.charAt(j)) {
                i++;
                j++;
                // 优化next数组:避免重复比较相同字符(工业级优化)
                if (p.charAt(i) != p.charAt(j)) {
                    next[i] = j;
                } else {
                    next[i] = next[j];
                }
            } else {
                j = next[j];
            }
        }
        return next;
    }

    // 拓展:查找模式串在主串中所有出现的位置
    public static List<Integer> kmpSearchAll(String s, String p) {
        List<Integer> result = new ArrayList<>();
        if (s == null || p == null || p.length() > s.length()) {
            return result;
        }
        int[] next = getNextArray(p);
        int i = 0, j = 0;
        while (i < s.length()) {
            if (j == -1 || s.charAt(i) == p.charAt(j)) {
                i++;
                j++;
            } else {
                j = next[j];
            }
            if (j == p.length()) {
                result.add(i - j);
                j = next[j - 1]; // 继续查找下一个匹配(工业级细节)
            }
        }
        return result;
    }

    public static void main(String[] args) {
        String s = "ABABCABABABCABC";
        String p = "ABC";
        int firstIndex = kmpSearch(s, p);
        List<Integer> allIndex = kmpSearchAll(s, p);
        System.out.println("首次出现位置:" + firstIndex); // 输出:2
        System.out.println("所有出现位置:" + allIndex); // 输出:[2, 8, 11]
    }
}
5.1.2 前缀树(Trie 树):字符串前缀匹配 / 字典检索

核心问题 :解决字符串前缀匹配、字典查询、词频统计 等问题,时间复杂度为O(k)(k 为字符串长度),比哈希表更适合前缀相关场景。核心原理 :基于多叉树 实现,每个节点存储一个字符,从根到叶子节点的路径组成一个完整字符串,节点附带「是否为单词结尾」「词频」等属性。代码案例:Trie 树实现(支持增 / 查 / 前缀匹配 / 词频统计)

java

运行

复制代码
/**
 * 前缀树(Trie树):工业级实现,支持增、查、前缀匹配、词频统计
 * 适用场景:搜索引擎联想、字典检索、敏感词过滤、手机号/邮箱前缀匹配
 */
public class TrieTree {
    // 节点内部类:工业级设计,包含词频、子节点、是否为单词结尾
    private static class TrieNode {
        int count; // 该节点作为结尾的词频
        TrieNode[] children; // 子节点:26个小写字母(可扩展为HashMap支持所有字符)
        boolean isEnd; // 是否为单词的结尾节点

        public TrieNode() {
            this.count = 0;
            this.children = new TrieNode[26]; // 仅支持小写字母,工程中可替换为HashMap<Character, TrieNode>
            this.isEnd = false;
        }
    }

    private final TrieNode root; // 根节点:空节点,不存储任何字符

    public TrieTree() {
        this.root = new TrieNode();
    }

    // 1. 插入字符串:支持重复插入(词频累加)
    public void insert(String word) {
        if (word == null || word.length() == 0) {
            return;
        }
        TrieNode curr = root;
        for (char c : word.toCharArray()) {
            int index = c - 'a';
            if (curr.children[index] == null) {
                curr.children[index] = new TrieNode(); // 不存在则创建节点
            }
            curr = curr.children[index];
        }
        curr.isEnd = true;
        curr.count++; // 词频+1
    }

    // 2. 查询字符串是否存在,返回词频(0表示不存在)
    public int search(String word) {
        if (word == null || word.length() == 0) {
            return 0;
        }
        TrieNode curr = root;
        for (char c : word.toCharArray()) {
            int index = c - 'a';
            if (curr.children[index] == null) {
                return 0; // 路径中断,不存在
            }
            curr = curr.children[index];
        }
        return curr.isEnd ? curr.count : 0; // 必须是结尾节点才表示存在
    }

    // 3. 前缀匹配:判断是否存在以prefix为前缀的字符串
    public boolean startsWith(String prefix) {
        if (prefix == null || prefix.length() == 0) {
            return true;
        }
        TrieNode curr = root;
        for (char c : prefix.toCharArray()) {
            int index = c - 'a';
            if (curr.children[index] == null) {
                return false;
            }
            curr = curr.children[index];
        }
        return true; // 路径存在即表示有前缀匹配
    }

    // 4. 拓展:删除字符串(工业级必备,支持词频递减,无词频则删除节点)
    public void delete(String word) {
        if (search(word) == 0) {
            return; // 不存在,直接返回
        }
        deleteRec(root, word, 0);
    }

    // 递归删除:后序遍历,无子节点则删除当前节点
    private boolean deleteRec(TrieNode node, String word, int depth) {
        if (depth == word.length()) {
            node.count--; // 词频-1
            node.isEnd = node.count > 0; // 词频为0则不再是结尾节点
            return node.count == 0 && isAllChildrenNull(node); // 无词频且无子节点则可删除
        }
        int index = word.charAt(depth) - 'a';
        TrieNode child = node.children[index];
        boolean needDeleteChild = deleteRec(child, word, depth + 1);
        if (needDeleteChild) {
            node.children[index] = null; // 删除子节点
            // 当前节点无其他子节点且非结尾节点,则可继续向上删除
            return isAllChildrenNull(node) && !node.isEnd;
        }
        return false;
    }

    // 辅助:判断节点的所有子节点是否为空
    private boolean isAllChildrenNull(TrieNode node) {
        for (TrieNode child : node.children) {
            if (child != null) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        TrieTree trie = new TrieTree();
        trie.insert("java");
        trie.insert("java"); // 重复插入,词频为2
        trie.insert("javascript");
        trie.insert("python");

        System.out.println("java词频:" + trie.search("java")); // 输出:2
        System.out.println("jav是否为前缀:" + trie.startsWith("jav")); // 输出:true
        System.out.println("php是否存在:" + trie.search("php")); // 输出:0

        trie.delete("java");
        System.out.println("删除后java词频:" + trie.search("java")); // 输出:1
        trie.delete("java");
        System.out.println("再次删除后java是否存在:" + (trie.search("java") > 0)); // 输出:false
        System.out.println("jav是否仍为前缀:" + trie.startsWith("jav")); // 输出:true(javascript仍存在)
    }
}
5.1.3 Manacher 算法:线性时间求最长回文子串(解决「中心扩展」的 O (n²) 痛点)

核心问题 :查找字符串中最长回文子串 (如 "abba""abcba"),中心扩展法的时间复杂度为O(n2),Manacher 算法通过预处理字符串 + 回文半径数组 + 中心右边界 优化至O(n)。适用场景 :密码学、字符串脱敏、回文相关业务(如车牌 / 手机号回文验证)。(注:Manacher 算法为面试高频难点,以下实现为工业级简化版,保留核心逻辑,易理解、可复用)

java

运行

复制代码
/**
 * Manacher算法:线性时间求最长回文子串(工业级简化版)
 * 预处理:在字符串每个字符间插入#,统一奇偶回文处理(如abc→#a#b#c#)
 * 核心:回文半径数组p[]、当前回文中心c、当前回文右边界r
 */
public class ManacherAlgorithm {
    // 主方法:返回字符串的最长回文子串
    public static String longestPalindrome(String s) {
        if (s == null || s.length() < 2) {
            return s;
        }
        // 1. 预处理字符串,插入#
        String t = preProcess(s);
        int n = t.length();
        int[] p = new int[n]; // 回文半径数组:p[i]表示以i为中心的回文半径
        int c = 0, r = 0; // 中心c,右边界r

        // 2. 线性遍历计算p[]
        for (int i = 1; i < n - 1; i++) {
            int mirror = 2 * c - i; // i关于c的镜像节点
            if (i < r) {
                p[i] = Math.min(r - i, p[mirror]); // 利用镜像优化,减少计算
            }
            // 中心扩展:尝试扩大回文半径
            while (t.charAt(i + p[i] + 1) == t.charAt(i - p[i] - 1)) {
                p[i]++;
            }
            // 更新中心c和右边界r
            if (i + p[i] > r) {
                c = i;
                r = i + p[i];
            }
        }

        // 3. 找到最大回文半径和对应中心
        int maxLen = 0, centerIndex = 0;
        for (int i = 1; i < n - 1; i++) {
            if (p[i] > maxLen) {
                maxLen = p[i];
                centerIndex = i;
            }
        }

        // 4. 还原原字符串的最长回文子串
        int start = (centerIndex - maxLen) / 2;
        return s.substring(start, start + maxLen);
    }

    // 预处理:插入#,首尾添加不同字符避免越界(如^#a#b#c#$)
    private static String preProcess(String s) {
        StringBuilder sb = new StringBuilder();
        sb.append("^");
        for (char c : s.toCharArray()) {
            sb.append("#").append(c);
        }
        sb.append("#$");
        return sb.toString();
    }

    public static void main(String[] args) {
        String s1 = "babad";
        String s2 = "cbbd";
        System.out.println("babad的最长回文子串:" + longestPalindrome(s1)); // 输出:bab或aba
        System.out.println("cbbd的最长回文子串:" + longestPalindrome(s2)); // 输出:bb
    }
}

5.2 高级数据结构融合算法(面试高频 / 工程底层)

这类算法是数据结构 + 算法思想 的融合,是大厂面试的压轴考点 ,也是 Redis、Tomcat、MyBatis 等框架的底层实现核心,掌握后可轻松应对复杂业务场景的算法设计。

5.2.1 并查集(Union-Find):解决连通性问题

核心问题 :判断多个元素是否属于同一集合合并两个集合 ,时间复杂度近乎O(1)(路径压缩 + 按秩合并优化)。核心原理 :基于数组 实现,每个元素指向自己的父节点,根节点表示集合的标识;通过路径压缩 (查找时将节点直接指向根节点)和按秩合并 (将小集合合并到大集合,减少树的高度)实现高效操作。适用场景:图的连通性判断、朋友圈问题、分布式节点分组、电商订单合并。

java

运行

复制代码
/**
 * 并查集(Union-Find):工业级实现,路径压缩+按秩合并(高度)
 * 核心操作:find(查找根节点+路径压缩)、union(合并两个集合)、isConnected(判断是否连通)
 */
public class UnionFind {
    private int[] parent; // 父节点数组:parent[i]表示i的父节点
    private int[] rank;   // 秩数组:rank[i]表示以i为根的集合的树高度
    private int count;    // 集合的数量

    // 构造方法:初始化n个独立的集合
    public UnionFind(int n) {
        this.count = n;
        this.parent = new int[n];
        this.rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i; // 初始时,每个元素的父节点是自己
            rank[i] = 1;   // 初始时,每个树的高度为1
        }
    }

    // 查找根节点:带路径压缩(工业级核心优化)
    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]); // 路径压缩:直接指向根节点
        }
        return parent[x];
    }

    // 合并两个集合:按秩合并(高度),小高度合并到大高度下(避免树退化)
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return; // 已属于同一集合,无需合并
        }
        // 按秩合并:小高度 → 大高度
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++; // 高度相同时,合并后高度+1
        }
        count--; // 集合数量-1
    }

    // 判断两个元素是否属于同一集合
    public boolean isConnected(int x, int y) {
        return find(x) == find(y);
    }

    // 获取集合的数量
    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        // 案例:朋友圈问题:5个人,0-1连通,1-2连通,3-4连通,求朋友圈数量
        UnionFind uf = new UnionFind(5);
        uf.union(0, 1);
        uf.union(1, 2);
        uf.union(3, 4);
        System.out.println("朋友圈数量:" + uf.getCount()); // 输出:2
        System.out.println("0和2是否连通:" + uf.isConnected(0, 2)); // 输出:true
        System.out.println("0和3是否连通:" + uf.isConnected(0, 3)); // 输出:false
    }
}
5.2.2 单调栈:解决「下一个更大 / 更小元素」问题

核心问题 :在线性时间内找到数组中每个元素的下一个更大元素、下一个更小元素、柱状图最大矩形、接雨水 等问题,时间复杂度O(n),解决暴力遍历的O(n2)痛点。核心原理 :维护一个单调递增 / 递减的栈 ,栈中存储数组的索引,遍历数组时,若当前元素破坏栈的单调性,则弹出栈顶元素并计算结果,直到栈恢复单调性,再将当前索引入栈。适用场景:数组极值查找、柱状图面积计算、雨水收集、表达式解析。

java

运行

复制代码
import java.util.*;

/**
 * 单调栈:工业级实现,解决「下一个更大元素」问题
 * 拓展:可直接修改为下一个更小元素、前一个更大/更小元素
 */
public class MonotonicStack {
    // 主方法:找数组中每个元素的下一个更大元素,无则返回-1
    public static int[] nextGreaterElement(int[] nums) {
        if (nums == null || nums.length == 0) {
            return new int[0];
        }
        int n = nums.length;
        int[] res = new int[n];
        Arrays.fill(res, -1);
        Deque<Integer> stack = new LinkedList<>(); // 单调栈:存储索引,保持栈内元素对应值单调递减

        for (int i = 0; i < n; i++) {
            // 当前元素 > 栈顶元素对应值:破坏单调性,弹出并计算
            while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
                int index = stack.pop();
                res[index] = nums[i]; // 栈顶元素的下一个更大元素为当前元素
            }
            stack.push(i); // 入栈当前索引
        }
        return res;
    }

    // 拓展:柱状图中最大的矩形(面试高频压轴题)
    public static int largestRectangleArea(int[] heights) {
        if (heights == null || heights.length == 0) {
            return 0;
        }
        int n = heights.length;
        int[] left = new int[n]; // left[i]:i左侧第一个更小元素的索引
        int[] right = new int[n]; // right[i]:i右侧第一个更小元素的索引
        Deque<Integer> stack = new LinkedList<>();
        int maxArea = 0;

        // 计算left数组
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
                stack.pop();
            }
            left[i] = stack.isEmpty() ? -1 : stack.peek();
            stack.push(i);
        }
        stack.clear();

        // 计算right数组
        for (int i = n - 1; i >= 0; i--) {
            while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
                stack.pop();
            }
            right[i] = stack.isEmpty() ? n : stack.peek();
            stack.push(i);
        }

        // 计算每个柱子作为高度的最大矩形面积
        for (int i = 0; i < n; i++) {
            int width = right[i] - left[i] - 1;
            maxArea = Math.max(maxArea, heights[i] * width);
        }
        return maxArea;
    }

    public static void main(String[] args) {
        int[] nums = {2, 1, 2, 4, 3};
        int[] nge = nextGreaterElement(nums);
        System.out.println("下一个更大元素:" + Arrays.toString(nge)); // 输出:[4, 2, 4, -1, -1]

        int[] heights = {2, 1, 5, 6, 2, 3};
        System.out.println("柱状图最大矩形面积:" + largestRectangleArea(heights)); // 输出:10
    }
}
5.2.3 跳表(SkipList):有序集合的高效实现(Redis ZSet 底层)

核心问题 :解决有序链表 的查找效率低(O(n))的问题,跳表通过多层索引 将查找效率优化至O(logn),且实现比红黑树更简单。核心原理 :在有序链表的基础上,构建多层稀疏索引(上层索引是下层索引的子集),查找时从顶层索引开始,快速定位到目标元素的大致范围,再逐层向下查找。适用场景 :有序集合存储、Redis ZSet 底层、排行榜系统(按分数排序)。(注:以下为Redis 简化版跳表实现,保留核心的增 / 查 / 删逻辑,和工业级实现一致)

java

运行

复制代码
import java.util.Random;

/**
 * 跳表(SkipList):Redis ZSet底层简化版,支持增、查、删、按序遍历
 * 核心:多层索引、随机层高、头节点/尾节点、前驱/后继指针
 * 时间复杂度:增/查/删 O(logn),比红黑树实现更简单
 */
public class SkipList {
    // 跳表节点类
    private static class Node {
        int value; // 存储值(Redis中为score+member)
        Node[] forward; // 前进指针:forward[level]表示当前节点在level层的下一个节点

        public Node(int value, int level) {
            this.value = value;
            this.forward = new Node[level];
        }
    }

    private static final int MAX_LEVEL = 16; // 最大层数(Redis默认32)
    private static final double P = 0.5;     // 随机层高的概率(Redis默认0.25)
    private final Random random;
    private int level; // 跳表现有最大层数
    private final Node head; // 头节点,所有层的起始节点

    public SkipList() {
        this.random = new Random();
        this.level = 1;
        this.head = new Node(Integer.MIN_VALUE, MAX_LEVEL);
    }

    // 随机生成节点的层数(工业级随机策略)
    private int randomLevel() {
        int l = 1;
        while (random.nextDouble() < P && l < MAX_LEVEL) {
            l++;
        }
        return l;
    }

    // 查找元素:返回节点,未找到返回null
    public Node find(int value) {
        Node curr = head;
        // 从顶层到底层逐层查找
        for (int i = level - 1; i >= 0; i--) {
            // 找到当前层小于value的最后一个节点
            while (curr.forward[i] != null && curr.forward[i].value < value) {
                curr = curr.forward[i];
            }
        }
        // 最后检查下一层是否为目标值
        if (curr.forward[0] != null && curr.forward[0].value == value) {
            return curr.forward[0];
        }
        return null;
    }

    // 插入元素
    public void insert(int value) {
        Node[] update = new Node[MAX_LEVEL]; // 存储每层需要更新的前驱节点
        Node curr = head;
        // 1. 找到每层的前驱节点,存入update
        for (int i = level - 1; i >= 0; i--) {
            while (curr.forward[i] != null && curr.forward[i].value < value) {
                curr = curr.forward[i];
            }
            update[i] = curr;
        }
        // 2. 随机生成新节点层数
        int newLevel = randomLevel();
        // 3. 若新层数超过现有层数,更新update和跳表层数
        if (newLevel > level) {
            for (int i = level; i < newLevel; i++) {
                update[i] = head;
            }
            level = newLevel;
        }
        // 4. 创建新节点并插入
        Node newNode = new Node(value, newLevel);
        for (int i = 0; i < newLevel; i++) {
            newNode.forward[i] = update[i].forward[i];
            update[i].forward[i] = newNode;
        }
    }

    // 删除元素
    public void delete(int value) {
        Node[] update = new Node[MAX_LEVEL]; // 存储每层需要更新的前驱节点
        Node curr = head;
        // 1. 找到每层的前驱节点,存入update
        for (int i = level - 1; i >= 0; i--) {
            while (curr.forward[i] != null && curr.forward[i].value < value) {
                curr = curr.forward[i];
            }
            update[i] = curr;
        }
        // 2. 检查是否存在目标节点
        curr = curr.forward[0];
        if (curr == null || curr.value != value) {
            return; // 不存在,直接返回
        }
        // 3. 逐层删除节点
        for (int i = 0; i < level; i++) {
            if (update[i].forward[i] != curr) {
                break; // 该层无目标节点,无需继续
            }
            update[i].forward[i] = curr.forward[i];
        }
        // 4. 更新跳表现有最大层数(删除空层)
        while (level > 1 && head.forward[level - 1] == null) {
            level--;
        }
    }

    // 按序遍历跳表(底层链表)
    public void traverse() {
        Node curr = head.forward[0];
        while (curr != null) {
            System.out.print(curr.value + " ");
            curr = curr.forward[0];
        }
        System.out.println();
    }

    public static void main(String[] args) {
        SkipList skipList = new SkipList();
        skipList.insert(3);
        skipList.insert(1);
        skipList.insert(5);
        skipList.insert(2);
        skipList.insert(4);

        System.out.println("按序遍历:");
        skipList.traverse(); // 输出:1 2 3 4 5

        System.out.println("查找5:" + (skipList.find(5) != null)); // 输出:true
        skipList.delete(3);
        System.out.println("删除3后遍历:");
        skipList.traverse(); // 输出:1 2 4 5
    }
}

5.3 分布式场景核心算法(微服务 / 高并发必备)

现在的 Java 开发均基于分布式 / 微服务架构 ,单机算法已无法满足业务需求,这部分是高级 Java 工程师的核心能力 ,涵盖分布式架构中最常用的 3 类算法,配套Java 简易工业级实现,可直接融入项目开发。

5.3.1 一致性哈希算法:分布式缓存核心(解决「缓存雪崩」「数据倾斜」)

核心问题 :解决分布式缓存中节点扩容 / 缩容时的数据迁移 问题,普通哈希算法(hash (key)% n)在节点数 n 变化时,会导致所有缓存失效(缓存雪崩),一致性哈希算法将数据迁移量降至O(1/n)。核心原理

  1. 节点缓存 key 映射到一个0~2^32-1的环形哈希空间;
  2. 数据 key 按顺时针方向映射到最近的节点
  3. 节点扩容 / 缩容时,仅影响该节点在环上的相邻节点,数据迁移量极小;
  4. 引入虚拟节点 解决「数据倾斜」问题(物理节点对应多个虚拟节点,均匀分布在环上)。适用场景:Redis 集群、分布式缓存、负载均衡、分布式存储。

java

运行

复制代码
import java.util.*;

/**
 * 一致性哈希算法:工业级实现,带虚拟节点(解决数据倾斜)
 * 核心:哈希环、虚拟节点、节点/key映射、节点增删
 * 适用场景:Redis集群、分布式缓存、负载均衡
 */
public class ConsistentHashing {
    private final SortedMap<Long, String> hashRing = new TreeMap<>(); // 哈希环:key=哈希值,value=节点名
    private final int virtualNodeNum; // 每个物理节点的虚拟节点数(推荐32/64)
    private static final String VIRTUAL_NODE_SUFFIX = "#"; // 虚拟节点后缀,区分物理节点

    // 构造方法:初始化物理节点和虚拟节点数
    public ConsistentHashing(List<String> physicalNodes, int virtualNodeNum) {
        this.virtualNodeNum = virtualNodeNum;
        // 初始化物理节点(添加虚拟节点到哈希环)
        for (String node : physicalNodes) {
            addNode(node);
        }
    }

    // 哈希函数:工业级常用MD5+Long(避免哈希冲突)
    private long hash(String key) {
        return Objects.hash(key); // 工程中可替换为MD5/SHA1哈希,提升分布均匀性
    }

    // 添加节点(物理节点+虚拟节点)
    public void addNode(String physicalNode) {
        for (int i = 0; i < virtualNodeNum; i++) {
            String virtualNode = physicalNode + VIRTUAL_NODE_SUFFIX + i;
            long hash = hash(virtualNode);
            hashRing.put(hash, physicalNode); // 虚拟节点映射到物理节点
            System.out.println("添加虚拟节点:" + virtualNode + ",哈希值:" + hash);
        }
    }

    // 删除节点(物理节点+虚拟节点)
    public void removeNode(String physicalNode) {
        for (int i = 0; i < virtualNodeNum; i++) {
            String virtualNode = physicalNode + VIRTUAL_NODE_SUFFIX + i;
            long hash = hash(virtualNode);
            hashRing.remove(hash);
        }
        System.out.println("删除物理节点:" + physicalNode + "及其所有虚拟节点");
    }

    // 核心:找到key对应的物理节点
    public String getNode(String key) {
        if (hashRing.isEmpty()) {
            throw new RuntimeException("哈希环为空,无可用节点");
        }
        long hash = hash(key);
        // 找到顺时针第一个大于等于key哈希值的节点
        SortedMap<Long, String> subMap = hashRing.tailMap(hash);
        Long nodeHash = subMap.isEmpty() ? hashRing.firstKey() : subMap.firstKey();
        return hashRing.get(nodeHash);
    }

    public static void main(String[] args) {
        // 初始化物理节点:Redis1、Redis2、Redis3
        List<String> physicalNodes = Arrays.asList("192.168.1.100:6379", "192.168.1.101:6379", "192.168.1.102:6379");
        // 每个物理节点对应10个虚拟节点
        ConsistentHashing ch = new ConsistentHashing(physicalNodes, 10);

        // 测试key映射
        String key1 = "user:100";
        String key2 = "order:200";
        String key3 = "product:300";
        System.out.println("key1:" + key1 + " → 节点:" + ch.getNode(key1));
        System.out.println("key2:" + key2 + " → 节点:" + ch.getNode(key2));
        System.out.println("key3:" + key3 + " → 节点:" + ch.getNode(key3));

        // 测试节点扩容:添加Redis4
        ch.addNode("192.168.1.103:6379");
        System.out.println("扩容后key1:" + key1 + " → 节点:" + ch.getNode(key1)); // 大概率仍映射到原节点,数据迁移量小

        // 测试节点缩容:删除Redis2
        ch.removeNode("192.168.1.101:6379");
        System.out.println("缩容后key2:" + key2 + " → 节点:" + ch.getNode(key2)); // 仅映射到相邻节点
    }
}
5.3.2 雪花算法(SnowFlake):分布式唯一 ID 生成(大厂标配)

核心问题 :解决分布式系统中全局唯一 ID 的生成问题,要求 ID唯一、有序、高性能、可溯源 ,雪花算法是大厂最常用的实现方案,纯内存生成,性能达 100w+/s。核心原理:将 64 位 Long 型 ID 分为 4 个部分:

  1. 1 位符号位:固定为 0,保证 ID 为正数;
  2. 41 位时间戳:精确到毫秒,可使用约 69 年(2^41-1 毫秒);
  3. 10 位机器位:可部署 1024 个节点(5 位数据中心 + 5 位机器 ID);
  4. 12 位序列号 :同一毫秒内,每个节点可生成 4096 个唯一 ID(2^12)。适用场景:分布式订单 ID、用户 ID、商品 ID、日志 ID 等全局唯一标识。

java

运行

复制代码
/**
 * 雪花算法(SnowFlake):工业级实现,线程安全(CAS+volatile)
 * 64位ID结构:0(1) + 时间戳(41) + 数据中心ID(5) + 机器ID(5) + 序列号(12)
 * 特性:唯一、有序、高性能、可溯源、无依赖(纯内存)
 */
public class SnowFlake {
    // 各部分的位数
    private static final int TIMESTAMP_BIT = 41;
    private static final int DATA_CENTER_BIT = 5;
    private static final int MACHINE_BIT = 5;
    private static final int SEQUENCE_BIT = 12;

    // 各部分的最大值(位运算计算)
    private static final long MAX_DATA_CENTER = (1L << DATA_CENTER_BIT) - 1;
    private static final long MAX_MACHINE = (1L << MACHINE_BIT) - 1;
    private static final long MAX_SEQUENCE = (1L << SEQUENCE_BIT) - 1;

    // 各部分的偏移量
    private static final long MACHINE_OFFSET = SEQUENCE_BIT;
    private static final long DATA_CENTER_OFFSET = SEQUENCE_BIT + MACHINE_BIT;
    private static final long TIMESTAMP_OFFSET = SEQUENCE_BIT + MACHINE_BIT + DATA_CENTER_BIT;

    // 起始时间戳:2024-01-01 00:00:00(可自定义,建议项目启动时间)
    private static final long START_TIMESTAMP = 1704067200000L;

    // 全局变量(volatile保证可见性,CAS保证原子性)
    private final long dataCenterId; // 数据中心ID
    private final long machineId;     // 机器ID
    private volatile long lastTimestamp = -1L; // 上一次生成ID的时间戳
    private volatile long sequence = 0L;       // 序列号

    // 构造方法:初始化数据中心ID和机器ID(需保证分布式环境下唯一)
    public SnowFlake(long dataCenterId, long machineId) {
        if (dataCenterId < 0 || dataCenterId > MAX_DATA_CENTER) {
            throw new IllegalArgumentException("数据中心ID超出范围:0~" + MAX_DATA_CENTER);
        }
        if (machineId < 0 || machineId > MAX_MACHINE) {
            throw new IllegalArgumentException("机器ID超出范围:0~" + MAX_MACHINE);
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }

    // 核心方法:生成分布式唯一ID(线程安全)
    public long nextId() {
        long currTimestamp = getCurrentTimestamp();
        // 1. 处理时钟回拨(工业级关键:避免时间戳倒退导致ID重复)
        if (currTimestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨,拒绝生成ID:" + (lastTimestamp - currTimestamp) + "ms");
        }
        // 2. 同一毫秒内,序列号自增
        if (currTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒内序列号耗尽,等待下一个毫秒
            if (sequence == 0) {
                currTimestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            // 不同毫秒,序列号重置为0
            sequence = 0L;
        }
        // 3. 更新上一次时间戳
        lastTimestamp = currTimestamp;
        // 4. 拼接64位ID并返回
        return (currTimestamp - START_TIMESTAMP) << TIMESTAMP_OFFSET // 时间戳部分
                | dataCenterId << DATA_CENTER_OFFSET                 // 数据中心部分
                | machineId << MACHINE_OFFSET                         // 机器部分
                | sequence;                                           // 序列号部分
    }

    // 辅助:获取当前毫秒级时间戳
    private long getCurrentTimestamp() {
        return System.currentTimeMillis();
    }

    // 辅助:等待下一个毫秒,直到获取到新的时间戳
    private long waitNextMillis(long lastTimestamp) {
        long curr = getCurrentTimestamp();
        while (curr <= lastTimestamp) {
            curr = getCurrentTimestamp();
        }
        return curr;
    }

    // 测试:多线程生成ID(验证线程安全)
    public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(1, 1);
        // 10个线程,每个线程生成1000个ID
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    System.out.println(Thread.currentThread().getName() + " → " + snowFlake.nextId());
                }
            }, "ID-Generator-" + i).start();
        }
    }
}
5.3.3 令牌桶算法:分布式限流核心(高并发系统必备)

核心问题 :解决分布式系统中接口限流、流量削峰 问题,令牌桶算法相比漏桶算法更灵活,支持突发流量 (桶内有令牌时可一次性处理)。核心原理

  1. 系统以固定速率生成令牌,放入令牌桶(桶有最大容量,满则丢弃);
  2. 每个请求必须获取一个令牌才能执行,无令牌则拒绝 / 阻塞;
  3. 支持突发流量:桶内预存的令牌可应对短时间的高并发请求。适用场景 :接口限流、网关限流(如 Spring Cloud Gateway)、秒杀系统、消息队列削峰。(以下为分布式令牌桶的 Java 实现,基于 Redis 实现令牌共享,解决单机令牌桶的分布式问题)

java

运行

复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.concurrent.TimeUnit;

/**
 * 分布式令牌桶算法:基于Redis实现(解决单机令牌桶的分布式问题)
 * 核心:Redis自增+过期时间实现令牌生成,SET NX实现原子性获取令牌
 * 适用场景:分布式接口限流、网关限流、秒杀系统
 * 依赖:Redis、Jedis客户端
 */
public class DistributedTokenBucket {
    private final Jedis jedis; // Redis客户端(工程中建议使用JedisPool/Redisson)
    private final String bucketKey; // Redis中令牌桶的key
    private final int capacity; // 令牌桶最大容量
    private final int rate;     // 令牌生成速率(个/秒)
    private final long interval = 1000; // 令牌生成间隔(毫秒)

    // 构造方法:初始化Redis、桶容量、生成速率
    public DistributedTokenBucket(Jedis jedis, String bucketKey, int capacity, int rate) {
        this.jedis = jedis;
        this.bucketKey = bucketKey;
        this.capacity = capacity;
        this.rate = rate;
        // 初始化令牌桶(后台线程持续生成令牌)
        startTokenGenerator();
    }

    // 后台线程:持续生成令牌(固定速率)
    private void startTokenGenerator() {
        new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    // 1. 获取当前令牌数
                    String currentStr = jedis.get(bucketKey);
                    int current = currentStr == null ? 0 : Integer.parseInt(currentStr);
                    // 2. 计算新的令牌数(不超过最大容量)
                    int newToken = Math.min(current + rate, capacity);
                    // 3. 更新令牌数到Redis(原子性)
                    jedis.set(bucketKey, String.valueOf(newToken));
                    // 4. 按间隔休眠
                    TimeUnit.MILLISECONDS.sleep(interval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "Token-Generator").start();
    }

    // 核心方法:获取令牌(非阻塞,返回是否成功)
    public boolean acquire() {
        return acquire(1); // 默认获取1个令牌
    }

    // 重载:获取n个令牌(原子性,Redis DECRBY实现)
    public boolean acquire(int n) {
        if (n <= 0 || n > capacity) {
            return false;
        }
        // 1. 令牌数自减n(原子性)
        long current = jedis.decrBy(bucketKey, n);
        // 2. 自减后令牌数>=0:获取成功;否则回滚,获取失败
        if (current >= 0) {
            return true;
        } else {
            jedis.incrBy(bucketKey, n); // 回滚
            return false;
        }
    }

    // 拓展:获取令牌(阻塞,直到获取成功)
    public void acquireBlocking() throws InterruptedException {
        while (!acquire()) {
            TimeUnit.MILLISECONDS.sleep(10);
        }
    }

    // 测试
    public static void main(String[] args) {
        // 初始化Redis客户端(工程中建议使用连接池)
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.auth("your_redis_password"); // 若Redis有密码需添加
        // 令牌桶:容量100,速率10个/秒
        DistributedTokenBucket tokenBucket = new DistributedTokenBucket(jedis, "limit:api:user", 100, 10);

        // 模拟100个请求获取令牌
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                if (tokenBucket.acquire()) {
                    System.out.println(Thread.currentThread().getName() + " 获取令牌成功,执行请求");
                } else {
                    System.out.println(Thread.currentThread().getName() + " 获取令牌失败,限流");
                }
            }, "Request-" + i).start();
        }
    }
}

六、算法与 Java 生态深度融合(新特性 + 主流框架)

Java 算法的学习不能脱离Java 生态 ,实际开发中算法并非单独实现,而是与Java 新特性(Stream / 虚拟线程)、主流框架(Spring/Netty/Redis) 深度结合。本部分聚焦算法在 Java 日常开发中的实际应用,让算法从 "刷题代码" 变为 "业务代码"。

6.1 算法与 Java8 + 新特性融合

Java8 + 的 Stream、Lambda、CompletableFuture,Java19 + 的虚拟线程,为算法实现提供了更简洁的语法、更高效的并行能力,无需手动编写多线程代码,即可实现算法的并行化。

6.1.1 Stream 流:简化线性算法实现

Stream 的过滤、映射、排序、归约 等操作,底层封装了排序、查找、枚举等基础算法,可大幅简化代码,同时支持并行流 实现算法的多核并行执行。代码案例:Stream 实现数组 / 集合的算法操作(排序 / 去重 / TopK / 统计)

java

运行

复制代码
import java.util.*;
import java.util.stream.Collectors;

/**
 * Stream+Lambda简化算法实现:排序、去重、TopK、数值统计、过滤
 * 底层:并行流基于Fork/Join框架,自动实现算法的并行化,无需手动创建线程
 */
public class StreamAlgorithm {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(5, 2, 8, 2, 9, 1, 5, 3, 7);

        // 1. 基础排序:升序/降序(底层基于Arrays.sort,快排/归并)
        List<Integer> sortedAsc = list.stream().sorted().collect(Collectors.toList());
        List<Integer> sortedDesc = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
        System.out.println("升序:" + sortedAsc); // [1,2,2,3,5,5,7,8,9]
        System.out.println("降序:" + sortedDesc); // [9,8,7,5,5,3,2,2,1]

        // 2. 去重+排序:一行实现(替代HashSet+手动排序)
        List<Integer> distinctSorted = list.stream().distinct().sorted().collect(Collectors.toList());
        System.out.println("去重升序:" + distinctSorted); // [1,2,3,5,7,8,9]

        // 3. TopK问题:取前3大元素(底层基于堆,效率O(nlogk))
        List<Integer> top3 = list.stream().sorted(Comparator.reverseOrder()).limit(3).collect(Collectors.toList());
        System.out.println("前3大元素:" + top3); // [9,8,7]

        // 4. 数值统计:求和、平均值、最大值、最小值(替代循环统计)
        int sum = list.stream().mapToInt(Integer::intValue).sum();
        double avg = list.stream().mapToInt(Integer::intValue).average().orElse(0);
        int max = list.stream().mapToInt(Integer::intValue).max().orElse(0);
        System.out.println("和:" + sum + ",平均值:" + avg + ",最大值:" + max); // 和:42,平均值:4.666...,最大值:9

        // 5. 并行流:大数据量下的并行过滤(利用多核CPU,效率远高于单线程)
        List<Integer> bigList = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            bigList.add((int) (Math.random() * 1000000));
        }
        long start = System.currentTimeMillis();
        // 并行流:parallelStream(),底层Fork/Join框架实现并行过滤
        List<Integer> filterParallel = bigList.parallelStream()
                .filter(num -> num > 500000)
                .collect(Collectors.toList());
        long end = System.currentTimeMillis();
        System.out.println("并行过滤1000万数据耗时:" + (end - start) + "ms");
    }
}
6.1.2 CompletableFuture:实现异步并行算法

Java8 的CompletableFuture支持异步任务编排 ,可将算法的独立子任务拆分为异步任务,实现并行执行 + 结果合并 ,比传统Thread/ExecutorService更简洁,适合分治算法的并行化。代码案例:CompletableFuture 实现并行归并排序(分治算法异步化)

java

运行

复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * CompletableFuture实现异步并行归并排序
 * 核心:将数组拆分为多个子数组,异步排序后,异步合并结果,充分利用多核CPU
 * 适用场景:大数据量排序、分布式计算的本地子任务并行
 */
public class CompletableFutureSort {
    // 线程池:根据CPU核心数设置,避免线程过多导致上下文切换
    private static final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); // Java19+虚拟线程,更轻量
    private static final int THRESHOLD = 1000; // 小数据量阈值,单线程排序

    // 主方法:异步并行归并排序
    public static CompletableFuture<List<Integer>> parallelMergeSort(List<Integer> list) {
        // 小数据量:单线程插入排序,直接返回
        if (list.size() <= THRESHOLD) {
            return CompletableFuture.completedFuture(insertionSort(list));
        }
        // 大数据量:拆分为左右两个子数组
        int mid = list.size() / 2;
        List<Integer> left = list.subList(0, mid);
        List<Integer> right = list.subList(mid, list.size());

        // 异步排序左、右子数组(两个子任务并行执行)
        CompletableFuture<List<Integer>> sortedLeft = parallelMergeSort(left).asyncSupply(executor);
        CompletableFuture<List<Integer>> sortedRight = parallelMergeSort(right).asyncSupply(executor);

        // 两个子数组排序完成后,异步合并结果
        return sortedLeft.thenCombineAsync(sortedRight, CompletableFutureSort::merge, executor);
    }

    // 插入排序:小数据量高效
    private static List<Integer> insertionSort(List<Integer> list) {
        List<Integer> res = new ArrayList<>(list);
        for (int i = 1; i < res.size(); i++) {
            int temp = res.get(i);
            int j = i - 1;
            while (j >= 0 && res.get(j) > temp) {
                res.set(j + 1, res.get(j));
                j--;
            }
            res.set(j + 1, temp);
        }
        return res;
    }

    // 合并两个有序数组
    private static List<Integer> merge(List<Integer> left, List<Integer> right) {
        List<Integer> res = new ArrayList<>();
        int i = 0, j = 0;
        while (i < left.size() && j < right.size()) {
            if (left.get(i) <= right.get(j)) {
                res.add(left.get(i++));
            } else {
                res.add(right.get(j++));
            }
        }
        res.addAll(left.subList(i, left.size()));
        res.addAll(right.subList(j, right.size()));
        return res;
    }

    public static void main(String[] args) throws Exception {
        // 生成100万随机数
        List<Integer> bigList = new java.util.Random().ints(0, 1000000)
                .limit(1000000)
                .boxed()
                .collect(Collectors.toList());

        // 异步并行排序
        long start = System.currentTimeMillis();
        List<Integer> sortedList = parallelMergeSort(bigList).get();
        long end = System.currentTimeMillis();

        System.out.println("100万数据并行排序耗时:" + (end - start) + "ms");
        System.out.println("排序结果验证:" + (sortedList.get(0) <= sortedList.get(500000) && sortedList.get(500000) <= sortedList.get(999999))); // true

        executor.shutdown();
    }
}
6.1.3 虚拟线程(Java19+):轻量级并行算法实现

Java19 引入的虚拟线程 (Project Loom)是 JVM 级别的轻量级线程,无需内核态切换,创建成本低(可创建百万级虚拟线程),适合细粒度的算法并行化 (如分治算法的大量子任务),比传统线程池效率提升 10 倍以上。上述案例中Executors.newVirtualThreadPerTaskExecutor()即为虚拟线程池,替换传统线程池后,无需修改其他代码,即可实现算法的轻量级并行

6.2 算法与 Java 主流框架的融合

Java 开发的核心是框架,算法是框架的底层核心 ,同时框架也为算法提供了落地载体 。以下是 3 个主流框架中算法的应用场景 + 集成方式,让算法真正融入业务开发。

6.2.1 Spring/Spring Boot:算法在业务层的落地
  • 限流算法 :整合到 Spring Boot 的拦截器 / 过滤器中,实现接口限流(如令牌桶算法 +HandlerInterceptor);
  • 雪花算法 :通过 Spring 的单例 Bean实现全局唯一 ID 生成,注入到 Service/DAO 层;
  • 缓存算法 :结合 Spring Cache,实现 LRU/LFU 缓存(如LinkedHashMap实现 LRU,整合@Cacheable);
  • 动态规划 / 贪心:在业务层实现最优解计算(如电商的优惠券组合、物流的路径规划)。

代码案例:Spring Boot 整合令牌桶算法实现接口限流

java

运行

复制代码
// 1. 自定义限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    int capacity() default 100; // 令牌桶容量
    int rate() default 10;      // 令牌生成速率(个/秒)
}

// 2. 限流拦截器:实现令牌桶算法,拦截加了@RateLimit的接口
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
    // 存储每个接口的令牌桶,key=接口方法名,value=令牌桶
    private final Map<String, TokenBucketLimiter> bucketMap = new ConcurrentHashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 仅拦截Controller的方法
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        RateLimit rateLimit = handlerMethod.getMethodAnnotation(RateLimit.class);
        if (rateLimit == null) {
            return true; // 无注解,放行
        }

        // 获取接口方法名,作为令牌桶的key
        String methodKey = handlerMethod.getMethod().getDeclaringClass().getName() + "." + handlerMethod.getMethod().getName();
        // 懒加载创建令牌桶
        TokenBucketLimiter bucket = bucketMap.computeIfAbsent(methodKey, k -> new TokenBucketLimiter(rateLimit.capacity(), rateLimit.rate()));

        // 获取令牌,失败则返回429限流
        if (!bucket.acquire()) {
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            response.getWriter().write("请求过于频繁,请稍后再试!");
            return false;
        }
        return true;
    }

    // 内部实现令牌桶算法(简化版,非分布式)
    static class TokenBucketLimiter {
        private final BlockingQueue<Object> bucket;
        private final ScheduledExecutorService scheduler;

        public TokenBucketLimiter(int capacity, int rate) {
            this.bucket = new ArrayBlockingQueue<>(capacity);
            this.scheduler = Executors.newSingleThreadScheduledExecutor();
            scheduler.scheduleAtFixedRate(() -> {
                try {
                    bucket.put(new Object());
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, 0, 1000 / rate, TimeUnit.MILLISECONDS);
        }

        public boolean acquire() {
            return bucket.poll() != null; // 非阻塞获取令牌
        }
    }
}

// 3. 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private RateLimitInterceptor rateLimitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(rateLimitInterceptor).addPathPatterns("/**");
    }
}

// 4. Controller使用限流注解
@RestController
@RequestMapping("/api")
public class UserController {
    // 限流:容量50,速率5个/秒
    @RateLimit(capacity = 50, rate = 5)
    @GetMapping("/user")
    public String getUser() {
        return "success:获取用户信息";
    }
}
6.2.2 Netty:算法在网络层的应用

Netty 是 Java 高性能网络框架,底层大量使用算法 + 数据结构 ,同时可在 Netty 中实现网络层算法

  • 编解码算法:Protobuf/JSON 编解码、Base64/MD5 哈希算法;
  • 多路复用算法:Reactor 模式(基于 IO 多路复用,底层是 epoll/kqueue 算法);
  • 心跳算法:基于定时任务的心跳检测(贪心算法实现心跳超时判断);
  • 粘包 / 拆包算法:基于定长 / 分隔符 / 长度字段的拆包算法(线性遍历 + 匹配)。
6.2.3 Redis:算法在缓存层的集成

Redis 是 Java 开发的必备缓存,底层实现了跳表、哈希表、布隆过滤器 等高级数据结构 / 算法,同时可通过 Redis 实现分布式算法

  • 分布式雪花 ID :基于 Redis 的INCR实现分布式唯一 ID(替代单机雪花算法);
  • 分布式令牌桶 :基于 Redis 的DECRBY/INCR实现分布式限流(前文已实现);
  • 布隆过滤器:Redis 的 BloomFilter 插件,实现缓存穿透防护;
  • 排序算法 :Redis 的SORT命令、ZSet(跳表)实现有序集合排序。

十一、算法落地 3 个经典 Java 实战项目(从代码到产品)

算法的最终价值是落地业务 ,以下选取 Java 开发中最经典的 3 个项目 (电商秒杀、风控系统、推荐系统),从架构设计、算法选型、核心代码、工程化落地四个维度,完整讲解算法如何融入项目,实现从 "刷题代码" 到 "生产代码" 的转变。

7.1 项目 1:电商秒杀系统(高并发算法落地)

7.1.1 核心业务问题

秒杀系统的核心痛点是高并发请求冲击(瞬间十万 / 百万 QPS),导致数据库宕机、超卖、接口超时。

7.1.2 核心算法选型

表格

问题场景 算法 / 数据结构 选型原因
全局唯一订单 ID 雪花算法 纯内存生成,高性能,唯一有序
接口限流 令牌桶算法(分布式) 支持突发流量,分布式一致
请求削峰 阻塞队列(ArrayBlockingQueue) 缓冲请求,逐步消费
库存超卖 乐观锁 / Redis 自增 保证库存操作的原子性
热点商品缓存 哈希表(ConcurrentHashMap) O (1) 查找,高并发安全
7.1.3 核心架构设计

用户请求 → Nginx反向代理 → Spring Boot网关(分布式令牌桶限流) → 阻塞队列削峰 → 消费者线程池 → Redis缓存(热点商品/库存) → 数据库(最终一致性)

7.1.4 核心工程化代码(Spring Boot)

java

运行

复制代码
// 1. 雪花算法单例Bean
@Configuration
public class SnowFlakeConfig {
    @Bean
    public SnowFlake snowFlake() {
        return new SnowFlake(1, 1); // 数据中心ID=1,机器ID=1
    }
}

// 2. 秒杀Service:整合队列+Redis+雪花算法
@Service
public class SecKillService {
    @Autowired
    private SnowFlake snowFlake;
    @Autowired
    private StringRedisTemplate redisTemplate;
    // 秒杀请求队列:容量1000,削峰
    private final BlockingQueue<Long> secKillQueue = new ArrayBlockingQueue<>(1000);

    // 初始化消费者线程池:5个线程消费队列
    @PostConstruct
    public void initConsumer() {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executor.execute(this::handleSecKill);
        }
    }

    // 秒杀入口方法:仅将请求放入队列,快速返回
    public String secKill(Long goodsId, Long userId) {
        // 1. 分布式限流(Redis令牌桶,省略实现)
        if (!redisTemplate.opsForValue().decrement("token:secKill:" + goodsId).isPresent()) {
            return "秒杀失败,请求过于频繁!";
        }
        // 2. 库存预检查(Redis)
        if (Long.parseLong(redisTemplate.opsForValue().get("stock:" + goodsId)) <= 0) {
            return "秒杀失败,库存不足!";
        }
        // 3. 请求入队,快速返回
        boolean offer = secKillQueue.offer(userId);
        return offer ? "秒杀请求已接收,请等待结果!" : "秒杀失败,系统繁忙!";
    }

    // 消费者方法:处理队列中的秒杀请求,实现核心逻辑
    private void handleSecKill() {
        while (true) {
            try {
                Long userId = secKillQueue.take();
                Long goodsId = 1L; // 示例:固定商品ID,实际从请求中获取
                // 1. Redis原子扣减库存(防止超卖)
                Long stock = redisTemplate.opsForValue().decrement("stock:" + goodsId);
                if (stock < 0) {
                    redisTemplate.opsForValue().increment("stock:" + goodsId); // 回滚
                    System.out.println("用户" + userId + "秒杀失败,库存不足!");
                    continue;
                }
                // 2. 生成全局唯一订单ID(雪花算法)
                Long orderId = snowFlake.nextId();
                // 3. 插入订单(异步写入数据库,最终一致性)
                CompletableFuture.runAsync(() -> {
                    // 省略数据库插入逻辑
                    System.out.println("用户" + userId + "秒杀成功,订单ID:" + orderId);
                });
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

7.2 项目 2:风控系统(数据挖掘算法落地)

7.2.1 核心业务问题

风控系统的核心是识别恶意请求 / 行为 (如刷单、薅羊毛、恶意注册),需要通过算法分析用户行为特征,实现精准风控

7.2.2 核心算法选型

表格

问题场景 算法 / 数据结构 选型原因
恶意 IP / 手机号拦截 布隆过滤器(Redis) 高效去重,占用空间小
用户行为相似度分析 余弦相似度算法 量化用户行为的相似性
异常行为检测 统计排序算法 识别偏离正常范围的行为
风控规则匹配 前缀树(Trie 树) 快速匹配风控规则关键词
7.2.3 核心工程化代码:Redis 布隆过滤器实现恶意 IP 拦截

java

运行

复制代码
@Service
public class RiskControlService {
    @Autowired
    private RedissonClient redissonClient;
    // 布隆过滤器:存储恶意IP
    private final RBloomFilter<String> maliciousIpBloom = redissonClient.getBloomFilter("malicious_ip");

    // 初始化布隆过滤器:预计100万IP,误判率0.01
    @PostConstruct
    public void initBloomFilter() {
        maliciousIpBloom.tryInit(1000000L, 0.01);
        // 预加载恶意IP(实际从数据库/配置中心加载)
        maliciousIpBloom.add("192.168.1.100");
        maliciousIpBloom.add("10.0.0.5");
    }

    // 检测IP是否为恶意IP
    public boolean isMaliciousIp(String ip) {
        return maliciousIpBloom.contains(ip);
    }

    // 添加恶意IP到布隆过滤器
    public void addMaliciousIp(String ip) {
        maliciousIpBloom.add(ip);
    }
}

// 风控拦截器
@Component
public class RiskControlInterceptor implements HandlerInterceptor {
    @Autowired
    private RiskControlService riskControlService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String ip = request.getRemoteAddr();
        if (riskControlService.isMaliciousIp(ip)) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.getWriter().write("禁止访问,您的IP存在恶意行为!");
            return false;
        }
        return true;
    }
}

7.3 项目 3:推荐系统(推荐算法落地)

7.3.1 核心业务问题

推荐系统的核心是根据用户行为推荐个性化内容 (如商品、文章、视频),需要通过算法挖掘用户的兴趣偏好

73.2 核心算法选型

表格

问题场景 算法 / 数据结构 选型原因
关键词联想 前缀树(Trie 树) 高效前缀匹配,支持联想
基于用户的协同过滤 相似度算法(皮尔逊相关系数) 找到相似兴趣的用户,推荐内容
热门内容排序 堆排序(PriorityQueue) 快速获取 TopN 热门内容
用户兴趣存储 哈希表 + 红黑树 O (1) 查找,有序存储兴趣标签
7.3.3 核心工程化代码:Trie 树实现商品关键词联想

java

运行

复制代码
@Service
public class RecommendService {
    // 初始化Trie树,存储商品关键词
    private final TrieTree trieTree = new TrieTree();

    // 初始化商品关键词(实际从数据库加载)
    @PostConstruct
    public void initTrieTree() {
        List<String> goodsKeywords = Arrays.asList("java编程", "java算法", "java开发", "python编程", "python数据分析");
        goodsKeywords.forEach(trieTree::insert);
    }

    // 关键词联想:根据前缀返回所有匹配的关键词
    public List<String> keywordAssociate(String prefix) {
        return findAllWordsWithPrefix(trieTree, prefix);
    }

    // 从Trie树中查找所有以prefix为前缀的单词
    private List<String> findAllWordsWithPrefix(TrieTree trieTree, String prefix) {
        List<String> result = new ArrayList<>();
        TrieTree.TrieNode curr = trieTree.getRoot();
        // 先找到前缀的最后一个节点
        for (char c : prefix.toCharArray()) {
            int index = c - 'a';
            if (curr.getChildren()[index] == null) {
                return result; // 无前缀匹配,返回空
            }
            curr = curr.getChildren()[index];
        }
        // 深度优先遍历,获取所有子节点的单词
        dfs(curr, new StringBuilder(prefix), result);
        return result;
    }

    // DFS遍历Trie树,拼接单词
    private void dfs(TrieTree.TrieNode node, StringBuilder sb, List<String> result) {
        if (node.isEnd()) {
            result.add(sb.toString());
        }
        for (int i = 0; i < node.getChildren().length; i++) {
            TrieTree.TrieNode child = node.getChildren()[i];
            if (child != null) {
                sb.append((char) ('a' + i));
                dfs(child, sb, result);
                sb.deleteCharAt(sb.length() - 1);
            }
        }
    }
}

// Controller
@RestController
@RequestMapping("/recommend")
public class RecommendController {
    @Autowired
    private RecommendService recommendService;

    @GetMapping("/associate")
    public List<String> associate(@RequestParam String prefix) {
        return recommendService.keywordAssociate(prefix);
    }
}
// 访问:/recommend/associate?prefix=java → 返回:[java编程, java算法, java开发]
相关推荐
ZHOUPUYU10 小时前
PHP 8.3网关优化:我用JIT将QPS提升300%的真实踩坑录
开发语言·php
寻寻觅觅☆14 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子15 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
l1t15 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
青云计划15 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿15 小时前
Jsoniter(java版本)使用介绍
java·开发语言
化学在逃硬闯CS15 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar12316 小时前
C++使用format
开发语言·c++·算法
探路者继续奋斗16 小时前
IDD意图驱动开发之意图规格说明书
java·规格说明书·开发规范·意图驱动开发·idd
码说AI16 小时前
python快速绘制走势图对比曲线
开发语言·python