惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
WordPress大学
WordPress大学
博客园_首页
罗磊的独立博客
D
Docker
N
Netflix TechBlog - Medium
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
D
DataBreaches.Net
I
InfoQ
L
LangChain Blog
GbyAI
GbyAI
V
V2EX
博客园 - 聂微东
P
Proofpoint News Feed
博客园 - 【当耐特】
腾讯CDC
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
Martin Fowler
Martin Fowler
有赞技术团队
有赞技术团队
U
Unit 42
博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏

博客园 - work hard work smart

Java 面试1 Java 常见面试问题 WebStorm 创建react工程 构建企业级 Text-to-SQL Agent:基于 LangGraph 的智能数据查询系统设计 Harness 工程:驾驭 AI Agent 的工程化艺术 DeepAgents 多智能体架构实战:从设计模式到后端选型 Vue 自定义组件完全指南:从零构建待办事项应用 使用 LangChain + Hugging Face 构建文本向量化服务 SQLAlchemy 使用详解 Python 中使用 Elasticsearch 的完整指南 Qdrant 向量数据库使用指南 OpenEvals 快速入门:LLM 评估指南 DeepEval 快速入门:LLM 应用评估指南 LangSmith 批量评估完全指南 Qwen-Agent 入门指南:快速构建智能体应用 LangSmith 集成实战:从追踪到评估的完整指南 初识 go-zero:一款让你写后端更规范、更高效的 Go 微服务框架 RAG 中为什么需要 Rerank,以及如何使用 Rerank LangChain4j RAG 核心组件与组合方式 如何使用 Elasticsearch 进行全文检索和向量检索 MinerU Docker 部署指南 5 分钟上手:为 Cline 配置一个免费的 MCP 天气服务 Neo4j 图数据库安装与 Spring Boot 集成实战指南 LangFuse 实战指南:用 @observe 三行代码给 LLM 应用加上全链路追踪 Function Call 深度解析:让大模型从"嘴炮"到"实干"的技术革命 Spring AI 提示词模板实战:告别硬编码,实现提示词工程化管理 LangChain4j 实战指南:用 Java 轻松构建 AI 应用 Spring AI 对话短期记忆实战:让大模型拥有"记忆力" Spring AI 提示词工程实战:让大模型更懂你的意图 Spring AI ChatClient 深度解析:优雅构建大模型应用的利器
手撕java常用代码
work hard work smart · 2026-09-14 · via 博客园 - work hard work smart

