首页 日常

总结一些高频出现的面经

hashmap结构

HashMap 本质上是一种散列表数据结构

0.png

为什么HashMap负载因子为0.75

HashMap负载因子:当当前容器容量大于容量 * 负载因子时,就会发生扩容。

HashMap默认的初始容量为16,负载因子为0.75,当容量为12时就会发生扩容。

原因:

HashMap一开始数据保存在数组中,当发生哈希碰撞时就会在数组后面加一个链表,当链表长度达到一定长度后就会转换成红黑树。

当负载因子为1时

负载因子为1就意味着只有当数组的16个值全部填充才会发生扩容,这个时候肯定会出现大量的哈希冲突,红黑树会变得很复杂,对查询很不利。

负载因子过大相当于用时间换取了空间。

当负载因子为0.5时

负载因子为0.5意味着数组有8个元素就开始扩容,填充的数据少了,哈希冲突也会减少,链表长度或红黑树高度会降低,查询时间就会提高,但空间利用率也会大大降低。

负载因子过小相当于用空间换取了时间。

负载因子为0.75

负载因子为0.75是时间和空间的权衡,而且这个解释在源码里也有

为什么HashMap容量是2的n次幂,扩容是2倍

0.png

1.png
第一张图是putVal方法里的(n - 1) & hash是在计算key的哈希值,第二张图是resize方法扩容时的,也是(n - 1) & hash把之前的元素重新计算哈希值存放到扩容后的数组中,n为数组的容量,&是位与操作,位与操作只有两位都是1才为1,hash是key经过哈希函数的哈希值,如果n是2的n次幂,那么n-1的二进制就是11111....1111这样能够保证充分的散列,计算出来的值能均匀分配在数组中。

volatile的作用和原理

作用:保证Java类的成员变量、类的静态成员变量的可见性

被volatile修饰:

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  • 禁止进行指令重排序。

不能保证原子性。

原理:

加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令。

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

1.它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2.它会强制将对缓存的修改操作立即写入主存;

3.如果是写操作,它会导致其他CPU中对应的缓存行无效。

使用volatile变量能够保证:

1.每次读取前必须先从主内存刷新最新的值。

2.每次写入后必须立即同步回主内存当中。

什么是原子性

某几个操作要么同时执行,要么同时不执行。

比如:转账

你的钱被扣除

收钱人收到钱

如果中间出错,没有原子性的话你的钱只会被扣除。

数据库隔离级别

数据库有四种隔离级别,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。

在事务并发过程中可能会出现脏读,不可重复读,幻读。

脏读、不可重复读、幻象读概念说明:  

  脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。

  不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。

  幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)

读未提交 (Read uncommitted)

读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。

事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。

分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。如无特殊情况,基本是不会使用这种隔离级别的。

怎么解决脏读?Read committed!读提交,能解决脏读问题。

读提交(Read Committed)

读提交,顾名思义,就是只能读到已经提交了的内容

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他买单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…

分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读

这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别。这种隔离级别能够有效的避免脏读,但除非在查询中显示的加锁,如:

select * from T where ID=2 lock in share mode;
select * from T where ID=2 for update;

不然,普通的查询是不会加锁的。

那为什么“读提交”同“读未提交”一样,都没有查询加锁,但是却能够避免脏读呢?

这就要说道另一个机制“快照(snapshot)”,而这种既能保证一致性又不加锁的读也被称为“快照读(Snapshot Read)”

假设没有“快照读”,那么当一个更新的事务没有提交时,另一个对更新数据进行查询的事务会因为无法查询而被阻塞(因为上了X锁,即写锁,所以不能得到S锁,即读锁),这种情况下,并发能力就相当的差。而“快照读”就可以完成高并发的查询,不过,“读提交”只能避免“脏读”,并不能避免“不可重复读”和“幻读”。

那怎么解决可能的不可重复读问题?Repeatable read !

可重复读(Repeated Read)

可重复读,顾名思义,就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他买单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。

分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

什么时候会出现幻读?

事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。

在这个级别下,普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效的避免“不可重复读”,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。

那怎么解决幻读问题?Serializable!

序列化 (Serializable)

这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。

Innodb如何解决幻读

在快照读读情况下,mysql通过mvcc来避免幻读。

在当前读读情况下,mysql通过next-key来避免幻读。

