news 2026/10/6 5:06:32

第43天:黑马点评Redis高并发链路复盘与栈算法实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第43天:黑马点评Redis高并发链路复盘与栈算法实战

第43天。今天的安排其实很明确:把黑马点评从登录到下单的所有核心链路重新过一遍,然后刷两道栈的题收尾。黑马点评这个项目我在第30天左右已经完整写过一版总结,但今天复习时明显感觉到,隔了十来天再看,很多细节确实会模糊——比如Lua脚本扣减库存的原子性边界、Feed流滚动分页的lastId怎么取、GEO底层的zset结构为什么能支撑附近的人。这些点光靠背笔记没用,必须自己对着源码重新走一遍链路才有安全感。所以这篇文章就按我今天复习的实际顺序来写,前半部分是黑马点评的复盘干货,后半部分是两道栈题的完整解法,最后聊几句怎么把项目复习和算法刷题在面试里串起来。

先说这个组合为什么合理。项目复习解决的是"你有没有真实落地经验"的问题,栈题解决的是"你数据结构底子扎不扎实"的问题。很多候选人项目讲得头头是道,一道"每日温度"写不出单调栈;也有的人算法题刷得飞起,一问缓存击穿就只会说"加锁"三个字。两个都抓,才不至于在某一轮被问穿。如果你也在准备Java后端面试,或者正处于刷题项目两手抓的阶段,今天的复盘思路可以直接抄。

1. 今天复习黑马点评:我为什么把项目又从头过了一遍

1.1 隔两周再看,为什么必须重新走源码

说实话,第30天我写复习笔记的时候,觉得自己已经把黑马点评吃透了。缓存穿透、击穿、雪崩三个概念能背,分布式锁的SETNX命令能默写,秒杀的一人一单逻辑也能画出来。但今天打开项目源码,从登录接口一处一处往下跟的时候,发现有几个地方只是"背过答案",并没有真正理解。

最典型的是缓存击穿里的"逻辑过期"方案。当时笔记上写着:不设过期时间,而是在value里存一个expire字段,后台开线程重建缓存。问题是,为什么逻辑过期能解决击穿?我当时其实没有想透,只是记住了步骤。今天重新看代码才意识到,逻辑过期的核心优势在于:缓存永远不会真正失效,请求永远能拿到旧数据,只有检测到逻辑过期才会尝试获取互斥锁去重建。这样即使用户请求量再大,也不会直接打到数据库——因为大家都在等那个重建缓存的线程把新数据写进去。而互斥锁方案是让大量请求阻塞等待,本质上是用"串行化"换"不穿透",两者取舍完全不同。

这种"重新走一遍源码"的价值,就是能把背下来的结论变成自己能推导的结论。我自己用的方法是:不看任何笔记,打开一个接口,试着给旁边的人讲清楚这段代码为什么要这么写,讲不出来的地方就是理解薄弱点,马上翻回去看注释和调用链。今天整个项目过完,大概花了三个多小时,其中大概四十分钟都花在卡壳的节点上。

1.2 黑马点评整体架构:一个Redis撑起的高并发教学现场

先给没接触过这个项目的朋友说一下它是什么。黑马点评本质是一个仿大众点评的本地生活平台,典型功能包括:短信验证码登录、商户查询缓存、优惠券秒杀、好友关注Feed流、附近商户搜索、签到统计、UV统计。技术栈以Spring Boot + MyBatis-Plus + MySQL + Redis为主,部分版本还会引入RabbitMQ做异步解耦。

项目里Redis不是单纯当缓存用,而是贯穿了一整条高并发业务线。登录态用Redis存,商户数据用Redis缓存,分布式锁用Redis实现,秒杀库存扣减和异步下单也用Redis完成,Feed流的排序用Redis的zset,附近商户用Redis的GEO,签到用BitMap,UV统计用HyperLogLog。一个数据库把这么多数据结构都用上了,这是这个项目最大的价值——面试官问Redis的时候,你几乎能从任何一个方向展开讲案例。

从架构上看,它的主流程可以概括为:前端请求进来,先过登录拦截器(Redis校验token并刷新有效期),再进入业务层。商户查询类请求优先走Redis缓存,缓存未命中才回源MySQL;秒杀类请求先经过Redis预扣减,成功后再异步创建订单;Feed流请求从Redis的收件箱里拉取关注博主的动态,滚动分页返回。理解这条主链路,比记住一个个孤立的知识点有用得多。

