JAVA数据结构与算法 - 基础:数组深度解析

数据结构与算法 - 基础:数组深度解析

一、数组的底层本质

1.1 内存视角下的数组

数组是所有数据结构中最朴素的一种。用最简单的物理比喻:数组就像一排连号的储物柜,每个柜子大小完全一致,编号从 0 开始依次递增。你不需要打开每个柜子才知道里面放了什么------只要知道起始位置和偏移量,就能立刻定位到任意一个柜子。

在 JVM 的堆内存中,数组对象的结构大致如下:

scss 复制代码
┌─────────────────────────────────────────────────────┐
│  对象头(Mark Word + 类型指针)                        │
├─────────────────────────────────────────────────────┤
│  length 字段 (int, 4字节)                            │
├──────────┬──────────┬──────────┬──────────┬─────────┤
│  元素[0]  │  元素[1]  │  元素[2]  │  ......  │元素[n-1] │
└──────────┴──────────┴──────────┴──────────┴─────────┘

这种排布方式带来了数组最重要的性能特征:随机访问的时间复杂度为 O(1)。计算任意元素 i 的地址只需要一个公式:

css 复制代码
address(i) = 基地址 + i × 每个元素占用的字节数

不需要循环,不需要遍历,一次乘法加一次加法就完成了定位。

1.2 基础操作实现

下面是一个完整的数组基础操作演示程序:

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

public class ArrayFundamentals {

    public static void main(String[] args) {
        // 三种声明与初始化方式
        int[] arr1 = new int[5];                    // 方式一:指定长度,默认值 0
        int[] arr2 = {10, 20, 30, 40, 50};          // 方式二:静态初始化
        var arr3 = new Integer[]{1, 2, 3, 4, 5};    // 方式三:匿名数组声明

        System.out.println("========== 数组基础操作演示 ==========");

        // 逐元素赋值
        for (int i = 0; i < arr1.length; i++) {
            arr1[i] = i * 100;
        }
        System.out.println("赋值后的 arr1: " + Arrays.toString(arr1));

        // 按下标读取
        System.out.println("arr2[2] = " + arr2[2]);  // 输出: 30

        // length 是属性不是方法
        System.out.println("arr2 长度: " + arr2.length);

        // 遍历的多种写法
        System.out.print("标准 for: ");
        for (int i = 0; i < arr3.length; i++) {
            System.out.print(arr3[i] + " ");
        }

        System.out.print("\n增强 for: ");
        for (Integer val : arr3) {
            System.out.print(val + " ");
        }

        // 边界检查:以下代码会抛出 ArrayIndexOutOfBoundsException
        // System.out.println(arr2[100]); // 取消注释测试越界
    }
}

二、多维数组的内部实现

2.1 二维数组 ≠ 矩阵

很多人以为 Java 的二维数组是一个连续的矩阵块,实际上它是一种"数组的数组"结构。int[3][4] 意味着堆上有 1 个长度为 3 的引用数组 + 3 个长度为 4 的实际 int 数组------它们可能在内存的任何位置。

java 复制代码
public class MultiDimensionalArray {

    public static void main(String[] args) {
        System.out.println("========== 二维数组结构分析 ==========");

        // 声明 3×4 的标准二维数组
        int[][] matrix = new int[3][4];
        initMatrix(matrix);
        printMatrixSimple(matrix);

        // 不规则(锯齿)二维数组:每一行长度可以不同
        System.out.println("\n--- 不规则二维数组 ---");
        int[][] jagged = {
                {1, 2, 3},
                {4, 5},
                {6, 7, 8, 9}
        };
        for (int i = 0; i < jagged.length; i++) {
            System.out.println("第" + i + "行长度: " + jagged[i].length);
        }

        // 三维数组示例:2×3×4
        System.out.println("\n--- 三维数组遍历 ---");
        int[][][] cube = new int[2][3][4];
        fillCube(cube);
        printCubeSlice(cube, 0);
    }

