国有企业搬砖三年由余,每日crud, 技术还在 jsp+ssm 每天挣扎不堪。 猛回头看一下现在的应届学子,后生可畏,总感觉我们当时都是渣渣,真是不知不觉中 Java 都已经卷成这样了吗。犹记得老师曾告诉我们说零几年那会你会写个HTML 都能月薪上万。现在你熟悉各种框架JVM,数据库可能连找个实习都费劲。说起来挺搞笑的,我现在出去感觉面试个实习生都费劲【手动狗头】。本博文一直更新,欢迎指正。如果有哪位老板想组团做私活的可以找我。本人熟悉 java 应用系统开发,欢迎联系。如果有学弟学妹们有 Java 方向的毕设问题,我也可以给你们看看(免费的),欢迎私信。

题目都是从牛客上找的面经,CSDN 找的答案。

目录

一. 字节

一年社招

1. 用过哪些Java容器?

2. ArrayList 和 LinkedList 特点及各自应用场景?

3. TreeMap 怎么按照自己想要的顺序排序

4. 说下 TreeMap 和 LinkedHashMap

5. 说下 HashMap

9. 怎么防止恶意请求刷接口

12. 你在项目中用Redis 的场景

13. 说下 Redis 有哪些数据类型

岸柏科技

一面

2. Mysql 有哪些锁

3. 解释一下ACID 都是什么

4. Innodb 中索引的实现

5. B+ 树

易通集团

4. List 的子类都有哪些

5. left join 和 right join

一号店

4. HashMap 的底层实现原理

5. HashMap 是线程安全的吗

8. 写一个单例模式。

快手

一面(一年)

2. 代码写死锁

6. CAS

跟谁学

(一年)

1. 线程池,怎么设定核心线程数

3. 并行和并发的区别

7. ArrayList 初始值,怎么扩容

招商银行

电话面

1. 讲讲 java 锁

2. mysql 事务的隔离级别

3. 什么是幻读以及mysql 如何避免幻读

5. 索引 B+ 树的优势在哪里

10. import java 和 javax 有什么区别?

11. 接口和抽象类的区别是什么?

一面

二面

泰克电子

面经一

13. wait 和 sleep 有什么区别?什么情况下会用到 sleep?

14. 怎么停止线程?

面经整理:

java 基础

线程安全的集合

并发

引用

类加载

IO

JVM

中间件、存储、以及其他框架

Redis(或其他缓存系统)


一. 字节

一年社招

先来个一个年字节的社招练练手

1. 用过哪些Java容器?

这个范围很广泛,一般来说来分为 Collection 和 Map 两大类。只针对数据进行存储的Collection, 和键值对存储的 Map, 最常用的集合 ArrayList,和LinkedList, 如果在多线程的情况下可以使用 Vector, 或者直接使用Collecitons 中的线程安全的方法。如果不要求顺序和重复值可以使用Set ,常用到的有 HashSet 和 TreeSet。队列我接触到基本上就是比如生产者消费者模式中这些固定的写法,线程池之类的队列。Map 常见的有 HashMap 和 TreeMap。

2. ArrayList 和 LinkedList 特点及各自应用场景?

ArrayList 采用数组进行存储,特点是存储访问快,插入慢,LinkedList 采用链表的方式进行存储,特点是访问慢插入快。

从数据结构实现上讲,ArrayList 是动态数据的数据结构实现,而 LinkedList 是双向链表的数据结构实现。

从随机访问效率上讲,ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。

从增加和删除效率上讲,在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

综合来讲,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,推荐使用LinkedList。

3. TreeMap 怎么按照自己想要的顺序排序

一般来说有两种方式,一种是根据 TreeMap 中 key 的字典顺序来排序,默认排序规则对应底层数据结构红黑树的中序遍历,另一种可以自定义排序规则,实现 Comparator接口, 有一个可能会遇到的坑是如果使用默认排序且 key 是数字字符串,比如 "1","3","21", 这样,如果想按照数字大小排序需要把 key 改成Long 类型。

3.1加餐:这里记录一下什么玩意是红黑树,一直也没有搞明白这个红黑树是干啥的。

1.  平衡树介绍

红黑树的本质是对概念模型: 2-3-4树 的一种实现,2-3-4树是阶数为 4 的 B 树,B 树,全名 Balance Tree, 平衡树。 这种结构主要用来做查找。 最重要的特性在于平衡,这样使我们能够在最坏情况下也保持 O(LogN) 的时间复杂度实现查找(一个不具备平衡性的查找树可能退化成单链表,时间复杂度会到 O(N))。 平衡的定义是说从空链接到根节点的距离。

由于 2-3-4 树是一颗阶数为 4 的 B 树,所以会存在一下节点:

2节点:包含1个元素的节点,有2个子节点

3节点:包含2个元素的节点,有3个子节点

4节点:包含3个元素的节点,有4个子节点

2节点存放着一个key[X] , 两个指针,分别指向小于X 的子节点和大于X 的子节点;3节点中存放在两个 key[X,Y], 三个指针,分别执行小于X 的子节点,介于 X,Y 之间的子节点和大于 Y 的子节点; 4节点一次类推。

234树节点至少有1个元素,符合二叉查找树的性质,即父节点大于左子节点,小于右子节点,但对于234 有多个元素时,每个元素必须大于它左边的和它的左子树中元素。

2.  234树到红黑树的转化

红黑树是对概念模型234树的一种实现,由于直接进行不同节点间的转化会造成较大的开销,所以选择以二叉树为基础,在二叉树的属性中加入一个颜色属性来表示234树种不同的节点。

234树种的2节点对应着红黑树中的黑色节点,而234树中的非2节点是以红节点+黑节点的方式存在,红节点的意义是与黑色父节点结合,表达着234书中的3,4节点。(看到这里好像刚明白存在几个子节点就叫几节点,好尴尬。)

我们先看234树到红黑树的节点转换。2节点直接转化为黑色节点;3节点这里可以有两种表现形式,左倾红节点或右倾红节点。而4节点被强制要求转化为一个黑父带着左右两个红色儿子。

图解:什么是红黑树? - 知乎

4. 说下 TreeMap 和 LinkedHashMap

TreeMap 底层数据结构就是红黑树,和HashMap 的红黑树结构一样,与HashMap 不同的是,TreeMap 利用了红黑树左节点小,右节点大的性质,根据 key 进行排序,使每个元素能够插入到红黑树大小适当的位置,维护了key 的大小关系,适用于需要进行排序的场景。

LinkedHashMap 本身是继承 HashMap 的,所以它拥有 HashMap 的所有特性,在此基础上还提供了两大特性

第一:按照插入顺序进行访问

链表特性

LinkedHashMap 的数据结构,就像是把LinkedList 的每个元素换成了 HashMap 的Node, LinkedHashMap 像两者的结合体,不过也正是因为增加了这些结构,才能把 Map 的元素都串联起来,形成一个链表,既然是一个链表,那就可以保证顺序了。

按照顺序新增

在 LinkedHashMap 初始化时,我们默认 accessOrder 为 false, 意思就是会按照插入顺序提供访问,插入方法使用的是父类 HashMap 的 put 方法,不过覆写了 put 方法执行中调用的 newNode以及newTreeNode 和 afterNodeAccess 方法,put 方法中的newNode以及newTreeNode 方法,可以控制新增节点追加到链表的尾部,这样每次新节点都追加到尾部,即可保证插入顺序了。

按照顺序访问

LinkedHashMap 只提供了单向访问,即按照插入的顺序从头到尾进行访问,不能像 LinkedList 那样可以做到双向访问,因此主要通过迭代器进行访问,在迭代器初始化的时候,默认从头节点开始访问,在迭代的过程中,不断访问当前节点的 after 节点即可

Map 对 key、value 和 entity 都提供出了迭代的方法,假设我们需要迭代 entity,就可使用 LinkedHashMap.entrySet().iterator() 这种写法直接返回 LinkedHashIterator ,LinkedHashIterator 是迭代器,我们调用迭代器的 nextNode 方法就可以得到下一个节点

先前在新增节点时,就已经使用put 方法中的newNode以及newTreeNode 方法来维护元素之间的插入顺序,所以迭代访问时非常简单,只需要不断的访问当前节点的下一个节点即可

第二:实现了访问最少最先删除功能,其目的是把很久都没有访问的 key 自动删除。

这种策略其实就是 LRU(Least recently used,最近最少使用),简单来说,在链表中的LRU,就是经常访问的元素会被追加到队尾,这样不经常访问的数据自然就被前移,慢慢靠近队头,然后我们可以通过设置删除策略,比如当 Map 元素个数大于多少时,把头节点删除,这就实现了最少最先的删除

5. 说下 HashMap

1. 概述