1.3 复习路径:拉代码、画流程、对着空屏给自己讲

我今天的复习路径不是从头看视频,而是直接拉出项目源码,按照"登录、缓存、秒杀、Feed流、附近商户、签到、UV"这个顺序,把每一个模块的核心代码找出来,在草稿纸上画一遍请求时序图,然后合上代码,对着自己画出来的图重新讲一遍业务逻辑。

为什么要画时序图?因为黑马点评的很多方案是多个组件协作完成的,光看代码看不出全局。比如秒杀功能,涉及Redis判断库存+Lua脚本扣减+Stream异步下单+数据库订单创建,这几个步骤环环相扣。只盯着某一个类看,会觉得"好像每个方法都不复杂",但面试官问"整个秒杀流程是怎么串起来的",就答不完整。画图能强迫你把每个参与者的职责理清楚:Redis负责什么、MySQL负责什么、MQ负责什么、订单服务负责什么。

这里也推荐一个复习小技巧:把每个模块录一段语音或者用文字写一篇"给自己讲"的笔记。不用追求文采,只需要能用三两句话说清楚"这个模块解决什么问题、用了什么技术、为什么这么选、有什么缺陷"。能写出来,才是真的理解。

2. 黑马点评核心模块复盘:缓存、锁与秒杀的关键答案

2.1 缓存三兄弟:穿透、击穿、雪崩,面试标准口径

这三个概念是黑马点评的高频考点,今天重新复盘一遍。

第一个是缓存穿透。指请求查询了一个数据库里根本不存在的数据,缓存永远不可能命中,于是一堆请求直接打到数据库。解决方案有两种:第一种是缓存空值,把查询结果为null的数据也写入Redis,设定一个较短的过期时间(比如5分钟),这样后续相同请求就能命中缓存。第二种是布隆过滤器,在缓存前挡一道,把所有可能存在的数据hash到一个bit数组里,查询前先判断key大概率是否存在,不存在直接拒绝。布隆过滤器的问题是存在误判率和删除困难,所以大多数业务场景里,缓存空值是更简单实用的方案。

第二个是缓存击穿。指一个热点key过期的那一瞬间,大量请求同时发现缓存没命中,全部涌向数据库。解决方案刚才讲过,有两种:互斥锁和逻辑过期。互斥锁的思路是让第一个发现缓存失效的线程去查数据库,其他线程阻塞等待,等缓存重建完成再放行。逻辑过期则是缓存永不真正过期,value里存一个逻辑过期时间,发现过期后尝试获取锁,由拿到锁的线程重建缓存,其他请求直接返回旧数据。要注意逻辑过期方案的缺点是数据一致性有窗口,适合允许短暂不一致的场景,比如商品详情页这种。

第三个是缓存雪崩。指大量key同时过期,或者Redis实例宕机,导致所有请求一起去压数据库。解决手段一是给过期时间加随机值,让key的过期时间错开,比如基础过期时间加0到300秒的随机偏移;二是Redis集群高可用,配合多级缓存兜底;三是服务层加限流和降级。面试时要把三种场景分开讲清楚,不要混在一起,这是最容易扣分的地方。

2.2 分布式锁:从SETNX到Lua,锁的重入与释放陷阱

黑马点评里分布式锁主要用在两个地方:缓存重建和秒杀下单。缓存重建的互斥锁、秒杀的一人一单并发控制,本质上都是"保证同一时刻只有一个线程操作共享资源"的需求。

实现细节上,早年大家喜欢用SETNX key value加锁,再用EXPIRE key seconds设过期时间,但这两个命令不是原子的,如果加锁后还没来得及设过期时间进程就挂了,锁会永久不释放。正确做法是用一条命令:SET key value NX EX seconds。释放锁的时候也不能直接DEL,因为持有锁的线程可能因为执行时间太长,锁已经过期被其他人抢走了,这时候旧线程去DEL会把别人的锁删掉。标准做法是删除前先比对value,是自己加锁时设置的唯一标识才删,而且比较和删除必须用Lua脚本保证原子性。

今天复习时我又重新看了一遍Lua脚本的写法,核心逻辑就两行:先get key判断value等于当前线程标识,等于才执行del。这个脚本在Redis里是原子执行的,所以不存在"判断完还没删就被其他线程抢锁"的窗口。顺着这个思路,可以自然引出Redisson的看门狗机制——Redisson为什么能自动续期?因为它给锁加了一个后台定时任务,每10秒检查一次锁是否还持有,如果持有就续期到30秒。这些扩展点面试时能讲出来,说明你是真的踩过并发问题的。