    private static void initMatrix(int[][] matrix) {
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                matrix[i][j] = i * matrix[i].length + j + 1;
            }
        }
    }

    private static void printMatrixSimple(int[][] matrix) {
        System.out.println("标准二维数组内容:");
        for (int[] row : matrix) {
            for (int val : row) {
                System.out.printf("%3d ", val);
            }
            System.out.println();
        }
    }

    private static void fillCube(int[][][] cube) {
        int value = 1;
        for (int i = 0; i < cube.length; i++) {
            for (int j = 0; j < cube[i].length; j++) {
                for (int k = 0; k < cube[i][j].length; k++) {
                    cube[i][j][k] = value++;
                }
            }
        }
    }

    private static void printCubeSlice(int[][][] cube, int layer) {
        System.out.println("三维数组第" + layer + "层:");
        for (int j = 0; j < cube[layer].length; j++) {
            for (int k = 0; k < cube[layer][j].length; k++) {
                System.out.printf("%3d ", cube[layer][j][k]);
            }
            System.out.println();
        }
    }
}

2.2 内存布局图

csharp 复制代码
int[2][3] 的内存视图:
栈上: matrix (引用)
       ↓
堆:   ┌─────────┬─────────┐
      │ ref[0]  │ ref[1]  │    ← 长度为 2 的引用数组
      └────┬────┴────┬────┘
           ↓         ↓
   ┌───┬───┬───┐  ┌───┬───┬───┐
   │0,0│0,1│0,2│  │1,0│1,1│1,2│  ← 两个长度为 3 的 int 数组
   └───┴───┴───┘  └───┴───┴───┘

三、Arrays 工具类全解

java.util.Arrays 是操作数组的瑞士军刀。即使你已经使用它多年,以下这个完整示例仍然可能包含你没注意过的功能:

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

public class ArraysUtilityDemo {

    public static void main(String[] args) {
        System.out.println("========== Arrays 工具类完整演示 ==========\n");

        int[] numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6, 0};

        // ======== 排序 ========
        System.out.println("--- 排序 ---");
        System.out.println("原数组:       " + Arrays.toString(numbers));

        int[] copy1 = Arrays.copyOf(numbers, numbers.length);
        Arrays.sort(copy1);
        System.out.println("全量排序:     " + Arrays.toString(copy1));

        int[] copy2 = Arrays.copyOf(numbers, numbers.length);
        Arrays.sort(copy2, 2, 7);
        System.out.println("区间排序[2,7):" + Arrays.toString(copy2));

        // ======== 查找 ========
        System.out.println("\n--- 二分查找 ---");
        int[] sorted = {1, 3, 5, 7, 9, 11, 13};
        System.out.println("有序数组: " + Arrays.toString(sorted));
        System.out.println("查找 7 的位置: " + Arrays.binarySearch(sorted, 7));    // 输出: 3
        System.out.println("查找 8 的位置: " + Arrays.binarySearch(sorted, 8));    // 输出: -5(插入点取反-1)

        // ======== 填充 ========
        System.out.println("\n--- 填充 ---");
        int[] filled = new int[5];
        Arrays.fill(filled, 42);
        System.out.println("全量填充 42: " + Arrays.toString(filled));
        Arrays.fill(filled, 1, 3, 99);
        System.out.println("区间[1,3)填99:" + Arrays.toString(filled));

        // ======== 相等判断 ========
        System.out.println("\n--- 相等判断 ---");
        int[] a = {1, 2, 3};
        int[] b = {1, 2, 3};
        System.out.println("a == b:        " + (a == b));           // false(引用比较)
        System.out.println("a.equals(b):   " + a.equals(b));       // false(未重写equals)
        System.out.println("Arrays.equals: " + Arrays.equals(a, b)); // true

        // ======== 拷贝 ========
        System.out.println("\n--- 拷贝 ---");
        int[] original = {10, 20, 30, 40, 50};
        int[] copyAll = Arrays.copyOf(original, original.length);
        int[] copyTruncated = Arrays.copyOf(original, 3);
        int[] copyExpanded = Arrays.copyOf(original, 8);
        System.out.println("原数组:     " + Arrays.toString(original));
        System.out.println("完整拷贝:   " + Arrays.toString(copyAll));
        System.out.println("截断拷贝(3):" + Arrays.toString(copyTruncated));
        System.out.println("扩展拷贝(8):" + Arrays.toString(copyExpanded));

        // 使用 System.arraycopy 做区段拷贝
        int[] dest = new int[5];
        System.arraycopy(original, 1, dest, 2, 3);
        System.out.println("System.arraycopy(从1开始,复制3个,放到dest[2]): "
                          + Arrays.toString(dest));

