面试ing

一、Spring MVC

1、Spring MVC的工作原理和执行流程
  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用处理器映射器HandlerMapping。
  3. 处理器映射器根据请求url找到具体的处理器Handler,生成处理器对象及处理器拦截器(如果有则生成),一并返回给DispatcherServlet;
  4. DispatcherServlet根据处理器执行链的处理器,能够找到其对应的处理器适配器HandlerAdapter
  5. 执行处理器Handler(Controller,也叫页面控制器)。
  6. Handler执行完成返回ModelAndView
  7. HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
  11. DispatcherServlet响应用户。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iheRuGqP-1653889778284)(C:\Users\dxty\Desktop\Spring MVC执行图.jpg)]

2、Spring MVC怎么设定重定向和转发的
  • 转发:在返回值前面加"forward:“,譬如"forward:user.do?name=method4”

  • 重定向:在返回值前面加"redirect:“,譬如"redirect:http://www.baidu.com”

3、Spring MVC怎么和AJAX互相调用

通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 :

  1. 加入Jackson.jar
  2. 在配置文件中配置json的映射
  3. 在接受Ajax方法里面可以直接返回Object、List等,但方法前面要加上@ResponseBody注解。
4、Spring MVC和struts2的区别
  1. springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。
  2. springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。
  3. Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。
5、Spring MVC拦截器如何使用
  1. 定义拦截器,实现HandlerInterceptor接口。接口中提供三个方法。
    • preHandle :进入 Handler方法之前执行,用于身份认证、身份授权,比如身份认证,如果认证通过表示当前用户没有登陆,需要此方法拦截不再向下执行
    • postHandle:进入Handler方法之后,返回modelAndView之前执行,应用场景从modelAndView出发:将公用的模型数据(比如菜单导航)在这里传到视图,也可以在这里统一指定视图
    • afterCompletion:执行Handler完成执行此方法,应用场景:统一异常处理,统一日志处理
  2. 拦截器配置:
    • 针对HandlerMapping配置(不推荐):springmvc拦截器针对HandlerMapping进行拦截设置,如果在某个HandlerMapping中配置拦截,经过该 HandlerMapping映射成功的handler最终使用该 拦截器。(一般不推荐使用)
    • 类似全局的拦截器:springmvc配置类似全局的拦截器,springmvc框架将配置的类似全局的拦截器注入到每个HandlerMapping中
6、什么是Spring MVC,有什么优缺点

Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。

:(1)可以支持各种视图技术,而不仅仅局限于JSP;

​ (2)与Spring框架集成(如IoC容器、AOP等);

​ (3)清晰的角色分配:前端控制器(dispatcherServlet) ,请求到处理器映射(handlerMapping),处理器适 配器(HandlerAdapter),视图解析器(ViewResolver)。

​ (4) 支持各种请求资源的映射策略。

缺:(1)没有明确的定义

​ (2)不适合小型,中等规模的应用程序

​ (3)增加系统结构和实现的复杂性

​ (4)视图与控制器之间过于紧密的连接

​ (5)视图对模型数据的低效率访问

7、Spring MVC的主要组件有哪些
  1. 前端控制器 DispatcherServlet(不需要程序员开发)作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
  2. 处理器映射器HandlerMapping(不需要程序员开发)作用:根据请求的URL来查找Handler
  3. 处理器适配器HandlerAdapter 注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。
  4. 处理器Handler(需要程序员开发)
  5. 视图解析器 ViewResolver(不需要程序员开发)作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
  6. 视图View(需要程序员开发jsp)View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)

二、Spring

1、什么的Spring,有什么优缺点?

Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。

优:(1)spring属于低侵入式设计,代码的污染极低;