HashMap 他是一个基于 哈希表 的 Map 接口的非同步实现(他与 Hashtalbe 类似,但 Hashtable 是线程安全的,所以是同步的实现),此实现提供可选的映射操作,允许使用null 值和 null 键,但是他并非有序。

2. HashMap 数据结构与实现原理:

在jdk 1.7 和 1.8 中,HashMap 的数据结构是有所差别的,进行一些优化来解决冲突问题,下面我们就分别从两个版本的角度分系一下他的改动与区别

在1.7 中,采用的 数组+链表 的形式,也就是采用了拉链法。

将哈希冲突值加入到每个数组的链表中,他的插入采用的头插法的形式(这种方法最大的弊端就是会使插入值产生环,从而无限循环,后面我们将详细讲解这种方法的弊端操作),进行 hash 值计算的时候, 1.7 采用的是9次扰动(4次位运算+5次异或运算)的方式进行处理,除此之外在扩容上也有所不同,在1.7 中采用的全部按照原来的方式进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)),而1.8中则采用按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量),下面上我们来详细讲解一下 1.8

1.8 的版本则采用数组+链表+红黑树的方式

这种方法大大优化了哈希冲突的问题,减少了搜索时间,当添加的目的达到阈值的时候可以将链表转换为红黑树的形式,而当红黑树的节点小于6 的时候就会从红黑树转化为链表的形式,而在进行插入值的时候则采用的是尾插法的形式,这种方法解决了成环的情况发生

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab=table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) ==null)tab[i] = newNode(hash, key, value, null);else{Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;   if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

上面代码辨识对数组进行put 操作时,节点的判断以及添加,里面有几个核心的点:

resize()

这个方法顾名思义:扩容,他在1.8 的时候进行两次调用,第一次实在数组进行初始化的时候对其进行扩容,而第二次则是在数组满的时候进行扩容,一次是开始,一次则是快结束的时候,让我们点进去看;

final Node<K,V>[] resize(){Node<K,V>[] oldTab = table; // 定义老数组// 判断老数组是否为空,来实现初始化阔从操作int oldCap = (oldTab == null) ?0 : oldTab.length; int oldThr = threshold; // 使初始容量暂时为创建数组时产生的容量,默认为16int newCap, newThr = 0; // 进行初始化赋值if(oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // 双重容量,也可以理解为进行扩容}else if (oldThr > 0) newCap = oldThr;else{newCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {//进行与加载因子的乘积,此时表示在自动扩容之前,数组达到多满的一种度量,//当超过这个乘积值的时候才进行扩容,加载因子的默认值为0.75float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr;}

从中我们可以看出,扩容方法对数组初始化以及为空都进行了详细的判断,许多人看到这里都会问为什么加载因子的默认值是 0.75 呢?那么这个问题我们会在接下来的面试题中进行简单的解释。让我回到上面的 put 方法中继续分析。

hash(key)

这个方法也是我们的重点,他是为了保证充分利用数组的每个位置(下标)并大大解决哈希冲突问题而诞生的,在 1.7 中我们提到了计算 hash 的时候进行了 9 次扰动,而在 jdk 1.8 中我们仅仅使用了 2 次扰动就进行了 hash 值的计算,

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

从上面代码可以看出他是通过 key.hashCode() 进行高 16 位和低 16 位进行了一次异或运算得到的,减少了hash 的碰撞问题。

两个主要的方法分系完了, 让我们整体描述一下 put 的方法整体流程,在这个执行流程中,可以清晰的看出值的插入以及两个核心方法的使用

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

至此我们的讲解完毕, 下下面看一下面试题

1.为什么采用hashcode的高16位和低16位异或能降低hash碰撞?hash函数能不能直接用key的hashcode?

因为key.hashCode调用的是key键值类型自带的哈希函数,他返回的是一个int类型的散列值,我们都知道int类型的-2147483648~2147483647**大约有40亿的空间,而在我们的数组中容量仅仅为16个,所以就会造成数组无法承载值的情况,因为必须是均匀分布才能有效地避免哈希碰撞的问题,所以就要对其进行取模运算。
2.HashMap默认加载因子为什么选择0.75?

因为提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小,因为在hashmap中影响性能的因为有俩个,一个是初始容量,一个便是加载因子,如果加载因子小的话就会造成空间利用率低,并提高了rehash的次数,为了保证最大程度上减少rehash的次数,0.75是最适宜的标准数也是折中的一种考虑。
3.jdk1.7插入数据方式有什么弊端?

上面我提到了形成环,那么到底是为什么呢,因为在使用的过程中,如果一个线程在插入一个节点的时候,另一个线程也在插入节点,而且两个线程插入的都是对方线程上的几点,这样当进行头插的时候会使链表发生反转,便形成了环状,造成了死循环,如下图:

答案截取自前程有光的博客:面试官:小伙子,你能给我说一下HashMap的实现原理吗?_前程有光的博客-CSDN博客_hashmap实现

看 HashMap 的源码后对 Doug Lea 和 Josh Bloch 真是服,根本理解不了。牛逼

6. ConcurrentHashMap 怎么实现的, 1.7和1.8分别说下

7. ConcurrentHashMap 怎么取的 size 值

8. 一个非常多元素的HashMap , rehash 非常耗时,所以需要在他rehash 过程中还能get、put ,你有什么解决方案或者思路,谈谈你的理解?

9. 怎么防止恶意请求刷接口

解决方案:采用注解方式

其实也就是spring拦截器来实现。在需要防刷的方法上,加上防刷的注解,拦截器拦截这些注解的方法后,进行接口存储到redis中。当用户多次请求时,我们可以累积他的请求次数,达到了上限,我们就可以给他提示错误信息。

10. 说下 ES 的倒排索引

11. 那 ES 怎么切词的呢,有写过切词插件吗?

12. 你在项目中用Redis 的场景

从自己当前负责参与开发的一个项目中来看,redis主要的应用场景有如下几个,第一个是保存用户信息,这个需要频繁的获取。比如在打开某一个页面进行查询时,就先需要获取用户信息,看用户是否具有查询权限;第二个应用场景是,当数据库查询比较慢时,也会使用到redis缓存,第一次查询可能会比较慢,就将结果缓存在redis中,当第二次进行访问时就快多了;第三个应用场景是使用字典表进行翻译某些字段的值,将字典表进行放在redis中进行存储;第四个应用场景是,当查询用户的权限时,将所有用户对应的权限信息放在redis中。

13. 说下 Redis 有哪些数据类型

在 Redis 中,所有的对象都被封装成 redisObject, 包括 type、encoding 两个属性

type:就是 Redis 支持的 string、hash、list、set、和 zset 五种类型

encoding:就是对应的数据结构

对redis 来说,所有key 都是字符串

1. String 字符串类型

是redis中最基本的数据类型,一个key对应一个value。

String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。

使用:get 、 set 、 del 、 incr、 decr 等

实战场景:

  1. 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
  2. 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
  3. session:常见方案spring session + redis实现session共享

2. Hash

是一个Map,指值本身又是一种键值对结构,如 value={{field1,value1},……fieldN,valueN}}

使用:所有hash的命令都是 h 开头的 hget 、hset 、 hdel 等

实战场景:

  1. 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等。

3. 链表

List 说白了就是链表(redis 使用双端链表实现的 List),是有序的,value可以重复,可以通过下标取出对应的value值,左右两边都能进行插入和删除数据。

实战场景:

  1. timeline:例如微博的时间轴,有人发布微博,用lpush加入时间轴,展示新的列表信息。

4. Set 集合

集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中 1. 不允许有重复的元素,2.集合中的元素是无序的,不能通过索引下标获取元素,3.支持集合间的操作,可以取多个集合取交集、并集、差集。

使用:命令都是以s开头的 sset 、srem、scard、smembers、sismember

实战场景

  1. 标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
  2. 点赞,或点踩,收藏等,可以放到set中实现

5. zset 有序集合

有序集合和集合有着必然的联系,保留了集合不能有重复成员的特性,区别是,有序集合中的元素是可以排序的,它给每个元素设置一个分数,作为排序的依据。

(有序集合中的元素不可以重复,但是score 分数 可以重复,就和一个班里的同学学号不能重复,但考试成绩可以相同)。

使用: 有序集合的命令都是 以 z 开头 zadd 、 zrange、 zscore

实战场景:

  1. 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。

14. 说说你在项目中怎么用的这些数据类型

15. 一次请求拿出来所有的数据有什么问题,那怎么改进?

16. Redis 怎么分片的

17. Redis 的删除策略

岸柏科技

一面

1. 自我介绍

2. Mysql 有哪些锁

1. 加锁的目的:

解决事务的隔离性问题,让事务之间相互不影响,每个事务进行操作的时候都必须先对数据加上一把锁,防止其它事务同时操作数据。

2. 锁是基于什么实现的:

数据库里面的锁是基于索引实现的,在Innodb 中我们的锁都是作用在索引上面的,当我们的SQL 命中索引时,那么锁住的就是命中条件内索引节点(行锁),如果没有命中索引的话,那我们锁的就是整个索引树(表锁)。(不得不说设计的很科学,服)

3. 锁的分类

基于锁的属性分类:共享锁、排他锁。

基于锁的粒度分类:表锁、行锁、记录锁、间隙锁、临键锁。

基于锁的状态分类:意向共享锁、意向排他锁。

 共享锁:

共享锁又称为读锁,简称 S 锁。当一个事务对数据加上读锁之后,其他事务只能对该数据加读锁,而无法对数据加写锁,知道所有的锁释放之后其他事务才能对其加持写锁。加了共享锁之后,无法再加排他锁,这也就可以避免读取数据的时候会被其他事务修改,从而导致重复度问题。

排他锁:

排他锁又称写锁,简称 X 锁;当一个事务对数据加上写锁之后,其他事务将不能再为数据加任何锁,知道该锁释放之后,其他事务才能对数据进行加锁。加了排他锁之后,其他事务就无法再对数据进行读取和修改,所以也就无法出现脏写和脏读的问题。

表锁

表锁指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放锁才能进行对表进行访问。特点:力度大,加锁简单,容易冲突;

行锁

行锁是对所有行级别锁的一个统称,比如下面说的记录锁、间隙锁、临键锁都是属于行锁,行锁是指加锁的时候锁住的是表的某一行或者多行记录,多个事务访问同一张表时,只有被锁住的记录不能访问,其他记录可以正常访问;特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高;

    记录锁

记录锁属于行锁中的一种,记录锁的范围只是表中的某一条记录,记录锁是说事务再加锁后所著的只是表的某一条记录。

触发条件:精准条件命中,并且命中索引;

例如:update user_info set name=’张三’ where id=1 ,这里的id是索引。

记录锁的作用: 加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。

间隙锁(Gap Lock)

间隙锁属于行锁中的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻 ID 之间出现空隙则会形成一个区间,遵循左开右闭原则。

                触发条件:范围查询,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ(重复读)的事务级别中。

                间隙锁作用:防止幻读问题,事务并发的时候。两次查询间隙区间内如果提交了一个区间内的记录会导致两次查询结果不一致。

临键锁(Next-Key Lock)

                临键锁也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住。

触发条件:范围查询,条件命中了索引。

临键锁的作用:结合记录锁和间隙锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之后,在范围区间内数据不允许被修改和插入。

状态锁

                状态锁包括意向共享锁和意向排他锁,把他们区分为状态锁的一个核心逻辑,就是因为这两个锁都是藐视是否可以对某一个表进行加表的状态。

意向锁的解释:当一个事务试图对整个表进行加锁(共享锁或排他锁)之前,首先需要获取得对应类型的意向锁。这个意思是比如一个事务对表内一条记录修改时,另一个事务也要修改一条信息,因为第一个事务加了排他锁,所以别的事务需要一条条检查索引是否加锁,所以第一个事务在加排他锁之前,获得了这个表的意向排他锁,第二个事务直接根据这个意向排他锁就知道有事务在对表进行操作,就可以避免了对整个索引树的每个节点扫描是否加锁。这个状态就是我们的意向锁。

意向共享锁

当一个事务试图对这个表进行加共享锁之前,首先需要获得这个表的意向共享锁。

意向排他锁

当一个事务试图对整个表急性加排他锁之前,首先需要获得这个表的意向排他锁。

3. 解释一下ACID 都是什么

什么是事务?

在数据库系统里而言,事务是代表一个或者一系列操作的最小逻辑单元,所有在这个逻辑单元内的操作要么全部成功,要么就全部失败,不存在任何中间状态,一旦事务失败那么所有的更改都会被撤销,一旦事务成功所有的操作结果都会被保存。

为什么要有事务?

事务机制存在的目的就是无论我们的操作过程中是成功、失败、异常、或是收到干扰的情况下,事务都能保证我们数据的一致性。(例子,转账操作)

事务的特性(ACID)

要实现事务的最终目的,需要集中机制组合才能实现,这几种机制就是事务的几个特性,分别是原子性、隔离性、一致性、持久性。用一句话总结这几个特性之间的关系,那就是“一致性是事务的最终目的,而原子性、隔离性、持久性其实都是为了实现一致性的手段。”

  1. 原子性(Atomicty):一个事务必须是一系列操作的最小单元,这个操作的过程中,要么整个执行,要么整个回滚,不存在只执行了其中某一个或者某几个步骤。
  2. 隔离性(Isolation):隔离性是说两个事务的执行都是独立隔离开来的,事务之前不会相互影响,多个事务操作一个对象时会以串行等待的方式保证事务相互之间是隔离的。
  3. 一致性(Consistency):事务要保证数据库整体数据的完整性和业务的数据的一致性,事务成功提交整体数据修改,事务错误则回滚到数据回到原来的状态。
  4. 持久性(Durability):持久性是指一旦事务成功提交后,只要修改的数据都会进行持久化,不会因为异常、宕机而造成数据错误或丢失。

4. Innodb 中索引的实现

Innodb的索引采用了B+树的存储结构来管理

①  叶子节点存储所有的数据,内节点只存储键值;

② 由于键值既在叶子节点上也在内节点上,需要一定的存储空间,空间换性能;

③ 查询的复杂度和B+树的层数有关,层数越少,性能越好;

④ 层数和每个数据页储存多少条数据有关,也就是和key_length有关。所以B+树不适合存储长的索引列。

Innodb索引分为聚簇索引(clustered index)和普通索引(secondary index):

聚簇索引:每张表必须有且只有一个聚簇索引来存放所有的数据。它的叶子节点存放了整张表的所有数据行。聚簇索引第一会选择我们建表时明确定义的主键列;如果没定义主键列,第二会选择第一个非空的唯一索引列;如果前二者都没有,Innodb会选择隐藏的Rowid(实例级别,6byte)做为聚簇索引。
普通索引:除了聚簇索引,其他的所有索引都为普通索引。它的叶子节点只存放普通索引列和对应的用于回表的键值,这个键值就是上面说的主键、第一个非空唯一索引或者Rowid。当然还包括TRXID,ROLLPTR列。

5. B+ 树

工作开发中最常接触到的 InnoDB 存储引擎中的 B+ 树索引, 要说到 B+ 树索引,就不得不提 二叉查找树,平衡二叉树和B 树这三种数据结构。B+ 树就是从他们三个演化来的。

二叉查找树

首先看一张图:

从图中可以看到,我们为 user 表建立了一个二叉查找树的索引。

图中的圆为二叉查找树的节点,节点中存储了键和数据。键对应id, 数据对应行数据。

二叉查找树的特点就是任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于当前节点的键值。顶端的节点我们称为根节点,没有子节点的节点我们称之为叶节点。

如果我们需要查找id = 12 的用户信息,利用我们创建的二叉查找树索引,查找流程如下:

  • 将根节点作为当前节点,把 12 与当前节点的键值 10 比较,12 大于 10,接下来我们把当前节点>的右子节点作为当前节点。
  • 继续把 12 和当前节点的键值 13 比较,发现 12 小于 13,把当前节点的左子节点作为当前节点。
  • 把 12 和当前节点的键值 12 对比,12 等于 12,满足条件,我们从当前节点中取出 data,即 id=12,name=xm。

利用二叉查找树我们只需要 3 次即可找到匹配的数据。如果在表中一条条的查找的话,我们需要6次才能找到。

平衡二叉树

上面我们讲解了利用二叉查找树可以快速的找到数据。但是,如果上面的二叉查找树是这样的构造:

这个时候可以看到我们的二叉查找树变成了一个链表。如果我们需要查找 id=17 的用户信息,我们需要查找 7 次,也就相当于全表扫描了。

导致这个现象的原因其实是二叉查找树变得不平衡了,也就是高度太高了,从而导致查找效率的不稳定。

为了解决这个问题,我们需要保证二叉查找树一直保持平衡,就需要用到平衡二叉树了。

平衡二叉树又称 AVL 树,在满足二叉查找树特性的基础上,要求每个节点的左右子树的高度差不能超过 1。

由平衡二叉树的构造我们可以发现第一张图中的二叉树其实就是一棵平衡二叉树。

平衡二叉树保证了树的构造是平衡的,当我们插入或删除数据导致不满足平衡二叉树不平衡时,平衡二叉树会进行调整树上的节点来保持平衡。具体的调整方式这里就不介绍了。

平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。

B 树

因为内存的易失性。一般情况下,我们都会选择将 user 表中的数据和索引存储在磁盘这种外围设备中。

但是和内存相比,从磁盘中读取数据的速度会慢上百倍千倍甚至万倍,所以,我们应当尽量减少从磁盘中读取数据的次数。

另外,从磁盘中读取数据时,都是按照磁盘块来读取的,并不是一条一条的读。

如果我们能把尽量多的数据放进磁盘块中,那一次磁盘读取操作就会读取更多数据,那我们查找数据的时间也会大幅度降低。

如果我们用树这种数据结构作为索引的数据结构,那我们每查找一次数据就需要从磁盘中读取一个节点,也就是我们说的一个磁盘块。

我们都知道平衡二叉树可是每个节点只存储一个键值和数据的。那说明什么?说明每个磁盘块仅仅存储一个键值和数据!那如果我们要存储海量的数据呢?

可以想象到二叉树的节点将会非常多,高度也会极其高,我们查找数据时也会进行很多次磁盘 IO,我们查找数据的效率将会极低!

为了解决平衡二叉树的这个弊端,我们应该寻找一种单个节点可以存储多个键值和数据的平衡树。也就是我们接下来要说的 B 树。

B 树(Balance Tree)即为平衡树的意思,下图即是一棵 B 树:

图中的 p 节点为指向子节点的指针,二叉查找树和平衡二叉树其实也有,因为图的美观性,被省略了。

图中的每个节点称为页,页就是我们上面说的磁盘块,在 MySQL 中数据读取的基本单位都是页,所以我们这里叫做页更符合 MySQL 中索引的底层数据结构。

从上图可以看出,B 树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的子节点,子节点的个数一般称为阶,上述图中的 B 树为 3 阶 B 树,高度也会很低。

基于这个特性,B 树查找数据读取磁盘的次数将会很少,数据的查找效率也会比平衡二叉树高很多。

假如我们要查找 id=28 的用户信息,那么我们在上图 B 树中查找的流程如下:

  • 先找到根节点也就是页 1,判断 28 在键值 17 和 35 之间,那么我们根据页 1 中的指针 p2 找到页 3。
  • 将 28 和页 3 中的键值相比较,28 在 26 和 30 之间,我们根据页 3 中的指针 p2 找到页 8。
  • 将 28 和页 8 中的键值相比较,发现有匹配的键值 28,键值 28 对应的用户信息为(28,bv)。

B+ 树

B+ 树是对 B 树的进一步优化。让我们先来看下 B+ 树的结构图:

根据上图我们来看下 B+ 树和 B 树有什么不同:

①  :

B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。之所以这么做是因为在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。

如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。

另外,B+ 树的阶数是等于键值的数量的,如果我们的 B+ 树一个节点可以存储 1000 个键值,那么 3 层 B+ 树可以存储 1000×1000×1000=10 亿个数据。

一般根节点是常驻内存的,所以一般我们查找 10 亿数据,只需要 2 次磁盘 IO。

② :

因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。

那么 B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。

有心的读者可能还发现上图 B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。

其实上面的 B 树我们也可以对各个节点加上链表。这些不是它们之前的区别,是因为在 MySQL 的 InnoDB 存储引擎中,索引就是这样存储的。

也就是说上图中的 B+ 树索引就是 InnoDB 中 B+ 树索引真正的实现方式,准确的说应该是聚集索引(聚集索引和非聚集索引下面会讲到)。

通过上图可以看到,在 InnoDB 中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。

MyISAM 中的 B+ 树索引实现与 InnoDB 中的略有不同。在 MyISAM 中,B+ 树索引的叶子节点并不存储数据,而是存储数据的文件地址。

聚集索引 VS 非聚集索引

        在上节介绍 B+ 树索引的时候,我们提到了图中的索引其实是聚集索引的实现方式。

那什么是聚集索引呢?在 MySQL 中,B+ 树索引按照存储方式的不同分为聚集索引和非聚集索引。

这里我们着重介绍 InnoDB 中的聚集索引和非聚集索引:

(聚簇索引):以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键。 这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引。

(非聚簇索引):以主键以外的列值作为键值构建的 B+ 树索引,我们称之为非聚集索引。

非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。

明白了聚集索引和非聚集索引的定义,我们应该明白这样一句话:数据即索引,索引即数据。

利用聚集索引和非聚集索引查找数据

前面我们讲解 B+ 树索引的时候并没有去说怎么在 B+ 树种进行数据的查找,主要就是因为还没有引出聚集索引和非聚集索引的概念。

下面我们通过讲解如何通过聚集索引以及非聚集索引查找数据表种数据的方式介绍一下 B+ 树索引查找数据的方法。

利用聚集索引查找数据

还是这张 B+ 树索引图,现在我们知道这就是聚集索引,表中的数据存储在其中。

假设现在我们要查找 id >= 18 并且 id < 40 的用户数据。

具体的查找过程如下:

① :

一般根节点都是常驻内存的,也就是说页 1 已经在内存中了,此时不需要到磁盘中读取数据,直接从内存中读取即可。

从内存中读取到页 1,要查找这个 id>=18 and id <40 或者范围值,我们首先需要找到 id=18 的键值。

从页 1 中我们可以找到键值 18,此时我们需要根据指针 p2,定位到页 3。

② :

要从页 3 中查找数据,我们就需要拿着 p2 指针去磁盘中进行读取页 3。

从磁盘中读取页 3 后将页 3 放入内存中,然后进行查找,我们可以找到键值 18,然后再拿到页 3 中的指针 p1,定位到页 8。

③ :

同样的页 8 页不在内存中,我们需要再去磁盘中将页 8 读取到内存中。

将页 8 读取到内存中后。因为页中的数据是链表进行连接的,而且键值是按照顺序存放的,此时可以根据二分查找法定位到键值 18。

此时因为已经到数据页了,此时我们已经找到一条满足条件的数据了,就是键值 18 对应的数据。

因为是范围查找,而且此时所有的数据又都存在叶子节点,并且是有序排列的,那么我们就可以对页 8 中的键值依次进行遍历查找并匹配满足条件的数据。

我们可以一直找到键值为 22 的数据,然后页 8 中就没有数据了,此时我们需要拿着页 8 中的 p 指针去读取页 9 中的数据。

④ :

因为页 9 不在内存中,就又会加载页 9 到内存中,并通过和页 8 中一样的方式进行数据的查找,直到将页 12 加载到内存中,发现 41 大于 40,此时不满足条件。那么查找到此终止。

最终我们找到满足条件的所有数据,总共 12 条记录:

(18,kl), (19,kl), (22,hj), (24,io), (25,vg) , (29,jk), (31,jk) , (33,rt) , (34,ty) , (35,yu) , (37,rt) , (39,rt) 。

下面看下具体的查找流程图

利用非聚集索引查找数据 

这个非聚集索引表示的是用户幸运数字的索引,此时表结构是这样的。

在叶子节点中,不再存储所有的数据了,存储的是键值和主键。对于叶子节点中的 x-y,比如 1-1。左边的 1 表示的是索引的键值,右边的 1 表示的是主键值。

查找的流程跟聚集索引一样,这里就不详细介绍了。我们最终会找到主键值 47,找到主键后我们需要再到聚集索引中查找具体对应的数据信息,此时又回到了聚集索引的查找流程。

下面看下具体的查找流程图:

在 MyISAM 中,聚集索引和非聚集索引的叶子节点都会存储数据的文件地址。

b+树详解_源头源脑的博客-CSDN博客_b+树

6. AUTO_INCREMENT 原理

7. 数据库的索引有哪几种?为什么要用B+树来做索引?组合索引和几个单个的索引有什么区别?数据库的大表查询优化了解吗?MVCC机制了解不?MVCC 机制有什么问题? mysql 慢语句调优做过吗?说说你是怎么做的?

8. Redis 了解吗?说说怎么用redis 实现分布式锁?

9. Redis 常用数据机构及底层数据结构实现

10. 如何解决 Redis 的并发竞争 Key 问题

11. 如何保证缓存与数据库双写时的数据一致性?

12. 死锁产生的原因

必要条件:

互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一进程所占用。

请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。

不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。

循环等待条件:在发生死锁时,必然存在一个进程-资源的环形链。

预防死锁:

资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)

