裸辞-疫情-闭关-复习-大厂offer(二)

简介: 裸辞-疫情-闭关-复习-大厂offer(二)

引子


2022 年 3 月辞职,没多久上海爆发疫情,蜗居在家准备面试。在经历 1 个月的闭关和 40+ 场 Android 面试后,拿到一些 offer。


总体上说,有如下几种面试题型:


  1. 基础知识


  1. 算法题


  1. 项目经历


  1. 场景题


场景题,即“就业务场景给出解决方案”,考察运用知识解决问题的能力。这类题取决于临场应变、长期积累、运气。


项目经历题取决于对工作内容的总结提炼、拔高升华、运气


  1. 争取到什么样的资源


  1. 安排了怎么样的分工


  1. 搭建了什么样的架构


  1. 运用了什么模式


  1. 做了什么样的取舍


  1. 采用了什么策略


  1. 做了什么样的优化


  1. 解决了什么问题


力争把默默无闻的“拧螺丝”说成惊天动地的“造火箭”。(这是一门技术活)


但也不可避免地会发生“有些人觉得这是高大上的火箭,有些人觉得不过是矮小下的零件”。面试就好比相亲,甲之蜜糖乙之砒霜是常有的事。除非你优秀到解决了某个业界的难题。


算法题取决于刷题,运气,相较于前两类题,算法题可“突击”的成分就更多了。只要刷题足够多,胜算就足够大。大量刷,反复刷。


基础知识题是所有题型中最能“突击”的,它取决于对“考纲”的整理复习、归纳总结、背诵、运气。Android 的知识体系是庞杂的,对于有限的个人精力来说,考纲是无穷大的。


这不是一篇面经,把面试题公布是不讲武德的。但可以分享整个复习稿,它是我按照自己划定的考纲整理出的全部答案。


整个复习稿分为如下几大部分:


  1. Android


  1. Java & Kotlin


  1. 设计模式 & 架构


  1. 多线程


  1. 网络


  1. OkHttp & Retrofit


  1. Glide


由于篇幅太长,决定把全部内容分成两篇分享给大家。其中,Android 和 Java & Kotlin 已经在第一篇分享过,这一篇的内容是剩下的加粗部分。


设计模式/原则 & 架构


设计原则


  • 单一职责原则:关于内聚的原则。高内聚、低耦合的指导方针,类或者方法单纯,只做一件事情


  • 接口隔离原则:关于内聚的原则。要求设计小而单纯的接口(将过大的接口拆分),或者说只暴露必要的接口


  • 最少知识法则


  • 关于耦合的原则。要求类不要和其他类发生太多关联,达到解耦的效果


  • 从依赖者的角度来说,只依赖应该依赖的对象。


  • 从被依赖者的角度说,只暴露应该暴露的方法。


  • 对象方法的访问范围应该受到约束:


  1. 对象本身的方法


  1. 对象成员变量的方法


  1. 被当做参数传入对象的方法


  1. 在方法体内被创建对象的方法


  1. 不能调用从另一个调用返回的对象的方法


  • 开闭原则:关于扩展的原则。对扩展开放对修改关闭,做合理的抽象就能达到增加新功能的时候不修改老代码(能用父类的地方都用父类,在运行时才确定用什么样的子类来替换父类),开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段


  • 里氏替换原则


  • 为了避免继承的副作用,若继承是为了复用,则子类不该改变父类行为,这样子类就可以无副作用地替换父类实例,若继承是为了多态,则因为将父类的实现抽象化,


  • 依赖倒置原则:即是面向接口编程,面向抽象编程,高层模块不该依赖底层模块,而是依赖抽象(比萨店不应该依赖具体的至尊披萨,而应该依赖抽象的披萨接口,至尊披萨也应该依赖披萨接口)


单例模式


目的:在单进程内保证类唯一实例


  1. 静态内部类:虚拟机保证一个类的初始化操作是线程安全的,而且只有使用到的时候才会去初始化,缺点是没办法传递参数


  1. 双重校验:第一校验处于性能考虑,若对象存在直接返回,不需要加锁。第二次校验是为了防止重复构建对象。对象引用必须声明为 volatile,通过保证可见性和防止重排序,保证单例线程安全。因为INSTANCE = new instance()不是原子操作,由三个步骤实现1.分配内存2.初始化对象3.将INSTANCE指向新内存,当重排序为1,3,2时,可能让另一个线程在第一个判空处返回未经实例化的单例。