​ (2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;

​ (3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提 供更好的复用。

​ (4)spring对于主流的应用框架提供了集成支持。

2、Spring的IOC的理解

(1)IOC就是控制反转,指创建对象的控制权转移给Spring框架进行管理,并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部依赖。

(2)最直观的表达就是,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。

3、Spring的AOP的理解

OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。

AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。

AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

(1)AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

4、Spring通知Advice有哪些类型

(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。

(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)

(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pZ4mKSKC-1653889778286)(C:\Users\dxty\Desktop\通知.jpg)]

5、BeanFactory和ApplicationContext有什么区别

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。

(1)BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

继承MessageSource,因此支持国际化。
资源文件访问,如URL和文件(ResourceLoader)。
载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
提供在监听器中注册bean的事件。
(2)①BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

    ②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 

    ③ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢。

(3)BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

(4)BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

6、Spring框架中的Bean是线程安全的吗?如果线程不安全,那么如何处理?

(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。

(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。

  • 有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。
  • 无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
7、Spring如何解决循环依赖问题
8、Spring事务的实现方式和实现原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过binlog或者undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。

(1)Spring事务的种类:

spring支持编程式事务管理和声明式事务管理两种方式:

①编程式事务管理使用TransactionTemplate。

②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

(2)spring的事务传播机制:

spring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。

9、Spring都用到了哪些设计模式

(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象

(2)单例模式:Bean默认为单例模式

(3)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略

(4)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术

(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate

(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller

(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。

(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库

10、Spring五个特性

**事务传播机制:**事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。

**事务隔离机制:**事务的隔离级别定义一个事务可能受其他并发务活动活动影响的程度,可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。

**只读:**如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。

**事务超时:**为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。

**回滚规则:**在默认设置下,事务只在出现运行时异常(runtime exception)时回滚,而在出现受检查异常(checked exception)时不回滚(这一行为和EJB中的回滚行为是一致的)。

三、MyBatis

1、什么是MyBatis,有什么优缺点

(1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句,可以严格控制sql执行性能,灵活度高。

(2)作为一个半ORM框架,MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

称Mybatis是半自动ORM映射工具,是因为在查询关联对象或关联集合对象时,需要手动编 获取。

(3)通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。

(4)由于MyBatis专注于SQL本身,灵活度高,所以比较适合对性能的要求很高,或者需求变化较多的项目,如互联网项目。
(1)优点:

① 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。

② 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;

③ 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。

④ 能够与Spring很好的集成;

⑤ 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

(2)缺点:

① SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。

② SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

2、#{}和${}的区别是什么

${}是字符串替换,#{}是预处理;

Mybatis在处理 时 , 就 是 把 {}时,就是把 {}直接替换成变量的值。而Mybatis在处理#{}时,会对sql语句进行预处理,将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;

使用#{}可以有效的防止SQL注入,提高系统安全性。

3、通常一个mapper.xml文件,都会对应一个Dao接口,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗

Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。

Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。

Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。

当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个SQL标签,比如标签,都会被解析为一个MapperStatement对象。

4、MyBatis是如何进行分页的?分页插件的原理是什么?

Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

   分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
5、简述Mybatis的插件运行原理以及如何编写一个插件

Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,最后在配置文件中配置你编写的插件。

6、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

延迟加载的基本原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。

当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

7、概述Mybatis缓存机制

(1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

(2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;

(3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。

8、Mybatis动态sql有什么用?执行原理?有哪些动态sql

Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断 并动态拼接sql的功能。

Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind

9、如何获取自动生成的(主)键值

insert 方法总是返回一个int值 ,这个值代表的是插入的行数。

如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。

10、Mybatis实现多表有几种方式?具体怎么操作的

有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;

嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。

四、SpringBoot

1、Spring Boot、Spring MVC、Spring有什么区别

1、Spring最重要的特征是依赖注入。所有 SpringModules 不是依赖注入就是 IOC 控制反转。
当我们恰当的使用 DI 或者是 IOC 的时候,我们可以开发松耦合应用。松耦合应用的单元测试可以很容易的进行。

2、Spring MVC 提供了一种分离式的方法来开发 Web 应用。通过运用像 DispatcherServelet,MoudlAndView
和 ViewResolver 等一些简单的概念,开发 Web 应用将会变的非常简单

3、SpringBoot是Spring 和 SpringMVC 的问题在于需要配置大量的参数。Spring Boot 通过一个自动配置和启动的项来目解决这个问题。为了更快的构建产品就绪应用程序,Spring Boot 提供了一些非功能性特征。

2、什么是Spring Boot Starter

首先,这个 Starter 并非什么新的技术点,基本上还是基于 Spring 已有功能来实现的。首先它提供了一个自动化配置类,一般命名为 XXXAutoConfiguration ,在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是 Spring 中原本就有的),然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置,然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了。当然,开发者也可以自定义 Starter

3、Spring Boot自动配置原理是什么

SpringBoot在启动时,@EnableAutoConfiguration会自动扫描mate-inf下Spring.facturies(非哥特瑞)文件,这个文件是以key-value的形式存储信息,该注解会将这些信息加载到Spring容器中去。

4、Spring Boot的核心特征
5、Spring Boot项目原理
6、为什么不建议在实际的应用程序中使用Spring Data Rest

我们认为 Spring Data Rest 很适合快速原型制造!在大型应用程序中使用需要谨慎。

通过 Spring Data REST 你可以把你的数据实体作为 RESTful 服务直接发布。

当你设计 RESTful 服务器的时候,最佳实践表明,你的接口应该考虑到两件重要的事情:

  • 你的模型范围。

  • 你的客户。

    通过 With Spring Data REST,你不需要再考虑这两个方面,只需要作为 TEST 服务发布实体。

这就是为什么我们建议使用 Spring Data Rest 在快速原型构造上面,或者作为项目的初始解决方法。对于完整演变项目来说,这并不是一个好的注意。

7、SpringBoot主类的三大注解

@SpringBootConfiguration是来声明当前类是SpringBoot应用的配置类

@EnableAutoConfiguration开启自动配置

@ComponentScan(康们四干)配置组件扫描的指令

五、Redis

1、什么是缓存雪崩以及如何解决

缓存雪崩就是指缓存由于某些原因(比如宕机、cache(电脑高速缓存存储器)服务挂了或者不响应)整体crash掉了,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。

解决方法:

1,采用加锁计数,或者使用合理的队列数量来避免缓存失效时对数据库造成太大的压力。这种办法虽然能缓解数据库的压力,但是同时又降低了系统的吞吐量。

2,分析用户行为,尽量让失效时间点均匀分布。避免缓存雪崩的出现。

3,如果是因为某台缓存服务器宕机,可以考虑做主备,比如:redis主备,但是双缓存涉及到更新事务的问题,update可能读到脏数据,需要好好解决。

2、什么是缓存穿透以及如何解决

缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象放进缓存。如果数据库查询对象为空,则不放进缓存。

解决办法:

1,如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

2,根据缓存数据Key的规则。例如我们公司是做机顶盒的,缓存数据以Mac为Key,Mac是有规则,如果不符合规则就过滤掉,这样可以过滤一部分查询。在做缓存规划的时候,Key有一定规则的话,可以采取这种办法。这种办法只能缓解一部分的压力,过滤和系统无关的查询,但是无法根治。

3,采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的BitSet中,不存在的数据将会被拦截掉,从而避免了对底层存储系统的查询压力。

布隆过滤器原理:

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

布隆过滤器的原理是,当一个元素被加入集合时,通过K个Hash函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

3、缓存与数据库双写一致

一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况

串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。

问题场景描述解决
先写缓存,再写数据库,缓存写成功,数据库写失败缓存写成功,但写数据库失败或者响应延迟,则下次读取(并发读)缓存时,就出现脏读这个写缓存的方式,本身就是错误的,需要改为先写数据库,把旧缓存置为失效;读取数据的时候,如果缓存不存在,则读取数据库再写缓存
先写数据库,再写缓存,数据库写成功,缓存写失败写数据库成功,但写缓存失败,则下次读取(并发读)缓存时,则读不到数据缓存使用时,假如读缓存失败,先读数据库,再回写缓存的方式实现
需要缓存异步刷新指数据库操作和写缓存不在一个操作步骤中,比如在分布式场景下,无法做到同时写缓存或需要异步刷新(补救措施)时候确定哪些数据适合此类场景,根据经验值确定合理的数据不一致时间,用户数据刷新的时间间隔
4、常见数据类型、对应的实现原理和应用场景

1、String

String类型是Redis 最基本的数据类型,string 类型的值最大能存储512MB,不仅仅可以是字符串,还可以是数字。

应用场景:

缓存:将数据以字符串方式存储

计数器功能:比如视频播放次数,点赞次数。

共享session:数据共享的功能

具体存储结构:Redis构建了一个叫做简单动态字符串Simple Dynamic String,简称SDS

2、hash哈希表

Redis hash 是一个 string 类型的 field 和 value 的映射表,Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。每个 hash 可以存储 2^32 -1 键值对(40多亿)

字典。键值对集合,即编程语言中的Map类型。适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值。

应用常见:存储、读取、修改对象属性,也可以用Hash做表数据缓存

具体的存储实现:在 hash 的内部包含了两个hashtable,一般情况下只是用一个

负载因子是1,扩容比为1

当hashtable 中元素逐渐变少时,Redis会进行缩容来减少空间占用,并且缩容不会受bgsave的影响,缩容条件是元素个数少于数组长度的10%。

3、list列表

Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边),最多可以存储2^32 -1个元素

List 应用场景非常多,也是 Redis 最重要的数据结构之一,比如 Twitter 的关注列表,粉丝列表都可以用 List 结构来实现。

应用场景:1、最新消息排行榜。2、消息队列以完成多程序之间的消息交换

底层实现采用的是双向链表的快速列表,插入和删除的效率很高,可以用来当消息队列用。链表查询的效率又由ziplist压缩列表来进行弥补。quickList是一个ziplist组成的双向无环链表。每个节点使用ziplist来保存数据。本质上来说,quicklist里面保存着一个一个小的ziplist。quickList类似于B树结构可以快速定位

4、set集合

Redis的Set是string类型的无序集合。集合是通过值为空的哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。哈希表实现,元素不重复,最大的优势在于可以进行进行集合的交、并、差操作。

底层数据结构以intset或者hashtable来存储。intset可以理解为数组,hashtable就是普通的哈希表(key为set的值,value为null)。set的底层存储intset和hashtable是存在编码转换的,使用intset存储必须满足两个条件,否则使用hashtable,条件为:1、集合对象保存的所有元素都是整数值。2、集合对象保存的元素数量不超过512个

intset内部其实是一个数组,而且存储数据的时候是有序的,因为在查找数据的时候是通过二分查找来实现的

应用场景:1、利用交集求共同好友。2、利用唯一性,可以统计访问网站的所有独立IP。3、好友推荐时根据tag求交集,大于某个临界阈值就可以推荐

5、SortedSet有序集合

Redis zset和set一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数score却可以重复。

适用于:排行榜;带权重的消息队列。可以用于一个大型在线游戏的积分排行榜

内部是以ziplist或者skiplist来实现。

只有同时满足2个条件是使用的是ziplist,其他时候则是使用skiplist。

条件是1、有序集合保存的元素数量小于128个。2、有序集合保存的所有元素的长度小于64字节。

当ziplist作为存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表结点来保存,

第一个节点保存元素的成员,第二个元素保存元素的分值。

当使用skiplist作为存储结构时,使用skiplist按序保存元素分值,使用dict来保存元素和分值的对应关系

跳跃表是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为O(logN)。在查找、插入和删除这些字典操作上效率可比拟于平衡二叉树(如红黑树)

5、Redis的持久化机制是什么?各自的优缺点

Redis提供两种持久化机制RDB和AOF机制:

1)RDB(Redis DataBase)持久化方式: 是指用数据集快照的方式(半持久化模式)记录redis数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。

优点:

1.只有一个文件dump.rdb,方便持久化。

2.容灾性好,一个文件可以保存到安全的磁盘。

3.性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。(使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能)

4.相对于数据集大时,比AOF的启动效率更高。

缺点:

1.数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。

2 备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。

2)AOF(Append-only file)持久化方式: 是指所有的命令行记录以redis命令请求协议的格式(完全持久化存储)保存为aof文件。

优点:

1.数据安全,aof持久化可以配置appendfsync属性,有always,每进行一次命令操作就记录到aof文件中一次。

2.通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。

3.AOF机制的rewrite模式。(AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall))

缺点:

1.AOF文件比RDB文件大,且恢复速度慢。

2.数据集大的时候,比rdb启动效率低。

5**、Redis主从模式,哨兵模式**

**redis的主从模式:**我们使用单机的redis服务器时,如果访问量过高,单个redis很有可能扛不住压力,主从模式下,我们可以搭建多个redis服务器,主服务器可读可写,一般情况下从服务器只读,常见的配置方案就是一主多从,主服务器会将数据同步到从服务器中。实现了redis的读写分离(主从分离)

**哨兵模式:**基于主从模式的,在主从模式下,如果我们的某个redis服务器宕机了,还有请求从该服务器上请求数据,那么必然会影响功能;哨兵模式下,所有的哨兵会监听所有的redis服务器,如果我们的主服务器宕机,哨兵会选举中某个从服务器作为主服务器,其余的从服务器就会以被选举出来的从服务器作为主服务器;当主服务器恢复之后,恢复之后的主服务器自动变为从服务器;哨兵之间也会互相监听。

六、线程

1、synchronized和lock的区别

synchronized和lock都是可重入锁。synchronized底层使用指令码方式或者使用对象头特定的字段来控制锁,在同步块的和出口分别有monitorenter和monitorexit指令。当执行monitorenter指令时,线程试图获取锁也就是获取monitor(monitor对象存在于每个Java对象的对象头中,synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因)的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。lock的底层是CAS乐观锁,依赖Abstract Queued Synchronized类。

区别:

来源: lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;

异常是否释放锁: synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

是否响应中断 lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

是否知道获取锁 Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度

2、什么是死锁?怎么预防死锁

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

**死锁的四个必要条件:**j

**互斥条件:**线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。

**不剥夺条件:**线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己主动释放。

**请求和保持条件:**线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。

**循环等待条件:**存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。即存在一个处于等待状态的线程集合{Pl, P2, …, pn},其中Pi等待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有

避免死锁的方式:

加锁顺序(线程按照一定的顺序加锁)

加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

死锁检测

3、线程阻塞的代价

Java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态和核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内存态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值,变量等,以便内核态调用结束后切换会用户态继续工作。 1.如果高频进行线程的切换操作,将消耗许多CPU的处理时间 2.如果对那些需要同步的简单的代码块,获取锁挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然是糟糕的。 synchronized会导致争不到锁的线程进入阻塞状态,所以它是重量级锁。

4、CAS是什么

CAS: compare and swap(比较和交换), CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)

5、AQS是什么

原理: Abstract Quened Synchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制。这个类在java.util.concurrent.locks包。

**AQS的核心思想:**如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。

6、线程池的工作原理

(1)线程池判断核心线程池里的线程是否执行任务,如果不是,则创捷一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。

(2)线程池判断工作队列是否已满。如果工作队列没有满,则将新的任务存储在这个工作队列进行等待。如果工作队列满了则执行第三步。

(3)线程池判断线程池的线程是否处于工作线程。如果没有,则创建一个新的工作线程执行任务。如果已经满了则执行饱和策略处理这个任务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzRoYJT1-1653889778286)(C:\Users\dxty\Desktop\111.png)]

7、线程状态切换

1)、新建状态(New):新创建了一个线程对象。new Thread()

2)、就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start()法。只能针对处于新建状态的线程对象调用start方法,否则IllegalThreadStateException该状态的线程位于可执行线程池中,变得可执行,等r’r’m待获取CPU的使用权。

3)、执行状态(Running):就绪状态的线程获取了CPU。执行程序代码。注意在一个多处理器的机器上会有多个线程并行执行现在大部分桌面和服务器操作系统都采用时间片轮转法的抢占式调度策略,在选择下一个执行线程时系统会考虑线程的优先级调用yield方法可以让运行状态的线程转入就绪

4)、堵塞状态(Blocked):堵塞状态是线程由于某种原因放弃CPU使用权。临时停止执行。直到线程进入就绪状态,才有机会转到执行状态。线程切换是由底层平台控制的,具有一定的随机性

堵塞的情况分三种:

(一)、等待堵塞:执行的线程执行wait()方法,JVM会把该线程放入等待池中。

(二)、同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。则JVM会把该线程放入锁池中。

(三)、其它堵塞:执行的线程执行sleep()或join()方法,或者发出了I/O请求时。JVM会把该线程置为堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完成时。线程又一次转入就绪状态。

5)、死亡状态(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。

直接调用该线程的stop方法也可以结束线程,但是这个方法容易导致数据不一致的问题,通常不推荐使用

当主线程结束时,其它线程不受任何影响,并不会随之结束,一旦子线程启动后则拥有和主线程相同的地位,并不受主线程的影响

注意:不要试图对一个已经死亡的线程调用start方法使其重新启动,该线程将不可再次作为线程执行,否则异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lYUwdsEB-1653889778287)(C:\Users\dxty\Desktop\2021020520485972.png)]