只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请求和保持条件)

可剥夺资源,即当某秦城获得了部分资源,但得不到其他资源,则释放已占有的资源(破坏不可剥夺条件)

资源有序分配法:系统给每类资源赋予一个编号,每个进程按标号递增的顺序请求资源,释放则相反(破坏环路等待条件)

13. 进程、线程区别,什么时候用线程

14. 如何实现一个线程池,Java 中线程池如何进行配置

15. linux 中有哪些常见的指令,进行介绍

16. select、poll、epoll 有没有了解过,讲解一下

17. 线程切换,引申到 Java 阻塞->运行

18. 页面置换算法有哪些介绍一下

易通集团

1. jdk 8 stream 如何保证线程安全?

2. 背包算法, 红黑树,双向链表

3. 双向链表实现;

4. List 的子类都有哪些

  1. Vector:内部是数组数据结构,线程安全。增删,查询都很慢。
  2. ArrayList:内部是数组数据结构,线程不安全,替代Vector,查询速度快,增删速度慢。如果需要使用多线程,我们可以给ArrayList加锁,或者使用其他的方法,Vector已经不再使用了。
  3. LinkedList:内部是链表数据结构,线程不安全,增删速度快,查询速度慢。

5. left join 和 right join

左连接和右链接

6. Map 类型,底层实现原理

