美团首次面试项目话术1——黑马点评篇

🌈hello,你好鸭,我是Ethan,一名不断学习的码农,很高兴你能来阅读。

✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
🏃人生之义,在于追求,不在成败,勤通大道。加油呀!

🔥个人主页:Ethan Yankang
🔥专栏:面试八股文||Java项目

🔥温馨提示:划到文末发现专栏彩蛋

🔥本篇概览:黑马点评项目针对项目的话术。


目录

0、自我介绍及总体项目

1、短信登录功能:

功能简述

细节处理

细节一:用户脱敏的处理。

细节二:刷新token与判断用户登录的双拦截器的使用以正确地处理共同的业务逻辑。

2、商户缓存功能

功能工作流程

细节处理

问题一:双写一致问题

问题二:缓存穿透问题及解决方案

问题三:缓存雪崩问题及解决思路

问题四:缓存击穿问题及解决思路

互斥锁的实现:

逻辑过期的实现:

3、优惠券秒杀功能

功能及流程:

优惠券秒杀业务下单核心逻辑分析:

细节处理:

问题一:超卖问题

问题二:一人多单问题(单体锁)

4、基于setnx的分布式锁功能

功能及流程:

细节处理:

细节一:加锁:

细节二:释放锁时误删锁(阻塞):

引出Redisson

5、基于Redisson的分布式锁功能

sentx的四大问题

使用Redisson解决问题

分布式锁-redission可重入锁原理

分布式锁-redission锁的MutiLock原理

6、秒杀优化

问题引出

解决方案

7、基于redis的stream实现的消息队列

8、达人探店-点赞排行榜

9、好友关注

细节处理:

细节一:Feed投喂方式

细节二:滚动分屏的实现

10、附近商户查询实现

功能及流程:

11、用户签到功能

12、UV统计

功能及流程:

------------------------------------------


0、自我介绍及总体项目

面试官, 首先我来介绍一下黑马点评这个项目吧。

项目描述:黑马点评项目是一个springboot开发的前后端分离项目,使用了redis集群、tomcat集群、MySQL集群提高服务性能。类似于大众点评,实现了短信登录、商户查询缓存、优惠卷秒杀、附近的商户、UV统计、用户签到、好友关注、达人探店  八个部分形成了闭环。其中重点使用了分布式锁实现了一人一单功能、

项目中大量使用了Redis 的知识。

项目所使用技术栈包括:

SpringBoot+nginx+MySql+Lombok+MyBatis-Plus+Hutool+Redis

制作一个思维导图,直接展示讲解,更加生动形象。

这是我制作的思维导图——请您过目:

面试官,您好,请问这次面试您的预期时间是多久呢?我想把整个项目制作流程与心得 体会大概完整的跟您汇报一下,但是这也取决与您的时间,调整我的讲解汇报深度。您您的时间预算还充裕吗?

不充裕——好的,那我将重点与您汇报。

充裕——好的,那我将详细为您介绍。

每个模块我分三个小点为您讲解:

分别是功能简述、工作流程和实现细节。

1、短信登录功能:

功能简述

首先这里是登录功能,项目完成了从使用session的登录到redis的登录过渡。使用了Threadlocal作为存放用户信息的载体。

大致的工作流程是:

当注册完成后,用户去登录会去校验用户提交的手机号和验证码,是否一致,如果一致,则根据手机号查询用户信息,不存在则新建,最后将用户数据保存到redis,并且生成token作为redis的key,当我们校验用户是否登录时,会去携带着token进行访问,从redis中取出token对应的value,判断是否存在这个数据,如果没有则拦截,如果存在则将其保存到threadLocal中,并且放行。

这里的技术实现技术转变在于集群不同服务器之间的session无法共享,如果在每一个节点之间实现拷贝session以达到负载均衡到每一台服务器都能正确读取之前的用户信息的目的的话,会造成无谓的内存浪费与繁重的服务器压力。并且在集群之间传播,可能会出现延迟。所以改为了redis来存放用户信息,进行登录判断,主要是得益于redis的本身的数据共享、内存存储、K-V结构等天生的快速缓存中间体的优势。

细节处理