工厂模式


  • 目的:解耦。将对象的使用和对象的构建分割开,使得和对象使用相关的代码不依赖于构建对象的细节


  • 增加了一层“抽象”将“变化”封装起来,然后对“抽象”编程,并利用”多态“应对“变化”,对工厂模式来说,“变化”就是创建对象。


  • 实现方式


  1. 简单工厂模式


  • 将创建具体对象的代码移到工厂类中的静态方法。


  • 实现了隐藏细节和封装变化,对变化没有弹性,当需要新增对象时需要修改工厂类


  1. 工厂方法模式


  • 在父类定义一个创建对象的抽象方法,让子类决定实例化哪一个具体对象。
  • 特点


  • 只适用于构建一个对象


  • 使用继承实现多态


  1. 抽象工厂模式


  • 定义一个创建对象的接口,把多个对象的创建细节集中在一起


  • 特点:使用组合实现多态


建造者模式


  • 目的:简化对象的构建


  • 它是一种构造复杂对象的方式,复杂对象有很多可选参数,如果将所有可选参数都作为构造函数的参数,则构造函数太长,建造者模式实现了分批设置可选参数。Builder模式增加了构造过程代码的可读性


  • Dialog 用到了这个模式


观察者模式


目的:以解耦的方式进行通信。将被观察者和具体的观察行为解耦。


  • 是一种一对多的通知方式,被观察者持有观察者的引用。


  • ListView的BaseAdapter中有DataSetObservable,在设置适配器的时候会创建观察者并注册,调用notifydataSetChange时会通知观察者,观察者会requestLayout


策略模式


  • 目的:将使用算法的客户和算法的实现解耦


  • 手段:增加了一层“抽象”将“变化”封装起来,然后对“抽象”编程,并利用”多态“应对“变化”,对策略模式来说,“变化”就是一组算法。


  • 实现方式:将算法抽象成接口,用组合的方式持有接口,通过依赖注入动态的修改算法


  • setXXListener都是这种模式


装饰者模式


  • 目的:用比继承更灵活的方式为现有类扩展功能


  • 手段:具体对象持有超类型对象


  • ~是继承的一种替代方案,避免了泛滥子类。


  • ~增加了一层抽象,这层抽象在原有功能的基础上扩展新功能,为了复用原有功能,它持有原有对象。这层抽象本身是一个原有类型


  • ~实现了开闭原则


外观模式


  • 目的:隐藏细节,降低复杂度


  • 手段:增加了一层抽象,这层抽象屏蔽了不需要关心的子系统调用细节


  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。


  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。


  • 实现方式:外观模式会通过组合的方式持有多个子系统的类,~提供更简单易用的接口(和适配器类似,不过这里是新建接口,而适配器是已有接口)


  • 通过外观模式,可以让类更加符合最少知识原则


  • ContextImpl是外观模式


适配器模式


  • 意图: 将现有对象包装成另一个对象


  • 手段:增加了一层抽象,这层抽象完成了对象的转换。(具体对象持有另一个而具体对象)


  • 是一种将两个不兼容接口(源接口和目标接口)适配使他们能一起工作的方式,通过增加一个适配层来实现,最终通过使用适配层而不是直接使用源接口来达到目的。


代理模式


  • 目的:限制对象的访问,或者隐藏访问的细节


  • 手段:增加了一层抽象,这层抽象拦截了对对象的直接访问


  • 实现方式:代理类通过组合持有委托对象(装饰者是直接传入对象,而代理通常是偷偷构建对象)


  • 分类 :代理模式分为静态代理和动态代理


  • 静态代理:在编译时已经生成代理类,代理类和委托类一一对应


  • 动态代理:编译时还未生成代理类,只是定义了一种抽象行为(接口),只有当运行后才生成代理类,使用Proxy.newProxyInstance(),并传入invocationHandler


  • Binder通信是代理模式,Retrofit运用动态代理构建请求。