7. 如何检测死锁?

8. 使用过的前端框架都有哪些?

一号店

1. JVM 虚拟机的内存结构

2. 垃圾回收算法

3. 类加载

4. HashMap 的底层实现原理

用 Node[] table 数组来实现,数组中放的是Node 节点,是一个静态内部类,实现了 Map.Entry 接口。节点内部有 key, value, hash, next 四个成员变量,其中 next 为指向下一个 Node 节点的引用。因此数据结构采用数组 + 单向链表 实现。

put 方法的步骤:

  1. 对 key 的hashcode 值再计算hash.
  2. 用计算出的hash 值,与表的length 求 & 算出索引位置。
  3. 如果索引碰撞,遍历链表,判断是否有 key 存在,若存在更新 value 值。(不过再 JDK 1.8 中链表元素时,会把链表结构转换成红黑树结构进行存储,提高查询的性能)
  4. 若未碰撞,直接放入表中。
  5. 放入前,如果元素个数超过负载因子的比例,则进行rehash, 扩容,之后插入。
  6. null key 的元素永远会放到 index 为 0 的链表里。扩容:初始容量为2 的整数次方,不够会用最接近给定容量的2 的整数次方,初始为16,灭磁扩容数据大小会加倍。然后所有元素重新再新table 中计算 hash 值, 重新得到桶位置,放入。

