互联网面试总结

Part1、BASE JAVA

1、hashcode 和 equals 之间的关系?

两者的概念:
equals() 的作用是用来判断两个对象是否相等,说白了就是用来判断内容是否相等的。

hashCode() 的作用是获取哈希码,也称为散列码,就像内存中的一个身份id一样;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。当然这里一般还需要hash函数等等进一步转化等操作最后作为key存入。

两者的关系:
我们以“类的用途”来将“hashCode() 和 equals()的关系”分2种情况来说明。

1、不会创建“类对应的散列表”
也就是说我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。

在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用。两个哪怕是相同的对象的hashCode也不一定相等。

2、会创建“类对应的散列表”
这里所说的“会创建类对应的散列表”是说:我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。

在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:

这个时候我们一般都需要重写hashcode和equals两个方法。equals判断两个对象内容是否相等,如果没有重写这个equals,只是通过hashcode是否相等来判断是否是同一个对象,如果产生了hash冲突就会发生错误。因为在同一个桶里可能会以链表的形式存放着多个hashcode相同的不同entry数组。同样的没有重写hashcode也是不行的,hashcode就像一个内存中的身份id,如果没有重写hashcode,因为我们在从Map集合里取值的时候是通过key和hashcode两个值来进行判断的,我们在put到集合和后get出来时可能取不出来对象了,因为它的hashcode值发生了改变。我们需要自己定义hashcode方法来保证它在put和get时的hashcode值不变。综上也就是说两个对象必须保证equals和hashcode的返回值都是true才可以。

2、介绍⼀下 Java 集合框架?

分为三块:List列表、Set集合、Map映射
List列表:

List列表在数据结构上可以被看做线性表,常用的有ArrayList和LinkList(不常用的有Vector(类似于ArrayList)),他们的底层存储结构有所不同,一个是数组,一个是链表;这两个是注重数据存储结构的区分和数据结构数据操作方法上的区分,也就是栈和队列;即Stack和Queue,Stack是一个继承了Vector的类,Queue是一个继承于Collection的接口(因为队列可以分很多种),LinkedList实现了Deque接口,Deque继承了Queue接口,常用的有ArrayBlockingQueue(基于数组),LinkedBlockingQueue(基于链表),PriorityBlockingQueue(实现优先级排序)等。

Map映射:

Map一种映射,用于储存关系型数据,会以键值对的形式保存key和value,并且key不允许重复。HashMap底层就是一个数组,然后根据根据存入的Key的HashCode来决定它存放的位置,其存入的其实就是一个个Entry单元,其Entry单元中有四个属性,分别为HashCode,Key,Vaule,和指向下一个Entry的指针,这样就形成了一个链表,当HashMap中的另一个拥有相同的HashCode值的不同的Key存入时,会将原来的Entry赋到新Entry的属性中,然后形成Entry链,查询的时候先比较HashCode,如果相同且Key值相同则直接取出,如果HashCode相同Key值不同则继续顺着链表寻找直到寻找到相同的Key值。
 
TreeMap与HashMap的不同:
表象上时TreeMap可以对Key进行排序,原因时TreeMap使用的是“红黑树”的二叉树结构储存Entry,也就是排序二叉树,左边恒放比此值小的数右边恒放比此值大的树,按照当前节点值与传入查询值的比较进行判断
决定其存放位置/查询其数值;
 
Set集合:
Set集合,相较于List列表都是单列的结构,但是List列表可以多次add相同的值,但是Set集合中存储的元素是不能重复,另外:在set集合中,hashset 集合比较两个对象是否相等,首先看
hashcode 方法是否相等,然后看 equals 方法是否相等。
 

3、HashMap 和 Hastable 底层实现有什么区别?Hashtable 和 ConcurrentHashTable 呢?

HashMap的概念:

首先我们知道数组有占用连续的存储空间,询址查询速度快,增删速度慢的特性,链表具有占用空间不连续,查询速度慢,增删快的特性,那么HashMap其实就是结合了两者的优点。HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。

HashMap的具体实现:

首先当我们去创建一个HashMap的时候,会先创建一个数组,这个数组的默认大小为16也就是阈值的默认大小,这个数组就是用来存放我们的Ehtry的。还会有一个加载因子,这个负载因子的默认大小为0.75,它决定了HashMap能够承载多大的哈希冲突以及HashMap的扩容能力,重点的还有一个Size表示当前所存取的元素的个数等等其他一些相关初始化参数。

HashMap存储键值对的过程:

  1. 调用key.hashcode()计算出键的hashcode值。
  2. 调用hashmap的hash()散列算法算出hash值,让我们最终存进去的位置更加的分散,然后最终其实调用了一个indexFor方法对这个hash值进一步进行处理,这里其实就是将这个hash值和hashMap数组长度-1的一个与操作,保证存入的位置一定是在我们的数组之内的。
  3. 根据hash值确定填充的数组位置。
  4. 如果hash值相同,则调用key.equals()方法,如果相同,则不存,如果不同,则进行尾插,成为一个Entry链表的过程。

ps:HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。

HashMap的取值过程

  1. 调用get(key)方法
  2. 调用key.hashcode()的方法得到hashcode值
  3. 调用散列算法hash()计算出hash值,依据hash值定位查询的数组位置
  4. 遍历链表,比较key.equals()是否为true,为ture时找到。

HashMap的扩容主要当我们的Size也就是当前存取的元素的个数大于阈值时,就会调用resize来进行扩容。进行2次幂的扩展(例:原数组的capacity为16,扩容后为32),此举的目的是不用重新计算每个元素的hash值,每个元素的在新数组的位置只有两种可能:要么在原位置,要么在原位置的基础上移动原capacity个长度。

ps:jdk8中 当链表长度大于8时,链表转换为红黑树,提高查找效率。

HashMap 和 Hastable 的区别

1.线程安全性不同
HashTable的方法是线程安全的,其方法大部分都相同只不过家了synchronize关键字保证其线程安全。HashMap不支持线程同步,所以效率上HashMap一般高于HashTable

2.key和value是否允许null值
Hashtable中,key和value都不能为null,而HashMap中可以允许key和value为null,且存储在数组索引为0处;
3.扩容
HashTable中,数组的默认大小是11,扩容的方式是 原数组长度*2+1
HashMap中,数组默认大小是16,扩容是按2倍的大小
4.hash值
HashTable中,直接使用key的hashcode,HashCode中,会对hashcode进行hash函数的进一步计算。

HashMap 在高并发下会出现链表环,从而导致程序出现死循环。高并发下避免 HashMap 出问题的方法有两种,一是使用 HashTable,二是使用 Collections.syncronizedMap。但是这样的效率很低因此就产生了ConcurrentHashMap

4、HashMap 和 TreeMap 什么区别?低层数据结构是什么?
5、Java 线程池⽤过吗?你经常⽤到哪些参数?底层如何实现的?
6、synchronize 和 Lock 什么区别? synchronize 什么情况下是对象锁?
什么时候是全局锁为什么?
7、ThreadLocal 是什么?底层⼯作原理是什么?能模拟写⼀个例⼦么?
8、volatile 的⼯作原理?
9、cas 知道吗?如何实现的?
10、请⽤⾄少四种写法写⼀个单例模式

Part2、JVM

1、请介绍⼀下 JVM 内存模型?⽤过什么垃圾回收器,简要说明其区别?
2、出现 GC 的问题,你是如何定位、解决问题的,说说解决思路和处理⽅法?
3、知道字节码吗?字节码都有哪些?
Integer x =5,int y =5,⽐较 x =y 都经过哪些步骤?
4、讲讲类加载机制?有哪些类加载器,这些类加载器⽤来加载哪些⽂件?
⼿写⼀下类加载的 Demo
5、知道 OSGI 吗? 它的实现原理是怎么样的?
6、请问你做过哪些 JVM 优化?使⽤什么⽅法达到什么效果?
7、String.class.getClassLoader().loadClass("java.lang.String") 和
Class.forName("java.lang.String")两种⽅法有什么区别?