模板方法模式


  • 目的:复用算法


  • 手段:新增了一层抽象(父类的抽象方法),这层抽象将算法的某些步骤泛化,让子类有不同的实现


  • 实现方式:在方法(通常是父类方法)中定义算法的骨架,将其中的一些步骤延迟到子类实现,这样可以在不改变算法结构的情况下,重新定义某些步骤。这些步骤可以是抽象的(表示子类必须实现),也可以不是抽象的(表示子类可选实现,这种方式叫钩子)


  • android触摸事件中的拦截事件是钩子


  • android绘制中的onDraw()是钩子


命令模式


  • 目的:将执行请求和请求细节解耦


  • 手段:增加了一层“抽象”将“变化”封装起来,然后对“抽象”编程,并利用”多态“应对“变化”,对命令模式来说,“变化”就是请求细节。新增了一层抽象(命令)


  • 这层抽象将请求细节封装起来,执行者和这层抽象打交道,就不需要了解执行的细节。因为请求都被统一成了一种样子,所以可以统一管理请求,实现撤销请求,请求队列


  • 实现方式:将请求定义成命令接口,执行者持有命令接口


  • java中的Runnable就是命令模式的一种实现


桥接模式


  • 目的:提高系统扩展性


  • 手段:抽象持有另一个抽象


  • 是适配器模式的泛化模式


访问者模式


  • 目的:动态地为一类对象提供消费它们的方法。


  • 重载是静态绑定(方法名相同,参数不同),即在编译时已经绑定,方法的参数无法实现运行时多态


  • 重写是动态绑定(继承),方法的调用者可实现运行时多态


  • 双分派:a.fun(b)在a和b上都实现运行时多态,实现方法调用者和参数的运行时多态。


  • 编译时注解使用了访问者模式,一类对象是Element,表示构成代码的元素(类,接口,字段,方法),他有一个accept方法传入一个Visitor对象


架构


关于 MVP,MVVM,MVI,Clean Architecture 的介绍可以点击如下文章:


如何把业务代码越写越复杂? | MVP - MVVM - Clean Architecture


写业务不用架构会怎么样?(一)


写业务不用架构会怎么样?(二)


写业务不用架构会怎么样?(三)


MVP 架构最终审判 —— MVP 解决了哪些痛点,又引入了哪些坑?(一)


MVP 架构最终审判 —— MVP 解决了哪些痛点,又引入了哪些坑?(二)


MVP 架构最终审判 —— MVP 解决了哪些痛点,又引入了哪些坑?(三)


“无架构”和“MVP”都救不了业务代码,MVVM能力挽狂澜?(一)


多线程


进程 & 线程


  • 系统按进程分配除CPU以外的系统资源(主存 外设 文件), 系统按线程分配CPU资源
  • Android系统进程叫system_server,默认情况下一个Android应用运行在一个进程中,进程名是应用包名,进程的主线程叫ActivityThread


  • jvm会等待普通线程执行完毕,但不会等守护线程


  • 若线程执行发生异常会释放锁


  • 线程上下文切换:cpu控制权由一个运行态的线程转交给另一个就绪态线程的过程(需要从用户态到核心态转换)


  • 一对一线程模型:java语言层面的线程会对应一个内核线程


  • 抢占式的线程调度,即由系统决定每个线程可以被分配到多少执行时间


阻塞线程的方法


  1. sleep():使线程到阻塞态,但不释放锁,会触发线程调度。


  1. wait():使线程到阻塞态,释放锁(必须先获取锁)


  1. yield():使线程到就绪态,主动让出cpu,不会释放锁,发生一次线程调度,同优先级或者更高优先级的线程有机会执行


线程安全三要素


  • 原子性:不会被线程调度器中断的操作。


  • 可见性:一个线程中对共享变量的修改,在其他线程立即可见。


  • 有序性:程序执行的顺序按照代码的顺序执行。


原子操作


  1. 除long和double之外的基本类型(int, byte, boolean, short, char, float)的赋值操作。


  1. 所有引用reference的赋值操作,不管是32位的机器还是64位的机器。


  1. java.concurrent.Atomic.* 包中所有类的原子操作。


死锁


四个必要条件


  1. 互斥访问资源


  1. 资源只能主动释放,不会被剥夺


  1. 持有资源并且还请求资源


  1. 循环等待 解决方案是:加锁顺序+超时放弃


线程生命周期