        // ======== Stream 流式操作 (JDK8+) ========
        System.out.println("\n--- Stream 流式操作 ---");
        int[] streamArr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int sum = Arrays.stream(streamArr).sum();
        double avg = Arrays.stream(streamArr).average().orElse(0);
        int max = Arrays.stream(streamArr).max().orElse(-1);
        System.out.println("求和: " + sum + ", 平均: " + avg + ", 最大值: " + max);

        int[] evens = Arrays.stream(streamArr)
                          .filter(x -> x % 2 == 0)
                          .toArray();
        System.out.println("所有偶数: " + Arrays.toString(evens));
    }
}

四、数组 vs ArrayList ------ 深度对比

4.1 核心差异矩阵

维度 数组 ArrayList
容量 固定,创建后不可变 动态,自动扩容(1.5倍)
元素类型 基本类型 + 引用类型 仅引用类型(有自动装箱)
泛型支持 不支持 支持 <T>
内存效率 更高(无额外包装) 稍低(对象头 + 扩容预留)
性能 略快 方法调用有一定开销
工具方法 依赖 Arrays 工具类 丰富的实例方法
线程安全 需自行同步 Vector / CopyOnWriteArrayList

4.2 性能对比实测

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

public class ArrayVsArrayList {

    private static final int SIZE = 10_000_000;
    private static final Random rand = new Random();

    public static void main(String[] args) {
        System.out.println("========== 数组 vs ArrayList 性能对比 (n=" + SIZE + ") ==========\n");

        testWriteRandomAccess();
        testReadRandomAccess();
        testSequentialSum();
    }

    /** 测试随机写入性能 */
    private static void testWriteRandomAccess() {
        int[] arr = new int[SIZE];
        ArrayList<Integer> list = new ArrayList<>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            list.add(0); // 预填充,排除扩容干扰
        }

        // 生成随机索引序列
        int[] indices = new int[100_000];
        for (int i = 0; i < indices.length; i++) {
            indices[i] = rand.nextInt(SIZE);
        }

        long t1 = System.nanoTime();
        for (int idx : indices) {
            arr[idx] = idx;
        }
        long t2 = System.nanoTime();

        long t3 = System.nanoTime();
        for (int idx : indices) {
            list.set(idx, idx);
        }
        long t4 = System.nanoTime();

        System.out.printf("随机写入(×%d): 数组=%.2fms  ArrayList=%.2fms  比值=%.2fx\n",
                indices.length,
                (t2 - t1) / 1_000_000.0,
                (t4 - t3) / 1_000_000.0,
                (double)(t4 - t3) / (t2 - t1));
    }

    /** 测试随机读取性能 */
    private static void testReadRandomAccess() {
        int[] arr = new int[SIZE];
        ArrayList<Integer> list = new ArrayList<>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            arr[i] = i;
            list.add(i);
        }

        int[] indices = new int[100_000];
        for (int i = 0; i < indices.length; i++) {
            indices[i] = rand.nextInt(SIZE);
        }

        long sum1 = 0, sum2 = 0;

        long t1 = System.nanoTime();
        for (int idx : indices) {
            sum1 += arr[idx];
        }
        long t2 = System.nanoTime();

        long t3 = System.nanoTime();
        for (int idx : indices) {
            sum2 += list.get(idx);
        }
        long t4 = System.nanoTime();

        System.out.printf("随机读取(×%d): 数组=%.2fms  ArrayList=%.2fms  比值=%.2fx\n",
                indices.length,
                (t2 - t1) / 1_000_000.0,
                (t4 - t3) / 1_000_000.0,
                (double)(t4 - t3) / (t2 - t1));
        System.out.println("  (校验: sum1=" + sum1 + ", sum2=" + sum2 + ")");
    }

    /** 测试顺序遍历求和 */
    private static void testSequentialSum() {
        int[] arr = new int[SIZE];
        ArrayList<Integer> list = new ArrayList<>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            arr[i] = i;
            list.add(i);
        }

        long t1 = System.nanoTime();
        long sum1 = 0;
        for (int val : arr) {
            sum1 += val;
        }
        long t2 = System.nanoTime();

        long t3 = System.nanoTime();
        long sum2 = 0;
        for (int val : list) {
            sum2 += val;
        }
        long t4 = System.nanoTime();

        System.out.printf("顺序遍历求和: 数组=%.2fms  ArrayList=%.2fms  比值=%.2fx\n",
                (t2 - t1) / 1_000_000.0,
                (t4 - t3) / 1_000_000.0,
                (double)(t4 - t3) / (t2 - t1));
    }
}

