面试题
- 面试之前要准备一段你写过的你认为最好的代码
- 自我介绍
- Java有哪些基本数据类型?
- 你觉得基本数据类型和引用类型有什么区别?
- 了解HashMap底层数据结构吗?
- 知道HashSet底层数据结构吗?
- ArrayList了解吗?有什么特点呢?
- 重点讲一下ArrayList的删除方法,删除一个指定元素 object ?
- 平时ArrayList的删除代码应该怎么写?(这问的是什么意思… …我答迭代器)
- 了解MySQL吗?
- 有一个表,表名是a,表中有个字段 name,找出重复的 name 和 它的次数,MySQL语句应该怎么写呢?
- 了解MySQL的索引吗?
- 哪些情况下要用索引?
- 在哪些字段上不应该建索引呢?(讲成了索引失效的情况)在哪些字段上应该建索引呢?
- 你准备的代码?讲解一下代码吧,核心的代码块在哪?
- 有什么问题要我吗?(问:你们开发是用到哪些框架呢? 面试官:SpringBoot,微服务用的是 Dobbu ,数据库用 MySQL,用 MyBatis,Java 8)
复盘:
1.基本数据类型和引用类型有什么区别?
基本数据类型存的是值,Java 有8种基本数据类型:Boolean、byte、short、char、int、long、double、float。
引用类型存的是地址。
2.HashMap
基于 Hash 的数组与链表相结合的数据结构。存放 key-value 形式的数据,底层是 Node 类型的数组 ,Node是单链表,根据 key 使用 本地方法 hashCode 为不同对象生成不同的整数值,然后用这个值与右移 16 位的值相与,可以增加随机性,再对数组长度取余求得索引,为提高效率,数组的长度会是 2 的幂次方。当链表长度达到 8 时会转化成 红黑树。当达到阈值的时候会进行双倍扩容,阈值是初始容量乘以负载因子的结果。
3.HashSet以及如何实现元素不重复
HashSet 底层也是个 HashMap,Set 是不允许重复的,在添加时,将元素作为 key ,value 是个固定的 Object类型的常量,add 的返回值是 :
return map.put(e, PRESENT)==null;
而 HashMap 的 put 方法,是通过 key 算出索引,如果该索引处没有值,就把 value 放进去,如果该索引处有值,就会遍历链表,比较 key 是否相同,这里的相同是通过 hashCode 和 equals() 方法的比较,相同的话会覆盖原值,并返回原值;不同的话说明出现碰撞、冲突了,尾插链表,返回 null。
因此,如果往 Set 里放的是重复的元素,通过 key 找到的索引处就放着常量 PRESENT ,覆盖,返回旧值, add 方法也就为 false ,而保证了 HashSet 元素的唯一性;如果放置的不是重复的元素,就是在 HashMap 里 put 了 元素和 PRESENT ,返回值为 null ,对应 add 方法返回的也就是 true 了,添加成功。
4.ArrayList
底层是 Object 类的数组,不传入初始容量的话默认容量是 10,以 1.5 倍扩容,查询效率高、增加、删除效率低。
remove (Object o) 方法:从头到尾遍历,删除第一次出现的元素,删除时是将该位置以后的元素依次向前移动一位。比较用的是 equals 方法。
使用时要注意(我感觉面试官是想问这个)还有个删除的方法 remove(int index),如果是整型数组,删除指定元素时传入的参数需要强转成 Integer 类型,否则就会被作为 index 下标对待。
5.找到重复的字段和重复次数
select name,count(*)
from user
group by name;
(太可怕了,嘴上说着分组 group by ,刚刚发现给面试官发的是order by 🙅 )
6.索引
MySQL 中索引的定义:用于快速查找的已排序的数据结构。
如上,Inno DB 支持 B+ 树索引【给每个数据页生成一个页目录记录:本页所有记录中的最小主键值 key 与 页码 page_no。
】,可以讲清楚了,其实My SQL还有其他索引: Memory 有哈希索引,而且当InnoDB注意到某些索引值被使用得非常频繁时,它会在内存中基于B+树上再创建一个哈希索引。
哈希索引:
Slot槽,也就是 根据每行 索引列的值求出的hashCode,+ value 是行指针。槽是按顺序存放的。
1.哈希索引使用情景:进行全键值的匹配查找
很多张表关联时。
不能用于排序、范围值查找。
当产生hash碰撞的时候,数据库要遍历拉链中所有的行指针,逐个取出数据行进行比较,数据量越大,冲突越多,查找代价越高。
什么情况下建索引(B+树)
(给出的是建了索引效率会变高的情况,仅供参考)
(1)为表的主键会自动建立唯一索引。
(2)全值匹配,如:
select * from user where name=’ohh’ and age=1;
(3)匹配左边的列
(搜索条件中的各个列必须是联合索引中从最左边连续的列)
(4)匹配列前缀
(5)匹配范围值,如:
select * from user WHERE name > 'Asa' and name < 'Barlow';
由于B+树中的数据页和记录是先按name列排序的,所以我们上边的查询过程其实是这样的:
找到name值为Asa的记录。
找到name值为Barlow的记录。
由于所有记录都是由链表连起来的(记录之间用单链表,数据页之间用双链表),所以他们之间的记录都可以很容易的取出来喽~
(6)在查询中与其它表关联的字段,也就是外键
(7)排序
(8)精确匹配某一列并范围匹配另外一列
什么情况下建索引(哈希)
键值唯一时候的等值查询。
什么情况下应不建索引(B+树)
(1)排序混用 ASC 、DESC
(2)排序列使用了复杂的表达式
必须保证索引列不是修饰过的形式
什么情况下应不建索引(哈希)
数据量大的时候,可能会有哈希冲突。
如何挑选索引
(1)只为用于搜索、排序或分组的列创建索引
(2)考虑列的基数,列的基数指的是某一列中不重复数据的个数,基数多大越好,为重复数据的列建索引意义不大
(3)索引列的类型尽量小
(4)如果列中放字符串,只对字符串的前几个字符进行索引
(5)让索引在表达式中单独出现,如:
WHERE my_col * 2 < 4
会去遍历记录,用不到索引的,应写成:
WHERE my_col < 4/2
今日所学补充
1.红黑树了解不?
本质上是个二叉搜素树,有 5 个特点,插入、删除等操作都要维持这 5 个特点:【”叶子节点“指的是叶子下的 null 节点】
(1)节点不是黑色就是红色的。
(2)根节点是黑色的。
(3)叶子节是黑色的。
(4)任意节点到叶子节点的路径中 黑色节点的个数是相同的。【黑色完美平衡】
(5)红色节点的两个子节点一定是黑色的。
2.同步和异步?阻塞和非阻塞?
同步和异步关注的是消息处理机制。
同步:调用方法或发出请求时,方法的返回需要时间,在这段时间里程序会等待,不做其他事情,直到方法返回才继续执行。
异步:… …不等待,做自己的事情,方法返回了再处理。
阻塞和非阻塞关注的是等待调用结果时的状态。
阻塞:调用结果返回前,当前线程会被挂起,不能不执行其他业务,直到信息返回。
非阻塞:… …不挂起… …
3.线程之间通信的方式?进程之间通信的方式?
进程间通信
- 套接字(socket)
- 文件
- (命名)管道(pipe):
半双工,数据只能单向流动,只能在具有(无)亲缘关系(如父子进程)的进程间。
- 全双工管道
- 信号量(semophonre)
是一个计数器,可以用来控制多个进程队共享资源的访问。
- 消息队列(message queue)
- 共享内存(shared memory)
线程间通信
线程间的通信方式上述进程间的方式都可沿用,且还有自己独特的几种:
- 互斥量
- 自旋锁
- 条件变量
- 读写锁
- 全局变量
- 信号——注意是线程信号
4.内存的虚拟地址空间
把多个物理内存碎片和部分磁盘空间重新定义为连续可用的内存,让程序认为自己还有连续可用的内存。
5.发生OOM时如何排查?
IDEA中有个插件 JProfilerl 可以分析内存。
7.synchronized 与 reentrantlock 有什么区别
都是可重入锁。
ReentrantLock 还有以下特点:
当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情。
可以实现公平锁。
锁可以绑定多个条件—— Condition 对象。
6.MySQL 的隔离级别
(1)读未提交
一个事务可以读到另一个事务未提交的的数据。
(2)读提交
一个事务只能读到另一个事务提交后的修改。
以上两种的问题:不可重复读、幻读。
(3)可重复读【默认】
一个事务范围内,在多次相同查询中,数据结果是一致的。
但可能有”幻读“ 呢,比如,查询归查询,它中间有 insert 或者 update 这样的操作。
(4)串行读
在读取的每一行上加锁【共享锁】。
会导致超时和锁竞争的问题。
修改语句:
set global transaction_isolation =‘XXX';
❀数据库为了维护事务的四大特性和隔离,一般使用加锁这种方式。
使用两阶段封锁协议:
(1)随时都可以执行锁定,InnoDB 会根据隔离级别在需要的时候自动加锁;
(2)锁只有在执行 commit 或者 rollback 的时候才会释放,并且所有的锁都是在同一时刻被释放。❀
8.MySQL 中的锁
多粒度锁
不同的存储引擎支持不同的锁机制。( InnoDB 存储引擎既支持行级锁,也支持表级锁。)
不同粒度锁的比较:
表级锁:开销小,加锁快;不会出现死锁(因为是按序的);锁定粒度大,发生锁冲突的概率最高,并发度最低。
表级锁更适合于以查询为主,并发用户少,只有少量按索引条件更新数据的应用。
♥行级锁【InnoDB 的行锁:是通过给索引上的索引项加锁,所以,只有使用索引检索数据时,才使用行级锁,否则将使用表锁;再有,即使访问不同行,如果使用相同的索引键,就会出现所冲突。】♥:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
InnoDB 实现了以下两种类型的行锁:
(1)共享锁又称读锁 /S 锁,多个事务可以同时为同一数据加共享锁。数据被添加共享锁后可以被多个事务同时读取,但是无法被修改。
(2)排他锁又称写锁 / X 锁,排他锁不能与其它锁共存,一旦一个事务获取了某一数据的排他锁,则其它事务都无法再获取该数据的其它锁,包括共享锁和排他锁。只有获取到排他锁的事务本身可以对该数据执行读取和修改操作。 update
, delete
, insert
都会 自动 【无须用户干预】给涉及到的数据加上排他锁,select 语句默认不会加任何锁类型。
♥手动设置共享锁 和 排他锁:♥
//想要查到的数据是最新的,并且不允许其他人来修改数据。但是自己不一定能够修改数据,
设置共享锁:select ... lock in share mode;
//想要查到的数据是最新的,而且查到后只能让自己修改
设置排他锁:select ... for update;
意向锁【表级锁,InnoDB自动加的】
比如,事务A锁住表中的一行(写锁)。事务B锁住这一行所在的整个表(写锁),这就形成了冲突。
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁,这两种意向锁都是表锁:
(1)意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
(2)意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。
有了意向锁之后,事务A在申请行锁(写锁)之前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,因为表上有意向排他锁之后事务B申请表的写锁时会被阻塞。
间隙锁【行级锁】
锁定索引记录中的间隙。保证索引不会插入数据。
如果将事务隔离级别设置为 读提交 可以明确禁用间隙锁。
临键锁
是行锁与间隙锁的组合。当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上记录锁(Record Lock),然后再对索引记录两边的间隙加上间隙锁(Gap Lock)。
自增锁【表级锁】
MySQL会为自增列设置一个表级锁。
各种SQL语句加的都是什么锁
(1)正常查询
在4个事务隔离级别中,除了在 串行化(Serializable) 时会加共享锁,其他的都不加锁。
(2)加了锁的查询
- 当使用 唯一索引 来select update delete 唯一行(唯一索引自然也就唯一行了 ) 的语句时,使用记录锁(record lock)。如:
select * from t where id = 10 for update; # id是唯一索引列
2.其他情况,包括id列没有索引或者是非唯一索引又或者是搜索条件里有多个查询条件(使每个列都有唯一索引),则使用间隙锁与临键锁。
临键锁是间隙锁和记录锁的组合。
三、update和delete
- 当使用 唯一索引列 确定的 唯一数据行 上进行的 update/delete,也使用记录锁:
update t set number=10 where id=1; - 其他情况,包括id列没有索引或者是非唯一索引又或者是 搜索条件里有多个查询条件(使每个列都有唯一索引),则加 排他临键锁(exclusive next-key lock)。
注意:如果update的是聚集索引记录,则对应的二级索引记录也会被隐式加锁,这是由InnoDB索引的实现机制决定的:二级过引的叶子上存的是聚集索引的主键值,当检索二级索引时,会二次扫描聚集索引。
四、insert
insert会用排它锁封锁被插入的索引记录,然后在插入区间加插入意向锁(insert intention lock)。
死锁
(1)解决死锁问题:
通过 innodb_lock_wait_timeout 来设置超时时间,InnoDB 中默认值是 50s,第一个被锁住的事务 A 等待超过 50s 后超时退出,其他事务才能得以执行。
通过设置 innodb_deadlock_detect = on, 发起死锁检测,发现死锁之后主动回滚死锁链条中的某一个事务,【使拥有最少行级排他锁的事务回滚。】让其他事务得以继续执行。 比如回滚事务 A,让事务 B 继续执行。
如果已经出现了死锁,可以用 SHOW INNODB STATUS 命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。
(2)减少死锁概率:
尽可能按照相同的访问顺序来访问
在同一个事务中,尽可能做到一次锁定所需要的所有资源
对于十分容易产生死锁的部分,尝试使用升级锁粒度,如使用表级锁定
一些优化锁性能的建议
(1)尽量使用较低的隔离级别;
(2)尽可能让检索都通过索引来完成,从而避免 InnoDB 因为无法通过索引键加锁而升级为表级锁;合理设计索引,让 InnoDB 在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围。
(3)加锁时最好一次性请求足够级别的锁,避免产生死锁
(4)已有 MVCC ,查询尽量不要加锁。
9.主键与唯一索引【字段不能重复】的区别
(1)主键是一种约束,唯一索引是一种索引,两者在本质上是不同的。
(2)主键创建后一定会创建一个索引,唯一性索引并不一定就是主键。
(3)唯一性索引列允许空值,而主键列不允许为空值。
(4)一个表最多只能创建一个主键,但可以创建多个唯一索引。