5. HashMap 是线程安全的吗

不安全为什么HashMap线程不安全?以及实现HashMap线程安全的解决方案_gougege0514的博客-CSDN博客_hashmap为什么线程不安全

6. 如何实现一个线程安全的HashMap?

7. ConcurrentHashMap 的底层实现原理?

8. 写一个单例模式。

class Singleton{private static Singleton instance = null;private Singleton(){}public static Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}
}

快手

一面(一年)

1. hashMap 原理

2. 代码写死锁

class DeadLock{public static void main(String[] args){private Object lockA;private Object lockB;Thread threada = new Thread(()->{synchronized(lockA){Thread.sleep(5000);synchronized(lockB){System.out.println("死锁");}}});Thread threadb = new Thread(()->{synchronized(lockB){Thread.sleep(5000);sychronized(lockA){System.out.println("死锁");}}});threada.start();threadb.start();}
}

3. synchronize

关键字 synchronized 拥有锁重入的功能

4. volatile

5. AtomicInteger

6. CAS

什么是 CAS

CAS(Compare-and-Swap), 即比较并替换的意思,它是一条 CPU 并发原语,用于判断内存中某个值是否为预期值,如果是则更改为新的值,这个过程是原子的。

CAS 机制当中使用了 3 个值: 内存地址 V, 旧的预期值 A, 计算后要修改的新值 B

  1. 两个线程同时对内存值V进行操作,V初始值为1
  2. 线程1、线程2都对V加1计算,预期值A=1,新值B=2
  3. 线程2先提交,预期值A==V,更新成功,将V更新为2
  4. 线程1提交时,发现预期值A=1,V=2,A!=V,提交失败,重新获取内存值V=2
  5. 线程1自旋,V=2,A=2,B=3,重新比较A==V成立,然后更新V=3,最后V=3结束

总结:更新一个变量的时候,只有当变量的预期值 A 和内存地址 V 中的实际值相同时,才会将内存地址 V 对应的值修改为 B,这个操作就是CAS

CAS 基本原理

CAS 主要包括两个操作:CompareSwap。

CAS 是一条 CPU 的原子指令,执行必须是连续的,执行过程中不允许被中断。

JDK 是在 1.5 版本后才引入 CAS 操作,在sun.misc.Unsafe这个类中定义了 CAS 相关的方法。

Java 中的应用

CAS主要封装在java并发编程工具包中,java.util.concurrent包

AtomicInteger 类解决 i++ 非原子性问题,通过volatile 关键字和 CAS 操作来实现

弊端

  • 典型的ABA

ABA 是 CAS 操作的一个经典问题,一个变量初始值为 A,修改为 B,然后又修改为 A,这个变量实际被修改过了,但是 CAS 操作无法感知到。

解决这个问题也很简单,只要在变量前加版本号,每次变量更新了就把版本号加1

  • 自旋开销

CAS 出现冲突后就会开始重复尝试,即自旋操作,如果资源竞争非常激烈,自旋长时间不能成功就会给 CPU 带来非常大的开销。

优化:限制自旋的次数,避免过度消耗 CPU;

  • 只能保证单个变量的原子性

当对一个共享变量执行操作时,可以使用 CAS 来保证原子性,如果要对多个共享变量进行操作时,CAS 是无法保证原子性的,比如需要将 i 和 j 同时加 1:

i++;j++;

优化:

1. 使用synchronized 进行加锁;

2. 将多个变量操作合成一个变量操作。AtomicReference 类来保证引用对象之间的原子性,把多个变量放在一个对象里来进行 CAS 操作。

AtomicReference 关键方法:

public final boolean compareAndSet(V expect, V update){return unsafe.compareSwapObject(this, valueOffset, expect, update);
}

7. 会不会线程池,然后接着问如果消息队列产生的信息过多,消费者无法及时消费,需要怎么解决呢?

跟谁学

(一年)

1. 线程池,怎么设定核心线程数

CPU 密集型

第一种是 CPU 密集型任务,比如加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。

最佳线程数 = CPU 核心数的 1 至 2 倍。

如果设置过多的线程,实际上并不会起到很好的效果。此时假设我们设置的线程数是 CPU 核心数的 2 倍以上,因为计算机的任务很重,会占用大量的 CPU 资源,所以这是 CPU 每个核心都是满负荷工作,而设置过多的线程数,每个线程都去抢占 CPU 资源,就会产生不必要的上下文切换,反而会造成整体性能的下降。

IO 密集型

第二种任务是耗时 IO 型,比如数据库、文件的读写,网络通信等任务,这种任务的特点是并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。

对于这种情况任务最大线程数一般会大于 CPU 核心数很多倍,因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果我们设置过少的线程数,可能导致 CPU 资源的浪费。而如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利用资源。

通用公式  

线程数 = CPU  核心数 * (1 + IO 耗时/CPU 耗时)

通过这个公式,我们可以计算出一个合理的线程数量,如果任务的 IO 耗时时间长,线程数就随之增加,而如果CPU 耗时长,也就是对于我们上面的 CPU 密集型任务,线程数就随之减少。

太少的线程数会使得程序整体性能降低,而过多的线程也会消耗内存等其他资源,所以如果想要更准确的话,可以进行压测,监控 JVM 的线程情况以及 CPU 的负载情况,根据实际情况衡量应该创建的线程数,合理并充分利用资源。

结论

  • 线程的 CPU 耗时所占比例越高,就需要越少的线程
  • 线程的 IO 耗时所占比例越高,就需要越多的线程
  • 针对不同的程序,进行对应的实际测试就可以得到最合适的选择
  • 线程数 >= CPU 核心数

2. 用过线程同步工具类吗?

3. 并行和并发的区别

并发:

在同一时间段,多个任务都在执行。宏观上是同时执行,微观上是顺序地交替执行。并发不一定等于并行。
并行:

单位时间内,多个任务同时执行。

并行(parallel):

指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的。

并发(concurrency):

指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。

并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。

4. 能不能自定义 java.lang.String(类加载)

5. 异常分类

6. Java 容器有哪些,来自同一个父类吗?

7. ArrayList 初始值,怎么扩容

  1. 不指定ArrayList的初始容量,在第一次add的时候会把容量初始化为10个,这个数值是确定的;
  2. ArrayList的扩容时机为add的时候容量不足,扩容的后的大小为原来的1.5倍,扩容需要拷贝以前数组的所有元素到新数组。

8. 可以再 iterator 循环的过程中删除元素吗?(fail-fast 和 fail-safe)

9. 怎么写一个函数式接口

10. Stream 怎么把二维数组变成一维

11. Spring  IOC  容器

12. left join 语句的作用, 后面 on 和 where 的区别

招商银行

电话面

1. 讲讲 java 锁

乐观锁 & 悲观锁