快照读, 读取专门的快照 (对于RC,快照(ReadView)会在每个语句中创建。对于RR,快照是在事务启动时创建的)

简单的select操作即可(不需要加锁,如: select ... lock in share mode, select ... for update)

针对的也是select操作

当前读, 读取最新版本的记录, 没有快照。 在InnoDB中,当前读取根本不会创建任何快照。

select ... lock in share mode

select ... for update

insert

update

delete

针对如下操作, 会让如下操作阻塞:    

insert
update
delete
  • 在RR(可重复读)级别下, 快照读是通过MVVC(多版本控制)和undo log来实现的,

当前读是通过手动加record lock(记录锁)和gap lock(间隙锁)来实现的。所以从上面的显示来看,如果需要实时显示数据,还是需要通过加锁来实现。这个时候会使用next-key技术来实现。

GC算法有哪些

可达性分析法

标记-清除

标记-复制

标记-整理

反射创建对象与正常创建对象的区别

new对象

java new对象属于静态编译,当代码生成EXE文件时会把所有模块都加载进去,当使用这个EXE时所有模块一起加载。如果程序较小的话没有什么影响,但是当程序稍大的话加载就会比较吃力。

new对象不能调用类的私有属性,方法。

反射创建对象

反射属于动态编译,编译过程不会把模块编译进去,只有使用时才会初始化这个模块,这样减轻了初始化的负担。

比如一个网站有听歌,看视频功能,只有当你使用这个功能的时候才会加载相关的模块。

动态编译相比较静态编译具有速度快,节省系统资源,利于扩展的优点

MySQL怎么实现自增主键

MyISAM 和 InnoDB 的区别

mysql5.5之前默认是MyISAM为默认存储引擎,5.5之后默认InnoDB为默认存储引擎。

行级锁

MyISAM只支持表级锁,InnoDB支持行级锁和表级锁,默认为行级锁

事务支持

MyISAM不支持事务,InnoDB支持事务,并具有提交和回滚事务的能力

外键支持

MyISAM不支持外键,InnoDB支持外键

一般不建议在数据库层面使用外键,应用层面可以解决。

数据异常崩溃后的安全恢复

MyISAM不支持,InnoDB支持

使用InnoDB的数据库崩溃后,数据库在重新启动时会使用redo log回到崩溃前的状态。

MySQL InnoDB使用redo log(重做日志)保证事务的持久性,使用undo log(回滚日志)保证事务的原子性,通过锁机制,MVCC保证事务的隔离性。

MVCC支持

MyISAM不支持,InnoDB支持。

MVCC可以看做行级锁的升级,可以有效减少加锁操作提升性能。

说一说mysql中的锁

InnoDB 存储引擎的锁的算法有三种:

1.Record lock:记录锁,单个行记录上的锁

2.Gap lock:间隙锁,锁定一个范围,不包括记录本身

3.Next-key lock:record+gap 临键锁,锁定一个范围,包含记录本身

HTTP和HTTPS区别

HTTP使用明文传输可被第三方截获不安全,HTTPS在HTTP基础上加了SSL/TSL协议,依靠证书验证服务器身份,并提供加密传输。

HTTPS比HTTP在应用层和传输层之间多了一个安全层为SSL/TSL协议。

HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费,Web服务器启用SSL需要获得一个服务器证书并将该证书与要使用SSL的服务器绑定。

HTTPS和HTTP使用的是完全不同的连接方式,端口也不同,HTTP是80,HTTPS是443。HTTP工作于应用层,HTTPS工作于传输层。

HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。

HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。

HTTPS工作流程:

  • 1、TCP 三次同步握手
  • 2、客户端验证服务器数字证书
  • 3、DH 算法协商对称加密算法的密钥、hash 算法的密钥
  • 4、SSL 安全加密隧道协商完成
  • 5、网页以加密的方式传输,用协商的对称加密算法和密钥加密,保证数据机密性;用协商的hash算法进行数据完整性保护,保证数据不被篡改。

进程,线程,协程

进程:并发执行的程序管理和分配资源的基本单位。

线程:进程的执行单位,一个进程可以拥有多个线程。

协程:比线程更轻量级的存在,一个线程也可以拥有多个协程。

进程和线程的区别:

地址空间:线程共享本进程的地址空间,每个进程是独立的地址空间。

