Terminal 4.

复盘

2026/04/14
loading

2026/4/14校招聘会复盘

SAP 外企,需要网上投递

海鼎, 没有技术交流

化工数字化,没有技术交流,说是学长学姐创立的公司(i can see from name

兴业数金,金融业,感觉是kpi!但是复盘。。。

  • 问项目里的threadlocal的生命周期,什么时候删除?为什么要删除?

  • 讲一下IO和NIO。(我不记得了!

    (B)IO是传统的 IO,是阻塞式的(Blocking),对于每个连接,都需要创建⼀个独⽴的线程来处理读写操作。

    NIO是非阻塞同步IO(Non-blocking),可以⽤⼀个线程处理多个客户端连接,通过 Selector 监听多个 Channel 来实现多路复⽤。还有缓冲区。

  • 问我有没有自己写synchronized或者lock?我说cas逻辑实现。。。(我草,这对吗

    他指的应该是自造lock,synchronized我不知道该怎么写

    那就是CAS+AQS

    首先,我需要一个全局可见的标志位来表示锁是否被占用。我会使用一个 volatile int state0 代表锁空闲。1 代表已被某个线程持有。 使用 volatile 是为了保证多线程间的可见性,确保每个线程都能实时看到最新的锁状态。

    当多个线程同时调用 lock() 时,我不能使用简单的 if(state == 0),因为这不具备原子性。 我会调用 Unsafe 类提供的 CAS (Compare And Swap) 方法。只有当 state 预期为 0 且成功将其修改为 1 的那个线程,才算真正拿到了锁。这保证了并发环境下的唯一性

    对于没有抢到锁的线程,我不能让它们一直死循环自旋(这会榨干 CPU),我会构建一个 FIFO(先入先出)的等待队列

    我会将当前线程封装成一个 Node 节点放入队列。然后调用 LockSupport.park() 方法挂起当前线程。此时线程进入阻塞状态,不再消耗 CPU 资源。

    当持有锁的线程执行完逻辑调用 unlock() 时:先将 state 重新设为 0。然后从等待队列的头部取出一个 Node,调用 LockSupport.unpark(thread)。 被唤醒的线程会重新尝试执行 CAS 操作去争抢锁。

  • Redis一般用来存什么数据?项目里存了什么数据?有没有做持久化?

  • 索引是不是越多越好?

  • 垃圾清除的方式了解吗?

好像没了。

东方海外货柜航运,这个好像是500强?虽然不是互联网。。。好像是外企?国企?,香港的。为什么这个公司消息这么少!

  • 技术交流之前,人事问我能不能做全栈(我:可以学.jpg)

  • 问我都上什么课

  • 项目是哪里来的

  • deepseek的流式响应是怎么样的?(只吐出来sse几个字,回答的不是很好

    DeepSeek 的流式响应底层基于 SSE 协议

    在我的项目中,我通过 Spring WebFlux 的 WebClient 异步消费这个流。之所以这么设计,是因为 AI 推理是一个长耗时过程,流式输出能将 TTFT(首字到达时间) 缩短到毫秒级,显著提升用户体验。

    具体的实现细节是:后端以响应式非阻塞的方式接收数据片,并实时转发给前端。这种‘随收随发’的模式避免了在 JVM 堆内存中缓存大段文本,保证了系统在高并发场景下的内存稳定性。同时,我也通过异步线程池和异常处理机制,确保了即使流式连接意外断开,后台资源也能被正确释放。

    1. 协议层面:基于 HTTP 的 SSE (Server-Sent Events)

    首先要点出 DeepSeek API 的本质:

    “DeepSeek 的流式响应遵循标准 SSE 协议。在请求体中,我们将 stream 参数设为 true,此时服务器不再一次性返回完整的 JSON,而是保持长连接,分段推送以 data: 开头的消息块,直到返回 [DONE] 标记。”

    2. 框架层面:为什么用 Spring WebClient?

    这是代码的核心亮点。面试官会看你为什么不用 RestTemplate 或 HttpClient。

    “在实现上,我选择了 Spring WebFlux 中的 WebClient

    • 非阻塞(Non-blocking):传统的 RestTemplate 是阻塞的,必须等结果全部回来。而 WebClient 基于 Reactor 框架,支持非阻塞 I/O。
    • Flux 流处理:通过 .bodyToFlux(String.class),我们将后端的响应流直接映射为一个异步序列(Flux)。只要网络缓冲区收到一个新的 Data Chunk,Flux 就会立即触发下游处理,而不需要等待整个响应结束。”

    3. 数据处理:Chunk 的解析逻辑

    结合你的 processChunk 方法,描述你是如何把“原始数据”变成“用户文字”的:

    “在处理流的过程中,我会订阅这个 Flux。每当有 Chunk 到达:

    1. 过滤结束符:首先判断字符串是否为 [DONE]
    2. 增量解析(Delta):LLM 返回的流式数据是 JSON 格式的 delta 片段。我使用 ObjectMapper 解析 JSON 路径 choices[0].delta.content
    3. 回调推送:解析出文字后,立即通过 Consumer<String> onChunk 回调给上层(通常是推送到 WebSocket),实现前端的‘打字机’效果。”

    4. 深度追问补丁:面试官可能会挖的坑

    坑 1:如果网络卡顿,Chunk 断开了怎么办?

    回答: “WebClient 的 Flux 提供了丰富的异常处理机制。我在代码中通过 .subscribe() 的第二个参数 onError 捕获异常。在生产环境下,还可以配合 .retry() 进行自动重试,或者在前端利用 WebSocket 的心跳机制感知断连。”

    坑 2:解析 JSON 时,如果一个 JSON 被拆成了两个 Chunk 怎么办?

    回答: “这是一个非常细的点。标准的 WebClient 在处理 MediaType.TEXT_EVENT_STREAM 时,会自动根据换行符(data:)进行分帧。即便底层的 TCP 包被拆分了,WebClient 也会帮我们组装好完整的 SSE 行再交给 bodyToFlux。这比直接操作 InputStream 要安全得多。”


    总结:你的“满分回答”模板

    “在我们的 SmartPai 项目中,DeepSeek 的流式交互是这样实现的:

    1. 请求构造:我们在请求体中封装了用户问题、知识库检索到的上下文(Context)以及历史对话,并将 stream 设置为 true
    2. 响应式获取:我们使用了 Spring WebClient。通过其响应式操作符,我们将 API 的 SSE 流转化为一个 Flux 序列。这样可以实现真正的异步非阻塞,不会阻塞业务线程池。
    3. 流式消费:通过订阅 Flux,我们将每一个到达的 JSON 块解析出 delta.content。只要解析出文字,就立即触发回调。
    4. 前端协同:这个回调会衔接我们之前的 WebSocket 通道,将文字逐字推送到前端,从而解决了大模型首字响应慢(TTFT)的痛点,提供了流畅的用户体验。”
  • llm有没有本地部署?(答:只是用了api!

  • mybatis二级缓存清楚吗(不是很清楚!

    mybatis的一级缓存是缓存在每一个sqlsession内部的,而mybatis的二级缓存一般是一个mapper(namespace)里的sqlsession共享的。但是会有数据不一致的问题(比如遇到多表查询)(本地内存),所以一般都是把缓存放在redis里。

  • 有没有在用ai编程?知不知道国内这些ai有什么区别

好像没了。这个公司不会都是拷打项目不问八股吧??那真的完鸟()应该ai也会问挺多的。

到此我的简历投完了,还有一个日企和金融的没投,其他的都不对口,也许下次还有招聘会的话应该打印10张,开发还是要的挺多的。。

感想:回答的时候先思考一会儿再回答!

美的笔试复盘

编程题

  • 把aaabbbbccc转换为a3b4c3,只有一个字母的时候不要写数字

​ 最直观的方法就是遍历字符串,统计连续字符出现的次数

​ 拼接字符串的场景记得用stringbuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
int count = 1; // 用于计数

for (int i = 0; i < s.length(); i++) {
// 如果当前字符是最后一个,或者当前字符和下一个字符不同
if (i + 1 == s.length() || s.charAt(i) != s.charAt(i + 1)) {
sb.append(s.charAt(i)); // 拼接字符
sb.append(count); // 拼接数字
count = 1; // 计数重置
} else {
// 如果和下一个字符相同,计数加1
count++;
}
}

其实我觉得这里的点是,因为你是往后看提前结束,所以count是从1开始的。

  • 很复杂的情景题但是没有什么思维量,把我吓跑了。。。忏悔中

    在该日历下的第X年,月Y满足是X最小素因数(当X等于1时Y等于1),一共有Z等于X + 233天,在前Y-1个月是Z/Y向下取整天,剩下的天数都在最后一个月(即第y月)。 一个星期同样是七天,已知第一年的一月一日是第一个星期的星期一。一共T组询问,每次询问第n星期一是该日历的哪年哪月哪

    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
    import java.util.Scanner;

    public class Main {
    public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    while (sc.hasNextLong()) {
    long n = sc.nextLong();
    // 第 n 个星期一距离 1年1月1日的天数 (1-based index)
    long targetDay = 1 + (n - 1) * 7;

    long currentX = 1;
    while (true) {
    long Y = getMinPrimeFactor(currentX);
    long Z = currentX + 233;

    if (targetDay <= Z) {
    // 答案就在这一年
    printResult(currentX, Y, Z, targetDay);
    break;
    }
    targetDay -= Z;
    currentX++;
    }
    }
    }

    // 获取最小素因数
    private static long getMinPrimeFactor(long x) {
    if (x == 1) return 1;
    if (x % 2 == 0) return 2;
    for (long i = 3; i * i <= x; i += 2) {
    if (x % i == 0) return i;
    }
    return x;
    }

    private static void printResult(long X, long Y, long Z, long dayInYear) {
    long dPrev = Z / Y;
    long currentMonth = 1;

    // 判定在哪个月
    while (currentMonth < Y) {
    if (dayInYear <= dPrev) {
    System.out.println(X + " " + currentMonth + " " + dayInYear);
    return;
    }
    dayInYear -= dPrev;
    currentMonth++;
    }
    // 在最后一个月
    System.out.println(X + " " + Y + " " + dayInYear);
    }
    }
  • 动态规划

好像和leecode42题差不多。

我算了一下,假设选择题用了10分钟,那就是80分钟写3道题,平均一道题是26分钟,感觉以后做题要计时了。

and这个是acm标准的输入输出,,,力扣做多了上手很不熟悉,,,

选择题

  • 对于“非静态内部类”(Member Inner Class),编译器会自动在构造器中传入外部类的引用。

    1
    2
    3
    4
    5
    6
    class Outer {
    class Inner {
    Inner() { // 看上去是无参构造器
    }
    }
    }

    编译器处理过后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Outer$Inner {
    // 1. 自动生成一个指向外部类的 final 字段
    final Outer this$0;

    // 2. 构造器被修改,强制要求传入 Outer 引用
    Outer$Inner(Outer outer) {
    this.this$0 = outer;
    }
    }

    原因:

    1
    2
    Outer out = new Outer();
    Outer.Inner in = out.new Inner(); // 必须用 out 来 new

    因为内部类的构造器需要那个外部类引用,所以你必须通过 out.new 来告诉 Java把 out 这个实例传给 Inner 的构造器

    但是静态内部类不会传

    如果内部类的生命周期比外部类长(比如在内部类里开了一个异步线程),那么即使外部类不再使用了,由于内部类构造器自动传入并持有了 this$0 引用,外部类也将永远无法被 GC 回收。

  • 内部类可以使用外部类的私有成员(当然!)

  • boolean是作为int处理的,但是在数组里不是

  • 还有一个execute不知道他在说什么!

  • 关于aqs他说了很多源码的词,我从功能上判断他们应该都是对的

  • this()和super()不可以同时出现,super()必须在代码的第一行。

  • 联合索引又称组合索引

  • 255.255.252.0每个子网的主机数量最多可以有多少台?

    第一步:将掩码转换为二进制

    子网掩码 255.255.252.0 的二进制表示如下:

    • 255: 11111111
    • 255: 11111111
    • 252: 11111100
    • 0 : 00000000

    组合起来就是:

    1
    11111111.11111111.11111100.00000000

    第二步:确定主机位(Host Bits)

    在掩码中,1 代表网络位,0 代表主机位。

    • 我们可以看到,末尾共有 10 个零(第三段有 2 个,第四段有 8 个)。
    • 因此,主机位数量 n = 10。

    第三步:套用公式计算

    每个子网的总 IP 地址数为 2^n。但由于每个子网中有 2 个特殊地址不能分配给主机,必须扣除:

    1. 网络地址(主机位全为 0)
    2. 广播地址(主机位全为 1)

    计算公式:

    1
    2
    3
    $$ \text{主机数量} = 2^n - 2 $$

    $$ 2^{10} - 2 = 1024 - 2 = 1022 $$

所以,每个子网最多可以有 1022 台主机。

  • 英特网服务提供商的英文缩写:ISP,Internet Service Provider

怎么这么多计算机网络的内容?!

CATALOG
  1. 1. 2026/4/14校招聘会复盘
    1. 1.1. 1. 协议层面:基于 HTTP 的 SSE (Server-Sent Events)
    2. 1.2. 2. 框架层面:为什么用 Spring WebClient?
    3. 1.3. 3. 数据处理:Chunk 的解析逻辑
    4. 1.4. 4. 深度追问补丁:面试官可能会挖的坑
      1. 1.4.1. 坑 1:如果网络卡顿,Chunk 断开了怎么办?
      2. 1.4.2. 坑 2:解析 JSON 时,如果一个 JSON 被拆成了两个 Chunk 怎么办?
    5. 1.5. 总结:你的“满分回答”模板
  • 美的笔试复盘
    1. 1. 编程题
    2. 2. 选择题
      1. 2.0.1. 第一步:将掩码转换为二进制
      2. 2.0.2. 第二步:确定主机位(Host Bits)
      3. 2.0.3. 第三步:套用公式计算