乐观锁:认为一个线程去拿数据的时候不会有其他线程对数据进行修改,所以不会上锁。

实现方式: CAS 机制、版本号机制

悲观锁: 认为一个线程去拿数据时一定会有其他线程对数据进行修改。所以一个线程在拿数据的时候都会顺便加锁,这样别的线程此时想拿这个数据就会阻塞。比如Java 里面的 synchronized 关键字的实现就是悲观锁。 实现方式: 就是加锁。

独享锁 & 共享锁

独享锁:该锁一次只能被一个线程所持有

共享锁:该所可以被多个线程所持有

举例:

synchronized 是独享锁;

可重入锁 ReentrantLock 是独享锁;

读写锁 ReentrantReadWriteLock 中的读锁 ReadLock 是共享锁, 写锁 WriteLock 是独享锁。

独享锁与共享锁通过 AQS(AbstractQueuedSynchronizer) 来实现的, 通过实现不同的方法,来实现独享或者共享。

互斥锁 & 读写锁

上面讲的独享锁 / 共享锁就是一种概念, 互斥锁 / 读写锁是具体的实现。

互斥锁的具体实现就是 synchronized、ReentrantLock。ReentrantLock 是JDK1.5 的新特性, 采用 ReentrantLock 可以完全替代替换 synchronized 传统的锁机制,更加灵活。

读写锁的具体实现就是读写锁 ReadWriteLock 。

可重入锁

定义:对于同一个线程在外层方法获取锁的时候,在进入内层方法时也会自动获取锁。

优点: 避免死锁

举例:ReentrantLock、synchronized

公平锁 & 非公平锁

公平锁:多个线程相互竞争时要排队,多个线程按照申请锁的顺序来获取锁。

非公平锁:多个线程相互竞争时,先尝试插队,插队失败再排队, 比如: synchronized、ReentrantLock

分段锁

分段锁并不是具体的一种锁,只是一种锁的设计。

分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的意向进行加锁操作。 CurrentHashMap 地城就用了分段锁,使用Segment, 就可以进行并发使用了, 而HashMap 确实非线程安全的,就差在了分段锁上。

偏向锁 & 轻量级锁 & 重量级锁

JDK 1.6 为了减少获得锁和释放锁所带来的性能消耗,在 JDK 1.6 里引入了 4 种锁的状态:无锁、偏向锁、轻量级锁和重量级锁,它会随着多线程的竞争情况逐渐升级,但不能降级。

研究发现大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了不让这个线程每次获得锁都需要 CAS 操作的性能消耗,就引入了偏向锁。当一个线程访问对象并获取锁时,会在对象头里存储锁偏向的这个线程的ID ,以后该线程再访问该对象时只需判断对象头的 Mark Word 里是否有这个线程的ID,如果有就不需要进行 CAS 操作,这就是偏向锁。当线程竞争更激烈时,偏向锁就会升级为轻量级锁,轻量级锁认为虽然竞争时存在的,但是理想情况下竞争的程度很低,通过自旋方式的等待一会儿上一个线程就会释放锁,但是当自旋查过了一定次数,或者线程持有锁,一个方程在自旋,又来了第三个线程访问时(反正就是竞争继续加大了),轻量级锁就会膨胀为重量级锁,重量级锁就是 Synchronized, 重量级锁会使处了此时拥有锁的线程意外的线程都阻塞。

2. mysql 事务的隔离级别

MySQL 中事务的隔离级别一共分为四种,分别如下:

  • 序列化(Serializable)
  • 可重复读(repeatable read)
  • 提交读 (read committed)
  • 未提交读 (read uncommitted)

四种不同的隔离级别含义分别如下:

序列化:如何隔离级别为序列化,则用户之前通过一个接一个顺序地执行当前的事务,这种隔离级别提供了事务之间最大限度的隔离。

可重复读:在可重复读在这一个隔离级别上,事务不会被看成是一个序列。不过,当前正在执行事务的变化任然不能被外部看到,也就是说,如果用户在另外一个事务中执行同条 SELECT 语句数次,结果总是相同的。(因为正在执行的事务所产生的数据变化不能被外部看到)。

读提交: 读提交隔离级别的安全性比可重复读隔离级别的安全性要差。处于读提交级别的事务可以看到其他事务对数据的修改。也就是说,在事务处理期间,如果其他事务修改了相应的表,那么同一个事务的多个 SELECT 语句可能返回不同的结果。

读未提交:读未提交提供了事务之间最小限度的隔离。除了容易产生虚幻的读操作和不能重复读操作外,处于这个隔离级别的事务可以读到其他事务还没有提交的数据,如果这个事务使用其他事务不提交的变化作为计算的基础,然后哪些未提交的变化被他们父事务撤销,这就导致了大量的数据变化。

在MySql 数据库中,默认的事务隔离级别是 可重复读

3. 什么是幻读以及mysql 如何避免幻读

4. 什么是间隙锁,什么隔离级别以及什么情况下触发

间隙锁实质上是对索引前后的间隙上锁,不对索引本身上锁。

根据检索条件向左寻找最靠近键锁条件的记录值A,作为左区间,向右寻找最靠近检索条件的记录值 B 作为右区间,即锁定的间隙为 (A, B)

间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的:

1. 防止间隙内有新数据被插入。

2. 防止已存在的数据,更新成间隙内的数据。

5. 索引 B+ 树的优势在哪里

B+树查询效率更高。B+树使用双向链表串连所有叶子节点,区间查询效率更高(因为所有数据都在B+树的叶子节点,扫描数据库 只需扫一遍叶子结点就行了),但是B树则需要通过中序遍历才能完成查询范围的查找。

B+树查询效率更稳定。B+树每次都必须查询到叶子节点才能找到数据,而B树查询的数据可能不在叶子节点,也可能在,这样就会造成查询的效率的不稳定。

B+树的磁盘读写代价更小。B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,通常B+树矮更胖,高度小查询产生的I/O更少。

6. redis 数据结构和底层实现

7. 有了解过 redis 集群吗,烧饼是做什么的

8. 讲讲消息队列

9. 在一个静态方法内调用一个非静态成员为什么是非法的?

10. import java 和 javax 有什么区别?

刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。所以,实际上java和javax没有区别。这都是一个名字。

