面试总结
/**
* Copyright (C), 2020
* FileName: Java后端面试总结
* Author: Marlon
* Email: gatesma@foxmail
* Date: 2020/2/18
* Description:
*/
文章目录
- 面试总结
- (一)Java基础
- 1. wait和sleep的区别
- 2. synchronized底层原理 是可重入锁吗
- 3. CAS原理,CAS会有什么问题,怎么解决ABA问题(可以谈Java内存模型)
- 7. String,StringBuffer,StringBuilder区别
- 8. Concurrent下面的包的原理
- 9. Java集合框架、map
- 10. Java 异常
- 11. Object有哪些方法
- 12. 深拷贝、浅拷贝
- (二)数据结构
- 1. B树、B+树、红黑树,数据结构是什么,查询复杂度是多少
- **1. B树**
- **2. B+树**
- **3. 红黑树**
- 2. 用语言描述一颗二叉树
- 3. 七大排序算法
- (三)数据库
- 1. 数据库索引
- 2. 数据库隔离级别有哪些,举个不可重复读的例子
- 3. 除了设置数据库隔离级别,还有什么方法可以解决不可重复读
- 4. 事务的特性,具体介绍
- 5. 为什么二级索引存主键 ID 不直接存数据位置
- 6. 一条SQL语句是怎么执行的
- 7. 什么是索引覆盖
- 8. MylSAM只支持表锁、InnoDB默认行锁?
- 9. 索引失效的几种情况
- 10. 事务特性ACID
- (四)框架
- 一、Spring
- 1. IOC加载过程
- 2. Bean的生命周期
- (五)、场景题
- 1. Top K(很关键)
- 2. 进程和线程,区别,哪个效率高,为什么
- 3. 死锁条件,解决
- 4. 进程同步的四种方法
- 5. TCP建立连接、断开链接
- 6. 拥塞控制、流量控制
- 7. HTTP、Https
- (七)JVM、Java多线程与并发
- 1. 垃圾回收机制GC,cms,G1,垃圾回收的算法
- 2. java多线程,状态图
- 3. 乐观锁和悲观锁
- 4. 分布式锁
- 5. 锁类型
- 6. sychronize和lock的区别
- 7. 三个线程顺序执行(锁、join、信号量)
- 8. **什么时候发生full gc**
- 9. Sychronize锁升级
- (八)算法(有题解,篇幅可能过长)
- 1. 有序链表合并
- 2. Trie树(LeetCode 208)
(一)Java基础
1. wait和sleep的区别
- 这两个方法来自不同的类分别是Thread和Object
- 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
- sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
- sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。
2. synchronized底层原理 是可重入锁吗
JVM基于进入和退出Monitor对象来显示方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法的结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit与之配对。
Synchronize是可重入锁,一个获得锁的线程可以再次进入一个同步块方法
3. CAS原理,CAS会有什么问题,怎么解决ABA问题(可以谈Java内存模型)
CAS并发原语提现在Java语言中就是sun.miscUnSafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令.这是一种完全依赖于硬件 功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题。
CAS问题:
- ABA
- 循环时间长,开销大
- 只能保证一个变量的原子操作
解决ABA:版本号,JDK
的Atomic
包里提供了一个类Atomic
包里提供了一个AtomicStampedReference
来解决ABA问题
####4. HashMap数据结构线程安全吗,举个例子HashMap怎么线程不安全
不安全,在resize的时候,两个线程同时操作会出现链表成环问题,导致查找一个不存在的key的时候,CPU利用率达到100%,解决方:使用ConcurrentHashMap
####5. java的基本数据类型和字节数
整型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
浮点型:float(4字节)、double(8字节)
字符:char(2字节)
布尔:boolean(1位)
####6. Java,volatile关键字
volatile 是 java 提供的最轻量级的同步机制,当变量定义为 volatile 后,可以保证此变量对多线程的可见性。多个线程可以读到内存中最新的值。volatile可以保证可见性和有序性,不能保证原子性。根据总线上的嗅探机制修改共享变量会使其他线程中的缓存变量失效,但不是立即从主内存中更新值,而是等到下一次use的时候,发现值是无效的,才会从主内存中读取新的值。
7. String,StringBuffer,StringBuilder区别
-
StringBuilder不是线程安全的,StringBuffer是线程安全的。
-
Java 中字符串属于对象,String的值是不可变的,这就导致每次对String的操作都会生成新的String对象。
-
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
-
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
8. Concurrent下面的包的原理
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
- 首先,声明共享变量为volatile;
- 然后,使用CAS的原子条件更新来实现线程之间的同步;
- 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
9. Java集合框架、map
10. Java 异常
所有异常都是由trowable类继承而来,但在下一层立即分解为两个分支:Error和Exception
- Error类层次结构描述了java运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。如果出现类这样的内部错误,除了报告给用户,并尽力使程序安全的终止外,再也无能无力类,这种情况很少遇见。
- 在设计Java程序时,需要关注Exception层次结构,这个层次结构又分解为两个分支:一个分支派生于RuntimeException;另一个包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于RuntionException;而程序本身没有问题,但像I/O错误这类问题导致的异常属于其他异常
11. Object有哪些方法
https://blog.csdn/qq_30264689/article/details/81903031
9个
clone、getClass、toString、finalize、equals、hashCode、wait、notify、notifyAll
12. 深拷贝、浅拷贝
https://blog.csdn/qpzkobe/article/details/81007463
-
clone方法执行的是浅拷贝
-
对于对象中的引用类型,浅拷贝复制引用,深拷贝复制值
-
数组中的clone():
一维数组:深克隆;(重新分配空间,并将元素复制过去)
二维数组:浅克隆。(只传递引用)如何实现二维数组的深克隆呢?对每一维数组调用clone方法。
(二)数据结构
1. B树、B+树、红黑树,数据结构是什么,查询复杂度是多少
B、B+树:https://my.oschina/u/4116286/blog/3107389
动态查找树主要包括:二叉查找树,平衡二叉树,红黑树,B树,B-树,查找的时间复杂度就为O(log2N),通过对数就可以发现降低树的深度就会提高查找效率。在大数据存储过程,大量的数据会存储到外存磁盘,外存磁盘中读取与写入某数据的时候,首先定位到磁盘中的某一块,这就有个问题:如何才能有效的查找磁盘中的数据呢,这就需要一种高效的外存数据结构,也就引出了下面的课题。
辅助索引把一个复杂度大概为log2(N)的二次搜索问题变成了logb(N)磁盘读,b是数据块的因素(树的分叉数)(也就是一个数据块容纳的条目数量)
红黑树的复杂度:包含n个内部节点的红黑树的高度是 O(log(n))。
1. B树
B树也称B-树,它是一颗多路平衡查找树。二叉树我想大家都不陌生,其实,B树和后面讲到的B+树也是从最简单的二叉树变换而来的,并没有什么神秘的地方,下面我们来看看B树的定义。
- 每个节点最多有m-1个关键字(可以存有的键值对)。
- 根节点最少可以只有1个关键字。
- 非根节点至少有m/2个关键字。
- 每个节点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。
- 所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。
- 每个节点都存有索引和数据,也就是对应的key和value。
总结:5阶B树,一个节点最多4个关键字,最少2个关键字,关键字顺序排列,每个节点既包含索引也有数据,叶子结点都在同一层,典型的B树:
2. B+树
B+树其实和B树是非常相似的,我们首先看看相同点。
- 根节点至少一个元素
- 非根节点元素范围:m/2 <= k <= m-1
不同点。
- B+树有两种类型的节点:内部结点(也称索引结点)和叶子结点。内部节点就是非叶子节点,内部节点不存储数据,只存储索引,数据都存储在叶子节点。
- 内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。
- 每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。
- 父节点存有右孩子的第一个元素的索引。
B+树相对于B树的优势:
- (磁盘读写的代价低)单一节点存储的元素更多(B+树不用存指针等信息),使得查询的IO次数更少,所以也就使得它更适合做为数据库MySQL的底层数据结构了。
- (稳定)所有的查询都要查找到叶子节点,查询性能是稳定的,而B树,每个节点都可以查找到数据,所以不稳定。
- (有序链表,方便查找)所有的叶子节点形成了一个有序链表,更加便于查找。
b+ 树为什么左右节点用指针连接:
B+树只有叶结点存储数据。叶结点连起来正好是所有数据的有序序列,可以用来做全表顺序扫描或者范围查询。
3. 红黑树
红黑树是一个平衡的二叉树,但不是一个完美的平衡二叉树。虽然我们希望所有查找都能在~lgN次比较内结束,但是这样在动态插入中保持树的完美平衡代价太高,所以,我们稍微放松一下限制,希望找到一个能在对数时间内完成查找的数据结构。这个时候,红黑树站了出来。
红黑树的复杂度:包含n个内部节点的红黑树的高度是 O(log(n))。
红黑树需要满足的五条性质:
-
性质一:节点是红色或者是黑色
在树里面的节点不是红色的就是黑色的,没有其他颜色,要不怎么叫红黑树呢,是吧。
-
性质二:根节点是黑色
根节点总是黑色的。它不能为红。
-
性质三:每个叶节点(NIL是空节点)是黑色
这个可能有点理解困难,可以看图:
这个图片就是一个红黑树,NIL节点是个空节点,并且是黑色的。
-
性质四:每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点)
就是连续的两个节点不能是连续的红色,连续的两个节点的意思就是父节点与子节点不能是连续的红色。
-
性质五:从任一节点到其每个叶节点的所有路径都包含相同数目的黑色节点;
当我们进行插入或者删除操作时所作的一切操作都是为了调整树使之符合这五条性质。
2. 用语言描述一颗二叉树
最多只有两个孩子结点的树结构,二叉树结点的最大度数为2,是一种用的很多的数据结构,二叉树类型:
(1)完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
(3)平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
3. 七大排序算法
(三)数据库
1. 数据库索引
索引是帮助Mysql高效获取数据的排好序的数据结构
索引的数据结构:
- 二叉树(存在问题:单边增长退化成链表)
- 红黑树(存在问题:层数太多,层数决定了查找效率)
- Hash表(对于等号查找很快,不适合范围查找’where id > 1 and id < 100’)
- B+树(mysql底层使用的数据结构)
- 非叶子结点不存储值,只存储索引,
- 叶子结点包含全部数据
- 叶子结点用指针链接,提高区间访问的性能(相较于Hash)
B树:Innodb默认一个结点16KB
,大约可以存下1170
个关键字,3层的红黑树大概可以存下2000w
个索引元素
重点(Mysql存储引擎与数据库索引):
注意:Mysql的存储引擎都是形容数据库表
的
MylSAM不支持事务和外键,只有表锁。InnoDB支持事务和外键,支持行锁
-
MylSAM索引文件和数据文件是分离的(非聚集),在文件夹下每一个数据表都对应两个文件:
- MylSAM需要3个文件:frm、MYD、MYI
- frm:表结构(frame)
- MYD:数据文件(Data)
- MYI:索引文件(Index)
-
InnoDB引擎索引实现(聚簇)(用的最多的存储引擎)
-
两个文件:frm、ibd,一个是表结构,一个是索引和数据一体文件
-
表数据文件本身就是一个按照B+树组织的一个索引文件
-
聚集索引:叶子结点包含了全部数据
-
为什么InnoDB表必须要有主键,且推荐使用整形的自增主键?
需要主键:引擎就是这么设计的,需要有一个主键用于构建B+树。
使用整形:查找过程中需要大量比较,整形比较的话较快,占用空间相对UUID也小
自增:自增可以保证插入数据只在B+树的后面插入,很少情况出现插入数据导致B+树分裂等平衡操作,可以节省时间
-
为什么非主键索引结构叶子结点存储的是主键值(一致性和节省存储空间)
- 保持一致性,当数据库表进行DML操作时,同一行记录的页地址会发生改变,因非主键索引保存的是主键的值,无需进行更改;
- 节省存储空间,后续补充,不太清楚原因。
-
2. 数据库隔离级别有哪些,举个不可重复读的例子
事务的隔离级别:(默认的事务隔离级别是可重复读)
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 read-uncommitted | 是 | 是 | 是 |
读已提交 read-committed | 否 | 是 | 是 |
可重复读 repeatable-read | 否 | 否 | 是 |
串行化 serializable | 否 | 否 | 否 |
-
未提交读,顾名思义,就是一个事务可以读取另一个未提交事务的数据。(脏读)
-
提交读,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。(不可重复读)
-
可重复读,就是单个事务开启的时候,数据是一定的,对该事务来说不会被修改,但会出现幻读。mysql隔离级别是可重复读,但是没有幻读,因为有mvcc。(解决不可重复读)
-
Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
- 脏读:事务A读取了事务B提交的数据,但是B事务由于某种原因导致事务回滚,但是A读取的仍然是事务B回滚之前的数据
- 不可重复读:事务A读取了一条数据,这时事务B将该条数据修改,事务A再次读取该条数据时,和最开始读取的数据不一致
- 幻读:事务A读取了一批数据,例如select * from user where age =10;读取出了5调数据。这时事务B又向user表插入了一条数据
不可重复读和幻读的区别(个人理解):
不可重复读:针对于修改同一条数据,会出现前后不一致的情况。****解决方式为添加行锁****
幻读:针对于一批数据,主要体现在新增和删除操作。****解决幻读需要锁整张表****
3. 除了设置数据库隔离级别,还有什么方法可以解决不可重复读
mysql中,默认的事务隔离级别是可重复读(repeatable-read),为了解决不可重复读,innodb采用了**mvcc(多版本并发控制)**来解决这一问题。mvcc是利用在每条数据后面加了隐藏的两列(创建版本号和删除版本号),类似于Java的CAS乐观锁机制
4. 事务的特性,具体介绍
事务的四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
-
原子性:
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么
全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性 -
一致性:
数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即
使在转账过程中系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做
的修改也不会保存到数据库中。) -
隔离性:
通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。(在前面的例
子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则
其看到支票帐户的余额并没有被减去200美元。) -
持久性:
一旦事务提交,则其所做的修改会永久保存到数据库。
5. 为什么二级索引存主键 ID 不直接存数据位置
二级索引:叶子节点中存储主键值,每次查找数据时,根据索引找到叶子节点中的主键值,根据主键值再到聚簇索引中得到完整的一行记录。
- 相比于叶子节点中存储行指针,二级索引存储主键值会占用更多的空间,那为什么要这样设计呢?
InnoDB在移动行时,无需维护二级索引,因为叶子节点中存储的是主键值,而不是指针。
-
那么InnoDB有了聚簇索引,为什么还要有二级索引呢?
聚簇索引的叶子节点存储了一行完整的数据,而二级索引只存储了主键值,相比于聚簇索引,占用的空间要少。当我们需要为表建立多个索引时,如果都是聚簇索引,那将占用大量内存空间,所以InnoDB中主键所建立的是聚簇索引,而唯一索引、普通索引、前缀索引等都是二级索引。
-
为什么一般情况下,我们建表的时候都会使用一个自增的id来作为我们的主键?
InnoDB中表中的数据是直接存储在主键聚簇索引的叶子节点中的,每插入一条记录,其实都是增加一个叶子节点,如果主键是顺序的,只需要把新增的一条记录存储在上一条记录的后面,当页达到最大填充因子的时候,下一跳记录就会写入新的页中,这种情况下,主键页就会近似于被顺序的记录填满。
若表的主键不是顺序的id,而是无规律数据,比如字符串,InnoDB无法加单的把一行记录插入到索引的最后,而是需要找一个合适的位置(已有数据的中间位置),甚至产生大量的页分裂并且移动大量数据,在寻找合适位置进行插入时,目标页可能不在内存中,这就导致了大量的随机IO操作,影响插入效率。除此之外,大量的页分裂会导致大量的内存碎片。
6. 一条SQL语句是怎么执行的
https://wwwblogs/wupeixuan/p/11626024.html
大体上,MySQL 分为 Server 层和存储引擎层两部分。
Server 层包括连接器、查询缓存、分析器、执行器等,以及所有的内置函数(如日期、时间、数学和加密函数等)和跨存储引擎的功能(如存储过程、触发器、视图)。
存储引擎层负责数据的存储和提取,支持 InnoDB、MyISAM、Memory 等多个存储引擎。MySQL 5.5.5 版本后默认存储存储引擎是 InnoDB。
-
连接器
在查询 SQL 语句前,肯定要先建立与 MySQL 的连接,这就是由连接器来完成的。连接器负责跟客户端建立连接、获取权限、维持和管理连接
-
查询缓存
MySQL 拿到查询请求后,会先查询缓存,看是不是执行过这条语句。执行过的语句及其结果会以 key-value 对的形式保存在一定的内存区域中。
-
分析器
如果查询缓存未命中,就要开始执行语句了。首先,MySQL 需要对 SQL 语句进行解析。
分析器先会做词法分析。SQL 语句是由多个字符串和空格组成的,MySQL 需要识别出里面的字符串分别是什么,代表什么。MySQL 从你输入的 select 这个关键字识别出来,这是查询语句。它也要把字符串 user_info 识别成表名,把字符串 id 识别成列名。之后就要做语法分析。根据词法分析的结果,语法分析器会根据语法规则,判断输入的 SQL 语句是否满足 MySQL 语法。如果你 SQL 语句不对,就会收到
You have an error in your SQL syntax
的错误提醒,比如下面这个语句 from 写成了 form。 -
优化器
经过分析器的词法分析和语法分析后,还要经过优化器的处理。优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序
-
执行器
MySQL 通过分析器知道了要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。
总结:主要通过对一个 SQL 语句完整执行过程进行讲解,介绍 MySQL 的逻辑架构,MySQL 主要包括连接器、查询缓存、分析器、优化器、执行器这几个模块。
7. 什么是索引覆盖
https://wwwblogs/AbnerLc/p/11923242.html
只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。
8. MylSAM只支持表锁、InnoDB默认行锁?
https://www.iteye/blog/lixiaohui-2352449
https://blog.csdn/djrm11/article/details/96499817
这个要从两个存储引擎的索引结构上面答:
MySQL锁大体分三种:
- 表锁:开销小,加锁快;不会出现死锁;锁粒度大,发生锁冲突几率高,并发度最低
- 行锁:开销大,加锁慢;会出现死锁;锁粒度小,发生锁冲突几率小,并发度最高
- 页锁:开销和加锁时间介于表锁和行锁之间;会出现死锁,锁粒度介于表锁和行锁之间
MySQL中不同的存储引擎之间的锁机制不一定相同,例如MyISAM和MEMORY采用的是表锁,BDB采用的是页面锁,但也支持表锁,InnoDB默认是行锁,但也支持表锁。
(由索引机制决定的)InnoDB之所以可以锁行,是因为Innodb的主索引结构上,既存储了主键值,又直接存储了行数据,可以方便的锁住行数据,而MyIsam索引指向另一片数据文件,没有办法精确锁住数据段
9. 索引失效的几种情况
https://zhuanlan.zhihu/p/95170837
函数、隐式转换、order by、or、like
-
- 索引条件使用函数操作或运算
EXPLAIN select * from t where month(update_time)=12; select * from t where id + 1 = 10
-
- 隐式转换
select “10” > 9 其实是用到了函数隐式转换 select CAST("10" AS signed int) > 9
-
- 搜索一个索引而在另一个索引上做order by,where A=a order by B,只使用A上的索引,因为查询只使用一个索引
-
- or会使索引失效。如果查询字段相同,也可以使用索引。例如where A=a1 or A=a2(生效),where A=a or B=b(失效)
-
- 组合索引未使用最左前缀,例如组合索引(A,B),where B=b不会使用索引;
-
- like未使用最左前缀,where A like ‘%China’;
10. 事务特性ACID
原子性:操作这些指令时,要么全部执行成功,要么全部不执行
一致性:事务开始和结束之间的中间状态不会被其他事务看到
隔离性:事务之间不相互干扰,一个事物不知道其他事务的存在
持久性:每一次的事务提交后就会保证不会丢失
(四)框架
一、Spring
1. IOC加载过程
refresh()的12个步骤,9处使用到后置处理器的地方,obtainBeanFactory和finshBeanFactoryInitialization是比较重要的方法。
2. Bean的生命周期
https://wwwblogs/javazhiyin/p/10905294.html
(五)、场景题
1. Top K(很关键)
- 硬盘1T,内存2G 有很多数据id, 有重复的id 怎么找到重复次数最多的Top10
方案:顺序读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,…x4999)中。这样每个文件大概是200M左右。如果其中的有的文件超过了2G大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过2G。 对每个小文件,统计每个文件中出现的词以及相应的频率(可以采用trie树/hash_map等),并取出出现频率最大的100个词(可以用含100个结点的最小堆),并把100个词及相应的频率存入文件,这样又得到了5000个文件。下一步就是把这5000个文件进行归并(类似与归并排序)的过程了。
- 排序 - 1亿数据,1M内存,求TOP10,看看堆排序如何实现
使用堆排序
- 海量日志数据,提取出某日访问百度次数最多的那个IP
首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个 IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。
- 在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数
**方案1:**采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。
**方案2:**也可采用与第1题类似的方法,进行划分小文件的方法。然后在小文件中找出不重复的整数,并排序。然后再进行归并,注意去除重复的元素。
- 腾讯面试题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?
与上第6题类似,第一反应时快速排序+二分查找。以下是其它更好的方法:
方案1:申请512M的内存,一个bit位代表一个unsigned int值。读入40亿个数,设置相应的bit位,读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在。
方案2:这个问题在《编程珠玑》里有很好的描述,大家可以参考下面的思路,探讨一下:又因为2^32为40亿多,所以给定一个数可能在,也可能不在其中;这里我们把40亿个数中的每一个用32位的二进制来表示假设这40亿个数开始放在一个文件中。然后将这40亿个数分成两类: 1.最高位为0 2.最高位为1 并将这两类分别写入到两个文件中,其中一个文件中数的个数<=20亿,而另一个>=20亿(这相当于折半了);与要查找的数的最高位比较并接着进入相应的文件再查找
- 一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。
方案1:这题是考虑时间效率。用trie树统计每个词出现的次数,时间复杂度是O(nle)(le表示单词的平准长度)。然后是找出出现最频繁的前10个词,可以用堆来实现,前面的题中已经讲到了,时间复杂度是O(nlg10)。所以总的时间复杂度,是O(nle)与O(nlg10)中较大的哪一个。
##(六)操作系统、计算机网络
2. 进程和线程,区别,哪个效率高,为什么
进程:操作系统资源分配和管理的基本单位。
线程:处理机调度的基本单位。进程至少要有一条线程
进程与线程的区别:
-
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
-
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
-
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
-
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
-
线程是处理器调度的基本单位,但是进程不是。
**效率问题:**io操作密集型的代码多线程效率更高,因为线程创建要比进程创建开销少。但是计算密集型的代码多 那么进程操作更快,因为多进可以应用多核技术
3. 死锁条件,解决
必要条件:
-
互斥:资源不共享
-
占有并等待:—个进程应占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有。
-
非抢占:资源不能被抢占,即资源只能被进程在完成任务后自愿释放。
-
循环等待
死锁预防:
- 破坏“占有且等待”条件
- 破坏“不可抢占”条件
- 破坏“循环等待”条件
死锁避免:银行家算法
死锁检测与解除:在检测到运行系统进入死锁,进行恢复。
4. 进程同步的四种方法
-
PV操作。用于进程间传递信号的一个整数值。在信号量上只有三种操作可以进行:初始化,P操作和V操作,这三种操作都是原子操作。
-
管程。一种数据结构。一个进程通过调用管程的一个过程进入管程。
-
消息传递。消息传递的实际功能以一对原语的形式提供:send(destination,message)
receive(source,message)
5. TCP建立连接、断开链接
6. 拥塞控制、流量控制
7. HTTP、Https
https://wwwblogs/an-wen/p/11180076.html
各版本差异:https://blog.51cto/13932385/2393194
超文本传输协议,是一种应用层协议。HTTP假定其下层协议提供可靠的传输。
HTTP请求方法(8种动作):
- Get:从互联网上获取一个资源
- Head:与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分,它只请求页面的首部。
- Post:向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。
- Put:向指定资源位置上传其最新内容。
- Delete:请求服务器删除资源。
- Trace:回显服务器收到的请求,主要用于测试或诊断。
- Options:这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用’*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。
- Connect:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。
- PATCH:是对 PUT 方法的补充,用来对已知资源进行局部更新 。
Http与Https的区别:
- HTTP 是不安全的,而 HTTPS 是安全的
- HTTP 标准端口是 80 ,而 HTTPS 的标准端口是 443
- 在 OSI 网络模型中,HTTPS的加密是在传输层完成的,因为SSL是位于传输层的,TLS的前身是SSL,所以同理
- HTTP无需认证证书,而https需要认证证书
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
(七)JVM、Java多线程与并发
1. 垃圾回收机制GC,cms,G1,垃圾回收的算法
《看书》
2. java多线程,状态图
1、新建(new):线程对象被创建后就进入了新建状态。如:Thread thread = new Thread();
2、就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其他线程调用了该对象的start()方法,从而启动该线程。如:thread.start(); 处于就绪状态的线程随时可能被CPU调度执行。
3、运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权限,暂时停止运行。直到线程进入就绪状态,才有机会进入运行状态。阻塞的三种情况:
1)等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。
2)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
3)其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或因异常退出了run()方法,该线程结束生命周期。
3. 乐观锁和悲观锁
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。在Java中java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
4. 分布式锁
https://blog.csdn/xlgen157387/article/details/79036337
在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件:
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。“所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。
基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;
-
基于数据库的实现方式
基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
注意:这只是使用基于数据库的一种方法,使用数据库实现分布式锁还有很多其他的玩法!
-
基于Redis的实现方式
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
-
基于ZooKeeper的实现方式
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
https://blog.csdn/Dinosaur_1117/article/details/89394219
创建临时顺序结点,避免死锁和惊群效应
5. 锁类型
①可重入锁:在执行对象中所有同步方法不用再次获得锁
②可中断锁:在等待获取锁过程中可中断
③公平锁:按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁的权力
④读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步的写
java中每个对象都可作为锁,锁有四种级别,按照量级从轻到重分为:无锁,偏向锁,轻量级锁,重量级锁。每个对象一开始都是无锁的,随着线程间争夺锁,越激烈,锁的级别越高,并且锁只能升级不能降级。
锁的实现机制与java对象头息息相关,锁的所有信息都记录在java的对象头中,用2字(32位JVM中1字=32bit)存储对象头,如果是数组类型使用3字存储(还需要存储数组长度),对象头中记录了hash值,GC年龄,锁的状态,线程拥有者,类元数据的指针
6. sychronize和lock的区别
https://www.jianshu/p/dd09194b120b
synchronized是一种悲观锁,每次都把自己关起来做事,怕被抢而lock底层是CAS乐观锁的体现,无所谓外界,如果被抢了,就重新去拿,很乐观,底层主要靠volatile和CAS实现的
- 1、Lock是java的一个interface接口,而synchronized是Java中的关键字,synchronized是由JDK实现的,不需要程序员编写代码去控制加锁和释放;
- 2、synchronized修饰的代码在执行异常时,jdk会自动释放线程占有的锁,不需要程序员去控制释放锁,因此不会导致死锁现象发生;但是,当Lock发生异常时,如果程序没有通过unLock()去释放锁,则很可能造成死锁现象,因此Lock一般都是在finally块中释放锁;
- 3、Lock可以让等待锁的线程响应中断处理,如tryLock(long time, TimeUnit unit),而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够中断,程序员无法控制;
- 4、synchronized是非公平锁,Lock可以设置是否公平锁,默认是非公平锁;
- 5、Lock的实现类ReentrantReadWriteLock提供了readLock()和writeLock()用来获取读锁和写锁的两个方法,这样多个线程可以进行同时读操作;
- 6、Lock锁的范围有局限性,仅适用于代码块范围,而synchronized可以锁住代码块、对象实例、类;
- 7、Lock可以绑定条件,实现分组唤醒需要的线程;synchronized要么随机唤醒一个,要么唤醒全部线程。
7. 三个线程顺序执行(锁、join、信号量)
https://www.jianshu/p/7e0abbce929c
8. 什么时候发生full gc
(集合类失效引用没有及时清除,数据库、IO操作连接等未及时关闭等)
老年代内存无法分配的时候
9. Sychronize锁升级
https://blog.csdn/baidu_38083619/article/details/82527461
Synchronized常用三种使用方式
1、修饰普通方法:锁对象即为当前对象
2、修饰静态方法:锁对象为当前Class对象
3、修饰代码块:锁对象为synchronized紧接着的小括号内的对象
(八)算法(有题解,篇幅可能过长)
1. 有序链表合并
递归
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
else if (l2 == null) {
return l1;
}
else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
时间复杂度:O(n + m)
空间复杂度:O(n + m)
迭代:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// maintain an unchanging reference to node ahead of the return node.
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// exactly one of l1 and l2 can be non-null at this point, so connect
// the non-null list to the end of the merged list.
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
时间复杂度:O(n + m)
空间复杂度:O(1)
2. Trie树(LeetCode 208)
更多推荐
Java后端常见面试题总结
发布评论