面向面试编程连载(二)

2023-03-18 10:08:57 浏览数 (2)

Spring 依赖注入概念和@Autowired 的用法。

代码语言:java复制
概念:实例不再由程序员实例化,而是通过spring容器帮我们new指定实例并且将实例注入到需要该对象的类。
依赖注入能够让相互协作的软件组件保持松散耦合
@Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。也可作用与集合上
这里授权服务配置类是继承了AuthorizationServerConfigurerAdapter,而AuthorizationServerConfigurerAdapter又实现了AuthorizationServerConfigurer接口!
源码AuthorizationServerConfigurer
@Autowired对List自动注入
//@Autowired注解用在接口的集合上面,所有实现该接口的实现类都会在该集合中
@Autowired(required = false)
private List<IAsynTask> tasks = Collections.emptyList();

Spring Bean 的生命周期。

Bean 的生命周期概括起来就是 4 个阶段:

实例化(Instantiation)

属性赋值(Populate)

初始化(Initialization)

销毁(Destruction)

Spring Bean 的生命周期Spring Bean 的生命周期

Spring Boot 启动流程以及底层源码

 Spring Boot 启动流程以及底层源码 Spring Boot 启动流程以及底层源码

索引的数据结构(比如 B 树)

代码语言:java复制
B Tree
B Tree相对于B-Tree有几点不同:
非叶子节点只存储键值信息。
所有叶子节点之间都有一个链指针。
数据记录都存放在叶子节点中。
查询速度快,但是占用空间
索引结构:B-Tree B Tree B:balance
B-Tree:平衡二叉树
特点:
1.具有数据节点
2.指向下层指针
3.指向数据指针
缺页查询,产生IO
B Tree:
特点:
1.具有数据节点
2.指向下层指针
命中数据3层查找后查询数据指针
加载更快,产生更少IO
效率:BTree更高,但从IO角度,Mysql选择B Tree

Hash 索引的特点


Hash 索引只能够用于使用 = 或者 <=> 运算符的相等比较(但是速度更快)。Hash 索引不能够用于诸如 < 等
用于查找一个范围值的比较运算符。依赖于这种单值查找的系统被称为 “键-值存储”;对于这种系统,尽可能地使用 hash 索引。
优化器不能够使用 hash 索引来加速 ORDER BY 操作。这种类型的索引不能够用于按照顺序查找下一个条目。
MySql 无法使用 hash 索引估计两个值之间有多少行(这种情况由范围优化器来决定使用哪个索引)。如果你将一张 MyISAM 或 InnoDB 表转换成一个 hash 索引的内存表时,一些查询可能会受此影响。
查找某行记录必须进行全键匹配。而 B-tree 索引,任何该键的左前缀都可用以查找记录

<font color = red>索引是为了加速对表中数据行的检索而创建的一种分散的存储结构</font>

建索引的语句

代码语言:sql复制
CREATE INDEX idx_xxx USING BTREE ON tablename (字段,字段,字段);

索引的种类尤其是复合索引以及对应的回表和最左匹配原则

代码语言:java复制
普通索引:最基本的索引,没有任何约束限制。
唯一索引:和普通索引类似,但是具有唯一性约束,可以有 null
主键索引:特殊的唯一索引,不允许有 null,一张表最多一个主键索引
组合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并
全文索引:对文本的内容进行分词、搜索
覆盖索引:查询列要被所建的索引覆盖,不必读取数据行
1、复合索引绑定的第一个列,没有出现在查询条件中;
举例说明:为emp表插入索引idx_age_deptid_name(age,deptid,name),但是在查询条件中未使用age,导致复合索引全部失效。

2、复合索引绑定的多个列是有顺序的,某一个列没有出现在查询条件中,存储引擎不能使用索引中该列及其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里没有deptid列,会导致复合索引中的deptid及其后的索引失效。

3.查询条件中出现某个列是范围查询的,存储引擎不能使用复合索引中该列其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里deptid列使用到了范围查询,会导致复合索引中的deptid其后的索引失效。

4.查询条件中某列使用否定条件的(!= <> IS NOT NULL),存储引擎不能使用索引中该列其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里deptid列使用到了否定条件,会导致复合索引中的deptid其后的索引失效。

5.查询条件中某列使用LIKE条件后的字段是以%开头的(如:’�C’),存储引擎不能使用索引中该列及其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里name列使用到了like ‘%a’,会导致复合索引中的name及其后的索引失效。

6.查询条件中某列使用函数的,存储引擎不能使用索引中该列及其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里name列使用到了like ‘%a’,会导致复合索引中的name及其后的索引失效。

7.查询条件中某列使用类型转换的(包括显示的和隐示的),存储引擎不能使用索引中该列及其后的所有列。
如:字符串类型的列NAME=3,就是隐示的类型转换,将INT型转换为字符串类型。如果写为NAME=’3’,就不是类型转换。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件name=3,会导致复合索引中的name及其后的索引失效。条件写成name=‘3’,索引就不会失效。

回表

如果索引的列在 select 所需获得的列中(因为在 mysql 中索引是根据索引列的值进行排序的,所以索引节点中存在该列中的部分值)或者根据一次索引查询就能获得记录就不需要回表,如果 select 所需获得列中有大量的非索引列,索引就需要到表中找到相应的列的信息,这就叫回表。

使用聚集索引(主键或第一个唯一索引)就不会回表,普通索引就会回表

索引下推优化,

可以在索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表字数。

最左匹配原则

带头大哥不能死,中间兄弟不能断

Spring AOP 底层原理

AOP 底层是采用动态代理机制实现的:接口 实现类

如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代

理对象。

没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用