资源:线程共享本进程的资源如:内存,CPU,IO等,不利于资源的管理、保护。进程之间资源是独立的,利于资源的管理、保护。

健壮性:多进程程序比多线程程序健壮,多进程程序一个进程崩溃后在保护模式下别的进程可以不受影响。多线程程序某个线程崩溃后整个进程都会崩溃。

执行过程:每个进程有独立的入口,执行开销大。线程不能独立运行,必须运行在应用程序之中,执行开销小。

可并发性:都可并发。

切换时进程开销大效率高,所以设计经常切换时,使用线程好于进程。

线程和协程的区别:

协程避免了无意义的调度,由此提升了性能,但是程序员必须自己承担调度的责任。协程也失去了线程多CPU使用的能力。

线程切换由系统控制,协程切换由自己控制,当前协程切换到其他协程由自己控制。

何时使用多进程,何时使用多线程:

对资源的管理保护要求高,不限制开销和效率时使用多进程。

要求效率高,频繁切换,对资源管理保护要求不高使用多线程。

Redis持久化方式

RDB

Redis每隔一段时间把数据生成一个快照存储到磁盘中,存储时会创建一个新的进程,主进程不进行IO操作。
如果需要大规模数据恢复且对数据完整性要求没那么高的话RGB比较适合。
不足:可能会丢失一部分数据,比如5分钟记录一次,如果这五分钟有数据写入,在存储到磁盘之前断电的话这五分钟之间的数据就没了。

RDB为默认持久化方式,常用。

AOF

记录Redis每条写操作到一个文件中,需要恢复的时候将文件中的每条指令执行一遍。
优点:数据完整性高。
缺点:指令多了占用空间大,数据恢复慢。

Redis常见数据结构

string

key-value类型

常用命令:set,get,strlen,exists,decr,incr,setex

应用场景:一般常勇在需要计数的场景,比如:用户访问次数,点赞数等等

list

list即为链表,易于数据的插入和取出,但是随机访问比较麻烦。Redis的list是个双项链表。

常用命令:rpush,loop,lpush,rloop,lrange,llen

应用场景:发布与订阅消息,消息队列,慢查询

hash

Redis中的hash类似于JDK8之前的HashMap,内部实现也差不多(数组+链表),hash是一个string类型的field和value映射表,特别适合存储对象,可以直接修改某个对象的某个字段的值。

常用命令:hset,hmset,hexists,hget,hgetall,hkeys,hvals等。

应用场景:存储对象。

set

Redis set类似Java中的HashSet,是一个无序集合,并且不能有重复,可以查找是否存在某个值,这是list不能做到的。而且可以进行两个集合的交集、并集、差集操作。

常用命令:sadd,spop,smembers,sismember,scard,sinterstore,sunion

应用场景:需要存放的数据不能重复以及需要获取数据交集并集等场景。

sorted set

和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。

常用命令:zadd,zcard,zscore,zrange,zrevrange,zrem 等。

应用场景:需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。

bitmap

bitmap存储的是连续的二进制数字,通过bitmap,只需要一个bit位来表示某个元素对应的值或者状态,key 就是对应元素本身,value对应0或1 。bitmap本身会极大的节省储存空间。

常用命令:setbit 、getbit 、bitcount、bitop

应用场景:适合需要保存状态信息(比如是否签到、是否登录)并需要对信息做进一步分析的场景。入:用户签到情况、活跃用户。

HashMap是否线程安全

不安全

原因

JDK1.7中由于多线程对HashMap进行扩容,A线程在扩容过程中被挂起,B线程完成了数据的迁移,等到A线程又得到时间片后重新执行之前的逻辑但是数据已经被更改,可能发生死循环,数据丢失。

JDK1.8中多线程进行put操作,调用putVal()方法,比如A线程在计算出key的哈希值后插入之前被挂起,B线程完成了与A的key有相同的哈希值的插入,等到A再得到时间片执行插入会把B的值覆盖掉。

改善

多线程使用ConcurrentHashMap

JDK1.7中的扩容问题在JDK1.8中已经被解决。

JDK1.8可以采用Collections.synchronizedMap(map),包装成同步map,就是把HashMap中的所有方法加上了synchronized。

垃圾回收过程

判断对象死亡

引用计数法