8、线程和进程的区别

进程是表示资源分配的基本单位,又是调度运行的基本单位。线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。如果把进程理解为在逻辑 上操作系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。

9、常用线程池

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

七、集合

1、HashMap的实现原理

HashMap概述:HashMap是基于哈希表的Map接口的非同步实现。此实现提高所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是他不保证该顺序恒久不变。

HashMap的数据结构:在java编程语言中,最基本的结构是两种,一个是数组,一个是引用,所有的数据结构都可以用这两个基本结构来构造,HashMap也不例外,HashMap实际上是一个“链表散列”的数据结构,即数组与链表的结合体。当数组长度为2的n次幂的时候,不同的key算得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置的链表,这样查询的效率也就比较高了。

Java中的哈希表【散列表】

哈希表(Hash table,也叫散列表)是根据关键码值(key value)而直接进行访问的数据结构。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找速度。这个映射函数叫散列函数,存放记录的数组叫做散列表。

**优:**不论哈希表中有多少数据,查找、插入、删除只需要接近常量的时间即O(1)的时间级。

**缺:**他是基于数组的,数组在创建后难以扩容,某些哈希表别基本填满时,性能下降得非常严重。

当往HashMap中put元素时首先根据key的hashcode重新计算hash值,根据hash值找到这个元素在数组中的位置,如果该数组在该位置上已经存放其他元素,那么在这个位置上是元素将以链表【jdk1.8之后阈值大于8时会自动转换为红黑树】的形式存放,新加入的放在链尾【JDK1.8+采用尾插法,JDK1.8-采用头插法】,如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。

