【面经牛客网整理】度小满一面

作者:毕业马上失业的不合格小码农
链接:https://www.nowcoder.com/discuss/517424?type=0&order=7&pos=41&page=0&source_id=discuss_center_0&channel=1009
来源:牛客网

度小满一面

自我介绍,盘比赛,盘项目

答:

HashMap底层实现?扩容原理?

答:

  • Map:以key-value键值对存储。适合做查找工作。

  • Cloneable:Cloneable是标记型的接口,它们内部都没有方法和属性,实现
    Cloneable来表示该对象能被克隆,能使用Object.clone()方法。如果没有实现
    Cloneable的类对象调用clone()就会抛出CloneNotSupportedException。

  • Serializable:public interface Serializable类通过实现 java.io.Serializable
    接口以启用其序列化功能。

  • iterator:可以使用迭代器遍历

  • 底层数据结构:1.7:数组+链表 1.8数组+链表+红黑树)(解决发生哈希碰撞后,链表过长而导致索引效率慢的问题)

  • 重要参数:容量,加载因子,扩容阈值,桶的树化阈值(》8),
    桶的链表还原阈值(《6),最小树形化容量阈值(》64)

  • 底层源码:构造函数(4个:0 容 容,加 包含子map)

插入操作:tab是否为空,如果是resize创建————
根据键值key计算hash值,计算下标————
插入时需要判断是否存在hash冲突————
如果不存在直接插入
p.hash == hash &&((k=p.key) == key
————如果存在哈希冲突————1、判断当前位置的key和需要插入的key是否相/
2、判断当前节点类型是否是红黑树
————如果相同新值覆盖旧值
————如果是红黑树直接在书中插入;如果是链表使用尾插法插入,插入后判断链表
节点是否大于数阈值,则将链表转换为红黑树
————插入成功后,判断实际存在的键值对数量size》最大容量,如果大于则扩容

扩容:
扩容机制:插入键值对后,发现容量不足————开始扩容————
异常情况判断(是否需要初始化,若当前容量大于最大值则
不扩容)————根据新容量(2倍)新建数组————
保存旧数组————遍历旧数组的每个数据————
重新计算每个数据在新数组中的存储位置————
将旧数组上的每个数据逐个转移到新数组中————
新数组table引用到hashmap的table属性上————
重新设置扩容阈值————结束
插入后扩容;尾插;原位置或原位置+旧容量

hash值是怎么计算的?(尽量避免哈希冲突)
(1)计算哈希码key.hashcode(2)二次处理哈希码(扰动)减少哈希冲突(3)最终计算存储的数组的位置h&(length-1)

线程不安全的原因:1.7可能会产生循环链表,resize时,使得get发生死循环。1.8尾插没有上述问题但是没有实现同步锁
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); hash%length==hash&(length-1)

HashMap和concurenthashmap区别?