线程从新建状态到就绪状态,就绪态的线程如果获得了cpu执行权就变成了运行态,运行完变成死亡态,如果运行中产生等待锁的情况(sleep,wait),则会进入阻塞态,当阻塞态的进程被唤醒后进入就绪态,参与cpu时间片的竞争,执行完毕死亡态。


线程池


  • 如果创建对象代价大,且对象可被重复利用。则用容器保存已创建对象,以减少重复创建开销,这个容器叫做池。线程的创建就是昂贵的,通过线程池来维护实例。


线程通信:等待通知机制


  • 等待通知机制是一种线程间的通信机制,可以调整多个进程的执行顺序。


  • 需要等待某个资源的线程可以调用 wait(),当某资源具备后,可以调用统一对象上的notify()


  1. notify():随机使一个线程进入就绪态,它需要和调用wait()是同一个对象(获得锁的线程才能调用)


  1. notifyAll():唤醒所有等待线程,让他们到就绪队列中


Condition


  • 是多线程通信的机制,挂起一个线程,释放锁,直到另一个线程通知唤醒,提供了一种自动放弃锁的机制。


  • await()挂起线程的同时释放锁(所以必须先获取锁,否则抛异常),signal 唤醒一个等待的线程


  • 每个Condition对象只能唤醒调用自己的await()方法的那个线程


  • 如果条件不用 Condition 实现,则线程可能不断地获取锁并释放锁,但因继续执行的条件不满足,cpu 负载打满。使用Condition 让等待线程不消耗cpu


  • await() 通常配合 while(){await()} 因为被唤醒是从上次挂起的地方执行,还需要再次判断是否满足条件


  • await()必须在拥有锁的情况下调用,以防止lost wake-up,即在await条件判断和await调用之间notify被调用了。当await条件满足后,还没来得及执行await时发生线程调度,另一个线程调用了notify()。然后才轮到await()执行,它将错过刚才的notify,因为notify在await之前执行。


interrupt()


  • 不会真正中断正在执行的线程,只是通知它你应该被中断了,自己看着办吧。


  • 若线程正运行,则中断标志会被置为true,并不影响正常运行


  • 如果线程正处于阻塞态,则会收到InterruptedException,就可以在 catch中执行响应逻辑


  • 若线程想响应中断,则需要经常检查中断标志位,并主动停止,或者是正确处理 InterruptedException


内存屏障


  • 用于禁止重排序,它分为以下四种:


  1. LoadLoad  Load1; LoadLoad; Load2  确保Load1数据的装载,之前于Load2及所有后续装载指令的装载。


  1. StoreStore  Store1; StoreStore; Store2  确保Store1数据对其他处理器可见(刷新到内存),之前于Store2及所有后续存储指令的存储。


  1. LoadStore Load1; LoadStore; Store2  确保Load1数据装载,之前于Store2及所有后续的存储指令刷新到内存。


  1. StoreLoad Store1; StoreLoad; Load2  确保Store1数据对其他处理器变得可见(指刷新到内存),之前于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。


volatile


  • 保证变量操作的有序性和可见性


  • 在每一个volatile写操作前面插入一个StoreStore屏障,可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中。


  • 在每一个volatile写操作后面插入一个StoreLoad屏障,避免volatile写与后面可能有的volatile读/写操作重排序。


  • 在每一个volatile读操作后面插入一个LoadLoad屏障,禁止处理器把上面的volatile读与下面的普通读重排序。


  • 在每一个volatile读操作后面插入一个LoadStore屏障,禁止处理器把上面的volatile读与下面的普通写重排序。


  • volatile就是将共享变量在高速缓存中的副本无效化,这导致线程修改变量的值后需立刻同步到主存,读取共享变量都必须从主存读取


  • 当volatile修饰数组时,表示数组首地址是volatile的而不是数组元素


CAS


  • Compare and Swap


  • 当前值,旧值,新值,只有当旧值和当前值相同的时候,才会将当前值更新为新值
  • Unsafe将cas编译成一条cpu指令,没有函数调用


  • aba问题:当前值可能是变为b后再变为a,此a非彼a,通过加版本号能解决


  • 非阻塞同步:没有挂起唤醒操作,多个线程同时修改一个共享变量时,只有一个线程会成功,其余失败,它们可以选择轮询。