五、数组的经典算法应用

5.1 双指针技巧

双指针是数组问题中最常用的技巧之一。基本思想是使用两个索引变量(如 left 和 right)从不同方向或不同速度遍历数组。

应用场景一:反转数组

java 复制代码
// 双指针从两端向中间移动,交换元素
// 时间复杂度 O(n),空间复杂度 O(1)
public static void reverse(int[] arr) {
    int left = 0;
    int right = arr.length - 1;
    while (left < right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
        left++;
        right--;
    }
}

应用场景二:两数之和(有序数组)

java 复制代码
public class TwoPointerAlgorithms {

    public static void main(String[] args) {
        System.out.println("========== 双指针算法演示 ==========\n");

        // 测试反转
        int[] nums1 = {1, 2, 3, 4, 5, 6, 7};
        System.out.print("反转前: ");
        printArray(nums1);
        reverseInPlace(nums1);
        System.out.print("反转后: ");
        printArray(nums1);

        // 测试两数之和
        int[] sortedArr = {2, 5, 7, 10, 13, 18, 25};
        int target = 23;
        int[] result = twoSum(sortedArr, target);
        if (result[0] != -1) {
            System.out.printf("\n两数之和: %d + %d = %d (下标 %d, %d)\n",
                    sortedArr[result[0]], sortedArr[result[1]], target,
                    result[0], result[1]);
        }

        // 测试移除重复元素
        int[] withDups = {1, 1, 2, 2, 2, 3, 4, 5, 5, 6};
        System.out.print("\n去重前: ");
        printArray(withDups);
        int newLen = removeDuplicates(withDups);
        System.out.print("去重后: ");
        for (int i = 0; i < newLen; i++) {
            System.out.print(withDups[i] + " ");
        }
        System.out.println("(有效长度=" + newLen + ")");
    }

    /**
     * 原地反转数组 ------ 对撞指针
     * 时间复杂度: O(n)
     * 空间复杂度: O(1)
     */
    public static void reverseInPlace(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        while (left < right) {
            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
            left++;
            right--;
        }
    }

    /**
     * 在有序数组中查找两数之和等于 target 的下标
     * 利用有序性使用对撞指针,时间复杂度 O(n)
     * 返回 [-1, -1] 表示未找到
     */
    public static int[] twoSum(int[] sortedArr, int target) {
        int left = 0;
        int right = sortedArr.length - 1;
        while (left < right) {
            int sum = sortedArr[left] + sortedArr[right];
            if (sum == target) {
                return new int[]{left, right};
            } else if (sum < target) {
                left++;   // 和太小,左指针右移增大和
            } else {
                right--;  // 和太大,右指针左移减小和
            }
        }
        return new int[]{-1, -1};
    }

    /**
     * 移除有序数组中的重复元素(原地修改)
     * 使用快慢指针,返回去重后的有效长度
     * 时间复杂度: O(n)
     * 空间复杂度: O(1)
     */
    public static int removeDuplicates(int[] arr) {
        if (arr.length == 0) return 0;
        int slow = 0; // 慢指针:跟踪已处理的不重复元素末尾
        for (int fast = 1; fast < arr.length; fast++) {
            if (arr[fast] != arr[slow]) {
                slow++;
                arr[slow] = arr[fast];
            }
        }
        return slow + 1;
    }

    private static void printArray(int[] arr) {
        System.out.print("[");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]);
            if (i < arr.length - 1) System.out.print(", ");
        }
        System.out.println("]");
    }
}

5.2 滑动窗口

滑动窗口用于处理连续子数组问题。窗口的左右边界都在数组范围内滑动,避免重复计算。

java 复制代码
import java.util.HashSet;

public class SlidingWindowDemo {

