admin管理员组文章数量:1794759
Java综合面试题(更新ing)
一、项目 1.1.知道你的项目到底是做什么的,有哪些功能。 1.2.知道你做的模块在整个项目中所处的位置及作用,并能清晰的阐述模块间的调用关系。 1.3.知道你项目的整体架构和使用到的中间件,并对中间件的原理有一定的了解。 1.4.能流畅阐述的自己在项目中解决过的比较复杂的问题(重点)。 1.5.自我介绍 二、Java基础知识 2.1.栈和队列的区别 2.2.接口和抽象类的区别
首先,接口和抽象类的共同点是都不可以直接实例化,接口和抽象类的实现类只有实现了两者中的方法才能进行实例化。
接口是公开的,不能有私有的方法或变量,接口中的所有方法都没有方法体,通过关键字interface实现。Java1.8之后,接口中default的方法也可拥有方法体了。
抽象类是可以有私有方法或私有变量的,通过把类或者类中的方法声明为abstract来表示一个类是抽象类,被声明为抽象的方法不能包含方法体。
实现接口的关键字为implements,继承抽象类的关键字为extends。Java中是单继承、多实现模式。
接口强调特定功能的实现,而抽象类强调所属关系。接口被用于常用的功能,便于日后维护和添加删除,而抽象类更倾向于充当公共类的角色,不适用于日后重新对立面的代码修改。功能需要累积时用抽象类,不需要累积时用接口。
2.3.int和Integer的区别,以及自动拆箱/装箱的相关问题int是我们常说的整形数字,是基本类型,Java的原始数据类型之一。
Integer是int对应的包装类,是引用类型,它有一个int类型的字段存储数据,并且提供了基本操作,比如数学运算、int和字符串之间转换等。
在Java1. 5中,引入了自动装箱和自动拆箱功能(boxing/unboxing),Java可以根据上下文,自动进行int和Integer类型转换,极大地简化了相关编程。
在int到Integer的自动装箱操作中,会调用Integer的静态工厂方法 valueOf(int i) 方法,在调用它的时候会利用一个缓存机制,带来了明显的性能改进。Integer中有一个IntegerCache内部类,valueOf()方法先判断了变量i的值是否在这个缓存范围内,也就是[-128,127],在这个范围内直接返回创建好的对象,并不会重新创建对象。
JVM调优:IntegerCache缓存类的上下限,下限不可以配置,上限可以通过JVM参数进行配置。调高后可以避免创建更多的Integer对象节省内存。
valueOf()方法:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }IntegerCache内部类:
private static class IntegerCache { //下限-128 static final int low = -128; static final int high; static final Integer cache[]; static { // 默认上限127 int h = 127; // JVM参数中若设置了上限,则以JVM参数为上限 String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); ... }JVM参数:-XX:AutoBoxCacheMax=20000 //Intger缓存池上限设置为20000
2.4.常量池相关问题 2.5.==和equals的区别 2.6.重载和重写的区别 2.7.String、StringBuilder、StringBuffer的区别 三、集合框架 3.1.ArrayList、LinkedList、HashMap、LinkedHashMap、ConcurrentHashMap的底层实现原理。- HashMap
- LinkedHashMap
- ConcurrentHashMap
- ArrayList
- LinkedList
扩容:oldtable[ i ]为:A->B->null
newtable[ j ]为:X->Y->null
移动oldtable[ i ]到newTable[ j ]中 头插法先插A后插B,正常结果为:B->A->X->Y->null,变成了逆序。
死锁如何产生:假设线程1准备转移此时A.next为B,A->B 此时挂起
线程2也同时进行扩容,并完成了完整的扩容:B->A->X->Y->null,此时挂起
线程1开始执行,发现A->B,同时B->A。形成环形链表。
此时newTable[ j ]为:B<->A X->Y->null,(已经形成环,并且后面链表丢失)。如果get一个此下标下没有的key,get()方法中的while(null != e)永远跳不出循环,所以会形成死锁。
总结: 链表头插法的会颠倒原来一个散列桶里面链表的顺序。在并发的时候原来的顺序被另外一个线程a颠倒了,而被挂起线程b恢复后拿扩容前的节点和顺序继续完成第一次循环后,又遵循a线程扩容后的链表顺序重新排列链表中的顺序,最终形成了环。
1.8中采取了尾插法,利用了尾指针Tail,完成了尾部插入,不会造成逆序。扩容转移后前后链表顺序不变,保持之前节点的引用关系。所以也不会产生并发产生环形链表从而死锁的问题。
3.3.JDK1.7版本和1.8版本的HashMap的区别。-
JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)
-
JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法。因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。
-
扩容后数据存储位置的计算方式也不一样:JDK1.7扩容时重新计算每个元素下标位置,重新调用hash算法,将新数组长度-1与元素hash值进行与运算计算下标。JDK1.8通过判断Hash值的新增参与运算的位是0还是1。如果是0元素就留在 原始位置,如果是1就转移到 原始位置+扩容前旧容量 的新位置。这样可以不用重新计算下标位置,直接迅速计算出了扩容后的储存方式。
比如16-1的二进制是000...001111,扩容后32-1的二进制是000...011111,倒数第五位由原来的0变更为了1,新增参与运算的位就是倒数第五位。每个key的hash值都取倒数第五位的值进行判断,如果是0元素就留在原始位置,如果是1就转移到原始位置+扩容前旧容量的新位置。
HashMap是根据hash算法计算的存放下标,所以是无序的。可以依靠一些有序的集合来进行排序。
- 把每一个HashMap的键值对作为一个Entry 存入到ArrayList里. 然后对ArrayList进行排序.
- 在Java8中还可以对HashMap的EntrySet进行流式处理,直接对EntrySet调用sorted方法,定义排序逻辑,然后再遍历使用。
Map还提供了一些有顺序的Map框架,比如TreeMap,按照键值的自然规则进行排序,也可以在初始化的时候传入比较器,自定义排序规则。还有LinkedHashMap,根据放入元素的顺序依次放入map中。
为什么是2的幂次方?
- 当长度为2的n次方时,hash值对数组长度取模等于(n-1)& hash,而做与运算要比取模速度快,因为与运算是位运算,计算机直接在内存中操作,避免了进制的类型转换。
- 当长度为2的n次方时,n-1转换二进制末尾的位数全都是1,因为在计算下标时(n-1)& hash做的是与运算,与运算中只要是0结果就为0,而增加1的比重可以降低相同下标结果的产生,降低hash冲突
-
put
HashMap在put方法中,它使用hashCode()和equals()方法。当我们通过传递key-value对调用put方法的时候,HashMap使用Key hashCode()和哈希算法来找出存储key-value对的索引。如果索引处为空,则直接插入到对应的数组中,否则,判断是否是红黑树,若是,则红黑树插入,否则遍历链表,若长度不小于8,则将链表转为红黑树,转成功之后 再插入。
-
get
.1 通过 hash & (table.length - 1)获取该key对应的数据节点的hash槽; .2 判断首节点是否为空, 为空则直接返回空; .3 再判断首节点.key 是否和目标值相同, 相同则直接返回(首节点不用区分链表还是红黑树); .4 否则再判断首节点.next为空, 则直接返回空; .5 首节点是树形节点, 则进入红黑树数的取值流程, 并返回结果; .6 否则则进入链表的取值流程, 并返回结果;
-
resize
HashMap底层是数组的结构,默认初始长度是16,负载因子是0.75,当数组存储达到长度的3/4时会触发HashMap的扩容,当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组。
讲一下为什么扩容是2倍,再讲一下1.7和1.8扩容时计算下标方式的不同。
1.代理模式: AOP 就是基于动态代理的,如果有AOP设置的切面拦截的对象,Spring框架会调用jdk动态代理或者cglib创建对应的代理对象,然后存到ioc容器。调用对象的方法时实际上就是调用代理对象的invoke方法,实现对方法的增强或扩展。
适配器模式: 在Spring MVC中,DispatcherServlet 根据请求信调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler后,开始由HandlerAdapter 适配器处理。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标Handler进行适配。
为什么要在 Spring MVC 中使用适配器模式? Spring MVC 中的 Handler类型众多,比如最早期的mvc框架可以直接实现Controller接口来注册一个Handler方法,也有实现Servlet接口和添加Controller注解等不同方式。不同类型的 Handler通过不同的方法来对请求进行处理。
BeanFactory 简单工厂模式;singleton 单例模式
2. 一、构造器注入;二、设值注入(setter方式注入);三、Feild方式注入(注解方式注入)。
3.IoC Inversion of Control (控制反转/反转控制):是⼀个技术思想,不是⼀个技术实现 描述的事情:Java开发领域对象的创建,管理的问题 传统开发⽅式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象 IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对 象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可 我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不⽤考虑对象的创建、管理等⼀系列 事情) 为什么叫做控制反转? 控制:指的是对象创建(实例化、管理)的权利 反转:控制权交给外部环境了(spring框架、IoC容器)
aop: aop实际是对oop面向对象编程的扩展,面向对象编程在各个对象顺序执行时会产生横切逻辑代码即重复代码,比如事务控制、权限校验等。aop思想强调的是将横切逻辑代码和业务逻辑代码分离,将横切逻辑代码进行单独抽取去影响到需要执行的方法中,每一个被影响的方法为一个点,多个点构成面,这就是aop中所说的面向切面编程。
6.2.Spring事务隔离级别和传播机制一个完整的事务必须满足它的四大特性(ACID):
- 原子性:在一个事务中的操作都是一个逻辑的单元,在执行事务序列时,这些操作要么全部成功,要么全部失败。
- 一致性:对于事务操作,其始终能够保证在事务操作成功或者失败回滚时能够达到一种一致的状态。
- 隔离性:各个事务之间的执行是相互不影响。
- 持久性:事务的持久性指的是事务一旦执行成功,那么其所做的修改将永久的保存在数据库中,此时即使数据库崩溃,修改的数据也不会丢失。
隔离级别问题:
-
脏读:(读未提交的内容 导致)
表示一个事务能够读取另一个事务中还未提交的数据。比如,A事务增删改查数据,尚未提交,B事务可以读到A事务尚未提交的这些数据。
-
幻读:(主要针对insert、delete操作,解决需要锁住整表)
A事务根据查询条件或得N条数据,B事务删除N条中的M条,或增加M条符合查询条件的M条数据后提交,事务A再进行查询得到了N ±M条数据,像产生了幻觉。
-
不可重复读:(主要针对update操作,解决需要加上行锁)
A事务读到某条数据的值后,B事务对此条数据进行修改后提交,A事务再次读这条数据读到了不同的值。两次重复读的值不同,即不可重复读。
Spring事务隔离级别:(ISOLATION)就是以上问题的解决方案
- DEFAULT:默认隔离级别,每种数据库支持的事务隔离级别不一样,Oracle默认(读已提交) Mysql默认(可重复读)。
- READ_UNCOMMITTED:读未提交,即能够读取到没有被提交的数据,这是隔离性最低的一种隔离级别,无法解决脏读、不可重复读、幻读问题。
- READ_COMMITED:读已提交,即能够读到那些已经提交的数据,能够防止脏读,避免不了幻读和不可重复读。
- REPEATABLE_READ:可重复读,解决脏读和不可重复读的问题,但是其无法解决幻读。
- SERLALIZABLE:串行化,提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,故此性能很低
Spring传播级别:(PROPAGATION)方法调用方法如何传播,七种级别,常用两种
- REQUIRED:Spring默认,支持当前事务,如果当前没有事务,就新建一个事务。
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
获取sqlsession: MyBatis应用程序根据XML配置文件创建SqlSessionFactory,SqlSessionFactory在根据配置,配置来源于两个地方,一处是XML配置文件,一处是Java代码的注解,获取一个SqlSession。SqlSession包含了执行sql所需要的所有方法,可以通过SqlSession实例直接运行映射的sql语句,完成对数据的增删改查和事务提交等,用完之后关闭SqlSession。
生成代理对象: 根据MyBatis 的配置规范配置好后,通过SqlSession.getMapper(XXXMapper.class) 方法,MyBatis会根据相应的接口声明的方法信,通过动态代理机制生成一个Mapper 实例,我们使用Mapper 接口的某一个方法时,MyBatis 会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select(“statementId”,parameterObject);或者SqlSession.update(“statementId”,parameterObject); 等等来实现对数据库的操作。
6.4.Mybatis的缓存机制(一级缓存和二级缓存),Mybatis的mapper文件中#和$的区别一级缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HahsMap)是互相不影响的。
二级缓存
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
#为占位符,$为拼接符
#为参数占位符,相当于JDBC中PreparedStatement,使用提前占位的方式进行参数注入,变量替换后会自动加上单引号。$ 为字符串替换,相当于JDBC中的普通的Statemetn,变量是直接以字符串拼接的方式替换参数,所以$这种方式可能会导致sql注入的问题。工作中一般使用#号。
6.5.SpringMVC的请求的处理流程 6.5.什么是MVC模式?SpringMVC?首先要清楚三层架构:表现层、业务层、持久层。
-
表现层:即web层,包括了展示层(前端)和控制层(Controller)
负责接收客户端请求,向客户端响应结果。表现层的设计一般使用MVC模式。(即MVC是表现层的模式,和其它层无关)
-
业务层:service层。
业务逻辑处理,事务控制。表现层依赖业务层,业务层不依赖表现层。
-
持久层:dao层
负责数据持久化。
MVC设计模式
- Model:模型,包括业务模型和数据模型。数据模型用来封装数据,业务模型用于处理业务。
- View:视图,展示数据。
- Controller:控制器,应用程序处理用户交互的部分,一般用来处理程序逻辑。
MVC模式是为了解决代码耦合,提倡分层架构,每层只维护自己层的代码,降低代码之间的耦合,增加代码的可维护性。
SpringMVC和Struts2一样都是基于MVC设计模式的,为了解决表现层问题的web框架。本质可以认为是对servlet的封装,简化对servlet的开发。 与Spring的联系:并无绝对关联关系,一般配合使用。Spring致力解决对象之间耦合关系,ioc容器思想贯穿整个全局环境,SpringMVC主要作用在表现层,也可将对象交于Spring的ioc容器中进行管理。所以也称Spring和SpringMVC为父子容器。
6.8.SpringBoot自动装配原理Springboot的自动装配是因为在启动类上贴有 @SpringbootApplication 注解,这个注解表明该类为一个spring的配置类。 项目启动时,会将贴有该注解的类的所在包名下的所有组件扫描加载到spring容器。 @SpringBootApplication注解内部是@SpringbootBootConfiguration+ @EnableAutoConfiguration + @ComponentScan的三大注解的集成
- @ComponentScan:开启组件扫描
- @SpringbootBootConfiguration:作用等同于@Configuration注解,用于表明这是一个spring的配置类
- @EnableAutoConfiguration:通过@import注解内部导入AutoConfigurationImportSelector(自动配置导入选择器),该类中会加载jar包中META-INF/spring.factories文件中提前预设的配置对象,实现自动配置定义的功能
sql优化的重点在于sql语句对索引的正确使用,我们在开发中常见的错误是对数据库表进行全盘扫描,从而影响性能,耗费时间。 所以对sql优化的基础就是避免全盘扫描数据库表。
- 1.where条件中有or;
- 2.复合索引未用左列字段;
- 3.like以%开头;
- 4.需要类型转换;
- 5.where中索引列有运算;
- 6.where中索引列使用了函数;
- 7.如果mysql觉得全表扫描更快时(数据少);
-
InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
-
InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
-
InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
-
InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁
InnoDB为什么推荐使用自增ID作为主键?
答:自增ID可以保证每次插入时B+索引是从右边扩展的,可以避免B+树和频繁合并和分裂(对比使用UUID)。如果使用字符串主键和随机主键,会使得数据随机插入,效率比较差。
版权声明:本文标题:Java综合面试题(更新ing) 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686819525a106767.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论