Part3、Spring

1、Spring 的 AOP 底层如何实现的?IOC 呢?
2、CGLib 知道吗?它和 JDK 动态代理什么区别?
你能写⼀个 JDK 动态代理么?

Part4、数据库

1.使用mysql索引有哪些原则?索引什么数据结构? B+tree和B Tree什么区别?

索引使用原则

1.为值唯一的建立唯一索引,比如会为主键约束和唯一约束自动建立索引,可以快速定位到对应记录。 

2.为经常需要使用该字段进行排序、分组和联合操作的字段建立索引

3. 为常作为查询条件的字段建立索引

4. 限制索引的数目: 索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁 盘空间就越大。修改表时,对索引的重构和更新很麻烦。

5.尽量使用数据量少的索引: 如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型 的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。

6.尽量使用前缀来索引: 如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的 字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样 可以提高检索速度。

7.尽量选择区分度高的列作为索引: 区分度表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,一般要求索引的字段区分度最少保证在0.1以上。

8.索引列不能参与计算,保持列“干净”,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。

9.尽量的扩展索引,不要新建索引: 比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可

索引的数据结构

常见的有b tree b+tree 散列 通讯R-树

B+tree和B Tree什么区别

B树概念和特点:

B Tree其实是一个平衡树查找树,也叫做B-树,那么对于一个m阶b树有以下几个特点:

1.每个节点最多有m-1个关键字。

2.根节点最少可以只有1个关键字。

3.非根节点至少有m/2个关键字,向上取整。

4.每个节点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。

5. 所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。矮胖树

6.每个节点都存有索引和数据,也就是对应的key和value。

B树插入操作:

插入的时候,我们需要记住一个规则:判断当前结点key的个数是否小于等于m-1,如果满足,直接插入即可,如果不满足,将节点的中间的key将这个节点分为左右两部分,中间的节点放到父节点中即可。

B树删除操作:

这步操作最重要的就是要注意规则非根节点至少有m/2个关键字,向上取整。父节点和子节点之间进行调整,并且每个节点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。

B树查询操作:

因为每个节点都有key和value所以可以快速定位。

 

B+树概念和特点:

1.每个节点最多有m-1个关键字。

2.根节点最少可以只有1个关键字。

3.非根节点至少有m/2个关键字,向上取整。

4.它将节点分成了两个部分,内部节点和叶子节点。内部节点就是所有非叶子节点,内部节点存索引key不存数据而叶子索引只存数据。

5.内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。

6.所有的叶子节点都有指向下一个相邻叶子节点的指针,以一个链表的形式从小到大串起来。

7.父节点存放着所有孩子节点的第一个元素的索引,也就是最小的。

B+树插入操作基本和B树一样这里就不赘述了

B+树删除操作:

因为叶子节点是以一个链表的形式串起来的,我们知道链表在删除时可以快速定位到,不用通过父节点再定位到叶子节点,也不许不要父节点和子节点之间的交换移动了,只需要移动兄弟节点,再看父节点哪些需要删除和添加。

B+树插入操作和删除操作很像通过下面链表插入再调整内部节点。

 

两者的区别:

B树所有的节点都存索引key和记录value,每一个节点都有指向记录的指针。B+树只有叶子节点存放指向记录的指针,内部节点只存索引key,叶子节点之间以链表的形式链接在一起。

B树的优点:对于在内部节点的数据,可直接得到,不必根据叶子节点来定位。适用于单个精准查询。

B+树的有点:由于内部节点存放的是索引,可以定位到多个叶子节点,并且叶子节点以链表的形式连接。对于一些遍历,范围性的扫描就比较适用。

2.mysql有哪些储存引擎?都有什么区别?

mysql的存储引擎:MySQL中的数据用各种不同的技术方式存储在文件(或者内存)中。

1. MyISAM

这种引擎是mysql最早提供的。这种引擎又可以分为静态MyISAM、动态 MyISAM 和压缩MyISAM三种:

静态MyISAM:如果数据表中的各数据列的长度都是预先固定好的,服务器将自动选择这种表类型。因为数据表中每列中每条数据所占用的空间都是一样的,对于这种表存取和更新的效率非常高。当数据受损时,恢复工作也比较容易做。

动态MyISAM:如果数据表中出现varchar、xxxtext或xxxBLOB字段时,服务器将自动选择这种表类型。由于每条记录的长度不一,所以多次修改数据后,数据表中的数据就可能离散的存储在内存中,进而导致执行效率下降。同时,内存中也可能会出现很多碎片。因此,这种类型的表要经常用optimize table 命令或优化工具来进行碎片整理。

压缩MyISAM:以上说到的两种类型的表都可以用myisamchk工具压缩。压缩就是为了进一步减小了占用的存储,但是这种表压缩之后不能再被修改。另外,因为是压缩数据,所以在读表的时候要进行解压缩。

2. MyISAM Merge引擎

这种类型是MyISAM类型的一种变种。会将几个相同的MyISAM表合并为一个虚表。常应用于日志和数据仓库。

3. InnoDB:

InnoDB表类型可以看作是对MyISAM的进一步更新产品,它提供了事务、行级锁机制和外键约束的功能。

 

以下我对于常见的MyISAM和InnoDB做一下对比分析:

 

MyISAM引擎的特点:
1、不支持事务

2、表级锁定(更新是锁整个表):更新表的时候是锁整个表的,虽然实现成本很小,但是大大降低了其并发性能。

3、读写互相堵塞:也就是读表的时候不能写,写的时候不能读。这和我在并发编程里学到的读锁和写锁功能很像,但是读的时候还是可以读的。

4、只会缓存索引:MyISAM可以在缓存区缓存索引,大大提高访问性能,减少磁盘的I/O,但是不会缓存数据。

5、读取速度较快,占用资源相对少。

6、不支持外键约束,但支持全文索引。

7、MyISQM引擎是mysql_5.5.5之前的索引。

 

MyISAM引擎使用的生产业务场景:
1、不需要事务支持的业务(转账、充值、付款这种就不行)。

2、一般为读数据比较多的应用。

3、并发访问相对低的业务(纯读、纯写高并发也可以)。

4、数据修改相对较少的业务(阻塞问题)。

7、硬件资源比较差的机器可以用MyISAM。

小结:对数据库进行单一操作的都可以使用MyISAM引擎。

 

InnoDB引擎特点:

1、支持事务:支持事务的四个级别(ACID)。

2、行级锁定:也就是只会锁定一行了是通过索引实现,但是全表扫描仍然是表锁。

3、读写阻塞与事务隔离级别相关。

4、具有非常高效的缓存特性:能缓存索引,也能缓存数据。

7、支持分区,表空间,类似oracle数据库。

8、支持外键约束,不支持全文索引。5.5版本以前不支持全文索引,5.5版本之后支持。

9、和MyISAM相比对硬件的资源要求比较高。

 

2.3 InnoDB引擎适用的生产应用场景
1、需要事务支持的业务(具有较好的事务特性)。如:充值转账。

2、行级锁定对高并发环境有很好的适用能力,但需要确保查询是通过索引来来完成的。

3、数据读写及更新都较为频繁的场景,如微博等。

5、硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存使用率,尽可能的较少磁盘的I/O。

 

4. memory(heap):

这种类型的数据表只存在于内存中。它使用散列索引,所以数 据的存取速度非常快。因为是存在于内存中,所以这种类型常应用于临时表中。

5. archive:

这种类型只支持select 和 insert语句,而且不支持索引。常应用于日 志记录和聚合分析方面。

3.设计高并发系统数据库层面应该怎么设计?数据库锁有哪些类型?如何实现?

高并发设计 数据库层面随着并发量和访问量的增加,会经历一系列转型

1. WEB应用和数据库部署在同一台服务器上:用户量、数据量、并发访问量都比较 小

2. WEB应用和数据库部署在各自独立的服务器上:可以做到分别升级应用服务器和数据库服务器,一般是小规模网站的典型部署方式。