2.3 秒杀下单全流程:Lua扣库存与异步订单的协作

秒杀是黑马点评里最复杂的模块,今天我把它的完整链路重新串了一遍。整个流程分为四步:

第一步,判断秒杀是否在有效时间内。这个直接在Redis里查秒杀活动的开始结束时间。第二步,用Lua脚本执行"判断库存是否充足 + 扣减库存 + 记录用户是否已买过"三个操作,一次网络请求内完成,避免并发超卖。第三步,把下单请求发给Stream消息队列,异步处理。第四步,后台消费者从队列里取出消息,创建数据库订单并扣减真实库存。

为什么不用数据库行锁直接扣库存?因为秒杀场景并发极高,所有请求都去MySQL抢行锁会拖垮数据库。先用Redis把大部分请求过滤掉——要么库存不足,要么已经买过——真正到数据库下单的请求量就小得多了,这时候再配合数据库的乐观锁或唯一索引兜底。这里有个容易忽略的细节:一人一单不能只靠"Redis里记录userId",因为Redis记录也可能被绕过,最好的兜底是在订单表上建联合唯一索引,从数据库层面保证同一个用户同一场活动只能有一条订单。面试官问到"超卖怎么办""一人一单怎么保证"时,把这套多层防线讲出来,基本就稳了。

2.4 Feed流与附近商户:推模式、滚动分页和GEO

这两个模块是黑马点评里比较有亮点的部分,也是很多面试官的追问点。

好友关注后查看动态Feed流,项目里用的是"推模式":当一个博主发布新笔记时,系统直接把笔记ID推送到所有粉丝的收件箱。收件箱用什么结构存?用的是zset,score存发布时间戳,member存笔记ID。粉丝刷动态时,直接从自己的收件箱里按时间倒序取数据。

这里的关键难点是滚动分页。Feed流不能用传统的page分页,因为动态是实时变化的,用页码会出现重复或遗漏。项目里用lastId加offset结合ZREVRANGEBYSCORE实现滚动分页:记录上一次返回的最后一条动态的时间戳和相同分数下已取数量,下一次查小于这个时间戳的数据,如果分数相同则跳过已读的offset。这个细节值得反复看,因为"滚动分页"是短视频和社交产品面试里的高频题。

附近商户模块用的是Redis的GEO。GEO底层是zset,每个商户的经纬度会被编码成一个52位的geohash整数作为score,查询附近商户时用GEOSEARCH key FROMLONLAT 经度 纬度 BYRADIUS 5 km ASC就能按距离排序返回。面试时能讲出"GEO底层是zset,通过geohash把二维坐标映射成一维分数"这句话,会比只说"用GEO查"显得专业得多。另外要注意,GEO的坐标信息只能追加不能修改,如果商户经纬度变了,需要删除后重新添加,这是一个容易踩的坑。

2.5 签到与UV统计:BitMap和HyperLogLog的底层逻辑

签到这个功能,核心是BitMap。Redis的BitMap本质是字符串,每一位用0或1表示是否签到,按天为单位偏移。比如用户ID是10086,签到月份是7月,那Redis的key可以设计成sign:10086:202507,第10天签到就把第10位设为1。统计当月签到总天数直接用BITCOUNT,统计连续签到天数则把最近一个月的数据取出来做位运算,从当前位开始往前逐位判断。

UV统计则用HyperLogLog。HyperLogLog的核心思想是用极小的内存统计一个集合的基数,标准误差在0.81%以内。几十万甚至上百万的UV,内存只占12KB左右。它是通过hash后分桶、计算前导零个数来估算唯一值数量。面试官如果问"亿级UV怎么统计",你给出HyperLogLog方案并说明误差范围,就已经超过大半候选人了。但要注意,HyperLogLog不能精确统计,也不能查询某个具体用户是否访问过,适合UV这种"只需要看量级"的场景。

3. 栈题第一道:有效的括号,基础栈的边界全在这

3.1 题目与思路:不是简单匹配,关键是抵消顺序

今天刷的第一道栈题是LeetCode 20题,有效的括号。题目要求判断一个只包含()[]{}的字符串是否有效,有效条件是左括号必须用同类型右括号闭合,并且按正确顺序闭合。