HashMap的线程安全问题:

HashMap并发修改操作时可能会出现环形锁,会出现死循环导致线程阻塞。在jdk1.8中,引入红黑树优化数组链表,同时改成了尾插,理论上是不会有环了。但是jdk1.8会出现size数据不准确,数据丢失问题。

需要注意jdk1.8中resize方法是在hashmap中的键值对大于阈值时或者初始化时,就调用resize方法进行扩容;每次扩展的时候,都是扩展2倍;扩展后Node对象的位置要么在原始位置,要么移动到原偏移量2倍的位置。

HashMap的结构1.7和1.8有哪些区别

Jdk1.7用的是头插法,而jdk1.8以及之后使用的都是尾插法。因为JDk1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题,但是在JDK1.8之后是因为加入红黑树使用尾插法,能够避免出现逆序且链表死循环问题。

扩容后数据存储的计算方式也不一样:在JDK1.7的时候是直接使用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是二的n次幂的原因,因为只有2的n次幂的情况时最后一位二进制数才一定是1,这样才能最大程度减少hash碰撞)(hash&length-1)

而在JSDK1.8的时候直接用了JDK1.7的时候计算的规律,也就是扩容前的原始位置+扩容的大小值=JDK1.8的计算方式,而不再是JDK1.7的那种异或的方法,但是这种方式就相当于只需要判断Hash值的新增参与运算的位是0还是1就直接迅速计算出扩容后的存储方式。在计算hash值的时候,JDK1.7用了9次扰动处理=4次位运算+5次异或,而JDk1.8只用了2次扰动处理=1次位运算+1次异或运算。

JDK1.7的时候使用的是数组+单链表的数据结构。在JDK1.8之后,使用的是数组+链表+红黑数的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转换成红黑树的数据结构把时间复杂度O(n)变成O(logN)提高了效率)

2、concurrentHashMap的实现原理

并发环境下为什么使用ConcurrentHashMap

\1. HashMap在高并发的环境下,执行put操作会导致HashMap的Entry链表形成环形数据结构,从而导致Entry的next节点始终不为空,因此产生死循环获取Entry