这里有两个细节:

细节一:用户脱敏的处理。

一个是刚开始在数据传输的过程中,用户的所有信息均在浏览器中传递,过于敏感,不太安全。

所以将部分可展示属性封装成单独的DTO进行传递。

其次是对于用户手机号作为key传播储存的替换,我们在后台生成一个随机串token,然后让前端带来这个token就能完成我们的整体逻辑了。

细节二:刷新token与判断用户登录的双拦截器的使用以正确地处理共同的业务逻辑。

刚开始时是在用户浏览需要验证身份的模块时,在这些模块的共同的拦截器中刷新token。但是考虑到用户可能会进行非必要登录的页面浏览,所有这时候token也需要刷新,但是之前的拦截器只能作用于需要验证登录身份的模块,对此无能为力。所以就增加了一层总的拦截器,作用于所有请求,用来刷新token有效期。保证了用户在使用过程中不会出现因为时间过期而重新登录的尴尬状态。

2、商户缓存功能

功能工作流程

在查询商户信息时,往往通过缓存来提升访问速度。

标准的操作方式就是查询数据库之前先查询缓存,如果缓存数据存在,则直接从缓存中返回,如果缓存数据不存在,再查询数据库,然后将数据存入redis。

这时就很自然地引出了两个问题:

细节处理

问题一:双写一致问题

数据库与缓存数据不一致,导致多线程安全问题

根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间

根据id修改店铺时,先修改数据库,再删除缓存。等下一次再来查询数据时,再从数据库中加载到缓存中。

问题二:缓存穿透问题及解决方案

客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。庞大的请求导致服务端宕机等损害。

我们有两种处理方法:

一个是返回并缓存空对象,一个是使用布隆过滤器隔离。两者各有优缺点。

布隆过滤器实现较为复杂所以就用了缓存空对象的方法。

业务逻辑就是:
先从前台提交要查询的商户id,打到redis中判断是否命中缓存,如果redis中命中缓存了,再判断缓存是否是空值,如果是的话,直接结束查询,不是的话就返回数据。如果redis没有命中的话,就来到数据库中查询,在数据库中查询到了数据,便将数据写入缓存,同时返回数据。如果没有的话,直接将空数据写入缓存,结束查询。

问题三:缓存雪崩问题及解决思路

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

问题四:缓存击穿问题及解决思路

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

解决方案:

互斥锁的实现:

核心思路:相较于原来从缓存中查询不到数据后直接查询数据库而言,现在的方案是进行查询之后,如果从缓存没有查询到数据,则进行互斥锁的获取,获取互斥锁后,判断是否获得到了锁,如果没有获得到,则休眠,过一会再进行尝试,直到获取到锁为止,才能进行查询

如果获取到了锁的线程,再去进行查询,查询后将数据写入redis,再释放锁,返回数据,利用互斥锁就能保证只有一个线程去执行操作数据库的逻辑,防止缓存击穿。

利用redis的setnx方法来表示获取锁

方案分析:我们之所以会出现这个缓存击穿(热点key)问题,主要原因是在于我们对key设置了过期时间,假设我们不设置过期时间,其实就不会有缓存击穿的问题,但是不设置过期时间,这样数据不就一直占用我们内存了吗,我们可以采用逻辑过期方案。

逻辑过期的实现:

我们把过期时间设置在 redis的value中,注意:这个过期时间并不会直接作用于redis,而是我们后续通过逻辑去处理。

流程分析:

假设线程1去查询缓存,然后从value中判断出来当前的数据已经过期了,此时线程1去获得互斥锁,那么其他线程会进行阻塞,获得了锁的线程他会开启一个 线程去进行 以前的重构数据的逻辑,直到新开的线程完成这个逻辑后,才释放锁, 而线程1直接进行返回,假设现在线程3过来访问,由于线程线程2持有着锁,所以线程3无法获得锁,线程3也直接返回数据,只有等到新开的线程2把重建数据构建完后,其他线程才能走返回正确的数据。

这种方案优点在于,异步的构建缓存,缺点在于在构建完缓存之前,返回的都是脏数据。

3、优惠券秒杀功能