这道题看似简单,但它考察的是对栈这一数据结构最本质的理解:栈天然适合处理"最近的、需要依次抵消的"匹配关系。思路也很直接:遇到左括号就压栈,遇到右括号就弹出栈顶元素,检查类型是否匹配。如果遍历过程中发现栈为空(说明右括号多余),或者弹出的左括号类型不匹配,直接返回false;遍历结束后如果栈不为空(说明有左括号剩余),也返回false。

3.2 代码实现与三个容易写错的地方

直接上代码:

class Solution { public boolean isValid(String s) { Deque<Character> stack = new ArrayDeque<>(); for (char c : s.toCharArray()) { if (c == '(' || c == '[' || c == '{') { stack.push(c); } else { if (stack.isEmpty()) { return false; } char top = stack.pop(); if (c == ')' && top != '(') { return false; } if (c == ']' && top != '[') { return false; } if (c == '}' && top != '{') { return false; } } } return stack.isEmpty(); } }

这里有三个容易写错的地方。第一个是必须用Deque而不是老的Stack类,Java官方已经不建议用Stack了,因为它继承了Vector,有很多锁开销,面试用ArrayDeque是更规范的写法。第二个是判断顺序,右括号触发时先判断栈是否为空,为空就直接返回false,这个判断漏掉的话,")"这种用例就会报错。第三个是遍历结束后必须检查栈是否为空,否则"(()"这种用例会被误判为有效。再加一个优化技巧:可以用HashMap把右括号映射到对应的左括号,代码会更简洁:

class Solution { public boolean isValid(String s) { Deque<Character> stack = new ArrayDeque<>(); Map<Character, Character> map = Map.of(')', '(', ']', '[', '}', '{'); for (char c : s.toCharArray()) { if (!map.containsKey(c)) { stack.push(c); } else if (stack.isEmpty() || stack.pop() != map.get(c)) { return false; } } return stack.isEmpty(); } }

3.3 这道题背后的面试意图:栈的基本功考察

有效的括号这种题,面试官看着简单,但能挖出不少东西。第一层是数据结构基本功,你知不知道栈是LIFO的,能不能用栈解决嵌套匹配问题。第二层是代码严谨性,边界条件处理得干不干净,空栈判断和不匹配判断有没有写全。第三层是扩展能力,面试官可能会追一句"如果是带通配符的括号匹配怎么做"或者"如果括号类型不止三种(比如加了尖括号)怎么改",这时候能反应过来只要改Map映射就行,说明是真的理解了。

我刷这道题的一个心得是:不要因为题简单就跳过,简单题的边界条件往往是最能暴露代码习惯的地方。今天写的时候我就因为忘了最后检查stack.isEmpty()错了一次,这种低级失误在面试白板编程时一旦出现,非常减分。

4. 栈题第二道:每日温度,把单调栈的思路彻底讲透

4.1 暴力解法为什么不行:从O(n^2)到O(n)

第二道题是LeetCode 739题,每日温度。题目给一个温度数组temperatures,要求返回一个等长数组,每个位置表示要等多少天才会等到更高的温度,如果没有就填0。

最直的思路是双层循环,对每个温度往后遍历找第一个更高温度的下标。这个解法的时间复杂度是O(n^2),在数组长度10万级别时直接超时。优化的突破口在于:很多元素的"下一个更高温度"其实可以提前从后往前推算出来,而且可以用一个栈来完成这种推算。

4.2 单调栈原理:为什么叫单调,栈里存的是什么

单调栈的核心思想是:维护一个栈内元素单调递减(从栈底到栈顶)的栈。具体到每日温度这道题,栈里存的是下标,而不是温度值本身。为什么存下标?因为答案要求返回的是"相隔几天"——用下标相减就能得到。为什么温度要单调递减?因为当新遍历到的温度比栈顶元素对应温度高时,说明栈顶元素等到了自己的"下一个更高温度",此时栈顶元素就可以出栈结算了。

整个过程可以理解成:所有人按顺序排队,但只排一个序列,每个人都在等右边第一个比自己高的人。新来的人如果比前面的人高,前面那些矮个子就可以离开队伍去结算等待天数;如果新来的人矮,那就入栈继续等。所以栈里一直是"从栈底到栈顶温度递减"的状态,这就是"单调栈"这个名称的来源。一个经常被人问到的问题是,单调栈到底"单调递增"还是"单调递减"?答案不固定,题目要求不同,维护方向也不同,关键是理解"什么条件下出栈结算"。