一个对象每被引用一次,引用计数就加一,当不被引用时减一。当引用计数为0是判断该对象死亡。

弊端:循环引用的情况下引用计数器不会为0

可达性分析法

从GC Roots开始出发,能够被探索到的加入到一个集合中,不能被探索到的判断对象已死。可以回收。

垃圾清理算法

垃圾回收算法有标记-清除,标记-复制,标记-整理。

新生代大多用标记-复制,老年代大多用标记-整理。(CMS使用标记-清除)

Hash索引和B+树索引的区别以及优缺点

Hash索引

哈希表是键值对集合,用哈希算法可以通过key快速得到value。

优点:

  • 访问速度快。

缺点:

  • 会产生哈希冲突,Hash采用数组+链表存储,产生哈希冲突时使用链地址法追加在后面。
  • 由于哈希冲突导致Hash不能够使用范围索引查询。
  • 无法对like 'xxx%'进行查询,因为Hash索引是根据key的哈希值定位bucket。
  • Hash索引中存放的是经过哈希处理的Hash值,Hash值的大小关系不一定和之前一样,所以无法对值进行排序。
  • Hash不支持多列联合索引。Hash索引计算对联合索引时会合并所有列计算Hash值,因此如果用到联合索引中的一个或几个时,联合索引会无法使用。
  • 因为存在哈希碰撞问题,在有大量重复键的情况下,Hash索引的效率极低。

B+树

B+树的查找比起Hash要慢一些,要从上到下查找一遍。B+树的叶子行成有序节点,方便范围查询。

优点:

  • 可以进行范围索引查询。
  • 有左前缀匹配,可以进行模糊查询。
  • B+树叶子结点行成有序序列,可以进行排序。
  • 性能稳定。

缺点:

  • 等值查询可能没有Hash索引快。

浏览器输入一个域名的流程

1.DNS解析

2.TCP连接

3.发送HTTP请求

4.服务器处理请求并返回HTTP报文

5.浏览器解析渲染页面

6.连接结束

DNS解析步骤:

1.浏览器查找本地hosts文件,如果找到该IP的映射关系就返回该映射。

2.如果hosts文件没有该IP的映射则去本地DNS解析器缓存查找,如果本地DNS解析器缓存有此IP的映射则返回该映射。

3.hosts文件和本地DNS解析器缓存都没有,首先查找TCP/IP参数中的首选DNS服务器,称为本地DNS服务器,如果该域名包含在本地DNS服务器的资源中中则返回解析结果给客户,此解析具有权威性。

4.如果要查询的资源在本地DNS服务器没有,但是该服务器已经缓存了此网址的映射关系,则调用这个IP映射,此解析不具有权威性。

5.如果本地DNS服务器本地区域文件和缓存解析都是失效,则会根据本地DNS服务器是否开启转发,如果未开启转发模式,则把请求发送给根DNS服务器,根DNS服务器判断该域名事谁来授权管理,并把负责的顶级域名服务器IP返回给本地DNS服务器,本地DNS服务器访问这个顶级DNS服务器(.com),如果顶级DNS服务器无法对该域名进行解析返回这个域名的下一级DNS服务器给本地DNS服务器,本地DNS服务器访问下一级DNS服务器(qq.com)。重复以上操作直到找到www.qq.com主机。

6.如果本地DNS服务器开启转发模式,则把请求转发给上一级DNS服务器进行解析,如果该服务器无法解析,或找根DNS服务器或继续转发给上一级DNS服务器。依次循环,无论开不开启转发最后都是把结果返回给本地DNS服务器,本地DNS服务器再返回给客户端。

GET POST区别

  • GET请求参数在URL中而且参数有长度限制,隐私性不高,POST请求参数在Request Body里面参数没有长度限制,隐私性高。
  • GET请求一般请求地址,POST一般给服务器发送数据。
  • GET请求发送一个TCP包,POST请求发送两个TCP包(火狐浏览器发送一个TCP包)。
  • GET请求刷新和回退没有影响,POST回退会重新提交数据。
  • GET请求可以被缓存,POST不会被缓存。
  • GET请求会存在浏览器记录中,POST不会存在浏览器记录中。
  • GET请求只能进行url编码,POST请求可以进行别的编码,比如传文件。

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=25rtvweals4k8




文章评论

目录