Java常见手撕

手写单例模式

public class Singleton {

	//注意private,static
    private static volatile Singleton instance;
	//注意private
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

#手写策略模式

1
2
3
interface PayStrategy {
void pay(int amount);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class AliPay implements PayStrategy {
@Override
public void pay(int amount) {
System.out.println("使用支付宝支付:" + amount);
}
}

class WeChatPay implements PayStrategy {
@Override
public void pay(int amount) {
System.out.println("使用微信支付:" + amount);
}
}

class CreditCardPay implements PayStrategy {
@Override
public void pay(int amount) {
System.out.println("使用信用卡支付:" + amount);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.HashMap;
import java.util.Map;

class StrategyFactory {

//静态
private static final Map<String, PayStrategy> strategies = new HashMap<>();
//静态
static {
strategies.put("ALI", new AliPay());
strategies.put("WX", new WeChatPay());
strategies.put("CARD", new CreditCardPay());
}
//静态

public static PayStrategy getStrategy(String type) {
return strategies.get(type);
}
}
1
2
PayStrategy strategy = StrategyFactory.getStrategy("ALI");
strategy.pay(100);

手写线程池

import java.util.*;
import java.util.concurrent.*;

public class SimpleThreadPool {

    // 任务队列
    private BlockingQueue<Runnable> taskQueue;

    // 工作线程
    private List<Worker> workers = new ArrayList<>();

    public SimpleThreadPool(int poolSize, int queueSize) {
        taskQueue = new ArrayBlockingQueue<>(queueSize);

        for (int i = 0; i < poolSize; i++) {
            Worker worker = new Worker();
            workers.add(worker);
            worker.start();
        }
    }

    // ================<mark> 提交无返回值任务 </mark>================
    public void execute(Runnable task) {
        try {
            taskQueue.put(task);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    // ================<mark> 提交有返回值任务 </mark>================
    public <T> Future<T> submit(Callable<T> task) {
        FutureTask<T> futureTask = new FutureTask<>(task);

        try {
            taskQueue.put(futureTask);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        return futureTask;
    }

    // ================<mark> Worker </mark>================
    private class Worker extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    Runnable task = taskQueue.take();
                    task.run();
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }
}

手写观察者模式

1
2
3
interface Observer {
void onEvent(Object data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.util.*;

class EventBus {

// 普通观察者
private final Map<String, List<Observer>> normalMap = new HashMap<>();

// 一次性观察者
private final Map<String, List<Observer>> onceMap = new HashMap<>();

// 普通订阅
public void subscribe(String eventType, Observer observer) {
normalMap
.computeIfAbsent(eventType, k -> new ArrayList<>())
.add(observer);
}

// 一次性订阅
public void subscribeOnce(String eventType, Observer observer) {
onceMap
.computeIfAbsent(eventType, k -> new ArrayList<>())
.add(observer);
}

// 取消订阅
public void unsubscribe(String eventType, Observer observer) {
List<Observer> list1 = normalMap.get(eventType);
if (list1 != null) {
list1.remove(observer);
}

List<Observer> list2 = onceMap.get(eventType);
if (list2 != null) {
list2.remove(observer);
}
}

// 发布事件
public void publish(String eventType, Object data) {

// 1️⃣ 处理普通观察者
List<Observer> normalList = normalMap.get(eventType);
if (normalList != null) {
for (Observer o : normalList) {
o.onEvent(data);
}
}

// 2️⃣ 处理一次性观察者
List<Observer> onceList = onceMap.get(eventType);
if (onceList != null) {
for (Observer o : onceList) {
o.onEvent(data);
}
// 关键:执行完直接清空
onceMap.remove(eventType);
}
}
}

#手写HashMap

import java.util.Objects;

public class MyHashMap<K, V> {

    static class Node<K, V> {
        K key;
        V value;
        Node<K, V> next;

        Node(K k, V v) {
            key = k;
            value = v;
        }
    }

    private Node<K, V>[] table = new Node[16];
    private int size;
    private static final float LOAD_FACTOR = 0.75f;

    // hash
    private int hash(K key) {
        return key == null ? 0 : key.hashCode();
    }
    
    // put
    public void put(K key, V value) {
        int idx = (table.length - 1) & hash(key);

        Node<K, V> cur = table[idx];

        while (cur != null) {
            if (Objects.equals(cur.key, key)) {
                cur.value = value; // 覆盖
                return;
            }
            cur = cur.next;
        }

        Node<K, V> newNode = new Node<>(key, value);
        newNode.next = table[idx];
        table[idx] = newNode;

        size++;

        if (size > table.length * LOAD_FACTOR) {
            resize();
        }
    }

    // get
    public V get(K key) {
        int idx = (table.length - 1) & hash(key);

        Node<K, V> cur = table[idx];

        while (cur != null) {
            if (Objects.equals(cur.key, key)) {
                return cur.value;
            }
            cur = cur.next;
        }

        return null;
    }

    // resize
    private void resize() {
        Node<K, V>[] old = table;
        table = new Node[old.length * 2];
        size = 0;

        for (Node<K, V> head : old) {
            while (head != null) {
                put(head.key, head.value);
                head = head.next;
            }
        }
    }
}

}

面试真题

栈模拟队列,如何实现,栈最少需要几个元素,2个栈都是5个元素的话,是否可以,有哪些场景会出问题

1. 如何实现:双栈法

栈是“后进先出”,队列是“先进先出”。要实现顺序的翻转,必须使用两个栈:

  • 输入栈 (Stack In):负责接收新插入的数据(push 操作)。
  • 输出栈 (Stack Out):负责弹出数据(pop 操作)。

核心逻辑:

  1. 入队:直接压入 Stack In。
  2. 出队:
    • 如果 Stack Out 不为空,直接从 Stack Out 弹出栈顶元素。
    • 如果 Stack Out 为空,则将 Stack In 里的所有元素逐一弹出并压入 Stack Out,然后再从 Stack Out 弹出。
    • 原理:数据从 In 到 Out 的搬运过程,正好实现了 LIFO 到 FIFO 的翻转。

2. 栈最少需要几个元素?

这是一个理论边界问题:

  • 最少需要 2 个栈:单栈无法改变数据的先后顺序(除非利用递归产生的系统栈,但那本质上还是利用了栈)。
  • 每个栈的容量:
    • 如果只谈“能否实现模拟”,栈的容量最少可以是 1。
    • 如果栈容量为 1,那么这个队列的总容量就是 2(In 存一个,Out 存一个)。

3. 如果 2 个栈都是 5 个元素,可以实现吗?容量是多少?

可以实现。
这种情况下,模拟出来的队列逻辑最大容量是 10。

模拟过程演练:

  1. 连续向队列 push 5 个元素,此时 Stack In 满了(5/5),Stack Out 是空的(0/5)。
  2. 此时执行一次 pop:Stack In 的 5 个元素全部搬到 Stack Out。此时 Stack In 空了(0/5),Stack Out 满了(5/5)。
  3. 关键点:此时 Stack In 又是空的了,你可以继续 push 5 个新元素。
  4. 最终状态:Stack In 有 5 个,Stack Out 有 5 个。队列里一共存了 10 个元素。

结论:逻辑容量 = Stack In 容量 + Stack Out 容量。

4. 哪些场景会出问题?(面试核心加分项)

虽然逻辑容量是 10,但在实际操作中,如果不满足特定条件,会发生**“阻塞”或“溢出”**:

场景 A:入队阻塞(In 满但 Out 不为空)

  • 现象:假设 Stack In 已经装了 5 个元素,此时你想 push 第 6 个。
  • 问题:由于 Stack Out 此时不为空(比如里面还有 1 个旧元素没被 pop 走),根据 FIFO 原则,你不能把 In 里的数据搬到 Out(否则会导致顺序错乱)。
  • 后果:此时虽然队列总数没到 10,但由于 In 栈物理空间满了,且无法“泄洪”到 Out 栈,导致第 6 个元素无法入队。
  • 工程意义:这说明模拟队列的入队能力受限于 Stack In 的瞬时可用空间。

场景 B:搬运时的原子性问题

  • 问题:当 Out 为空触发搬运时,必须一次性把 In 里的所有元素全部倒过去。
  • 错误操作:如果搬了一半停止了,或者中途又有新元素进入 In 且在搬运未完成时就参与了 Out 的弹出,会导致队列内部的数据顺序彻底混乱。

场景 C:空队列异常

  • 现象:当 Stack In 和 Stack Out 同时为空时。
  • 问题:此时执行 pop 操作。
  • 后果:程序必须有抛出 NoSuchElementException 或返回 null 的处理逻辑,否则会触发底层栈的 EmptyStackException。

查看日志文件最后几行的linux命令是什么,从一个test.txt文件中,取出第10行中的所有数字

tail app.log

5. 搜索关键字

1
2
3
4
5
6
7
8
搜索内容
grep "error" app.log
grep -i "redis" app.log
grep -n "timeout" app.log

-i 忽略大小写

-n 显示行号

7. 查看上下文(前后 5 行)

1
grep -i -C 5 "keyword" app.log

线上排障通常先用 tail -f 实时看日志,再结合 grep 定位关键字,用 sed 按时间截取,用 awk 做统计分析,大文件优先用 less 避免直接打开卡死。

8.awk与sed

1
2
3
4
5
6
7
8
9
10
awk '{print $1}' file.txt
把每一行的第1列打印出来
$1 = 第1列
$2 = 第2列
$3 = 第3列
...
$0 = 整行

sed -n '10p' file.txt
只打印第10行

Java常见手撕
https://kyy-logs.github.io/2026/04/11/语言/java/Java常见手撕/
作者
Yangyang Kong
发布于
2026年4月11日
许可协议