4.3 代码实现与复杂度分析

class Solution { public int[] dailyTemperatures(int[] temperatures) { int n = temperatures.length; int[] ans = new int[n]; Deque<Integer> stack = new ArrayDeque<>(); for (int i = 0; i < n; i++) { while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { int idx = stack.pop(); ans[idx] = i - idx; } stack.push(i); } return ans; } }

关键点有两个:一个是while循环,新温度进来后,把所有比它温度低的栈顶元素都结算一遍,而不是只结算一次;另一个是结算完成后把当前下标入栈,等待它右边的更高温度。每个元素最多入栈一次、出栈一次,所以时间复杂度O(n),空间复杂度O(n)。

顺手把复杂度分析放在一起对比:

方案时间复杂度空间复杂度适用场景
暴力双层循环O(n^2)O(1)数据量小
单调栈O(n)O(n)数据量大,必须线性解

4.4 扩展:接雨水、下一个更大元素、backtrace里的栈回溯

每日温度只是单调栈的入门题,它的解题模板可以迁移到很多经典题上。最典型的扩展是LeetCode 42题接雨水——本质是找每个位置左右两侧的最高柱子,用"维护单调递减栈、遇到更高的就结算"的思路就能解。还有LeetCode 496题下一个更大元素、LeetCode 84题柱状图中最大的矩形,都是同一个模板。

这里顺便提一个容易混淆的点:栈在真实世界的应用远不止算法题。程序运行时的函数调用栈,就是典型的栈结构,函数调用时压栈、返回时弹栈。崩溃排查时用到的backtrace栈回溯,就是沿着调用栈逐层恢复出"是谁调用了谁"的现场,arm架构下的调用栈回溯还要考虑寄存器和栈帧的布局。搞懂这些,再看算法题里的栈,会有一种"原来数据结构真的是从底层长出来的"的感觉。同样,大家常说的"堆和栈"里的栈,其实也是指运行时内存中的栈区,它和算法里的栈结构在思想上一脉相承——后进先出。

5. 面试现场:栈题和项目怎么串起来讲

5.1 面对"你最近在学什么"的开放式提问

面试经常会有一个开放式问题:你最近在做什么?这时候把今天的复习内容组合起来说是很加分的。你可以说:"我最近在复习一个基于Redis的本地生活项目,重点关注缓存一致性和高并发秒杀场景;同时每天保持刷两到三道算法题,近几天集中过一遍栈和单调栈的题目。"这样既展示了项目深度,又展示了持续学习的技术热情,两个信息点互相佐证。

5.2 黑马点评高频追问和答题口径

我根据今天复习的内容,整理了几个高频追问的标准答题口径。

第一个:缓存击穿和缓存穿透有什么区别?答:穿透是查一个不存在的数据,缓存和数据库都没有;击穿是缓存中热点key过期,大量请求同时回源数据库。一个针对不存在数据,一个针对热点数据失效。第二个:Redis分布式锁怎么防止锁误删?答:加锁时设置唯一标识,释放锁时先get比对,是自己的锁才del,且用Lua保证比较和删除原子执行。第三个:秒杀超卖问题怎么解决?答:三层防线,第一层Redis Lua脚本原子扣减库存,第二层数据库乐观锁控制库存更新,第三层订单表唯一索引兜底一人一单。第四个:Feed流为什么不用MySQL分页?答:动态是实时写入的,传统分页有偏移量不准的问题,用zset按时间戳滚动分页更合适。这些口径最好写在本地笔记里,面试前快速过一遍。

5.3 栈题在面试中的常见追问方向

栈题本身也可能被问出花来。比如做完每日温度,面试官可能会问"如果有平温怎么办"(答案:严格大于才出栈,等于不出栈,所以答案是0);或者问"如果题目改成找右边第一个小于当前温度的,单调栈怎么调"(答案:把while条件改成小于,维护递增栈)。再比如有效的括号变种:如果括号嵌套深度限制为K层怎么办(每次入栈时记录深度,超过K直接返回false);如果字符串特别长怎么优化(遇到不合法的右括号直接提前短路,不需要全部遍历)。这些变种问题在面经里出现频率很高,建议刷题时顺手多想一想,不要只满足于AC。

6. 第43天复盘小记:踩坑与下一步计划

6.1 今天复习踩到的两个坑,写出来提醒自己