\2. HashTable虽然是线程安全的,但是效率低下,当一个线程访问HashTable的同步方法时,其他线程如果也访问HashTable的同步方法,那么会进入阻塞或者轮训状态。

\3. 在jdk1.6中ConcurrentHashMap使用锁分段技术提高并发访问效率。首先将数据分成一段一段地存储,然后给每一段数据配一个锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问。然而在jdk1.8中的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构

JDK1.6分析

ConcurrentHashMap采用分段锁的机制,实现并发的更新操作,底层由Segment数组和HashEntry数组组成。Segment继承ReentrantLock用来充当锁的角色,每个 Segment 对象守护每个散列映射表的若干个桶。HashEntry 用来封装映射表的键 / 值对;每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。

JDK1.8分析

改进一:取消segments字段,直接采用transient volatile HashEntry<K,V> table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。

改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。

3、HashMap扩容机制

扩容必须满足两个条件:

(1)、存放新值的时候当前已有元素必须大于阈值;

(2)、存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值计算出的数组索引位置已经存在值)

HashMap在添加值的时候,它默认能存储16个键值对,直到你使用这个HashMap时,它才会给HashMap分配16个键值对的存储空间,(负载因子为0.75,阈值为12),当16个键值对已经存储满了,我们在添加第17个键值对的时候才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。

HashMap也有可能存储更多的键值对,最多可以存储26个键值对,我们来算一下:存储的前11个值全部发生hash碰撞,存到数组的同一个位置中,(这时元素个数小于阈值12,不会扩容),之后存入15个值全部分散到数组剩下的15个位置中,(这时元素个数大于等于阈值,但是每次存入元素并没有发生hash碰撞,不会扩容),11+15=26,当我们存入第27个值得时候满足以上两个条件,HashMap才会发生扩容;

4、ArrayList和Vector的区别

同步性:Vector是线程安全的,用synchronized实现线程安全,而ArrayList是线程不安全的,如果只有一个线程会访问到集合,那最好使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们再去考虑和编写线程安全的代码。

数据容量增长:二者都有一个初始容量大小,采用线性连续存储空间,当存储的元素的个数超过了容量时,就需要增加二者的存储空间,Vector增长原来的一倍,ArrayList增加原来的0.5倍。

5、HashMap和concurrentHashMap的区别

HashMap不支持并发操作,没有同步方法,线程不安全。ConcurrentHashMap支持并发操作,通过继承 ReentrantLock(JDK1.7重入锁)/CAS和synchronized(JDK1.8内置锁)来进行加锁(分段锁),每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。

JDK1.8之前HashMap的结构为数组+链表,JDK1.8之后HashMap的结构为数组+链表+红黑树;JDK1.8之前ConcurrentHashMap的结构为segment数组+数组+链表,JDK1.8之后ConcurrentHashMap的结构为数组+链表+红黑树。

6、ArrayList和LinkedList的区别

1)是否保证线程安全:ArrayList和LinkedList都是不同步的,也就是不保证线程安全。

2)底层数据结构:ArrayList底层使用Object数组,LinkedList底层使用的是双向链表的数据结构(JDK1.6采用的是循环链表,JDK1.7取消了循环)。

3)插入和删除是否受元素位置的影响:ArrayList采用数组存储,所以插入和删除的时间复杂度受元素位置的影响;LinkedList采用链表存储,所以对于add()方法的插入,删除元素的时间复杂度不受元素位置的影响近似O(1),如果要做指定位置i插入和删除,时间复杂度近似O(i),因为需要移动到指定位置再插入。

4)是否支持快速随机访问:LinkedList不支持高效的随机元素访问,ArrayList支持。

5)内存空间占用:ArrayList的空间浪费主要体现在List列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多空间(因为要存放直接后继和直接前驱以及数据)

7、HashMap中的死锁原因分析

JDK7的transfer方法,使用头插法会造成死循环问题。HashMap是非线程安全的,高并发的情况下容易造成死锁。Hash表的初始大小有限,当put一定数量的元素就得进行扩容,原表会调用扩容方法(rehash)把原表copy到一个新的表中,单线程下rehash就是遍历原来链表,从新的链表头部挨个放入,放入的过程中会依次执行transfer方法,多线程的时候会出现同时put操作并都进入transfer环节,假设有线程A和B同时插入value,则AB同时进行扩容操作,就会出现连续指向造成环形链表,一旦取值进入此环形链表就会陷入死循环。

8、cocurrentHashMap和HashTable的区别

ConcurrentHashMap融合了hashtable和hashmap二者的优势。hashtable是做了同步的,即线程安全,hashmap未考虑同步。所以hashmap在单线程情况下效率较高。hashtable在的多线程情况下,同步操作能保证程序执行的正确性。但是hashtable是阻塞的,每次同步执行的时候都要锁住整个结构,ConcurrentHashMap正是为了解决这个问题而诞生的,

ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术(一个Array保存多个Object,使用这些对象的锁作为分离锁,get/put时随机使用任意一个)。它使用了多个锁来控制对hash表的不同部分进行的修改。在JDK 1.6中,有HashEntry结构存在,每次插入将新添加节点作为链的头节点(同HashMap实现),而且每次删除一个节点时,会将删除节点之前的所有节点拷贝一份组成一个新的链,而将当前节点的上一个节点的next指向当前节点的下一个节点,从而在删除以后有两条链存 在,因而可以保证即使在同一条链中,有一个线程在删除,而另一个线程在遍历,它们都能工作良好,因为遍历的线程能继续使用原有的链。

Java8中,采用volatile HashEntry保存数据,table元素作为锁;从table数组+单向链表加上了红黑树。红黑树是一种特别的二叉查找树,特性为:1.节点为红或者黑 2.根节点为黑 3.叶节点为黑 4.一节点为红,则叶节点为黑 5.一节点到其子孙节点所有路径上的黑节点数目相同。

9、HashSet和TreeSet的区别

HashSet

HashSet有以下特点:

不能保证元素的排列顺序,顺序有可能发生变化

不是同步的

集合元素可以是null,但只能放入一个null

当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。

简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相等

注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

2. TreeSet类

TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。

TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0

自然排序

自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。

Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。0

1、TreeSet 是二差树实现的,Treeset中的数据是自动排好序的,不允许放入null值。

2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束。

3、HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的 String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例 。

八、Java基础