    public static void main(String[] args) {
        System.out.println("========== 滑动窗口算法演示 ==========\n");

        // 测试最大子数组和
        int[] arr1 = {2, 1, 5, 1, 3, 2};
        int k = 3;
        int maxSum = maxSubarraySum(arr1, k);
        System.out.printf("数组: %s, 窗口大小 k=%d\n", arrayToString(arr1), k);
        System.out.printf("最大子数组和 = %d\n\n", maxSum);

        // 测试最长无重复子串长度
        int[] arr2 = {1, 2, 3, 2, 5, 6, 1, 7};
        int maxLen = longestUniqueSubarray(arr2);
        System.out.printf("数组: %s\n", arrayToString(arr2));
        System.out.printf("最长无重复子数组长度 = %d\n\n", maxLen);

        // 测试最小长度子数组(和 >= target)
        int[] arr3 = {2, 3, 1, 2, 4, 3};
        int target = 7;
        int minLen = minSubarrayLen(arr3, target);
        System.out.printf("数组: %s, target=%d\n", arrayToString(arr3), target);
        System.out.printf("和>=%d的最小子数组长度 = %d\n", target, minLen);
    }

    /**
     * 固定窗口大小:求长度为 k 的连续子数组的最大和
     * 时间复杂度: O(n)
     */
    public static int maxSubarraySum(int[] arr, int k) {
        if (arr.length < k) return -1;
        int windowSum = 0;
        // 先计算第一个窗口的和
        for (int i = 0; i < k; i++) {
            windowSum += arr[i];
        }
        int maxSum = windowSum;
        // 滑动窗口:加入右侧新元素,移除左侧旧元素
        for (int i = k; i < arr.length; i++) {
            windowSum += arr[i] - arr[i - k];
            maxSum = Math.max(maxSum, windowSum);
        }
        return maxSum;
    }

    /**
     * 可变窗口大小:求不包含重复元素的最长连续子数组的长度
     * 使用 HashSet 记录窗口内的元素
     * 时间复杂度: O(n)
     */
    public static int longestUniqueSubarray(int[] arr) {
        HashSet<Integer> window = new HashSet<>();
        int left = 0;
        int maxLen = 0;
        for (int right = 0; right < arr.length; right++) {
            // 遇到重复时,从左侧收缩直至移除重复
            while (window.contains(arr[right])) {
                window.remove(arr[left]);
                left++;
            }
            window.add(arr[right]);
            maxLen = Math.max(maxLen, right - left + 1);
        }
        return maxLen;
    }

    /**
     * 可变窗口大小:求和 >= target 的最短连续子数组的长度
     * 时间复杂度: O(n)
     */
    public static int minSubarrayLen(int[] arr, int target) {
        int left = 0;
        int windowSum = 0;
        int minLen = Integer.MAX_VALUE;
        for (int right = 0; right < arr.length; right++) {
            windowSum += arr[right];
            // 满足条件时,尝试收缩左侧
            while (windowSum >= target) {
                minLen = Math.min(minLen, right - left + 1);
                windowSum -= arr[left];
                left++;
            }
        }
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }

    private static String arrayToString(int[] arr) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < arr.length; i++) {
            sb.append(arr[i]);
            if (i < arr.length - 1) sb.append(", ");
        }
        sb.append("]");
        return sb.toString();
    }
}

六、常见面试题精解

6.1 如何高效地向一个有序数组中插入元素

有序数组插入的难点在于需要"腾出位置"。如果直接遍历找到插入点再移动,整个过程是 O(n)。

java 复制代码
// 二分定位插入点 + System.arraycopy 批量移动
// 时间复杂度: O(log n + n) = O(n),但比逐个移动快得多
import java.util.Arrays;

public class SortedArrayInsert {
    public static int[] insert(int[] arr, int value) {
        // 使用二分查找找到插入位置
        int pos = Arrays.binarySearch(arr, value);
        if (pos < 0) {
            pos = -(pos + 1); // 转换为插入点
        }
        // 扩容并复制
        int[] newArr = Arrays.copyOf(arr, arr.length + 1);
        System.arraycopy(newArr, pos, newArr, pos + 1, newArr.length - pos - 1);
        newArr[pos] = value;
        return newArr;
    }

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        System.out.println("原数组: " + Arrays.toString(arr));
        arr = insert(arr, 4);
        System.out.println("插入 4: " + Arrays.toString(arr));
        arr = insert(arr, 0);
        System.out.println("插入 0: " + Arrays.toString(arr));
        arr = insert(arr, 10);
        System.out.println("插入10: " + Arrays.toString(arr));
    }
}