synchronized


  • 隐式加锁释放锁


  • 可修饰静态方法,实例方法,代码块


  • 当修饰静态方法的时,锁定的是当前类的 Class 对象(就算该类有多个实例,使用的还是同一把锁)。


  • 当修饰非静态方法的时,锁定的是当前实例对象 this。当 饰代码块时需要指定锁定的对象。


  • 通过将对变量的修改强制刷新到内存,且下一个获取锁的线程必须从内存拿。保证了可见性


  • 同一时间只有一个线程可以执行临界区,即所有线程是串行执行临界区的


  • happen-before 就是释放锁总是在获取锁之前发生。


  • synchronized特点


  1. 可重入:可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁。线程可以再次进入已经获得锁的代码段,表现为monitor计数+1


  1. 不公平:synchronized 代码块不能够保证进入访问等待的线程的先后顺序


  1. 不灵活:synchronized 块必须被完整地包含在单个方法里。而一个 Lock 对象可以把它的 lock() 和 unlock() 方法的调用放在不同的方法里


  1. 自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,synchronized是自旋锁。如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高


  • 1.8 之后synchronized性能提升:


  • 偏向锁:目的是消除无竞争状态下性能消耗,假定在无竞争,且只有一个线程使用锁的情况下,在 mark word中使用cas 记录线程id(Mark Word存储对象自身的运行数据,在对象存储结构的对象头中)此后只需简单判断下markword中记录的线程是否和当前线程一致,若发生竞争则膨胀为轻量级锁,只有第一个申请偏向锁的会成功,其他都会失败


  • 轻量级锁:使用轻量级锁,不要申请互斥量,只需要用 CAS 方式修改 Mark word,若成功则防止了线程切换


  • 自旋(一种轻量级锁):竞争失败的线程不再直接到阻塞态(一次线程切换,耗时),而是保持运行,通过轮询不断尝试获取锁(有一个轮询次数限制),规定次数后还是未获取则阻塞。进化版本是自适应自旋,自旋时间次数限制是动态调整的。


  • 重量级锁:使用monitorEnter和monitorExit指令实现(底层是mutex lock),每个对象有一个monitor


  • 锁膨胀是单向的,只能从偏向->轻量级->重量级


ReentrantLock


  • 手动加锁手动释放:JVM会自动释放synchronized锁,但可重入锁需要手动加锁手动释放,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。手动加锁并释放灵活性更高


  1. 可中断锁:lockInterruptibly(),未获取则阻塞,但可响应当前线程的interrupt()被调用


  1. 超时锁:tryLock(long timeout, TimeUnit unit)  ,未获取则阻塞,但阻塞超时。


  1. 非阻塞获取锁:tryLock() ,未获取则直接返回


  1. 可重入:若已获取锁,无需再次竞争即可重新进入临界区执行,state +1,出来的时候需要释放两次锁 state -1


  1. 独占锁:同一时间只能被一个线程获取,其他竞争者得等待(AQS独占模式)


  • 性能:竞争不激烈,Synchronized的性能优于ReetrantLock,激烈时,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态


  • 是AQS的实现类,AQS中有一个Node节点组成双向链表,存放等待的线程对象(被包装成Node)


  • 获取锁流程:


  1. 尝试获取锁,公平锁排队逻辑:判断锁是否空闲,若空闲还要判断队列中是否有排在更前面的等待线程,若无则尝试获取锁。若当前独占线程是自己,表示重入,则增加state值。非公平锁抢占逻辑:直接进行CAS state操作(从0到1),若成功则设置当前线程为锁独占线程。若失败会判断当前线程是否就是独占线程若是表示重入,state+1


  1. 获取失败则入AQS队列,然后在挂起线程:将线程对象包装成EXCLUSIVE模式的Node节点插入到AQS双向链表的尾部(cas值链尾的next结点+自旋保证一定成功),并不断尝试获取锁,或中断Thread.interrupted()


  • 释放锁流程:


  1. 释放锁表现为将state减到0


  1. 调用unparkSuccessor()唤醒线程(非公平时如何唤醒)