1、 面向对象三大特征

封装:将功能或者信息的细节尽可能隐藏起来,让使用者只关注功能的使用,不关心实现细节

**继承:**关键字为extends,子类可以继承父类的成员变量和成员方法,java中类继承是单继承

**多态:**一个事物有多种表现形式

​ 多态的体现之一:父类型引用子类对象

​ 多态体现之二:重写,当父类的某个方法不能满足子类的需求,子类就需要重写该方法,子 类的域修饰符不能比父类的小.

​ 多态体现之三:重载:当父类某个方法的参数不能满足子类的需求,子类需要重载该方法

优:

高内聚低耦合 可维护性高

复用率,重用性高 扩展性强 执行效率高

2、 面向对象五大原则

1.单一职责原则SRP(Single Responsibility Principle):一个类,最好只做一件事,只有一个引起它变化。也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

2.开放封闭原则OCP(Open-Close Principle):对扩展开发,对修改封闭。

3.里氏替换原则LSP(the Liskov Substitution Principle LSP):子类必须能够替换其基类。里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。

4.依赖倒置原则DIP(the Dependency Inversion Principle DIP):依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

5.接口隔离原则ISP(the ISP):使用多个小的专门接口,而不是使用一个大的接口。

3、String、StringBuffer、StringBuilder的区别

String是不可变字符串,只要内容发生改变,就会产生一个新的字符串对象,原字符串内容不会发生改变;

StringBuffer和StringBuilder是可变字符串,修改字符串内容不会产生新的对象,他两都继承于同一个抽象类AbstractBuilder,但是StringBuffer实现了序列化接口,因此StringBuffer是线程安全的,StringBuilder是线程不安全的,单线程场合使用stringbuilder更合适,多线程场合使用StringBuffer更合适。StringBuilder的性能远大于stringBuffer。

4、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

**重写(Override)😗*从字面上看,重写就是 重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。

**重载(Overload)😗*在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。

**区别:**方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

5、Collection和Collections的区别

Collection 是一个 集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

Collections是一个集合框架的帮助类,里面包含一些对集合的排序,搜索以及序列化的操作。

6、java中==和equals的区别是什么

== 等于比较运算符,如果进行比较的两个操作数都是数值类型,即使他们的数据类型不相同,只要他们的值相等,也都将返回true.如果两个操作数都是引用类型,那么只有当两个引用变量的类型具有父子关系时才可以比较,而且这两个引用必须指向同一个对象,才会返回true.(在这里我们可以理解成==比较的是两个变量的内存地址)

equals()方法是Object类的方法,在Object类中的equals()方法体内实际上返回的就是使用==进行比较的结果.但是我们知道所有的类都继承Object,而且Object中的equals()方法没有使用final关键字修饰,那么当我们使用equal()方法进行比较的时候,我们需要关注的就是这个类有没有重写Object中的equals()方法.

区别:== 是java提供的等于比较运算符,用来比较两个变量指向的内存地址是否相同.而equals()是Object提供的一个方法.Object中equals()方法的默认实现就是返回两个对象==的比较结果.但是equals()可以被重写,所以我们在具体使用的时候需要关注equals()方法有没有被重写.

九、数据库

1、数据库是什么

数据库是指长期存储在计算机内有组织可共享的数据集合。数据库总的数据按照一定的数据模型组织、描述和存贮,具有较小的冗余度、较高的数据独立性和易扩展性,并在一定范围内可以被多个用户所共享。

2、什么是数据库事务?

事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。