Cglib 生成一个被代理对象的子类来作为代理。

就是由代理创建出一个和 impl 实现类平级的一个对象,但是这个对象不是一个真正的对象,

只是一个代理对象,但它可以实现和 impl 相同的功能,这个就是 aop 的横向机制原理,这

样就不需要修改源代码。

HashMap在java1.7之前底层数据结构是数组 链表,1.8之后是数组 链表 红黑树,

在1.7以前的put方法采用的是头插法,当hash碰撞次数到达8,且桶内元素到达64个的时候形成链表,但是在极端情况下会造成链表过长,效率变低,并且在rehash的时候,头插法会造成回环链首尾相连,形成死锁,在java1.8以后采用红黑树,除了添加效率都高,是线程不安全的,不安全示例

代码语言:txt复制
public class HashMapTest {

    public static void main(String[] args) {
        HashMapThread thread0 = new HashMapThread();
        HashMapThread thread1 = new HashMapThread();
        HashMapThread thread2 = new HashMapThread();
        HashMapThread thread3 = new HashMapThread();
        HashMapThread thread4 = new HashMapThread();
        thread0.start();
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

class HashMapThread extends Thread {
    private static AtomicInteger ai = new AtomicInteger();
    private static Map<Integer, Integer> map = new HashMap<>();

    @Override
    public void run() {
        while (ai.get() < 1000000) {
            map.put(ai.get(), ai.get());
            ai.incrementAndGet();
        }
    }
}

JDK1.8 之前

JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。 ## HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n -

1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在

元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,

直接覆盖,不相同就通过拉链法解决冲突。

所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了

防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。

JDK1.8 之后

当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据

HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会

执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize() 方法对数组扩容。

1.通常代替HashMap的安全由HashTable代替,但是多线程下他的put.get方法都是synchronized,效率太低,

2.Collections.synchronizedMap(),底层仍是synchronized

3.java9实现Collections.of()

ConcurrentHashMap 与 ConcurrentSkipListMap

ConcurrentHashMap 加锁

ConcurrentSkipListMap 不需要加锁,浪费空间,

4.ConcurrentHashMap

ConcurrentHashMap如何保证线程安全,在1.7以前由划分segment分段锁机制,共计16个并发级别,隔离级别太大,有很多空间就浪费了,太小就段内的元素过多

1.8以后是cas算法C语言写得,无锁算法,put添加的时候,链表 红黑树

put方法(无锁添加)

3、HashMap 的扩容机制是怎样的?

一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的 2 倍。

HashMap 的容量是有上限的,必须小于 1<<30,即 1073741824。如果容量超出了这个

数,则不再增长,且阈值会被设置为 Integer.MAX_VALUE。

JDK7 中的扩容机制

空参数的构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数

组。

有参构造函数:根据参数确定容量、负载因子、阈值等。

第一次 put 时会初始化数组,其容量变为不小于指定容量的 2 的幂数,然后根据负载因子

确定阈值。

如果不是第一次扩容,则 新容量=旧容量 x 2 ,新阈值=新容量 x 负载因子 。

JDK8 的扩容机制

空参数的构造函数:实例化的 HashMap 默认内部数组是 null,即没有实例化。第一次调

用 put 方法时,则会开始第一次初始化扩容,长度为 16。 ## 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的 2 的幂数,将

这个数设置赋值给阈值(threshold)。第一次调用 put 方法时,会将阈值赋值给容量,

然后让 阈值 = 容量 x 负载因子。 ## 如果不是第一次扩容,则容量变为原来的 2 倍,阈值也变为原来的 2 倍。(容量和阈值都

变为原来的 2 倍时,负载因子还是不变)。

此外还有几个细节需要注意:

首次 put 时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;

不是首次 put,则不再初始化,直接存入数据,然后判断是否需要扩容;

4、ConcurrentHashMap 的存储结构是怎样的?

Java7 中 ConcurrnetHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个

线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它

的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变,默认 Segment 的

个数是 16 个。

Java8 中的 ConcurrnetHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由

Java7 中的 Segment 数组 HashEntry 数组 链表 进化成了 Node 数组 链表 / 红

黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红

黑树,在冲突小于一定数量时又退回链表。

5、线程池大小如何设置?

CPU 密集型任务(N 1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N (CPU 核心数) 1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,

或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而

在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。

I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而

线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程

使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是

2N。

如何判断是 CPU 密集任务还是 IO 密集任务?

CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。单

凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相

比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。

6、IO 密集=Ncpu*2 是怎么计算出来?

I/O 密集型任务任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理

I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在

I/O 密集型任务的应用中,我们可以多配置一些线程。例如:数据库交互,文件上传下

载,网络传输等。IO 密集型,即该任务需要大量的 IO,即大量的阻塞,故需要多配置线

程数。

7、G1 收集器有哪些特点?

G1 的全称是 Garbage-First,意为垃圾优先,哪一块的垃圾最多就优先清理它。

G1 GC 最主要的设计目标是:将 STW 停顿的时间和分布,变成可预期且可配置的。

被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:

并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者

CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程

执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留

了分代的概念。

空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法

实现的收集器;从局部上来看是基于“标记-复制”算法实现的。

可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共

同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明

确指定在一个长度为 M 毫秒的时间片段内。

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的

Region(这也就是它的名字 Garbage-First 的由来)

8、你有哪些手段来排查 OOM 的问题?

增加两个参数 -XX: HeapDumpOnOutOfMemoryError -

XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信

息到指定目录。

同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题大概出在什么区域。

使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如 HashMap 做缓存未

清理,时间长了就会内存溢出,可以把改为弱引用。

image.pngimage.png

0 人点赞