盛最多水的容器
要点:right-left, 移动小的那边
java
class Solution {
public int maxArea(int[] height) {
//双指针
int left = 0;
int right = height.length -1;
int ans = 0;
while(left < right){
int area = (right - left) * Math.min(height[left], height[right]);
ans = Math.max(ans, area);
if(height[left] < height[right]){
left++;
}else{
right--;
}
}
return ans;
}
}
接雨水
要点: 双指针找到短的那边 ,记录leftmax, rightmax
java
class Solution {
public int trap(int[] height) {
int maxleft = height[0];
int maxright = height[height.length -1];
int left = 1;
int right = height.length - 2;
int ans = 0;
while(left <= right){
if(maxleft < maxright){
if(maxleft <= height[left]){
maxleft = height[left];
}else{
ans += maxleft - height[left];
}
left++;
}else{
if(maxright <= height[right]){
maxright = height[right];
}else{
ans += maxright - height[right];
}
right--;
}
}
return ans;
}
}
判断子序列
要点: s一个指针i, t一个指针j
java
class Solution {
public boolean isSubsequence(String s, String t) {
//双指针
int i = 0;
int j = 0;
while(j < t.length() ){
char c = t.charAt(j);
if(i < s.length()&& s.charAt(i) == c){
i++;
}
j++;
}
return i == s.length();
}
}
随机知识
业务
1.登录业务
(1)双token机制
token是什么?
(2)切面限流
1. 什么是 Token?为什么要用 Token?
问:你能解释一下 Token 是什么吗?与 Session 有什么区别?
答:
Token 可以理解为服务器颁发给客户端的临时凭证 ,客户端每次请求时带上它,证明"我是合法登录的用户"。它类似一张有时效、有权限的门禁卡。
与 Session 的主要区别:
- Session:服务器存储用户信息(有状态),需要共享存储(如 Redis)才能多机共用。
- Token:客户端存储 Token(通常用 JWT),服务器只负责签发和验证(无状态),天然适合分布式系统。
2. 双 Token(Access Token + Refresh Token)的流程
问:你们项目里是怎么做登录态管理的?为什么需要双 Token?
答:
我们使用的是双 Token 方案:
- Access Token:有效期短(15分钟),用于调用业务接口(加购物车、下单等)。
- Refresh Token :有效期长(7天),不参与业务,唯一的职责是去换取新的 Access Token。
流程:
- 登录成功后,服务端返回一对 Token。
- 客户端将 Access Token 放在请求头中调用业务接口。
- 若 Access Token 过期(返回 401),客户端拿 Refresh Token 调用
/refresh接口。 - 服务端验证 Refresh Token 有效后,颁发新的 Access Token(有时也刷新 Refresh Token)。
- 客户端用新 Token 重试原来的请求。
好处:
- 用短期 Access Token 降低泄露风险。
- 用长期 Refresh Token 实现无感续期,提升体验。
- 修改密码或异常时,仅吊销 Refresh Token 即可强制下线。
3. 注解和切面是如何联系上的?
问:我看到一个限流注解 @AccessLimit,但它只是一个注解,没有任何执行逻辑。请问它怎么起到限流作用的?注解和切面之间是怎么联系起来的?
答:
注解本身只是元数据 ,真正执行逻辑的是 AOP 切面 。它们的联系是通过 Spring AOP 的切入点表达式 建立的,步骤如下:
-
定义注解
@AccessLimit,包含limit、time等属性。 -
编写切面类
AccessLimitAspect,并标注@Aspect和@Component。 -
在切面中写一个
@Around方法,切入点表达式为@annotation(accessLimit)。java
@Around("@annotation(accessLimit)")
public Object limit(ProceedingJoinPoint joinPoint, AccessLimit accessLimit) {
// 读取注解参数,进行限流检查...
}
-
Spring 启动时,解析该表达式,自动为所有标注了
@AccessLimit的方法创建代理对象。 -
运行时调用该方法,会先执行切面中的限流逻辑。若通过,再通过
joinPoint.proceed()调用原始方法;若超限则直接返回错误,原始方法不再执行。
关键点: 不是注解主动找到切面,而是切面主动声明"我要处理所有带有这个注解的方法"。没有切面,注解就是单纯的注释。
4. 双 Token 的常见追问(展示深度)
追问1:Refresh Token 存储在哪里?有什么安全要求?
Refresh Token 必须放在 HttpOnly Cookie 或移动端安全存储中,绝不能放在 localStorage,否则易被 XSS 窃取。Access Token 可以放在内存或短时 Cookie 中。
追问2:如果 Refresh Token 也被偷了怎么办?
可以使用 Refresh Token 轮换:每次续签时颁发新的 Refresh Token,旧的一个立即失效。这样攻击者只能使用一次,且服务端可检测异常(如一个 Refresh Token 被用了两次)。此外,还可以绑定设备指纹或 IP 来增强安全性。
追问3:为什么 Access Token 通常用 JWT,而 Refresh Token 有时要存数据库?
Access Token 需要高频验证,用无状态的 JWT 性能好;Refresh Token 需要支持吊销、轮换、次数记录等,通常用数据库或 Redis 存储状态,便于精细控制。
缓存
缓存到底存的是什么?
你打开商城首页,看到"全部商品"那一页,这一页数据在 Redis 里存的样子是:
key: "product:list::1:12"
value: [商品A, 商品B, 商品C ... 一共12个商品]
这就是缓存:把一整个页面内容存起来,下次再来就直接拿,不用查数据库。
为什么不同条件要拆开存?
假设你是老板,商场里有三个货架,每个货架摆法完全不一样:
| 货架 | 摆法 |
|---|---|
| 默认排序 | 最新商品摆最前面 |
| 价格升序 | 最便宜的摆最前面 |
| 价格降序 | 最贵的摆最前面 |
这三个货架的顺序完全不同,必须分开记。
同样道理:
product:list:手机::1:12 → [华为新品, iPhone15, ...] 按最新时间排
product:list:手机:price_asc:1:12 → [红米, iPhoneSE, iPhone15, ...] 按最便宜排
这两份数据长得不一样,不能放同一个格子里,否则会乱套。
为什么 page 也要存?
因为每个货架有 10 页。你是老板,你不能把所有商品全写在同一张纸上------纸太长了。你分 10 页写:
| 纸 | 内容 |
|---|---|
| 第 1 页 | 商品 A, B, C ... L |
| 第 2 页 | 商品 M, N, O ... X |
| 第 3 页 | 商品 Y, Z ... |
如果你把第 1 页和第 2 页的东西混在一起存,那当用户想看第 1 页时,你会分不清哪些是该给他看的。
总结成一句话
每个不同组合(不同分类 + 不同排序 + 不同页)都是不同的"页面",每个页面单独存一份缓存。 这就像给 150 张不同的照片各拍了一张拍立得,哪张被看到了就拿出来用,不用重新拍。
用户有多少种玩法,就有多少种缓存。
Key 就是"地址标签"
CacheConstants.PRODUCT_LIST = "product:list:" ← 固定前缀,告诉"这是商品列表"
"手机" ← 分类
":" ← 分隔符
"price_asc" ← 排序方式
":" ← 分隔符
"1" ← 第几页
":" ← 分隔符
"12" ← 每页几条
最终 Key = product:list:手机:price_asc:1:12
Value 就是那个页面的数据
[红米, ¥999, 库存200件], [iPhone SE, ¥3499, 库存50件], ... 共12条
(实际存的是 JSON 字符串,就是这 12 个商品的完整信息)
回答你最后的问题
用户不管怎么排序,我都有对应的缓存是吗?
不是一开始就有的 ,是"谁先点,谁触发创建":
| 用户操作 | Key | 状态 |
|---|---|---|
| 第一个人打开首页 | product:list::1:12 |
没有缓存 → 查 DB → 写缓存 |
| 第二个人打开首页 | product:list::1:12 |
缓存命中 → 直接返回 |
| 有人点"价格升序" | product:list::price_asc:1:12 |
没有缓存 → 查 DB → 写缓存 |
| 后来又有个人点"价格升序" | product:list::price_asc:1:12 |
缓存命中 → 直接返回 |
只要有一个用户选过这个组合,这个组合就会被缓存,后面来的人全走缓存。 如果永远没人选"价格降序第 3 页",它也不会被创建,不占内存。
这种方法叫做惰性缓存(lazy caching)------按需生成,按需缓存。
碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第16天。努力连续更新100天!以后每天就按,秋招项目【java+agent】,科研,必做项目,算法,八股,锻炼身体来总结。今天效率一般。明天加油!!!
总结:不要放弃呀,
1.算法要系统过一遍【灵神】2/27
2.秋招项目,【java】开始实际看业务,2/10;【agent】还在学,决定把helloagent看一遍,3/16
3.科研要跑一下,无
4.检测项目也得总结文档,无,
5.训练项目看看先选择好,无
6.背八股,无
7.锻炼身体,1h
昨天玩手机三点才睡觉欸嘿嘿,今天状态不好,明天再接再厉!
【要吃好喝好睡好!!!保持心情愉悦】