leecodecode【双指针题2】【2026.5.26打卡-java版本】
盛最多水的容器
要点:right-left, 移动小的那边
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
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
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
昨天玩手机三点才睡觉欸嘿嘿,今天状态不好,明天再接再厉!
【要吃好喝好睡好!!!保持心情愉悦】
