8种:byte、short、int、long、float、double、char、boolean。
记忆方法:按“整数型-浮点型-字符型-布尔型”分类记。
- 整数型4种(byte、short、int、long,范围从小到大);
- 浮点型2种(float、double,精度从低到高);
- 字符型1种(char);
- 布尔型1种(boolean)。
Java的引用数据类型包括:类(class)、接口(interface)、数组(array)、枚举(enum)、注解(annotation)。
记忆方法:可记为“类接数枚注”,对应“类、接口、数组、枚举、注解”,通过谐音和顺序帮助记忆,这些类型都指向对象,存储的是对象的引用地址。
(1) 使基本数据类型**具备对象特性**,可参与泛型、集合操作
(2)提供**类型转换**、常量等实用方法(如Integer.parseInt())
(3)方便在**需要对象的场景**中使用基本类型(如反射、序列化)
记忆方法:“包装基本成对象,泛型集合能进入,转换常量有帮助”——核心是将基本类型“包装”成对象,解决基本类型无法直接用于泛型、集合等需要对象的场景,并提供实用工具方法。
装箱:将基本数据类型转换为对应包装类对象(如int→Integer)。
拆箱:将包装类对象转换为对应基本数据类型(如Integer→int)。
记忆方法:“基本进箱变对象(装箱),对象出箱回基本(拆箱)”——把“箱”理解为包装类,形象记忆转换方向和结果。
三大特点:封装、继承、多态。
记忆方法:“封继多”——谐音联想,封装是隐藏细节,继承是复用扩展,多态是同一行为不同实现,三者是面向对象的核心支柱。
==:
- 基本类型:比较值是否相等
- 引用类型:比较对象地址是否相同(是否为同一对象)
equals:
- 默认(Object类):同==,比较地址
- 重写后(如String、包装类):比较对象内容是否相等
记忆方法:“==比地址,重写equals比内容”——基本类型特殊,==直接比 value;引用类型==比地址,equals需看是否重写,重写后通常比内容。
HashCode是对象的哈希值,由Object类的hashCode()方法生成,是一个int类型数值。
主要作用:
(1) 用于哈希表(如HashMap)中快速定位对象,提高查询效率
(2) 与equals配合使用,遵循“相等对象必同哈希码,同哈希码对象不一定相等”原则
记忆方法: “哈希码是对象指纹,表中定位快如神,相等对象码必等,同码对象未必同”——核心记住哈希码的定位作用及与equals的关系准则。
Java容器主要分为两类:
(1) Collection接口(存储单个元素):
- List(有序可重复):ArrayList、LinkedList、Vector
- Set(无序不可重复):HashSet、TreeSet、LinkedHashSet
- Queue(队列):LinkedList、PriorityQueue
(2) Map接口(存储键值对):HashMap、TreeMap、LinkedHashMap、Hashtable、ConcurrentHashMap
记忆方法: “Collect存单元素,List有序Set唯一,Map键值成对居;Array快链增删易,Hash无序Tree有序”——按接口分类,结合典型实现类的特点辅助记忆。
ArrayList扩容机制:
1. 初始容量默认为10(无参构造)
2. 当添加元素导致容量不足时,触发扩容
3. 扩容公式:新容量 = 旧容量 + 旧容量/2(即1.5倍)
4. 若1.5倍后仍不足,则直接扩容至所需容量
5. 通过Arrays.copyOf()复制原数组到新数组
记忆方法: “初始10满就扩,1.5倍是基础,不够直接按需扩,复制数组换新家”——核心记住触发条件、扩容倍数及底层复制操作。
HashMap的扩容机制(以JDK1.8为例):
1. 默认初始容量16,负载因子0.75,当元素数超过容量×负载因子(12)时触发扩容
2. 扩容后容量变为原来的2倍(左移1位)
3. 扩容时重新哈希:原数组元素重新计算在新数组中的位置(因容量是2的幂,可通过高位运算简化计算)
4. 链表长度≥8且数组容量<64时,先扩容而非转红黑树
记忆方法: “16初始0.75载,超12就扩两倍来,重算位置新位栽,链长8时先扩再树开”——核心记容量、负载因子、扩容倍数及与红黑树转换的关联。
单例模式主要实现方式:
1. 饿汉式:类加载时初始化实例(线程安全,可能浪费资源)
2. 懒汉式(线程不安全):首次使用时创建,多线程有风险
3. 懒汉式(线程安全):加synchronized同步(效率低)
4. 双重检查锁:两次判空+volatile,兼顾安全与效率
5. 静态内部类:利用类加载机制实现延迟加载和线程安全
6. 枚举:天然防止反射和序列化破坏单例,简洁安全
记忆方法: “饿汉加载急,懒汉用再砌;双重锁防弃,静态内部避;枚举最彻底”——按初始化时机和安全性特点分类记忆,枚举是最优方案之一。
JDK1.8的主要新特性:
1. Lambda表达式:简化匿名内部类写法,函数式编程支持
2. Stream API:简化集合数据处理,支持链式操作
3. 接口默认方法:接口可定义带实现的default方法
4. 新时间API:LocalDateTime等,线程安全且易用
5. 方法引用:通过::符号复用已有方法
6. Optional类:避免空指针异常的容器类
记忆方法:“拉姆达简化代码,Stream流处理集合,接口默认方法好,时间API更可靠,方法引用少冗余,Optional空指针跑”——按功能领域串联核心特性,突出各自解决的问题。
字节流和字符流的区别:
1. 处理单位:字节流以字节(8位)为单位,字符流以字符(16位,对应Unicode)为单位
2. 适用场景:字节流适合处理二进制数据(如图片、音频),字符流适合处理文本数据(需考虑编码)
3. 底层实现:字符流底层依赖字节流,会使用缓冲区并进行编码转换
记忆方法: “字节8位通吃二进制,字符16位专司文本义;字节直接读写快,字符编码转换来”——核心区分处理单位和适用场景,字符流本质是带编码处理的字节流封装。
记忆方法: “JVM是引擎,JRE加类库能运行,JDK含工具可开发”——从功能范围理解:JDK ⊇ JRE ⊇ JVM,开发用JDK,运行用JRE,跨平台靠JVM。
String、StringBuffer、StringBuilder的区别:
(1) 可变性:String不可变(每次修改生成新对象);StringBuffer和StringBuilder可变(直接修改自身)
(2) 线程安全:StringBuffer线程安全(方法加synchronized);StringBuilder线程不安全
(3) 性能:String修改性能差;StringBuilder性能优于StringBuffer(无同步开销)
记忆方法: “String不可变,缓冲(Buffer)安全慢,构建(Builder)可变快,单线程用后者,多线程选前者”——核心记可变性、线程安全与性能的对应关系,按使用场景选择。
线程创建有4种方式:
1. 继承Thread类,重写run()方法
2. 实现Runnable接口,重写run()方法
3. 实现Callable接口,重写call()方法(有返回值,可抛异常)
4. 使用线程池(如ExecutorService)创建
记忆方法: “继承Thread,实现Runnable,还有Callable带返回,线程池里批量给”——前两种无返回值,Callable有返回值,线程池适合管理多个线程。
线程池(以ThreadPoolExecutor为例)核心参数有7个:
1. 核心线程数(corePoolSize):常驻核心线程数量
2. 最大线程数(maximumPoolSize):线程池允许的最大线程数
3. 空闲时间(keepAliveTime):非核心线程闲置后的存活时间
4. 时间单位(unit):keepAliveTime的时间单位
5. 工作队列(workQueue):用于存放等待执行的任务
6. 线程工厂(threadFactory):创建线程的工厂
7. 拒绝策略(handler):任务满时的处理策略
记忆方法: “核心最大定范围,空闲时间加单位,工作队列存任务,工厂策略不可无”——按线程数量控制、任务管理、辅助配置三类划分记忆。
Java线程池主要类型(基于Executors工具类创建):
1. FixedThreadPool:**固定线程数的线程池**,核心线程=最大线程
2. SingleThreadExecutor:**单线程线程池**,仅1个线程执行任务
3. CachedThreadPool:**可缓存线程池**,线程数可动态增减(60秒空闲销毁)
4. ScheduledThreadPool:**定时/周期性执行任务的线程池**
5. SingleThreadScheduledExecutor:**单线程定时任务线程池**
记忆方法: “固定数、单线程、可缓存、定时行”——按线程数量特性和任务类型区分,Fixed固定、Single单一、Cached动态、Scheduled定时。
线程池工作原理:
1. 提交任务时,先判断核心线程是否已满:未满则创建核心线程执行任务
2. 核心线程满时,任务放入工作队列等待
3. 队列满时,判断是否达到最大线程数:未达到则创建非核心线程执行任务
4. 达到最大线程数时,触发拒绝策略处理任务
5. 非核心线程空闲超过keepAliveTime时销毁,核心线程默认不销毁(可通过allowCoreThreadTimeOut调整)
记忆方法: “核心线程先干活,满了队列来暂存,队列满了扩线程,扩到最大就拒绝,空闲线程会销毁”——按任务提交后的流程顺序记忆,从核心线程到队列再到最大线程数的递进逻辑。
线程的状态(JDK定义)有6种:
1. 新建(New):刚创建,未调用start()
2. 运行(Runnable):包含就绪(等待CPU调度)和正在运行
3. 阻塞(Blocked):等待锁释放(如synchronized竞争)
4. 等待(Waiting):无时间限制等待(如wait()、join())
5. 超时等待(Timed Waiting):有时间限制等待(如sleep(ms)、wait(ms))
6. 终止(Terminated):线程执行完毕
记忆方法: “新启运行,阻等超时,最终终止”——按线程生命周期顺序:从新建到启动运行,中间可能进入阻塞、等待或超时等待,最后执行完毕终止。
synchronized与Lock的区别主要体现在以下几个方面:
记忆方法: “sync内置自动放,Lock手动需释放,尝试中断加公平,Lock灵活性能强”——核心区分实现层面、释放方式和功能灵活性。
start()和run()的核心区别在于是否启动新线程:
记忆方法: “start()启新线,run()只是方法现;start()调一次,run()可多遍”——关键记住start()的核心作用是启动新线程,而run()只是线程要执行的任务逻辑。
wait()和sleep()的核心区别在于是否释放锁及使用场景:
记忆方法: “wait放锁等通知,sleep持锁到点醒;wait属于Object,sleep是Thread静态”——核心区分锁的释放行为和使用目的,这是两者最关键的差异。
悲观锁和乐观锁是并发控制的两种思想,核心区别在于对待数据冲突的假设和处理方式:
记忆方法: “悲观总防冲突来,先锁资源再操作;乐观默认冲突少,提交时候再校验”——核心记住两者对冲突的假设和处理时机的差异。
在并发编程中,**锁(Lock)是控制多个个线程对共享资源访问的同步机制**,用于解决多线程同时操作共享数据时可能出现的线程安全问题(如数据不一致、脏读、竞态条件等)。
简单说,锁的核心作用是**保证同一时间只有一个(或指定数量的)线程能执行特定代码块**,从而实现对共享资源的有序访问。
锁的关键特性:
1. 排他性:多数锁(如synchronized)具有排他性,即同一时间只有一个线程能持有锁
2. 可见性:释放锁时,线程对共享资源的修改会同步到主内存,确保其他线程可见
3. 有序性:通过控制线程执行顺序,避免指令重排序导致的并发问题
常见锁的实现:
- Java中的synchronized关键字(隐式锁)
- JUC包中的Lock接口及其实现类(如ReentrantLock,显式锁)
- 数据库中的行锁、表锁等
记忆方法:“锁是共享资源的门卫,同一时间只放一个(批)线程进,保证数据安全不出错”——核心理解锁是并发环境下保护共享资源的“控制机制”。
死锁是多线程并发时出现的一种状态:**两个或多个线程相互持有对方需要的资源(锁),且都不主动释放,导致所有线程永远阻塞,无法继续执行**。
死锁产生的4个必要条件:
1. 互斥条件:资源(锁)只能被一个线程持有
2. 持有并等待:线程持有已获得的资源,同时等待其他资源
3. 不可剥夺:资源不能被强制从持有线程中剥夺
4. 循环等待:线程间形成相互等待资源的循环链
举例:
- 线程A持有锁1,等待锁2
- 线程B持有锁2,等待锁1
- 两者相互等待,无法继续,形成死锁
解决思路:
- 破坏其中任一必要条件(如按固定顺序获取锁,避免循环等待)
- 使用tryLock()设置超时,超时后释放已持有的锁
- 定期检测并中断死锁线程
记忆方法: “线程互相等资源,你不放手我也攥,循环等待陷僵局,四个条件全占全”——核心记住死锁是线程间相互等待资源的僵持状态及必要条件。
Java中的锁种类丰富,可按不同维度分类,常见的有:
记忆方法: “偏向轻量重量级,可重公平与读写,隐式显式自旋锁,按需选择锁不同”——按锁的升级路径、功能特性和使用方式分类记忆,重点理解各类锁的适用场景。
第一范式(1NF):确保每列不可再分,是原子值。
记忆:列不可分,像“地址”拆成“省/市/街道”才符合。
第二范式(2NF):满足1NF,且非主键列完全依赖主键(消除部分依赖)。
记忆:非主属性“完全”靠主键,不能只依赖主键的一部分(如复合主键场景)。
第三范式(3NF):满足2NF,且非主键列不依赖其他非主键列(消除传递依赖)。
记忆:非主属性只“直接”依赖主键,不通过其他列间接依赖(如“学号→系名→系主任”需拆分)。
整体记忆: 一拆原子,二去部分,三消传递。
数据库事务有四大特性,简称ACID:
原子性(Atomicity):事务是不可分割的最小单位,要么全执行,要么全不执行。
记忆:“原子”不可再分,对应事务要么完成要么回滚。
一致性(Consistency):事务执行前后,数据库从一个一致状态变到另一个一致状态。
记忆:“一致”即数据符合预设规则(如约束、逻辑),不会因事务出问题。
隔离性(Isolation):多个事务并发执行时,彼此互不干扰,像单独执行一样。
记忆:“隔离”即事务间有边界,避免互相影响。
持久性(Durability):事务提交后,修改永久保存,不受系统故障影响。
记忆:“持久”即数据写入后稳定存在,不会丢失。
整体记忆:ACID四个字母对应,联想“酸(ACID)性事务”更易记。
MySQL有四种事务隔离级别,从低到高依次为:
读未提交(Read Uncommitted):允许读取未提交的数据,可能出现脏读、不可重复读、幻读。
记忆:“未提交就读”,隔离性最低,问题最多。
读已提交(Read Committed):只能读取已提交的数据,解决脏读,仍可能有不可重复读、幻读。
记忆:“提交后才读”,避免脏读,但同一事务内两次读结果可能不同。
可重复读(Repeatable Read):事务内多次读取结果一致,解决不可重复读,仍可能有幻读(MySQL通过MVCC机制实际避免了幻读)。
记忆:“重复读都一样”,**MySQL默认级别**。
串行化(Serializable):事务串行执行,完全隔离,无并发问题,但性能最差。
记忆:“串行排队”,隔离性最高,像单线程一样执行。
记忆口诀: 未提、已提、可重复、串行化(按隔离级别递增顺序)。
读未提交:可能出现脏读、不可重复读、幻读(三种问题都有)。
记忆:隔离最低,“啥问题都可能碰上”。
读已提交:解决脏读,仍可能不可重复读、幻读。
记忆:“提交后才读”,避免了“读脏数据”,但同一事务内两次读可能不一样(不可重复读),或多了新数据(幻读)。
可重复读:解决脏读、不可重复读,理论上可能幻读(MySQL实际通过MVCC避免)。
记忆:“重复读结果不变”,但可能看到新插入的行(幻读,逻辑上存在)。
串行化:无任何问题(脏读、不可重复读、幻读都解决)。
记忆:“串行执行”,相当于单线程,没有并发冲突。
规律记忆: 隔离级别从低到高,解决的问题依次增加——读未提交(全有)→读已提交(去脏读)→可重复读(再去不可重复读)→串行化(全解决)。
脏读:一个事务读取到另一个未提交事务的修改数据,若后者回滚,前者读的数据就是“脏”的。
记忆:“读了没提交的,还可能被撤回的数据”。
不可重复读:同一事务内,两次读取同一数据,结果因其他事务提交的修改而不同。
记忆:“重复读同一条,结果不一样(被修改了)”。
幻读:同一事务内,两次执行相同查询,结果因其他事务插入新数据而多了“不存在过”的行。
记忆:“像幻觉一样多了新数据,针对新增/删除”。
区分:不可重复读侧重“修改”,幻读侧重“新增/删除”。
数据库事务的底层实现依赖三大核心机制:
锁机制
通过行锁、表锁等控制并发访问,避免事务间干扰(隔离性)。
MVCC(多版本并发控制)
为数据保留多个版本,实现读写不阻塞,是隔离级别的核心支撑(如可重复读)。
记忆:日志保安全(原子、持久),锁控并发(隔离),MVCC提性能,共同保障事务特性。
数据库锁主要分为:
记忆法:“粒功特”三类别,粒度“行表页”由小到大;功能“共排”(共享、排他)分读写;特殊锁记“意、间、自”(意向、间隙、自增)。
SQL查询中导致索引失效的常见情况:
WHERE SUBSTR(name,1,3)='abc'
)WHERE age+1=30
)NOT
、!=
、<>
、NOT IN
等否定操作符LIKE
以通配符开头(如WHERE name LIKE '%abc'
)WHERE name=123
,实际应为字符串)OR
连接包含非索引列的条件记忆法:“函数运算否,like左通配,字符无引号,组合不左配,or含非索引,优化器选错”。
数据库索引常见结构有:
MATCH AGAINST
)记忆法:“B+哈希全文R,聚簇非聚分存否”——前四个按功能分类,后两个按存储方式区分,B+树为核心结构。
B树和B+树的核心区别:
记忆法:“B树存数在节点,B+数据叶链接;B查中途可停下,B+叶终稳如一;B树占空比较大,B+索引更省地”。
适合建索引的字段特征:
记忆法:“频用(查询条件)、高异(区分度)、短字(长度)、主外键”——高频使用且特征显著的字段优先建索引。
不适合建立索引的情况:
记忆法:“量少、常改、区分低,少用、文本不建引”——数据量小、变动频繁、特征不明显的字段无需索引。
复合索引的最左匹配原则是指:当使用复合索引(多字段组合的索引)时,查询条件需从索引的最左侧字段开始匹配,且不能跳过中间字段,否则索引无法完全生效。
例如,对(a, b, c)建立复合索引:
- 能命中索引的情况:WHERE a=?、WHERE a=? AND b=?、WHERE a=? AND b=? AND c=?
- 不能命中索引的情况:WHERE b=?、WHERE c=?、WHERE a=? AND c=?(跳过了b)
记忆法:“复合索引像排队,必须从左依次对,中间跳过全白费”——类比排队需从第一个人开始依次识别,跳过中间则无法完整匹配。
索引回表是指在使用非聚簇索引(二级索引)查询时,索引叶子节点只存储索引列和主键值,当查询需要获取索引列之外的其他字段时,需通过主键值再次查询聚簇索引(主键索引)以获取完整数据的过程。
例如:InnoDB中,若对name列建索引(非聚簇索引),查询SELECT age FROM user WHERE name='张三'
时,会先通过name索引找到主键值,再用主键查聚簇索引获取age,这就是回表。
记忆法:“非聚索引存主键,要取其他需回表”——非聚簇索引只含部分信息,需返回聚簇索引拿完整数据,类似“先查目录找页码,再翻页码看内容”。
避免索引回表的核心是让查询所需字段都包含在索引中,无需额外查询聚簇索引。主要方法:
覆盖索引:将查询需要的所有字段都加入索引(包括查询条件和返回字段),形成“索引覆盖查询”。
例:对(a,b)建复合索引,查询SELECT a,b FROM t WHERE a=?
,索引已包含所需字段,无需回表。
主键索引查询:直接通过主键索引查询,因聚簇索引本身包含完整数据,天然无回表。
记忆法:“字段全在索引中,覆盖查询免回表;主键查询最直接,数据自带不绕道”。
Redis常用的数据类型有:String(字符串)、List(列表)、Hash(哈希)、Set(集合)、Sorted Set(有序集合)。
记忆方法:可记为“字列哈希集,有序也在内”。“字”对应String,“列”对应List,“哈希”对应Hash,“集”对应Set,“有序”对应Sorted Set,通过谐音和归类帮助记忆。
Redis的持久化方式有两种:RDB和AOF。
选择方式:
- 追求性能和备份效率选RDB;
- 追求数据安全性选AOF;
- 关键场景可两者结合,兼顾安全与性能。
记忆方法:“R快A全,结合更保险”。R(RDB)的特点是快,A(AOF)的特点是全,结合使用更可靠。
能存。
需要先将Java对象序列化为字节数组或字符串(如JSON、XML格式),再存入Redis;读取时反序列化还原为对象。
记忆方法:“对象要进门,先穿序列化的衣”。把对象比作访客,Redis比作房间,序列化是必须的“准入手续”。
可以通过以下方式保证Redis中存放的都是热点数据:
allkeys-lru
(移除最近最少使用的键)或volatile-lru
(只对设置过期时间的键使用LRU)记忆方法:“策略控淘汰,预热加时效,监控调内容”——通过淘汰策略控制非热点数据,提前预热热点数据,设置合理时效,并动态调整来保证热点特性。
缓存击穿:一个热点key突然失效(过期),大量瞬间量请求同时刻直达数据库,导致数据库压力骤增。
解决方法:
1. 热点key永不过期(避免失效触发)
2. 互斥锁(同一时间只让一个请求查库并更新缓存)
3. 预热热点数据并延长过期时间
记忆方法:“热点失效击穿库,永不过期锁保护,预热延长更稳固”——点出问题核心,对应三种解决思路。
缓存雪崩:大量缓存key在同一时间过期失效,或Redis服务宕机,导致所有请求瞬间涌向数据库,造成数据库崩溃。
解决方法:
1. 过期时间加随机值(避免集中过期)
2. 服务熔断与限流(保护数据库)
3. 缓存集群(避免单点故障)
4. 热点数据永不过期
记忆方法:“缓存集体失效崩,随机过期防集中,熔断限流加集群,热点永活保稳定”——概括问题及核心解决方案。
缓存穿透:查询一个不存在的数据,因缓存和数据库都无此数据,导致每次请求都穿透到数据库,造成资源浪费。
解决方法:
1. 缓存空值(对不存在的key缓存空结果,设置短期过期)
2. 布隆过滤器(预先过滤不存在的key,阻止请求到数据库)
3. 接口层校验(非法或无效请求直接拦截)
记忆方法:“查无数据穿到底,空值缓存暂隔离,布隆过滤提前挡,接口校验校验先拦截”——形象描述问题及多层防护方案。
Redis单线程却速度快的原因:
记忆方法:“内存操作无瓶颈,单线程免切换,多路复用高并发,结构优化效率佳”——从存储介质、线程模型、IO处理、数据结构四个维度记忆核心原因。
Redis 处理客户端键值对读写命令的核心逻辑(解析、执行、返回)始终是由一个单线程来串行执行的,这是其简单性和高性能的基石。但从 Redis 4.0 开始,它使用多线程来处理一些异步后台任务(如删除),从 Redis 6.0 开始,更支持使用多线程来并行处理网络 I/O(读请求和写回复),以进一步提升在多核CPU上的性能。
理解记忆:
版本 | 核心命令处理 | 网络 I/O | 后台任务(如删除) |
---|---|---|---|
Redis < 4.0 | 单线程 | 单线程 | 单线程 |
Redis 4.0+ | 单线程 | 单线程 | 多线程(可选) |
Redis 6.0+ | 单线程 | 多线程(可选) | 多线程(可选) |
Redis的内存淘汰策略分为以下8种:
noeviction
:默认策略,内存满时拒绝写操作,返回错误allkeys-lru
:从所有key中淘汰最近最少使用的volatile-lru
:仅从设置过期时间的key中淘汰最近最少使用的allkeys-random
:从所有key中随机淘汰volatile-random
:仅从设置过期时间的key中随机淘汰volatile-ttl
:仅从设置过期时间的key中,淘汰剩余生存时间最短的allkeys-lfu
:从所有key中淘汰最不经常使用的volatile-lfu
:仅从设置过期时间的key中淘汰最不经常使用的记忆方法:“两默认两随机,两LRU两LFU,volatile只找带过期的”
- 默认(noeviction)+ 随机(random)+ LRU(最近最少)+ LFU(最不经常)各分两类
- 带volatile前缀的仅作用于设置了过期时间的key,不带的作用于所有key
Redis采用三种策略结合删除过期键:
记忆方法:“惰性查时删,定期随机清,内存满时按策略”——三种机制分别在访问时、定时、内存满时工作,互补解决过期键问题。
记忆方法:
“两栈两器一堆区”
(两栈:虚拟机栈、本地方法栈;两器:程序计数器、方法区;一堆:堆。按线程私有/共享分类记忆更清晰)
记忆方法:
“ Bootstrap 启动核心,Extension 扩展功能,Application 加载应用,自定义按需变通”
(按加载范围从小到大,结合名称含义记忆)