答:线程安全方面
JDK1.7 的 ConcurrentHashMap 底层采用分段的数组+链表 实现,JDK1.8 采用的数据结构HashMap1.8 的结构一样, 数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
实现线程安全的方式(重要): ① 在 JDK1.7 的时候,ConcurrentHashMap
(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一
把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使
用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

线程安全集合类?为什么线程安全?

答:Collection—Set—CopyOnWriteArraySet
—List—CopyOnWriteArrayList
—Vector
Map—ConcurrentMap—ConcurrentHashMap
—Hashtable
同步容器中的所有自带方法都是线程安全的,因为方法都使用synchronized关键字标注。但是,对这些集合类的复合操作无法保证其线程安全性。需要客户端通过主动加锁来保证。

CopyOnWriteList线程安全吗?底层实现?

答:CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略。
能够看到,每个CopyOnWriteArrayList对象都有一个array数组用来存放具体元素,而ReenTrantLock则用来保证只有一个线程对Array进行修改。ReenTrantLock本身是一个独占锁,同时只有一个线程能够获取。

很简单,那弱一致性是怎么回事呢,它是指返回迭代器后,其他线程对list的增删改对迭代器是不可见的。

什么叫线程安全?什么叫不安全?线程不安全原因?如何解决线程安全?

答:《Java并发编程》中说道,一个对象是否应该是线程安全的取决于它是否会被多个线程访问。线程安全这个性质,取决于程序中如何使用对象,而不是对象完成了什么。保证对象的线程安全性需要使用同步来协调对其状态的访问,若是做不到这一点,就会导致脏数据和其他不可预期的后果。
编写线程安全的代码,本质上就是管理对状态的访问,而且通常是共享的可变的状态。
通俗的讲,一个对象的状态就是他的数据,存储在状态变量中,比如静态域或实例域。
所谓共享,是指一个变量可以被多个线程访问;所谓可变,是指变量的值在其生命周期内可以改变。真正要做的是在不可控制的并发访问中保护数据。
无论何时,只要有多于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。java中首要的同步机制是synchronized关键字,它提供了独占锁。除此之外,术语同步还包括volatile变量,显示锁和原子变量的使用

当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方法码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。

做法:

  • 无状态对象是线程安全的;
  • 利用像AtomicLong这样已有的线程安全对象管理类的状态是非常实用的。
  • 用锁来保护状态(注意:1、通常简单性与性能之间是相互牵制的,实现一个同步策略时,不用过早地为了性能而牺牲简单性2、有些耗时的计算或操作,比如网络或控制台IO难以快速完成,执行这些操作期间不要占用锁)
  • 同步的弱形式:volatile变量。(只有当volatile变量能够简化实现和同步策略的验证时,才使用它们。当验证正确性必须推断可见性问题时应该避免使用)

synchronized和lock区别?

答:1、synchronized是关键字,是JVM层面的底层什么都帮我们做好了;而lock是一个接口,是JDK层面的有丰富的API;
2、synchronized会自动释放锁,而lock必须手动释放锁;
3、synchronized是不可中断的,lock可以中断也可以不中断;
4、通过lock可以知道线程有没有拿到锁,而synchronized不可以;
5、synchronized能锁住方法和代码块,而lock只能锁住代码块;
6、lock可以使用读锁提高多线程读效率;
7、synchronized是非公平锁,ReetrantLock可以控制是否是公平锁;
最大的区别其实在我们是否需要丰富的API,和实际场景;

jvm了解吧?讲一下堆内存区域

答: 内存模型:(主要目标:定义程序中各个变量的访问规则) 为了保证共享内存的正确性(可见性原子性有序性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。解决了CPU多级缓存,处理器优化,指令重排序等导致的内存访问问题。

 java内存模型:符合内存模型规范的,屏蔽了各种硬件和操作系统
   差异的访问差异的,保证了java程序在各种平台下对内存的访问都
   能保证效果一致的机制和规范。
 java内存模型规定了所有变量都存储在主内从中,每条线程还有
   自己的工作内存,工作内存中保存了主内存共享变量的副本拷贝,
   线程对变量的操作都必须在工作内存中进行,而不能直接读写主
   内存。不同线程之间也无法直接访问对方工作内存中的变量,线
   程间变量的传递需要自己的工作内存与主内存之间进行数据同步。
   JMM就作用于同步过程,它规定了何时进行数据同步以及如何进行
   数据同步。
 内存间交互操作:
   1、lock(锁定):作用于主内存的变量,标识为线程独占
   2、unlock(解锁):作用于主内存的变量,它把一个处于锁定
      状态的变量释放出来,释放后才可以被其他线程锁定
   3、read(读取):作用于主内存的变量,把一个变量的值
     从主内存传输到线程工作内存中。
   4、load(载入):作用于工作内存的变量,把read操作
     从主内存得到的变量值放入工作内存的变量副本
   5、use(使用):作用于工作内存的变量。把工作内存中
     一个变量的值传递给执行引擎。
   6、assign(赋值):作用于工作内存的变量,它把一个执行
     引擎接受到的值赋值给工作内存的变量,每当虚拟机遇到
	 一个给变量赋值的字节码指令时执行这个操作。
   7、store(存储):作用于主内存的变量,把工作内存中一个
     变量的值传递到主内存中,以便随后的write操作使用。
   8、write(写入):作用于主内存的变量,把store操作从
     工作内存得到的变量放入主内存中。

内存分配策略?

答: 1、对象优先在Eden分配
2、大对象直接进入老年代
3、长期存活的对象将进入老年代
4、动态对象年龄判定
5、空间分配担保

新生代什么时候会成为老年代?

答:1、长期存活的对象

2、大对象直接进入老年代

3、minor gc后,survivor仍然放不下

4、动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代

讲一下垃圾回收算法?

答: 1、将内存中不再被使用的对象进行回收;
2、按照新生代旧生代的方式来对对象进行回收;
3、主要回收区域:堆,方法区;
4、对象被标记为垃圾的方法:可达性分析法(判断对象的引用链是否可达)
;引用计数法(被引用 +1,完成引用-1.
5、垃圾回收算法:(1)标记清除算法(2)复制算法(3)标记整理算法(4)分代回收算法
6、Full GC的条件:jvm会首先检查老年代的连续空间是否大于新生代对象总大小或历次晋升的平均大小,如果条件成立首先进行 minor gc,否则full GC;

标记清除法如何标记需要清除的对象?

答:

数据库有哪些索引?

答:数据结构角度:B+tree索引
hash索引
fulltext索引
存储角度:聚簇索引
非聚簇索引
逻辑角度:主键索引
唯一索引
普通索引
联合索引
覆盖索引

主键索引和唯一索引区别?为什么要用主键索引?

答:一 主键和唯一索引都要求值唯一,但是它们还是有区别的:

①.主键是一种约束,唯一索引是一种索引;
②.一张表只能有一个主键,但可以创建多个唯一索引;
③.主键创建后一定包含一个唯一索引,唯一索引并一定是主键;
④.主键不能为null,唯一索引可以为null;
⑤.主键可以做为外键,唯一索引不行;

二 主键约束比唯一索引约束严格,当没有设定主键时,非空唯一索引自动称为主键。对于主键和唯一索引的一些区别主要如下:
1.主键不允许空值,唯一索引允许空值
2.主键只允许一个,唯一索引允许多个
3.主键产生唯一的聚集索引,唯一索引产生唯一的非聚集索引

注:聚集索引确定表中数据的物理顺序,所以是主键是唯一的(聚集就是整理数据的意思)

索引底层实现?

答:数据库的索引是使用B+树来实现的。
(为什么要用B+树,为什么不用红黑树和B树)
B+树是一种特殊的平衡多路树,是B树的优化改进版本,它把所有的数据都存放在叶节点上,中间节点
保存的是索引。这样一来相对于B树来说,减少了数据对中间节点的空间占用,使得中间节点可以存放
更多的指针,使得树变得更矮,深度更小,从而减少查询的磁盘IO次数,提高查询效率。另一个是由于叶节点之间有指针连接,所以可以进行范围查询,方便区间访问。而红黑树是二叉的,它的深度相对B+树来说更大,更大的深度意味着查找次数更多,更频繁的磁盘IO,所以红黑树更适合在内存中进行查找。

B+树更适合操作系统文件索引和数据索引的原因:

  1. B+树磁盘读写代价更低,B+树的内部节点没有指向关键字具体信息的指针。因此内部节点相对于
    B-树更小,如果把所有同一内部节点的关键字放入同一块磁盘当中。盘所能容纳的关键字数量也就
    更多。一次性读入内存中需要查找的关键字也就越多,相对的IO读写次数就有所降低。
  2. B+树查询效率更加稳定。由于非终结点并不是最终指向文件内容的节点,而只是叶子节点中关键
    字的索引。所以任何关键字的查找必须走一条从根节点到叶子节点的路径。所有的关键字查询路径
    长度都是相同的,导致了每一个数据查询的效率相当。

B+树如何实现数据查找的?

答:B Tree/B+ tree 主要运用于 系统文件查找。
原因在于: 大规模的数据存储中,树的结点的存储的元素数量有限的,如果采用二叉查找树的结构存储数据,导致树的深度过大,造成对磁盘I/O读写古语频繁,进而导致查询的效率低下,一种解决方案 使用 多叉树的结构,减小树的深度,扩大每一层树的结点,B树中每一结点X 不只有两个儿子结点,多个儿子 结点,与自平衡二叉查找树不同,B -树为系统最优化大块数据的读写操作,运用在数据库和文件系统中。

1.简单模拟,采用一课3叉树进行模拟,事实上每一颗B树不只有3个子女结点,发现 磁盘操作3次I/O操作和3次内存读取。

  • 根据根结点指针找到文件目录的根磁盘块1,将其中的信息导入内存。【磁盘 IO 操作 1次】
  • 此时内存中有两个文件名17、 35 和三个存储其他磁盘页面地址的数据。根据算法我们发现:17<29<35,因此我们找到指针 p2 。
  • 根据p2指针,我们定位到磁盘块3,并将其中的信息导入内存。【磁盘IO操作 2次】
  • 此时内存中有两个文件名26, 30 和三个存储其他磁盘页面地址的数据。根据算法我们发现:26<29<30,因此我们找到指针 p2 。
  • 根据p2指针,我们定位到磁盘块8,并将其中的信息导入内存。【磁盘IO操作 3次】
  • 此时内存中有两个文件名28, 29 。根据算法我们查找到文件名29,并定位了该文件内存的磁盘地址

B+树结构?和B树的区别?

答:

  • 所有的叶子节点包含了全部的关键字以及这些关键字指向文件的指针,并且:所有叶子节点中的关键字按大小顺序排列(范围查询)
  • 相邻的叶子节点顺序链接(相当于是构成了一个顺序链表)
  • 所有叶子节点在同一层

区别:1、B+树磁盘读写代价更低;
2、B+树查询效率更稳定
3、B+树更利于数据库的扫描

B+树时间复杂度?B树时间复杂度?

答:和树的高度相关

SQL选用索引的执行成本如何计算?

答:1、IO成本:即从磁盘把数据加载到内存的成本,默认情况下,读取数据页的IO成本是1,MySQL是以页的形式读取数据的,即当用到某个数据时,并不会只读取这个数据,而会把这个数据相邻的数据也一起读到内存中,这就是局部性原理,所以MySQL每次会读取一整页,一页的成本就是1.
2、CPU成本:将数据读入内存后,还要检测数据是否满足条件和排序等CPU操作的成本,先入它与行数有关,默认情况下,检测记录的成本是0.2.

redis底层如何扩容?

答: 1.hash算法:分多个实例存储:增加Redis服务器的数量,在客户端对存储的key进行hash运算,存入不同的Redis服务器中,读取时,也进行相同的hash运算, 找到对应的Redis服务器
2.集群
3.对Redis的访问分为写和读

索引创建原则

建立必要索引:
1、表的主键、外键必须有索引;
2、数据量超过300的表应该有索引
3、经常与其他表进行连接的表,在连接字段上应该建立索引;
4、经常出现在where子句中的字段,特别是大表的字段,应该建立索引;
5、索引应该建在选择性高的字段上;
6、索引应该建在小字段上,对应大的文本字段甚至超长字段,不要建立索引;
7、复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替;
(1)正确选择复合索引中的主列字段,一般是选择性较好的字段;
(2)复合索引的几个字段是否经常同时以AND方式出现在where子句中?单字段查询是否极少甚至没有?如果是,可以建立复合索引;否则考虑单字段索引;
(3)如果复合索引中包含的字段经常单独出现在where子句中,则分解为多个单字段索引;
(4)如果复合索引所包含的字段超过三个,那么仔细考虑其必要性,考虑减少复合的字段;
(5)如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;
8、频繁进行数据操作的表,不要建立太多的索引;
9、删除无用的索引

索引优化

1、避免对列的操作
例:amount/30<1000秒—》amount<1000*30秒
2、避免不必要的类型转换
3、增加查询的范围限制
4、尽量去掉“IN”“OR”
5、尽量去掉“<>”
6、去掉where子句中的is null 和 is not null
7、索引提高数据分布不均匀时查询效率
8、利用HINT强制指定索引
9、屏蔽无用索引
10、分解复杂查询
11、like子句尽量前端匹配
12、用case语句多重扫描

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值