3、MySQL事务四大特性(ACID

原子性(Atomic):事务开始后所有的操作,要么全部不做,要么全部做完,不可能滞留在中间环节(由DBMS的事务管理子系统来实现)

一致性(Consistent):事务开始前和结束后,数据库的完整性没有被破坏(由DBMS的完整性子系统执行测试任务)

隔离性(Isolated):同一时间只允许一个事务请求同一份数据,不同的事务之间彼此没有任何干扰.(由DBMS的并发控制子系统实现)

持久性(Durable):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚(由DBMS的恢复管理子系统实现)

DBMS **(Database Management System)**数据库管理系统

4、什么是脏读?幻读?不可重复读?

脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。

不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

5、什么是事务的隔离级别?MySQL的默认隔离级别是什么?

为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。

SQL 标准定义了四个隔离级别:

READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。

READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

这里需要注意的是:Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别

事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。

InnoDB 存储引擎在分布式事务的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。

6、数据库三大范式

**第一范式:**每个列都不可以再拆分。列不可分

**第二范式:**在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。完全依赖

**第三范式:**在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。允许传递依赖。

在设计数据库结构的时候,要尽量遵守三范式,如果不遵守,必须有足够的理由。比如性能。事实上我们经常会为了性能而妥协数据库的设计。

7、MySQL存储引擎MyISAM与InnoDB区别

存储引擎Storage engine:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。

常用的存储引擎有以下:

**Innodb引擎:**Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。

MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。

MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。

区别:

InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。

InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。

MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。

InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。

InnoDB引擎的四大特征:

插入缓存(insert buffer) 二次写(double)

自适应哈希索引(ahi) 预读(read ahead)

8、什么是索引?

索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。

索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的

9、索引有哪些优缺点?

索引的优点

可以大大加快数据的检索速度,这也是创建索引的最主要的原因。

通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

索引的缺点

时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;

空间方面:索引需要占物理空间。

主键保证了实体完整性,外键保证依赖完整性,用户自定义完整性。

10、如何选择数据库引擎?

如果没有特别需求默认选择innodb

MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站

Innodb:更新(删除)操作频率也高,或者保证数据完整性;并发量高,支持事务和外键。比如OA自动化办公系统。

11、五大索引类型

普通索引MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点

唯一索引索引列中的值必须是唯一的,但是允许为空值

主键索引是一种特殊的唯一索引,不允许有空值

组合索引在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合

全文索引只有在MyISAM引擎上才能使用,只能在CHAR、VARCHAR、TEXT类型字段上使用全文索引。全文索引就是在一堆文字中,通过其中的某个关键字等,就能找到该字段所属的记录行

空间索引是对空间数据类型的字段建立的索引,MySQL中的空间数据类型有四种,GEOMETRY、POINT、LINESTRING、POLYGON。在创建空间索引时,使用SPATIAL关键字。要求引擎为MyISAM,创建空间索引的列,必须将其声明为NOT NULL。

12、数据库优化

1、数据库建表的时候遵循数据库三大范式

2、

3、合适的数据冗余

4、慢查查询 索引失效

十、消息队列

1、 什么是消息队列,应用场景

消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。

2、消息队列的模式

**点对点模式:**生产者发送一条消息到queue(队列),一个queue可以有很多消费者,但是一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有 一个可用的消费者,所以Queue实现了一个可靠的负载均衡。

**发布订阅模式:**发布者发送到topic(主题)的消息,只有订阅了topic的订阅者才会收到消息。topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到这个消息的拷贝。

十一、JVM

1、GC是什么?为什么要有GC?

GC是垃圾收集的意思 ,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回 收会导致程序或系统的不稳定甚至崩溃, Java 提供的GC功能可以自动监测对象是否超过 作用域从而达到自动回收内存的目的 ,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理, 因为垃圾收集器会自动进行管理。要请求垃圾收集 ,可 以调用下面的方法之一 :System.gc()或Runtime.getRuntime().gc(),但JVM可以屏蔽掉显 示的垃圾回收调用 。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东。移动智能终端用户通常觉得 iOS 的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在于 Android 系统中垃圾回收的不可预知性。

2、JVM垃圾回收机制

**标记清除算法:**将已死亡的对象标记后统一清除,之后分配内存时将空闲列表记录的空闲内存分配,缺点是内存不连续,遇到较大对象不好分配内存

**标记整理算法:**和标记清除算法很类似,在标记死亡对象后加了一步,将存活对象都向一端移动(一般是头部),之后清理掉所有界外区域。

**复制算法:**将内存分为两等份,只在一遍分配空间,分配满了之后,将存活的对象复制到另一边后清理这边全部空间,之后在另一边分配空间,循环往复。缺点是:1、内存缩水;2、对象存活率高的情况下效率低。

**分代收集算法:**这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。 就是根据不同代的特点,在前面三种算法中选取合适的算法进行收集。

JVM垃圾回收器有以下七种:

新生代收集器:Serial、ParNew、Parallel Scavenge

老年代收集器:Serial Old、CMS、Parallel Old

堆内存垃圾收集器:G1

3、java的类加载器都有哪些,有什么区别?

1.启动类加载器 Bootsrap ClassLoader:它是最顶层的类加载器,是由C++编写而成, 已经内嵌到JVM中了。在JVM启动时会初始化该ClassLoader,它主要用来读取Java的核心类库JRE/lib/rt.jar中所有的class文件,如果需要将自己写的类加载器加载请求委派给引导类加载器,那直接使用null代替即可。

2.扩展类加载器 Extension ClassLoader:负责加载\lib\ext目中的jar包。

3.应用程序类加载器 Application ClassLoader:是类加载 ClassLoader.getSystemClassLoader()方法的返回值,因此称为系统类加载器,负责加载用户路径上指定的类库。一般情况下是默认的类加载器。

4.自定义类加载器 Custom ClassLoader:负责加载用户自定义的jar包

4、Java代码的执行流程

Xxx.java经过java编译器(java.exe)的编译,生成字节码文件.class,.class文件运行在jvm上;

Java编译器输入的指令流基本上是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集结构.

5、Java类加载过程

1.加载

类加载过程的一个阶段,ClassLoader通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个class对象。

2.验证

目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身的安全,主要包括四种验证:文件格式的验证,元数据的验证,字节码验证,符号引用验证。

3.准备

为类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值,(如static int i = 5 这里只是将 i 赋值为0,在初始化的阶段再把 i 赋值为5),这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。

4.解析

这里主要的任务是把常量池中的符号引用替换成直接引用

5.初始化

初始化阶段就是执行类构造器方法的过程

这里是类记载的最后阶段,如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量。(前面已经对static 初始化了默认值,这里我们对它进行赋值,成员变量也将被初始化)

6、堆和栈的区别

**物理地址:**堆的物理地址分配对象是不连续的。因此性能较差。在GC的时候也要考虑到不连续的分配,所以有各种算法(如新生代使用复制算法,老年代使用标记-压缩)。栈使用的是数据结构的,先进后出的原则,物理地址分配是连续的,所以性能强。

**内存区别:**堆是不连续的,所以分配的内存是在运行期确认的,因此大小不固定,一般堆的大小远远大于栈。栈是连续的,所以分配的内存大小要在编译期就确认,因此大小是固定的。

**存放的内容:**堆存放的是对象的实例和数组。因此该区更关注的是数据的存储。栈存放的是局部变量,操作数栈,返回结果。该地区更关注的是程序方法的执行。

**注:**静态变量放在方法区,静态的对象还是放在堆。

**程序的可见度:**堆对于整个应用程序都是共享可见的。栈只对线程是可见的,所以是线程私有。生命周期和线程相同。

7、Java/JVM内存分区

一、方法区

JVM方法区是用于保存已经被虚拟机加载的类元信息(包括类的版本、字段、方法、接口和父类等信息)、运行时常量信息(static、final定义的常量)、字符串常量信息(String a=“dfc”)。

二、虚拟机栈

栈这部分区域主要是用于线程运行方法的区域,此区域属于线程私有的空间,每一个线程创建后都会申请一个自己单独的栈空间,每一个方法的调用都会对应着一个栈帧。

栈帧里存储着方法的局部变量表(保存着变量的数据)、操作数栈(进行运算时存放数据的空间)、动态连接(指向常量池的引用)和方法返回地址(当前方法返回后的数据存放的地方)信息。

每调用一个方法都会生成一个新的栈帧,调用方法的过程就是一个压栈和出栈的过程,遵循先进后出的原则

三、本地方法栈

由于java需要与一些底层系统如操作系统或某些硬件交换信息时的情况,这个时候就需要通过调用native本地方法来实现,本地方法栈和虚拟机栈功差不多,区别在于本地方法栈是虚拟机调用native方法时使用的。

四、程序计数器

程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器,程序计数器记录着某个线程当前执行指令的位置,此区域属于线程隔离区。

因为CPU是根据时间片的方式分配资源的,它在某一个线程上进行调度的时间是一个或者多个时间片,所以当CPU从A线程切换到B线程执行时,就需要记录A线程当前指令所在的位置,方便CPU再次切回A线程时可以从上次的位置开始继续执行指令, 而程序计数器就是用来记录线程指令历史位置的区域。

五、堆

堆内存主要是用来存放创建的对象数据,此区域属于线程共享区对于开发人员来说这块区域是我们关注的比较多,因为很多优化都是针对这块区域来进行的,为了能更清楚的描述堆里的数据和分区信息,所以会结合垃圾回收的一些机制来描述这块内存区域

8、双亲委派机制

1.如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
2.如果父类的加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终会到达顶层的启动类加载器。(从这里就可以看出来,类加载请求都会先到达启动类加载器)
3.如果父类加载器可以完成类加载任务,就成功返回,倘若无法完成此加载任务,则委派给它的子加载器去加载。
如图所示,如果有个类加载请求来了,会一直向上委托,直到引导类加载器;然后引导类加载器尝试加载,如果它不能加载,则会给他的子加载器扩展类加载器加载;如果扩展类加载器还是不能加载;则再到下一级系统类加载器。

十二、Shiro

1、什么是Shiro

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

2、Shiro三大核心组件

Subject:当前用户的操作(所有的Subject实例都被绑定到一个SecurityManager上)

SecurityManager:用于管理所有的Subject(Shiro架构的心脏,用来协调内部各安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务)

Realms:用于进行权限信息的验证(本质上是一个特定的DAO,必须指定至少一个Realm用来进行身份验证和授权 )

3、什么是粗颗粒和细颗粒权限?

对资源类型的管理称为粗颗粒度权限控制,即只控制到菜单、按钮、方法。粗粒度的例子比如:用户具有用户管理的权限,具有导出订单明细的权限。

对资源实例的控制称为细颗粒度权限管理,即控制到数据级别的权限,比如:用户只允许修改本部门的员工信息,用户只允许导出自己创建的订单明细。

4、Shrio运行原理

Application Code:应用程序代码,就是我们自己的编码,如果在程序中需要进行权限控制,需要调用Subject的API。

Subject主体:代表的当前用户。所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager,可以将Subject当成一个门面,而真正的执行者是Securitymanager。

SecurityManager:安全管理器,所有与安全有关的操作都会与SecurityManager交互,并且它管理所有的Subject。

Realm:Shiro是从Realm来获取安全数据(用户、角色、权限)。就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以吧Realm看成DataSource,即安全数据源。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pjpsLSle-1653889778288)(file:///C:/Users/dxty/AppData/Local/Temp/msohtmlclip1/01/clip_image002.png)]

5、身份认证和授权过程:

1.身份认证

a.通过subject对象login方法提交token,token中有用户名和密码信息

b.回调自定义的realm对象中的doGetAuthenticationInfo方法

c.在doGetAuthenticationInfo我们通过该方法的参数 获取到刚才提交的用户名和密码

d.根据获取到的用户名和密码从数据库查找对应的用户

e.如果能在数据库中找到对应的用户,验证通过,将用户信息保存到AuthenticationInfo对象中,将AuthenticationInfo对象作为方法的返回值即可;如果没有查找到对应用户,那么抛出异常

f.在调用login方法的地方捕获异常,根据异常信息,获取登录失败的原因,如果没有异常,说明登录成功

2.授权过程

a.身份认证通过之后,我们调用了hasRole、isPermitted等和角色、权限有关的方法

b.每调用一次a中的方法,就会回调一次realm对象中和授权相关的方法 doGetAuthorizationInfo

c.在授权的方法中,我们通过该方法的参数,可以获取到登录用户的账号,根据账号

从数据库中查询到该账号相关的角色以及权限,然后存储到AuthorizationInfo对象

d.每次需要角色或者权限相关的操作,我们就可以通过AuthorizationInfo获取到对应信息

进行访问控制。

数据库中如何建立 角色、权限表

一般情况下需要5张表

user --> 用户表

role --> 角色表

permission --> 权限表

user_role --> 用户-角色关联表

role_permission --> 角色-权限关联表

十三、Nginx

**1.**代理

**正向代理:**客户端将请求发送到代理服务器,客户端很明确我要请求的资源的路径,代理服务器将收到的请求发送给业务服务器(例如Apache(Tomcat))。特点业务服务器只知道该请求是来自于某一个代理服务器,不明确发送请求的客户端是哪一个.隐藏了客户端.

**反向代理:**客户端将请求发送到代理服务器,代理服务器根据制定好的规则,将该请求交给合适的业务服务器来处理.业务服务器是知道该请求是来自于哪一个客户端的,但是客户端不清处是哪一个业务服务器处理的请求.隐藏了业务服务器.一般用在分布式、集群

**2.**负载均衡

1.反向代理服务器接收到的请求量,称之为负载量

2.反向代理服务器根据制定好的规则分发请求到对应的业务服务器,均衡规则

负载均衡的策略(算法)

1.轮询(默认):接收到的请求按照顺序逐一分配到不同的后端服务器,通过weight设置服务器的权重,权重越高,被分配到请求的概率就越大。默认所有服务器权重一样

例如第一个请求交给了第一个业务服务器,第二个请求交给第二个,第n个就交给第n个

2.ip_hash:同一个ip下的请求,都是同一个服务器处理。有效的解决了集群或者分布式下的 session共享问题。

3.fair:哪个服务器的响应时间短效率高,被分配到请求的概率就大

4.url_hash:同一个url,都是同一个服务器处理,一般用在静态资源服务器上。

十四、Spring Cloud

Eureka(尤瑞卡):服务治理组件,包括服务端的注册中心和客户端的服务发现机制;
Ribbon(瑞班):负载均衡的服务调用组件,具有多种负载均衡调用策略;
Hystrix(海四最四):服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力;
Feign(非嗯):基于Ribbon和Hystrix的声明式服务调用组件;
Zuul(让昂):API网关组件,对请求提供路由及过滤功能。

Eureka:服务注册于发现

Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求

Ribbon:实现负载均衡,从一个服务的多台机器中选择一台

Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪 崩的问题

Zuul:网关管理,由 Zuul 网关转发请求给对应的服务

十五、设计模式

1、单例模式

2、生产者消费者模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值