3. 数据库服务器采用集群方式部署:也就是物理层面上是一个磁盘阵列,有多个数据库实例以虚拟IP的方式向外部应用程序提供透明的数据库的链接服务。能承担的负载是比较大的基本上可以满足大多数常见WEB应用,但是还是不能满足大用户量、高负载、数据库读写访问非常频繁的应用。

4. 数据库采用主从部署方式:在一些面向大众用户的博客、论谈等系统中存在众多的数据库查询操作,并且在多数情况下都是读操作远大于写操作的。于是就有了主从部署。

主从复制: 说白了也就是对主数据库的一个拷贝,将一台数据库服务器中的数据复制到另外一台数据库服务器中。几乎所有的主流数据库都支持复制,对于Mysql来说明只需要开启主服务器和主服务器上对应的二进制文件,从服务器上进行简单的配置和授权。从机的复制都是要依据主机的这个二进制文件。主机进行了更新,那么从机就会自动备份。对于redis非关系型数据库同样可以主从复制,在redis中至少需要一主二从,两个从机,所以一般会拷贝两份redis.conf这个文件,主机和两个从机分别设置不同的端口号并开启服务,然后再设置他们之间的主从关系即可。

读写分离:为保证数据库数据的一致性,我们会将更新操作也就是写的操作只放在主机上完成,而从机就只能进行读操作。大多数情况下的数据库读操作比写操作更加密集,而且查询条件相对复杂,数据库的大部分性能消耗在查询操作上了。值得注意的是主从复制数据是异步完成的,这就导致主从数据库中的数据有一定的延迟。以博客为例,用户登录后发表了一篇文章,他需要马 上看到自己的文章,但是对于其它用户来讲可以允许延迟一段时间(1分钟/5分钟/30 分钟),不会造成什么问题。这时对于当前用户就需要读主数据库,对于其他访问量更大的外部用户就可以读从数据库。

5.分库:

主从部署数据库中,当写操作占了主数据库的CPU消耗的50% 以上的时候,也就是说相较于读的操作,cpu更多的是去执行写的操作,我们再增加从服务器的意义就不是很大了。这个时候我们需要采用数据库垂直分区技术。 最简单的垂直分区方式是将原来的数据库中独立的业务进行分拆,比如WEB站点的BLOG和论坛,是相对独立的,与其它的数据的关联性不是很强,这时可以将原来的的数据库拆分为一个 BLog库,一个论坛库,以及剩余的表所组成的库。这三个库再各自进行主从数据库 方式部署,这样整个数据库的压力就分担啦。其实说白了就是将一个大的数据库划分成多个小的数据库,这样去读和写的时候需要操作的区域会减小,速度就会提升。以前可能需要操作两万条数据的区域才可以现在只需要操作5000条的小范围空间就可以了。

6.分表:

数据库垂直分割:

数据库水平分割:数据库垂直分区这种扩展方式又无能为力了, 我们需要的是水平分区。 水平分区意味着我们可以将同一个数据库表中的记录通过特定的算法进行分离,分别保存在不同的数据库表中,从而可以部署在不同的数据库服务器上。很多的大规模的 站点基本上都是主从复制+垂直分区+水平分区这样的架构。对于那些频繁访问导致站点接近崩溃的热点数据,我们必须分区。 在对数据分区的时候,我们必须要存在一个分区索引字段,通过这个字段来达到准确定位的目的。

数据库锁

共享(S)锁:多个事务可封锁一个共享页;任何事务都不能修改该页; 通常是该页 被读取完毕,S锁立即被释放。

排它(X)锁:仅允许一个事务封锁此页;其他任何事务必须等到X锁被释放才能对该 页进行访问;X锁一直到事务结束才能被释放。

更新(U)锁:用来预定要对此页施加X锁,它允许其他事务读,但不允许再施加U锁 或X锁;当被读取的页将要被更新时,则升级为X锁;U锁一直到事务结束时才能被释放。其实他就是一个预先动作。

 

posted @ 2021-02-28 17:48  Yaoyaoo  阅读(126)  评论(0)    收藏  举报