11. 接口和抽象类的区别是什么?

  1. 接⼝的⽅法默认是 public,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认 现),而抽象类可以有⾮抽象的⽅法。
  2. 接⼝中除了 static、final 变量,不能有其他变量,⽽抽象类中则不⼀定。
  3. ⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过 extends 关键字扩 展多个接⼝。
  4. 接⼝⽅法默认修饰符是 public,抽象⽅法可以有 public、protected 和 default 这些修饰符。(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。
  5. 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。

备注:在JDK8中,接口也可以定义静态方法,并且只可以用接口名调用。如果同时实现两个接口,接口中定义了一样的默认方法,则必须重写,不然会报错。

一面

1. 消息推送机制,怎么像安卓主动推送消息

2. rpc

3. 单例模式

4. redis 的底层数据结构

5. mysql 的索引

6. b 树和 B+ 树

7. java list 的区别

8. HashMap 的底层和 put 操作

9. HashMap 多线程下会出现的问题

二面

1. jvm 分区

2. 什么是幻读以及mysql 如何避免幻读

3. redis 持久化 RDB AOF

4. mysql 锁机制

泰克电子

面经一

1. 单项链表的归并排序

2. 链表做加法

3. 1G 大小文件,里面每行是最大16k 的单词,限制内存1m, 统计单词频率最多的 100 个单词。

4. 什么是 RPC? 怎么实现幂等性?

5. 熔断会影响性能吗?有遇到过线上发生熔断吗?不加会怎样?

6. redis keys 命令有什么缺点?

7. 跨库分页的实现?

8. 分库分表有哪些策略?怎么保证 id 的唯一

9. 对 uuid 的理解?知道哪些 GUID、Random 算法?

10. aop 的实现原理、动态代理的过程

11. tomcat 与 spring、controller 的关系

12. 容器的内存和 jvm 的内存有什么关系?参数怎么配置?

13. wait 和 sleep 有什么区别?什么情况下会用到 sleep?

sleep():

属于 Thread 类 ,sleep 是 Thread 线程类的方法

sleep() 方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态遗产保持着,当指定的时间到了又会自动恢复运行状态。

在调用sleep() 方法的过程中,线程不会释放对象。

sleep() 可以在任何地方使用

wait():

属于 Object 类,wait() 是 Object 顶级类的方法。

当调用 wait() 方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 。

wait() 只能在同步方法或者同步块中使用。

CPU及资源释放

sleep,wait 调用后都会暂停当前线程并出让cpu 的执行时间,但不同的是 sleep 不会释放当前持有的对象的锁资源,到时间后会继续执行,而 wait 会放弃所有锁并需要 notify/notifyAll 后重新获取到对象锁资源后才能继续执行。

sleep 和 wait 的区别:

1. sleep 是Thread 的静态方法,wait 是 Object 的方法,任何对象实例都能调用。

2. sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。

3. 他们都可以被 interrupted 方法中断。

Thread.sleep(1000) 意思是在未来的 1000 毫秒内本线程不参与 CPU 竞争, 1000 毫秒过去之后,这时候或许另外一个线程正在使用 CPU, 那么这时候操作系统是不会重新分配 CPU 的,直到那个线程挂起或结束,即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。另外值得一提的是Thread.Sleep(0)的作用,就是触发操作系统立刻重新进行一次CPU竞争,竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。

wait(1000)表示将锁释放1000毫秒,到时间后如果锁没有被其他线程占用,则再次得到锁,然后wait方法结束,执行后面的代码,如果锁被其他线程占用,则等待其他线程释放锁。注意,设置了超时时间的wait方法一旦过了超时时间,并不需要其他线程执行notify也能自动解除阻塞,但是如果没设置超时时间的wait方法必须等待其他线程执行notify。

14. 怎么停止线程?

如何停止一个线程?

1. 不能简单的停止 stop() 一个线程。因为停止 stop() 会直接把线程停止,这样就没有给线程足够的时间来处理想要在停止前保存数据的逻辑,任务戛然而止,会导致出现数据完整性等问题;

2. 虽然线程不能再中间被停止/干掉,但是任务是可以停止的;想让线程结束的目的是让任务结束,而不是强制线程结束。有两种方式结束任务,分别是:Interrupt 和 boolean 标志位;

3. 使用线程中断机制 interrupt 停止线程,分两种情况。如果原生支持 interrupt : sleep、wait 等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。非原生如果支持 interrupt, 每执行一次任务,调用 interrupted() 或 isInterrupted() 询问一遍系统是否已经中断了,如果中断了系统会告诉我们已经中断了,然后可以实现中断的逻辑。

4. 其中,interrupt 底层的原理是在 Native 层加锁。然后判断 interrupted 值是否等于true, 使得直接返回,表示已经是中断状态了;否则,把 interrupted 置为 true, 发出中断通知。实现原理和 boolean 标志位的实现逻辑很像。

5. interrupted() 与 isInterrupted() 之间的区别是::在Java层,interrupted()是静态方法,获取当前线程的中断状态后并清空,重复调用后续返回false;isInterrupted()是非静态方法,获取线程对象对应线程的中断状态,不清空,可重复调用,中断清空前一直返回true。在Native层,interrupted()比isInterrupted()多调用了SetInterruptedLocked(false)方法,清空当前中断状态,其他的都一致。

6. 使用volatile boolean标志位停止线程:线程中设置一个boolean标志位值为false,线程里不断读取这个boolean值,其他地方可以修改这个boolean值;为了保证内存可见性,给boolean标志位添加volatile保证可见性;当某一个线程修改boolean标志位为true,线程中能立刻看到。

7. 如何选择interrupt和boolean标志位去停止线程?interrupt()和boolean标志位的原理是一致的。除非是用到了系统方法时(如:sleep) 或者 使用阻塞队列在线程中执行put()时发生阻塞,使用 interrupt();否则,建议使用boolean标志位,性能更优,毕竟interrupt使用JNI有一定的开销。

这道题想考察什么?

(1)考察要点

●是否对线程的用法有了解;是否对线程的stop方法有了解(初级)
●是否对线程stop过程中存在的问题有认识;是否熟悉interrupt的中断用法(中级)
●是否能解释清楚使用 boolean 标志位的好处;是否知道interrupt底层的细节;通过该题目能够转移话题到线程安全,并阐述无误(高级)

(2)题目刨析

●如何停止一个线程?
●官方停止线程的方法被废弃,所以不能直接简单的停止线程?如何设计可以随时被中断而取消的任务线程?

为什么不能简单的停止(Stop())一个线程?

答:因为停止 stop() 会直接把线程停止,这样就没有给线程足够的时间来处理想要停止前保存数据的逻辑,任务戛然而止,会导致出现数据完整性等问题。

如下图:Thread1被停止stop()后,立即释放内存锁;Thread3获得内存锁,马上加锁。Thread1根本没有清理内存的机会,原来写数据写到一半,现在没有机会继续写了,因为线程被杀掉了;接着,Thread3获得了时间片,开始读数据时发现内存状态异常,读到一个莫名其妙的数据,因为Thread1还没清理干净就停止线程了,留下一个烂摊子给Thread3,Thread3也会面临crash。所以,这样的停止操作是非常危险的。也正是这个原因,无论什么语言都把停止的api废弃了。

为什么暂停和继续(suspend() 和 resume())也被废弃了?

如何停止一个线程?_码上得天下的博客-CSDN博客_如何停止一个线程

15. 深拷贝、浅拷贝的区别

二者的区别:

浅拷贝:在拷贝一个对象时,对对象的基本数据类型的成员变量进行拷贝,但对音容类型的成员变量只进行引用的传递,并没有创建一个新的对象,当对引用类型的内容修改会影响被拷贝的对象。

深拷贝:在拷贝一个对象时,除了对基本数据类型的成员变量进行拷贝,对引用类型的成员变量进行拷贝时,创建一个新的对象来保存引用类型的成员变量。

深拷贝和浅拷贝的应用:

浅拷贝:

java 中的 clone 方法是一个浅拷贝,引用类型依然在传递引用

深拷贝:

实现深拷贝的方法有两种方法:

(1). 序列化该对象,然后反序列化回来,就能得到一个新的对象了。

(2). 继续利用 clone() 方法,对该对象的引用类型变量再实现一次 clone() 方法。

16. java 异常体系? RuntimeException Exception Error 的区别

java中Throwable是异常体系的超类,直接来源于Object类,Throwable可以分为Error类和Exception类,分别表示错误和异常

Error类是程序无法处理的错误,由jvm产生和抛出,遇到错误,jvm一般会选择终止线程。

Exception类分为RuntimeException(运行时异常)和非运行时异常。

17. spring boot starter 自加载是怎么实现的?在生命周期哪个阶段?

18. Spring 请求处理的过程?

19. b+ 树与 二叉树的区别, 有点?索引 为什么不用红黑树?

20. 多列索引的结构

21. restful 的作用?有哪些优点和缺点?

22. nginx 达到了上限了怎么办?怎么对 nginx 负载均衡? dns?

23. 对于单例,你知道哪些实现方法?

面经整理:

java 基础

集合

集合分为两大块: java.util 包下的非线程安全集合和 java.util.concurrent 下的线程安全集合。

List:

ArrayList 与 LinkedList 的实现和区别

Map:

HashMap: 了解其数据结构、hash 冲突如何解决(链表和红黑树)、扩容机制、扩容时避免 rehash 的优化

LinkedHashMap: 了解基本原理、哪两种有序、如何用它实现LRU

TreeMap:了解数据结构、了解其 key 对象为什么必须要实现 Compare 接口、如何用它实现一致性哈希。

Set

Set 基本上都是由对应的Map 实现。

常见问题:

HashMap 如何解决hash 冲突,为什么

HashMap 中的链表需要转成红黑树?

HashMap 什么时候会触发扩容?

jdk1.8 之前并发操作 HashMap 时为什么会由死循环的问题?

HashMap 扩容时每个 entry 需要再计算一次hash 吗?

HashMap 的数组长度为什么要保证是 2 的幂?

如何用 LinkedHashMap 实现 LRU?

如何用 TreeMap 实现一致性 hash?

线程安全的集合

Collections.synchronized

了解其实现原理

CopyOnWriteArrayList

了解写时复制机制、了解其适用场景、思考为什么没有 ConcurrentArrayList

ConcurrentHashMap

了解实现原理、扩容时做的优化、与 HashTable 对比。

BlockingQueue

了解LinkedBlockingQueue、ArrayBBlockingQueue、DelayQueue、SynchronousQueue

常见问题:

ConcurrentHashMap 时如何再保证并发安全的同时提高性能?

ConcurrentHashMap 时如何让多线程同时参与扩容?

LinkedBlockingQueue、DelayQueue 是如何实现的?

CopyOnWriteArrayList 是如何保证线程安全的?

并发

synchronized

了解偏向锁、轻量级锁、重量级锁、的概念以及升级机制、以及和ReentrantLock 的区别

CAS

了解AtomicInteger 实现原理、CAS 适用场景、如何实现乐观锁

AQS

了解 AQS 内部实现、及依靠AQS 的同步类比如 ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier 等的实现

ThreadLocal

了解 ThreadLocal 适用场景和内部实现

ThreadPoolExecutor

了解线程池的工作原理以及几个重要参数的设置

常见问题:

synchronized 与 ReentrantLock 的区别

乐观锁和悲观锁的区别?

如何实现一个乐观锁?

AQS 是如何唤醒下一个线程的?
ReentrantLock 如何实现公平和非公平锁是如何实现?

CountDownLatch 和 CyclicBarrier 的区别?

适用 ThreadLocal 时要注意什么? 比如说内存泄漏

说一说往线程池提交一个任务会发生什么?

线程池的几个参数如何设置?

线程池的非核心线程什么时候会被释放?

如何排查死锁?

引用

了解 Java 中的软引用、弱引用、虚引用的合适场景以及释放机制

常见问题:

软引用什么时候会被释放

弱引用什么时候会被释放

类加载

了解双亲委派机制

常见问题:

双亲委派机制的作用?

Tomcat 的 classloader 结构

如何自己实现一个 classloader 打破双亲委派

IO

了解 BIO 和 NIO 的区别、了解多路复用机制

常见问题:

同步阻塞、同步非阻塞、异步的区别?

select、poll、epoll 的区别?

reactor 线程模型是什么?

JVM

GC: 垃圾回收基本原理、集中常见的垃圾回收的特性、重点了解 CMS (或 G1) 以及一种重要的参数

内存区域:能说清 jvm 的内存划分

常见问题:

CMS GC 回收分为哪几个阶段?分别做了什么事情?

CMS 有哪些重要参数?

Concurrent Model Failure 和 ParNew promotion failed 什么情况下会发生?

CMS 的优缺点

有做过哪些 GC 调优?

为什么要划分成年轻代和老年代?

年轻代为什么被划分成 eden、survivor 区域?

年轻代为什么采用的是标记清除、标记整理算法

什么情况下使用堆外内存?要注意些什么?

堆外内存如何被回收?

jvm 内存区域划分是怎么样?

中间件、存储、以及其他框架

Spring bean 的生命周期、循环依赖问题、AOP 的实现、spring 事务传播

常见问题:

java 动态代理和 cglib 动态代理的区别

spring 中 bean 的生命周期是怎样的?

属性注入和构造器注入哪种会有循环依赖的问题?

Dubbo(或其他Rpc 框架)

了解一个常用 RPC 框架如 Dubbo 的是实现:服务发现、路由、异步调用、限流降级、失败重试

常见问题

Dubbo 如何做负载均衡?

Dubbo 如何做限流降级?

Dubbo 如何优雅的下线服务?

Dubbo 如何实现异步调用的?

RocketMq (或其他消息中间件)

了解一个常用消息中间件如 RocketMq 的实现:

如何保证高可用和高吞吐、消息顺序、重复消费、事务消息、延迟消息、死信队列

常见问题

RocketMq 如何保证高可用的?

RocketMq 如何保证高吞吐的?

RocketMq 的消息是有序的吗

RocketMq 的消息局部顺序是如何保证的?

RocketMq 事务消息的实现机制?

RocketMq 会有重复消费的问题吗?如何解决?

RocketMq 支持什么级别的延迟消息?如何实现的?

RocketMq 是推模型还是拉模型?

Consumer 的负载均衡是怎么样的?

Redis(或其他缓存系统)

redis 工作模型、redis 持久化、redis 过期淘汰机制、redis 分布式集群的常见形式、分布式锁、缓存击穿、缓存雪崩、缓存一致性问题

常见问题:

redis 性能为什么那么高?

单线程的redis 如何利用多核cpu 机器

redis 的缓存淘汰策略?

redis 如何持久化数据?

redis 有哪几种数据结构?

redis 集群有哪几种形式?

有海量 key 和 value 都比较小的数据,在 redis 中如何存储才更省内存?

如何保证 redis 和 DB 中的数据一致性?

如何解决缓存穿透和缓存雪崩?

如何用 redis 实现分布式锁?

Mysql

事务隔离级别、锁、索引的数据结构、聚簇索引和非聚簇索引、最左匹配原则、查询优化(explain 等命令)

常见问题:

Mysql(innondb 下同)有哪集中事务隔离级别?

不同事务隔离级别分别会加那些锁?

mysql 的行锁、表锁、间隙锁、意向锁分别是做什么的?

说说什么是最左匹配?

如何优化慢查询?

mysql 索引为什么用的是 b+ tree 而不是 b tree、 红黑树

分库分表如何选择分表键

分库分表的情况下,查询时一般时如何做排序的?

各大公司面试题(社招)相关推荐

  1. 大公司为什么很少招易语言程序员

    我没用过易语言,但是在大公司工作过,我来从业务的角度跟你讲一讲为什么"大公司 "不招易程序员.我在电信做过开发,像电信.银行.证券或者百度.阿里.QQ这些"大公司&quo ...

  2. 一些大公司面试题整理

    腾讯面试题 1.int a = 1; int result = a+++3<<2; 2.int a = 2; int result = (a++ > 2)?(++a):(a+=3); ...

  3. 区块链开发人员短缺?各大公司献上连环招

    点击上方"CSDN",选择"置顶公众号" 关键时刻,第一时间送达! [CSDN编者按]随着区块链的日益火爆,成百上千相关的创业公司像雨后春笋般在世界各地纷纷涌现 ...

  4. 大疆校招和社招 各岗位内推码--更新20220717

    内推码定期更新, 内推码通用于各个岗位,通用于校招和社招.薪资待遇还可以,双休,10105的工作节奏.内推码一码一人, 如果发现用过了,换一个即可.

  5. 面试题:大公司面试题 !=!=未看

    作者:Xoper.ducky 链接:https://www.nowcoder.com/discuss/3043?type=2&order=0&pos=5&page=2 来源:牛 ...

  6. 嵌入式面试常见问题(十一)—各大公司面试题

    8.科大讯飞笔试题 笔试时间:2020.09.12,19:00--21:00 岗位:嵌入式软件开发 题型:四道编程题,4*10=40分 1.定义一个n*m矩阵,找到两个不在同一行同一列的数字,使得成绩 ...

  7. 操作系统各大公司笔试题汇总

    1.在段页式存储管理中,其虚拟地址空间是() A.一维                               B.二维                                C.三维   ...

  8. C/C++ 大公司笔试题(一)

    1.局部变量能否和全局变量重名? 答:能,局部会屏蔽全局.要用全局变量,需要使用 ":: " 局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全 ...

  9. 给不起钱的大公司,算不上大公司

    这是昨天给一位同学聊职业规划中说的一句话. 这位同学在每次做选择前都会跟我聊下,感觉职业规划咨询跟卖菜差不多,那些觉得我的建议不错的同学,总是会在一年两年后继续找我,挺好的,喜欢跟大家一起沟通吹水并一 ...

最新文章

  1. python字符串endswith,Python字符串| 具有示例的endswith()方法
  2. dnslog 在 sql注入中的应用
  3. 谷歌历史版本_《地图中的历史》,本书中的地图,我们为你找到了高清版本
  4. Swift5.1 语言参考(十) 语法汇总
  5. 如何理解android的函数,通过Android源码理解回调函数
  6. 公众号和订阅号的区别
  7. 六类网线钳能压五类水晶头吗_六类网线可以用五类水晶头不?
  8. Excel无法打开文件xxx.xlsx,因为文件格式或文件扩展名无效。请确定文件未损坏,并且文件扩展名与文件的格式匹配...
  9. 干5年外包,突然失业了。。。
  10. ManualResetEvent用法详解
  11. JAVA SpringBlade 微服务开发平台框架,企业级的SaaS多租户微服务平台,基于Spring Boot 2.7
  12. 一款可以批量检测百度违规屏蔽关键词工具
  13. 计算机屏幕闪烁黑屏,显示器屏幕一闪一闪的黑屏怎么办_电脑屏幕黑屏一闪一闪如何解决...
  14. 如何将统一参考文献的格式?
  15. Linux 发展史,以及常用的一些命令行。
  16. python opencv读取图像像素值_python-opencv--图像像素通道读取及修改
  17. 10步教你来优化WordPress速度 为服务器和访客减压
  18. B2C的革命: QQ网购
  19. uniapp 设置 style 动态背景
  20. 【论文笔记_知识蒸馏_2022】Masked Generative Distillation

热门文章

  1. java使用 openoffice+swftools+flexpaper 在window下完成简单的文件预览
  2. ftp(文件传输协议)服务
  3. 【houdini 核心概念】Stamp
  4. 如何卸载有密码保护的symantec客户端
  5. 智慧工地管理平台系统赋能建筑工地绿色施工
  6. TPM密钥管理、使用
  7. Matlab绘制特殊图形------直方图
  8. Revit建模:楼板三维视图旋转模型如何使图案跟着旋转
  9. 【第十三讲】TMS320F28335开发板之DMA模块
  10. 自搭ngrok服务器