小言_互联网的博客

面试总结1

207人阅读  评论(0)

用时:45min

1、事务的隔离级别、事务的特征

2、redis的延迟双删策略

3、Spring的生命周期

4、简单的描述一下 Gateway、Feign、Ribbon

5、你使用的配置中心nacos,为什么不用eureka

6、简单的说一下HashMap底层原理吧、负载因子是什么

7、介绍一下ConcurrentHashMap、包括底层结构

8、说一下ArrayList和LinkedLinst的区别、默认初始化容量、扩容机制、扩容会带来哪些问题、扩容带来的问题如何解决

9、sql左连接、右连接、内连接有什么区别、SQL语句你还清楚吗

一、 事务的隔离级别、事务的特征

1.1、事务的隔离级别

Serializable(串行化):提供严格的事务隔离。要求失去序列化执行,事务只能一个接一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

Repeatable read(可重复读):处理更新丢失、脏读和不可重复读取。读取数据的事务将会禁止写事务,但允许读事务,写事务则禁止任何其他事务。可通过“共享读锁”和“排他写锁”实现。

Read committed(读已提交):处理更新丢失、脏读。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。可通过“瞬间共享读锁”和“排他写锁”实现。

Read uncommitted(读未提交):只处理更新丢失。如果一个事务已经开始写数据,则不允许其他事务同时进行写操作,但允许其他事务读此行数据。可通过“排他写锁”实现。

1.2、事务的特征

Atomicity(原子性):一个事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复到事务开始前的状态。

Consistency(一致性):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。

Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

二、redis的延迟双删策略 

1、什么是redis的延迟双删

延迟双删策略是分布式系统中数据库存储和缓存数据保持一致性的常用策略,但它不是强一致。其实不管哪种方案,都避免不了Redis存在脏数据的问题,只能减轻这个问题,要想彻底解决,得要用到同步锁和对应的业务逻辑层面解决。

2、为什么要进行延迟双删

1、一般情况下就是我们redis缓存了数据库的数据后,此时我们的数据库进行了update修改数据操作,那么当redis打算进行清除原先的旧数据(脏数据)的过程中比如说服务器突然宕机了,那么当请求过来时,访问到的还是redis缓存的脏数据。

2、还有一种可能是我们先进行了redis缓存清除数据,但是还未进行update修改数据库中的数据,那么此时请求过来后访问查询到的还有原来的脏数据(旧数据)。

因此为了解决这些请求访问到脏数据的问题,我们就需要进行延迟双删。

3、如何实现延迟双删

实现过程:先进行缓存清除,再执行update,最后(延迟N秒)再执行缓存清除。 

上述中(延迟N秒)的时间要大于一次写操作的时间,一般为3-5秒。

        原因:如果延迟时间小于数据(写操作)写入redis的时间,会导致请求1清除了缓存,但是请求2缓存还未写入的尴尬。。。

 三、Spring的生命周期

整体来说就4个步骤:实例化bean,属性赋值,初始化bean,销毁bean

首先就是实例化bean,容器通过获取BeanDefinition对象中的信息进行实例化 然后呢就是属性赋值,利用依赖注入完成 Bean 中所有属性值的配置注入 接着就是初始化bean,如果在配置文件中通过 init-method 属性指定了初始化方法,调用该初始化方法。 最后就是销毁bean,和init-method一样,通过再配置文件中给destroy-method指定函数,就可以调用到销毁方法了,但是有可能不会执行销毁方法(这是因为java是由虚拟机执行的,当执行完其他的方法后,虚拟机就退出了,因此就不会再执行这个销毁的方法),那么我们就需要用到一个勾子函数。

四、简单的说一下HashMap底层原理吧 

在jdk1.8之前,底层是通过数组+链表实现的,当我们创建hashmap时会先创建一个数组,当我们用put方法存数据时,先根据key的hashcode值计算出hash值,然后用这个哈希值确定在数组中的位置,再把value值放进去,如果这个位置本来没放东西,就会直接放进去,如果之前就有,就会生成一个链表,把新放入的值放在头部,当用get方法取值时,会先根据key的hashcode值计算出hash值,确定位置,再根据equals方法从该位置上的链表中取出该value值,当容量超过当前容量的0.75倍之后,就会自动扩容为原来容量的2倍。这个0.75就是负载因子。

但是在jdk1.8之后,HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+ 红黑树组成。因为在1.7的时候,这个链表的长度不固定,所以如果key的hashcode重复之后,那么对应的链表的数据的长度就无法控制了,get数据的时间复杂度就取决于链表的长度了,为了提高这一部分的性能,加入了红黑树,如果链表的长度超过8位之后,会将链表转换为红黑树,极大的降低了时间复杂度

HashMap 线程不安全,即任一时刻可以有多个线程同时 写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap

负载因子:

负载因子解释:

负载因子是和扩容机制有关的,意思是如果当前容器的容量,达到了我们设定的最大值,就要开始执行扩容操作。
举个例子来解释:比如说当前的容器容量是16,负载因子是0.75,16*0.75=12,也就是说,当容量达到了12的时候就会进行扩容操作。
他的作用很简单,相当于是一个扩容机制的阈值。当超过了这个阈值,就会触发扩容机制。

负载因子大小选择及作用:

HashMap源码已经为我们默认指定了负载因子是0.75。

负载因子的作用:

负载因子的大小决定了HashMap的数据密度。
负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越长(多),造成查询或插入时比较次数增多,性能会下降。
负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中链表也就越少,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内存空间,而且经常扩容也会影响性能。


 

五、介绍一下ConcurrentHashMap、包括底层结构

相较于HashTable锁住的是对象整体,ConcurrentHashMap基于lock实现锁分段技术。首先将Map存放的数据分成一段一段的存储方式,然后给每一段数据分配一把锁,当一个线程占用锁访问其中一个段的数据时,其他段的数据也能被其他线程访问。ConcurrentHashMap不仅保证了多线程运行环境下的数据访问安全性,而且性能上有很大的提升

concurrentHashMap的底层结构:

concurrentHashMap 本质就是加了锁的HashMap

ConcurrentHashMap 1.7与1.8的区别总结 :

1.锁结构不同

JDK1.7

ConcurrentHashMap基于Segment+Entry数组实现的。

Segment是Reentrant的子类,而其内部也维护了一个Entry数组(这个Entry数组和HashMap中的 Entry数组是一样的)。

Segment其实是一个锁,可以锁住一段哈希表结构,而ConcurrentHashMap中维护了一个 Segment数组,所以是基于分段锁实现的。

JDK1.8

ConcurrentHashMap摒弃了Segment,而是采用synchronized+CAS来实现的。锁的粒度也从段 锁缩小为结点锁

2.put()的执行流程有所不同

JDK1.7

ConcurrentHashMap要进行两次定位,先对Segment进行定位,再对其内部的数组下标进行定 位。

定位之后会采用加锁机制。并且在整个put操作期间都持有锁。

JDK1.8

只需要一次定位,并且采用CAS+synchronized的机制。如果对应下标处没有结点,说明没有发生 哈希冲突,此时直接通过CAS进行插入,若成功,直接返回。若失败,则使用synchronized进行加 锁插入

 六、说一下ArrayList和LinkedLinst的区别、默认初始化容量、扩容机制、扩容会带来哪些问题、扩容带来的问题如何解决

1、ArrayList介绍:

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增 式再分配的数量。

它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。 在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能
ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接 口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对 象,这就是快速随机访问。
ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。

和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList, 而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

2、ArrayList扩容机制

ArrayList默认初始化容量为10,扩容的话底层采用的是grow方法,方法中的 oldCapacity表示旧容量,newCapacity表示新容量,扩容的话只需要将oldCapacity向右移动1位,其效果就相当于oldCapacity/2

为什么扩容时采用位移运算符(右移)而不采用算术运算符的方式:

        因为位移运算符的速度远远快于算术运算符,因为程序仅仅移动一下就可以了而不用去计算,这样就提高了效率,节省了资源。

我们知道扩容的话,是生成一个比旧容量更大的数组,然后把旧的容量数组放入到新的容量数组当中,但是我们频繁的扩容的话,就会频繁的创建然后放入进来,这样是比较浪费资源和效率的,因此我们一般指定一个初始化的容量值。

3、LinkedList介绍:

LinkedList是一个实现了List接口和Deque接口的双端链表。 

LinkedList底层的链表结构使它支持高效的插入和删除操作,但是查询操作时效率就会很低(因为每次查询操作的时候,都会从第一个节点开始遍历查询),

另外它实现了Deque接口,使得 LinkedList类也具有队列的特性; 

LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的 synchronizedList方法。

也可以延申的把node节点介绍一下。

七、sql左连接、右连接、内连接有什么区别

sql左连接、右连接、内连接有什么区别?
总结:语法公式不同、基础表不同、结果集不同。
一、语法公式不同:

左连接:
左连接的关键字是left join,语法公式为select *from  a left join  b on a .id=b .id。
右连接:
右连接的关键字是right join,语法公式为select *from  a right join b on a .id=b .id。
内连接:
右连接的关键字是inner join,语法公式为select *from a inner join b on a .id=b .id。

二、基础表不同:

左连接:左连接的基础表为left join左侧数据表,右表只会展示符合搜索条件的记录。
右连接:右连接的基础表为right join右侧数据表,左表只会展示符合搜索条件的记录,左表不足的地方用null填充。
内连接:并不以谁为基础,它只显示符合条件的记录

三、结果集不同:

左连接:返回包括左表中的所有记录和右表中连接字段相等的记录


 

右连接:返回包括右表中的所有记录和左表中连接字段相等的记录


 

内连接:只返回两个表中连接字段相等的行

 

 

只总结了这七点,其余的微服务的中间件这些,可以看我前面记的笔记即可~ 


转载:https://blog.csdn.net/lwj_07/article/details/127865416
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场