功能及流程:

redis实现全局唯一ID、优惠券秒杀解决单体架构的一人一单问题、乐悲观锁使用等等

优惠券秒杀业务下单核心逻辑分析:

当用户开始进行下单,我们应当去查询优惠卷信息,查询到优惠卷信息,判断是否满足秒杀条件与秒杀资格,比如时间是否充足,如果时间充足,则进一步判断库存是否足够,秒杀资格在于是否已经拿到了券,只能保证一人一券。如果两者都满足,则扣减库存,创建订单,然后返回订单id,如果有一个条件不满足则直接结束。

细节处理:

问题一:超卖问题

假设线程1过来查询库存,判断出来库存大于1,正准备去扣减库存,但是还没有来得及去扣减,此时线程2过来,线程2也去查询库存,发现这个数量一定也大于1,那么这两个线程都会去扣减库存,最终多个线程相当于一起去扣减库存,此时就会出现库存的超卖问题。

解决方案:加锁

超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁:而对于加锁,我们通常有乐观锁、悲观锁两种解决方案。

这里使用的是类似于CAS的乐观锁处理

他的操作逻辑是在操作时,对数据的版本号进行+1 操作,然后要求version 如果是1 的情况下,才能操作,那么第一个线程在操作后,数据库中的version变成了2,但是他自己满足version=1 ,所以没有问题,此时线程2执行,线程2 最后也需要加上条件version =1 ,但是现在由于线程1已经操作过了,所以线程2,操作时就不满足version=1 的条件了,所以线程2无法执行成功。

这样就解决了超卖问题。

问题二:一人多单问题(单体锁)

优惠卷是为了引流,为了防止一个人可以无限制的抢这个优惠卷,所以我们应当增加一层逻辑,让一个用户只能下一个单,而不是让一个用户下多个单

这里是很值得推敲的一个细节:

直接加一层对应用户的订单量count>0的判断之后,仍然会出现高并发线程安全问题,只是比没加>0的条件判断之前少一点而已。

所以这里需要加锁。而且必须要控制锁的粒度,这里是采用在方法上使用intern方法办证获取的是同一把锁。即控制了粒度,又控制了唯一性。并且使用了spring的事务来保证原子性。(防止当前方法事务还没有提交,但是锁已经释放)

以上就是单体设备的加锁状态,我们使用了分布式锁对其进行了升级保证了在不同JVM之间仍能工作。

现在的问题还是和之前一样,并发过来,查询数据库,都不存在已购 订单(为何???),所以我们还是需要加锁,但是乐观锁比较适合更新数据,而现在是插入数据,所以我们需要使用悲观锁操作。

4、基于setnx的分布式锁功能

功能及流程:

利用redis 的setNx 方法使得满足分布式系统或集群模式下多进程可见并且互斥的锁。

我们利用redis 的setNx 方法,当有多个线程进入时,我们就利用该方法,第一个线程进入时,redis 中就有这个key 了,返回了1,如果结果是1,则表示他抢到了锁,那么他去执行业务,然后再删除锁,退出锁逻辑,没有抢到锁的哥们,等待一定时间后重试即可

细节处理:

有两个方法是需要实现的:

1、加锁

2、释放锁

细节一:加锁:

利用setnx方法进行加锁,同时增加过期时间,防止死锁,此方法可以保证加锁和增加过期时间具有原子性。

细节二:释放锁时误删锁(阻塞):

两种情况:

情况一:持有锁的线程在锁的内部出现了阻塞,导致他的锁自动释放,这时其他线程,线程2来尝试获得锁,就拿到了这把锁,然后线程2在持有锁执行过程中,线程1反应过来,继续执行,而线程1执行过程中,走到了删除锁逻辑,此时就会把本应该属于线程2的锁进行删除,这就是误删别人锁的情况说明

解决方案:解决方案就是在每个线程释放锁的时候,去判断一下当前这把锁是否属于自己,如果不属于自己,则不进行锁的删除,假设还是上边的情况,线程1卡顿,锁自动释放,线程2进入到锁的内部执行逻辑,此时线程1反应过来,然后删除锁,但是线程1,一看当前这把锁不是属于自己,于是不进行删除锁逻辑,当线程2走到删除锁逻辑时,如果没有卡过自动释放锁的时间点,则判断当前这把锁是属于自己的,于是删除这把锁。