第一个坑是复习时差点陷入"看视频回放"的舒适区。我一开始打开之前存的课程视频想倍速过一遍,看了十分钟发现完全没有记忆留存,立刻关掉了,改成拉源码对着画图。这里想提醒大家,项目复习最忌讳用看视频的方式,因为人很容易在视频里产生"我会了"的错觉,合上电脑全忘。正确方式是主动回忆,哪怕想不起来翻笔记也比被动看视频强。

第二个坑是写每日温度时,一开始想用暴力法提交后再看答案,结果提交超时后养成了"AC就行"的心态,差点跳过单调栈分析。后来逼着自己把单调栈的结算过程用具体例子推演了一遍,才发现之前理解有偏差——我一直以为栈里存的是温度值,实际上必须存下标。这种"其实没懂"的感觉,只有自己动手模拟一组数据才能发现。刷题不能只看答案,要能亲手在纸上跑通一遍才算数。

6.2 接下来的安排:从Redis细节到更多栈题变种

今天复盘完黑马点评,我发现自己对"缓存一致性——先更新数据库还是先删缓存"这个问题还讲不透彻,明天的计划是专门研究Cache Aside模式和延迟双删策略,把这一块吃透。栈题方面,我准备明天把接雨水和柱状图最大矩形这两道单调栈进阶题刷完,然后做一个小结,对比"下一个更大元素"和"最大矩形面积"两种结算模式的区别。刷题加项目交替进行,每天都有输出,这种感觉是刷题马拉松里最宝贵的正反馈。

按我个人经验,项目复盘和算法刷题的最佳比例大概是二比一或者一比一。第43天是一个很微妙的时间节点,既要防止刷题刷到麻木,又要防止项目复习变成背八股。如果能每天都像今天这样,用一个核心项目模块加一组同类算法题做组合,坚持到100天的时候,你会发现面试里那些"既问项目又问算法"的压力面,其实也没有想象中可怕。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 5:06:25

Agent触达外部系统的中间件设计:路由、权限与追踪全解析

前一阵子在一个多智能体协作项目里&#xff0c;我彻底被“Agent 能不能稳定触达外部系统”这件事折磨了一遍。模型能推理、会规划&#xff0c;但真到要调接口、改数据、发通知的时候&#xff0c;各种断连、错路由、权限卡壳接踵而来。后来我们把项目的连接层整体抽出来&#xf…

作者头像 李华
网站建设 2026/10/6 5:06:15

OpenShell:给终端接入AI外脑的命令行智能助手实践

最近这两周&#xff0c;我在几个技术社群里反复看到了“OpenShell”这个项目名&#xff0c;一开始以为是哪家公司又发了新壳子&#xff0c;点进去看才发现&#xff0c;它的定位挺有意思&#xff1a;不是让你脱离终端&#xff0c;而是给终端加一个AI外脑。我自己的工作节奏基本没…

作者头像 李华
网站建设 2026/10/6 5:02:25

C语言数组完全指南:从内存布局到指针退化

1. 数组的本质&#xff1a;C语言的第一道分水岭很多初学者把数组当成“一堆变量的合集”&#xff0c;这个理解不能算错&#xff0c;但远远不够。我接触过不少在浙大翁恺老师的课程里跟到数组章节就卡住的学生&#xff0c;也见过在PAT乙级题上因为数组用不好而反复超时、越界的选…

作者头像 李华
网站建设 2026/10/6 5:00:31

PSO优化BP神经网络分类模型:原理、实现与调参指南

如果你是科研小白&#xff0c;大概率体会过被 BP 神经网络支配的恐惧&#xff1a;隐层节点到底设几个、学习率调到多少合适、初始权重随手一给……结果模型要么死活不收敛&#xff0c;要么收敛到某个糟糕的局部最优解&#xff0c;分类准确率就是上不去。我当年做实验时也被这个…

作者头像 李华
网站建设 2026/10/6 4:59:03

74LS194移位寄存器实验:循环移位与奇偶分频电路设计详解

移位寄存器这个实验&#xff0c;我前前后后带过好几轮本科生&#xff0c;也看很多人在课程设计、考研复试里栽在它上面。表面上看&#xff0c;74LS194就是一个4位双向移位寄存器&#xff0c;任务就是把几个LED接成左右循环点亮&#xff0c;再做一个奇偶分频电路。可一旦动手&am…

作者头像 李华