ReentrantReadWriteLock


  • 并发度比ReentrantLock高,因为有两个锁,使用AQS,读锁是共享模式,写锁是独占模式。读多写少的情况下,提供更大的并发度


  • 可实现读读并发,读写互斥,写写互斥


  • 使用一个int state记录了两个锁的数量,高16位是读锁,低16位是写锁


  • 获取写锁过程:除了考虑写锁是否被占用,还要考虑读锁是否被占用,若是则获取锁失败,否则使用cas置state值,成功则置当前线程为独占线程。


  • 读并发也有并发数限制,获取读锁时需验证,并使用ThreadLocal记录当前线程持有锁的数量


  • 可能发生写饥饿,因为太多读


  • 锁降级:当一个线程获取写锁后再获取读锁,然后释放写锁


  • 不支持锁升级是为了保证可见性:多个线程获取读锁,其中任意线程获取写锁并更新数据,这个更新对其他读线程是不可见的


StampedLock


  • 实现读读并发,读写并发。


  • 在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作
  • 用二进制位记录每一次获取写锁的记录


CountdownLatch


  • 用作等待若干并发任务的结束


  • 内部有一个计数器,当值为0时,在countdownLatch上await的线程就被唤醒


  • 通过AQS实现,初始化是置AQS.state为n,countdow()通过自旋+cas将执行state--效果


CyclicBarrier


  • 用于同步并发任务的执行进度


  • 使用 ReentranntLock 保证count线程安全,每次调用await() count--,然后在condition上阻塞,当count为0时,会signalAll()


目录
相关文章
|
6月前
|
算法 Java 关系型数据库
2年5个月13天,从外包到拿下阿里offer,没想到屌丝也能有今天
不说太多废话,但起码要让你先对我有一个基本的了解。本人毕业于浙江某二本院校,算是科班出身,毕业后就进了一家外包公司做开发,当然不是阿里的外包,具体什么公司就不透露了,在外包一呆就呆了整整2年多,直到现在才从外包离开,如今拿到阿里的offer准备入职了。
|
6月前
|
Dubbo NoSQL Java
太为难我了,阿里面试了7轮(5年经验,拿下P7岗offer)
今年的大环境非常差,互联网企业裁员的现象比往年更严重了,可今年刚好是我的第一个“五年计划”截止的时间点,说什么也不能够耽搁了,所以早早准备的跳槽也在疫情好转之后开始进行了。但是,不得不说,这次阿里面试真的太难为我了,可以说是和面试官大战了7个回合,不过好在最后给了offer。
|
6月前
|
设计模式 算法 NoSQL
Java开发三年四面字节跳动复习一个月斩获offer,寒冬并不可怕
目前互联网行业形势越来越严峻,我接连投递了很多的简历,得到的回复却是寥寥无几,索性好好复习了大概一个半月的样子,挑战字节跳动成功!!接下来分享我在字节面试遇到的面试题,欢迎大家文末留言与我一起讨论!
|
6月前
|
消息中间件 Dubbo Java
疫情下的机遇,阿里直招怒斩"P7"offer,自曝狂啃六遍的面试笔记
工作肯定会找的,面试肯定要过的,小编在这里为大家整理了我的一位朋友,一位从中游公司跳槽到阿里P7的面试题库
|
存储 消息中间件 前端开发
裸辞-疫情-闭关-复习-大厂offer(一)
裸辞-疫情-闭关-复习-大厂offer(一)
106 0
|
存储 缓存 编解码
裸辞-疫情-闭关-复习-大厂offer(二)(下)
裸辞-疫情-闭关-复习-大厂offer(二)
98 0
|
存储 缓存 编解码
裸辞-疫情-闭关-复习-大厂offer(一)(中)
裸辞-疫情-闭关-复习-大厂offer(一)
85 0
|
存储 缓存 安全
裸辞-疫情-闭关-复习-大厂offer(一)(下)
裸辞-疫情-闭关-复习-大厂offer(一)
82 0
|
存储 缓存 网络协议
裸辞-疫情-闭关-复习-大厂offer(二)(中)
裸辞-疫情-闭关-复习-大厂offer(二)
104 0
|
SQL 缓存 NoSQL
冲刺金三银四,这份豪礼【面试锦囊】真舍不得给你们
大家好,我是小羽马上就是金三银四啦,最近有很多粉丝跟我交流关于面试方面的经验以及分享,也有部分是在准备今年的金三银四的春招。小羽也一直没出关于面试方面的文章,这篇就结合自己之前的面试经历以及...
181 0
冲刺金三银四,这份豪礼【面试锦囊】真舍不得给你们