情况二:线程1的拿锁,比锁,删锁,实际上并不是原子性的,这期间也会出现阻塞而导致误删锁的情况,解决方法就是在redis中使用lua脚本实现多条命令原子性问题。

引出Redisson

我们一路走来,利用添加过期时间,防止死锁问题的发生,但是有了过期时间之后,可能出现误删别人锁的问题,这个问题我们开始是利用删之前 通过拿锁,比锁,删锁这个逻辑来解决的,也就是删之前判断一下当前这把锁是否是属于自己的,但是现在还有原子性问题,也就是我们没法保证拿锁比锁删锁是一个原子性的动作,最后通过lua表达式来解决这个问题

5、基于Redisson的分布式锁功能

sentx的四大问题

基于setnx实现的分布式锁存在下面的问题:

重入问题重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是使用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。

不可重试:是指目前的分布式只能尝试一次,我们认为合理的情况是:当线程在获得锁失败后,他应该能再次尝试获得锁。

超时释放:锁超时释放虽然可以避免死锁,但如果是业务执行耗时较长,也会导致锁释放,存在安全隐患。

主从一致性: 如果Redis提供了主从集群,当我们向集群写数据时,主机需要异步的将数据同步给从机,而万一在同步过去之前,主机宕机了,就会出现死锁问题。

使用Redisson解决问题

Redisson 是一个 Java Redis 客户端,它提供了分布式锁的实现。

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。

分布式锁-redission可重入锁原理

在Lock锁中,他是借助于底层的一个voaltile的一个state变量来记录重入的状态的,比如当前没有人持有这把锁,那么state=0,假如有人持有这把锁,那么state=1,如果持有这把锁的人再次持有这把锁,那么state就会+1 ,如果是对于synchronized而言,他在c语言代码中会有一个count,原理和state类似,也是重入一次就加一,释放一次就-1 ,直到减少成0 时,表示当前这把锁没有被人持有。

分布式锁-redission锁的MutiLock原理