1、两个线程,基数和偶数交替打印

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class PrintOddEven {
    private static int num = 1;
    private static final int MAX = 100;
    private static final ReentrantLock lock = new ReentrantLock();
    // 定义两个条件变量:一个专属于奇数线程,一个专属于偶数线程
    private static final Condition oddCondition = lock.newCondition();
    private static final Condition evenCondition = lock.newCondition();

    public static void main(String[] args) {
        // 奇数线程
        new Thread(() -> {
            while (num <= MAX) {
                lock.lock();
                try {
                    // 不是奇数该打印的时候,就在奇数条件上等待
                    if (num % 2 == 0) {
                        oddCondition.await(); // 释放锁并等待
                    }
                    if (num <= MAX) {
                        System.out.println(Thread.currentThread().getName() + ": " + num++);
                        // 打印完奇数,精准唤醒等待中的偶数线程
                        evenCondition.signal();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
            }
        }, "奇数线程").start();

        // 偶数线程
        new Thread(() -> {
            while (num <= MAX) {
                lock.lock();
                try {
                    // 不是偶数该打印的时候,就在偶数条件上等待
                    if (num % 2 != 0) {
                        evenCondition.await(); // 释放锁并等待
                    }
                    if (num <= MAX) {
                        System.out.println(Thread.currentThread().getName() + ": " + num++);
                        // 打印完偶数,精准唤醒等待中的奇数线程
                        oddCondition.signal();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
            }
        }, "偶数线程").start();
    }
}

2、手写一个阻塞队列

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class MyBlockingQueue<T> {
    private final Queue<T> queue = new LinkedList<>();
    private final int capacity; // 队列容量
    private final ReentrantLock lock = new ReentrantLock();
    // 两个条件变量:一个给生产者等,一个给消费者等
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    public MyBlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    // 生产者:入队
    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            // 核心:用 while 防止虚假唤醒,队列满则等待
            while (queue.size() == capacity) {
                System.out.println(Thread.currentThread().getName() + " 队列已满,生产者等待...");
                notFull.await(); // 释放锁,挂起
            }
            queue.offer(element);
            System.out.println(Thread.currentThread().getName() + " 生产了: " + element + ",当前队列大小: " + queue.size());
            // 生产成功,唤醒等待的消费者
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    // 消费者:出队
    public T take() throws InterruptedException {
        lock.lock();
        try {
            // 核心:队列空则等待
            while (queue.isEmpty()) {
                System.out.println(Thread.currentThread().getName() + " 队列为空,消费者等待...");
                notEmpty.await(); // 释放锁,挂起
            }
            T element = queue.poll();
            System.out.println(Thread.currentThread().getName() + " 消费了: " + element + ",当前队列大小: " + queue.size());
            // 消费成功,唤醒等待的生产者
            notFull.signal();
            return element;
        } finally {
            lock.unlock();
        }
    }

    // 获取当前队列大小(加锁保证可见性)
    public int size() {
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }
}

3、求10亿数字的大文件,计算出最大的100个数字

解决海量数据 Top K 的标准套路是两步走:

第一步:分治(Hash 分片,大化小)
把 10 亿个数据通过哈希函数拆分到多个小文件中,保证相同数据落在同一个文件,且每个小文件能读入内存。

遍历大文件,对每个数字 x 计算 hash(x) % 1000,将其写入对应的第 i 个小文件。

这样得到 1000 个小文件,每个文件约 100 万个数字(约 4MB),完全可以读入内存。

第二步:小顶堆(局部 Top K)
对每个小文件,用一个大小为 100 的小顶堆求出局部最大的 100 个数。

为什么用小顶堆? 因为我们要找最大的 K 个,堆顶是最小的元素,方便判断新元素是否比堆顶大。

遍历小文件中的数字:

如果堆未满(size < 100),直接入堆。

如果堆已满,且当前数字 > 堆顶,则弹出堆顶,将当前数字入堆。

否则,跳过(当前数字太小,没资格进前 100)。

最终,每个小文件得到一个局部 Top 100。

第三步:全局归并
将 1000 个小文件产生的 1000 × 100 = 10 万个数字汇总,再次用大小为 100 的小顶堆,求出最终的全局 Top 100。

import java.util.PriorityQueue;
import java.util.Random;

public class TopK {

    /**
     * 从数组中找出最大的 K 个数(小顶堆实现)
     * @param nums 输入数组
     * @param k 目标数量
     * @return 最大的 K 个数(数组)
     */
    public static int[] findTopK(int[] nums, int k) {
        if (k <= 0 || nums == null || nums.length == 0) {
            return new int[0];
        }
        // 小顶堆,默认就是小顶堆(堆顶最小)
        PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);

        for (int num : nums) {
            if (minHeap.size() < k) {
                minHeap.offer(num);
            } else if (num > minHeap.peek()) {
                // 比堆顶大,说明有资格进前 K
                minHeap.poll(); // 弹出最小的
                minHeap.offer(num); // 加入新的
            }
        }

        // 将堆中元素转为数组返回
        int[] result = new int[minHeap.size()];
        int i = 0;
        for (int num : minHeap) {
            result[i++] = num;
        }
        return result;
    }

    public static void main(String[] args) {
        // 模拟 10 亿数据太大,这里用 1000 万数据做演示
        int dataSize = 10_000_000;
        int[] data = new int[dataSize];
        Random random = new Random();
        for (int i = 0; i < dataSize; i++) {
            data[i] = random.nextInt(1_000_000_000);
        }

        int k = 100;
        long start = System.currentTimeMillis();
        int[] topK = findTopK(data, k);
        long end = System.currentTimeMillis();

        System.out.println("耗时: " + (end - start) + "ms");
        System.out.println("Top 100 最小值: " + topK[0]); // 堆顶是最小的
    }
}

3、手写限流器 令牌桶实现

每秒最多capacity个容量的token

public class TokenBucketRateLimiter {
    private final long capacity;        // 桶容量
    private final long rate;            // 令牌生成速率(个/秒)
    private long tokens;                // 当前令牌数
    private long lastRefillTime;        // 上次补充令牌的时间

    public TokenBucketRateLimiter(long capacity, long rate) {
        this.capacity = capacity;
        this.rate = rate;
        this.tokens = capacity;
        this.lastRefillTime = System.currentTimeMillis();
    }

    public synchronized boolean tryAcquire() {
        refill();
        if (tokens > 0) {
            tokens--;
            return true;
        }
        return false;
    }

    private void refill() {
        long now = System.currentTimeMillis();
        long elapsedTime = now - lastRefillTime;
        // 计算这段时间内应该补充的令牌数
        long newTokens = elapsedTime * rate / 1000;
        if (newTokens > 0) {
            tokens = Math.min(capacity, tokens + newTokens);
            lastRefillTime = now;
        }
    }
}

4、手写一个快速排序算法

“快速排序是分治算法。核心是选一个基准值,通过分区把小于它的放左边,大于它的放右边,基准值归位。然后递归处理左右子数组。

选一个基准数,把比它小的扔左边,比它大的扔右边,然后对左右两边重复这个动作。

 极简图解(一看就懂)
假设数组是 [5, 3, 8, 4, 2],我们选最左边的 5 作为基准数。
目标:把小于 5 的放左边,大于 5 的放右边。
定义两个指针,i 指向最左(5),j 指向最右(2)。
j 先动:从右往左找比 5 小的数,找到了 2。
i 再动:从左往右找比 5 大的数,找到了 8。
交换 8 和 2,数组变成 [5, 3, 2, 4, 8]。
继续:j 往左走,找到了 4(比5小);i 往右走,碰到了 j,停止。
最后一步:把基准数 5 和 i 当前位置的数(4)交换。
数组变成 [4, 3, 2, 5, 8]。
看!5 已经归位了。 左边 [4, 3, 2] 全比 5 小,右边 [8] 全比 5 大。
接下来,只要对左边的 [4, 3, 2] 和右边的 [8] 重复上面的动作,整个数组就排好了。

public class SimpleQuickSort {

    public static void quickSort(int[] arr, int left, int right) {
        // 1. 递归终止条件:区间里没有数或者只有一个数,天然有序
        if (left >= right) {
            return;
        }

        // 2. 选最左边的数作为基准值
        int pivot = arr[left];
        int i = left;
        int j = right;

        // 3. 开始分区(核心就这个 while 循环)
        while (i < j) {
            // 先从右往左,找比基准值小的数
            while (i < j && arr[j] >= pivot) {
                j--;
            }
            // 再从左往右,找比基准值大的数
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            // 交换这两个数
            if (i < j) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }

        // 4. 基准值归位(和 i 位置交换)
        arr[left] = arr[i];
        arr[i] = pivot;

        // 5. 递归处理左边和右边
        quickSort(arr, left, i - 1);
        quickSort(arr, i + 1, right);
    }

    public static void main(String[] args) {
        int[] arr = {5, 3, 8, 4, 2};
        quickSort(arr, 0, arr.length - 1);

        // 打印结果
        for (int num : arr) {
            System.out.print(num + " ");
        }
        // 输出:2 3 4 5 8
    }
}

5、原生生成器

有一个随机生成器 rand(),以概率 p 输出 1,以概率 1-p 输出 0。请实现一个函数 rand50(),让它以严格 50% 的概率输出 1,50% 的概率输出 0。

/**
 * 原生成器:以概率 p 输出 1,以概率 1-p 输出 0
 */
public int rand() {
    // 假设这个方法已经存在,我们只能调用它
    // 内部实现未知,但保证 P(1) = p, P(0) = 1-p
    return Math.random() < p ? 1 : 0;
}

/**
 * 目标:以严格 50% 的概率输出 1,50% 输出 0
 */
public int rand50() {
    while (true) {
        int a = rand();
        int b = rand();

        // 情况 1:先 0 后 1,输出 1
        if (a == 0 && b == 1) {
            return 1;
        }
        // 情况 2:先 1 后 0,输出 0
        if (a == 1 && b == 0) {
            return 0;
        }
        // 情况 3:(0,0) 或 (1,1),丢弃,重新循环
    }
}