6.2 找出数组中第 k 大的元素(不排序)

java 复制代码
import java.util.PriorityQueue;

public class KthLargest {
    /**
     * 使用最小堆维护 k 个最大元素
     * 时间复杂度: O(n log k)
     * 空间复杂度: O(k)
     */
    public static int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        for (int num : nums) {
            minHeap.offer(num);
            if (minHeap.size() > k) {
                minHeap.poll(); // 移除堆中最小的
            }
        }
        return minHeap.peek();
    }

    public static void main(String[] args) {
        int[] arr = {3, 2, 1, 5, 6, 4};
        System.out.println("数组: [3, 2, 1, 5, 6, 4]");
        System.out.println("第 2 大: " + findKthLargest(arr, 2)); // 5
        System.out.println("第 4 大: " + findKthLargest(arr, 4)); // 3
    }
}

七、数组的边界与陷阱

Java 中数组最容易踩的坑:

1. 数组是协变的

java 复制代码
Object[] objects = new String[5];        // 编译通过
objects[0] = "hello";                    // 运行正常
objects[0] = 42;                         // 运行时 ArrayStoreException

数组在编译期不检查元素类型兼容性,这是数组相对于泛型集合的一个安全隐患。所以 Joshua Bloch 在《Effective Java》中明确建议:优先使用泛型集合而非数组

2. length 是属性,size() 是方法

数组的 length 是 JVM 级别的属性,不是方法调用;而 ArrayList.size() 是方法调用。它们看起来相似,但底层机制完全不同。

3. Arrays.asList 返回的 List 长度不可变

java 复制代码
List<Integer> fixed = Arrays.asList(1, 2, 3);
fixed.set(0, 99);    // OK:修改元素
fixed.add(4);        // UnsupportedOperationException!

Arrays.asList 返回的是 Arrays 内部类 ArrayList(不是 java.util.ArrayList),它是数组的一个视图,长度固定。

八、总结

数组作为一切数据结构的基础,它的核心优势是:

  • O(1) 随机访问:这是数组最不可替代的特性
  • 内存紧凑:无额外指针开销,缓存友好
  • 简单可靠:没有复杂的内部逻辑

它的核心劣势也同样清晰:

  • 容量固化:一旦创建无法改变大小
  • 插入/删除慢:需要移动大量元素
  • 类型安全性弱:协变数组可能引发运行时异常

在实战中,你应当在以下场景优先使用数组:

  1. 数据量可控且规模固定
  2. 需要频繁的随机读写
  3. 对内存占用敏感的场景
  4. 基础类型的批量处理(避免装箱开销)

当你需要动态增长、频繁在中间插入删除、或需要强类型安全时,应当果断切换到对应的集合类。

数组是许多高级数据结构的构建材料。栈可以用数组实现,堆可以用数组表示完全二叉树,散列表底层也是数组。理解数组,就是理解数据结构世界的第一块基石。

相关推荐
WL_Aurora2 小时前
Java多线程详解(二):线程池、同步机制与并发工具类
java·多线程·并发
Java技术小馆2 小时前
微信本地数据提取/接入AI神器
java
日月云棠3 小时前
JAVA数据结构与算法 - 基础:核心概念与框架总览
java·后端
倚栏听风雨3 小时前
Spring AI 源码解析:MessageChatMemoryAdvisor 是如何让大模型"记住你"的
后端
传说之后3 小时前
分布式事务指南:从二阶段锁到两阶段提交,了解核心设计
后端
代码丰3 小时前
Spring Boot 做 RAG 文档上传:1GB 文件会不会打爆内存?
后端
蝎子莱莱爱打怪3 小时前
我花两年业余时间做了个IM系统,然后呢😂??
后端·flutter·面试
Dicky-_-zhang3 小时前
分布式系统限流熔断实战:保护微服务稳定性
java·jvm
叫我少年3 小时前
.NET 11 来了:Kestrel 提速 40%,还有这些你可能不知道的变化
后端