为了提高redis的可用性,我们会搭建集群,现在以主从集群为例(共有三种集群,详见此处

此时我们去写命令,写在主机上, 主机会将数据同步给从机,但是假设在主机还没有来得及把数据写入到从机去的时候,此时主机宕机,哨兵会发现主机宕机,并且选举一个slave变成master,而此时新的master中实际上并没有锁信息,此时锁信息就已经丢掉了

解决方案:

每个节点都加上锁才算成功

为了解决这个问题,redission提出来了MutiLock锁,使用这把锁咱们就不使用主从了,每个节点的地位都是一样的, 这把锁加锁的逻辑需要写入到每一个主丛节点上,只有所有的服务器都写入成功,此时才是加锁成功,假设现在某个节点挂了,那么他去获得锁的时候,只要有一个节点拿不到,都不能算是加锁成功,就保证了加锁的可靠性。

举例说明:

当我们去设置了多个锁时,redission会将多个锁添加到一个集合中,然后用while循环去不停去尝试拿锁,但是会有一个总共的加锁时间,这个时间是用需要加锁的个数 * 1500ms ,假设有3个锁,那么时间就是4500ms,假设在这4500ms内,所有的锁都加锁成功, 那么此时才算是加锁成功,如果在4500ms有线程加锁失败,则会再次去进行重试.

WatchDog 机制是 Redisson 提供的一种自动延期机制。当线程获取锁成功后,Redisson 会启动一个 WatchDog 线程,每隔一段时间(默认是 30 秒)自动延长锁的超时时间,确保锁不会因为超时而被释放。这可以避免因业务逻辑执行时间过长导致的锁过期问题,提高分布式锁的稳定性和可靠性。

6、秒杀优化

问题引出

秒杀是对时间很敏感的业务模块,回顾之前的业务逻辑,可以在提升整体效率。

整体业务如下:

1、查询优惠卷

2、判断秒杀库存是否足够

3、查询订单

4、校验是否是一人一单

5、扣减库存

6、创建订单

在这六步操作中,又有很多操作是要去操作数据库的,而且还是一个线程串行执行。这样就很慢。抽丝剥茧,我只要确定他能做这件事,然后我后边慢慢做就可以了,我并不需要他一口气做完这件事。所以我们可以将判断他能否做这件事,与之做这件事的过程分离开来执行,消息队列的方式可以用来完成我们的需求

解决方案

优化方案:我们将耗时比较短的逻辑判断放入到redis中,比如是否库存足够,比如是否一人一单,这样的操作,只要这种逻辑可以完成,就意味着我们是一定可以下单完成的,我们只需要进行快速的逻辑判断,根本就不用等下单逻辑走完,我们直接给用户返回成功, 再在后台开一个线程,后台线程慢慢的去执行queue里边的消息。

优化后的业务逻辑如下:

  • 新增秒杀优惠券的同时,将优惠券信息保存到Redis中

  • 基于Lua脚本,判断秒杀库存、一人一单,决定用户是否抢购成功

  • 如果抢购成功,将优惠券id和用户id封装后存入阻塞队列

  • 开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能

这里使用的是,基于Redis的Stream结构作为消息队列,实现异步秒杀下单。

7、基于redis的stream实现的消息队列

基于Redis的Stream结构作为消息队列,实现异步秒杀下单。

Stream 是 Redis 5.0 引入的一种新数据类型,可以实现一个功能非常完善的消息队列。

8、达人探店-点赞排行榜

采用一个可以排序的set集合,就是咱们的sortedSet

9、好友关注

基于set集合中,有交集并集补集的api实现共同好友、互关、实现Feed流推送、分页查询收件箱功能

针对用户的操作:可以对用户进行关注和取消关注功能。

细节处理:

细节一:Feed投喂方式

关注推送也叫做Feed流,直译为投喂。为用户持续的提供“沉浸式”的体验,通过无限下拉刷新获取新的信息。

采用时间线排序的方式,用推拉结合的模式推送消息。

推拉模式是一个折中的方案,站在发件人这一段,如果是个普通的人,那么我们采用写扩散的方式,直接把数据写入到他的粉丝中去,因为普通的人他的粉丝关注量比较小,所以这样做没有压力,如果是大V,那么他是直接将数据先写入到一份到发件箱里边去,然后再直接写一份到活跃粉丝收件箱里边去,现在站在收件人这端来看,如果是活跃粉丝,那么大V和普通的人发的都会直接写入到自己收件箱里边来,而如果是普通的粉丝,由于他们上线不是很频繁,所以等他们上线时,再从发件箱里边去拉信息。

细节二:滚动分屏的实现

我们这个地方可以采用sortedSet来做,可以进行范围查询,并且还可以记录当前获取数据时间戳最小值,就可以实现滚动分页了

10、附近商户查询实现

功能及流程:

使用redis的GEO数据结构存储经纬度,实现附近商户查询功能。

当我们点击美食之后,会出现一系列的商家,商家中可以按照多种排序方式,我们此时关注的是距离,这个地方就需要使用到我们的GEO,向后台传入当前app收集的地址(我们此处是写死的) ,以当前坐标作为圆心,同时绑定相同的店家类型type,以及分页信息,把这几个条件传入后台,后台查询出对应的数据再返回。

11、用户签到功能

功能及流程

利用redi中的bitmap数据结构存储签到信息,把每一个bit位对应当月的每一天,形成了映射关系。

12、UV统计

功能及流程:

使用redis的hyperLogLog数据结构完成网站独立访客量统计功能。

UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。

利用redis的此数据结构可以轻松完成向HyperLogLog百万级数据的添加,而内存占用却低得发指。

------------------------------------------

面试最后的话

面试官,说说面试之外的事情吧,其实今年我只投了美团一家,一方面是因为美团的校招真的很有诚意,大家都能感受到。在一个原因呢,是因为我对自己有深刻的认知,我觉得大家都去为了抢一个实习机会撞破了头的时候,这里面可能有很危险的存在,因为众利勿往。因为我觉得做一个事业,你是把他当成眼前的仅仅为了混一份实习经验,或者实习的经济收入,而速成某项技能,我觉得这不是真正的热爱。而是着眼于自己5年10年甚至更远的将来所做的每一步决策。

所以我重新将计算机基础、计算机网络、操作系统等计算机科学必知必会的基础好好地过了一遍。目前在学习算法。虽然不是很深入,但是我相信自己对此由微到宏的研习,会让自己对这份技术越来越明晰,也会对自己的业务又跟深层次的理解。

现如今能得到贵公司的面试邀约,若有幸能成为实习生,我会全力做好自己应有之职,与大家庭抱团取暖,将咱们的业务再拔新高。

谢谢您!



💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖

热门专栏推荐

🌈🌈计算机科学入门系列                     关注走一波💕💕

🌈🌈CSAPP深入理解计算机原理        关注走一波💕💕

🌈🌈微服务项目之黑马头条                 关注走一波💕💕

🌈🌈redis深度项目之黑马点评            关注走一波💕💕

🌈🌈Java面试八股文系列专栏            关注走一波💕💕

🌈🌈算法leetcode+剑指offer              关注走一波💕💕


总栏

🌈🌈​​​​​​JAVA后端技术栈                          关注走一波💕💕  

🌈🌈JAVA面试八股文​​​​​​                          关注走一波💕💕  

🌈🌈JAVA项目(含源码深度剖析)    关注走一波💕💕  

🌈🌈计算机四件套                               关注走一波💕💕  

🌈🌈算法                                        ​​​​​​     ​关注走一波💕💕  

🌈🌈必知必会工具集                           关注走一波💕💕

🌈🌈书籍网课笔记汇总                       关注走一波💕💕  

🌈🌈考试复习资料                              关注走一波💕💕  

🌈🌈C/C++技术栈                              关注走一波💕💕  

🌈🌈GO技术栈                                   关注走一波💕💕  


分栏

🌈🌈JAVA后端技术栈

🌈🌈spring                                      关注走一波💕💕         ​

🌈🌈redis                                        关注走一波💕💕

🌈🌈MySQL                               ​​​     关注走一波💕💕 

🌈🌈mybatis                ​​​​​​​        ​​​​     ​​​​      关注走一波💕💕

🌈🌈mybatisplus                           关注走一波💕💕

🌈🌈MQ          ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        关注走一波💕💕

🌈🌈微服务                                     关注走一波💕💕

🌈🌈设计模式                                 关注走一波💕💕

🌈🌈分布式锁                                 关注走一波💕💕


🌈🌈JAVA八股文​​​​​​​​​​​​​​​​​​​​​​​​​​​​JAVA面试八股文(redis、MySQL、框架、微服务、MQ、JVM、设计模式、并发编程、JAVA集合、常见技术场景)                                                          关注走一波💕💕    

                                                             


🌈🌈JAVA项目(含源码深度剖析)

🌈🌈黑马头条(微服务)             关注走一波💕💕

🌈🌈黑马点评(redis)               关注走一波💕💕


🌈🌈计算机四件套

🌈🌈计算机基础                           关注走一波💕💕

🌈🌈计算机基础                           关注走一波💕💕

🌈🌈计算机网络                           关注走一波💕💕

🌈🌈数据结构与算法                    关注走一波💕💕


🌈🌈算法

🌈🌈leetcode                              关注走一波💕💕

🌈🌈剑指offer                             关注走一波💕💕


🌈🌈必知必会工具集                   关注走一波💕💕


🌈🌈书籍网课笔记汇总

🌈🌈CSAPP笔记                        关注走一波💕💕

🌈🌈计算机科学速成课               关注走一波💕💕

🌈🌈CS自学指南                        关注走一波💕💕

🌈🌈读书笔记与每日记录           关注走一波💕💕


🌈🌈考试复习资料​​​​​​​                      关注走一波💕💕


🌈🌈C/C++技术栈                      关注走一波💕💕                           


🌈🌈GO技术栈                          关注走一波💕💕                                                    


📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤收藏✅ 评论💬,大佬三连必回哦!thanks!!!
📚愿大家都能学有所得,功不唐捐!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值