面试总结3.0

一、Redis面试

1.Reids支持的数据类型

String 字符串

Hash 哈希表

List 列表

Set 集合

Sorted Set 有序集合

2.Redis持久化策略

Redis支持两种持久化策略分别为RDB和AOF

RDB模式是默认模式,可以在指定时间间隔内生成数据快照,默认保存到dump.rdb文件中,当redis重启时会自动加载dump.rdb文件中的内容到内存去。

Rdb文件是紧凑文件,直接使用rdb文件就可以还原数据。数据保存会由子进程进行保存,不影响父进程做其他事情,恢复数据高于AOF。

AOF:监听执行的命令,如果发现执行了修改数据的操作,直接同步到数据库文件中,同时会把命令记录到日志中。即使突然出现问题,由于日志文件中已经记录命令,下次启动时也可以按照日志进行恢复数据。AOF默认是关闭的,需要在配置文件redis.conf中开启AOF,Redis支持RDB和AOF同时生效,如果同时存在,AOF优先级高于RDB。AOF相对于RDB数据更加安全。

3.Redis缓存穿透

穿透:访问缓存中不存在,数据库中存在的数据,导致绕过redis直接访问数据库了

在实际开发中,添加缓存工具的目的,减少对数据库的访问次数,增加访问效率。肯定会出现Redis中不存在的缓存数据。例如:访问id=-1的数据。可能出现绕过redis依然频繁访问数据库的情况,称为缓存穿透,多出现在查询为null的情况不被缓存时。

解决办法:

把值为null的数据也加入到redis缓存中

布隆过滤器

如果查询出来为null数据,把null数据依然放入到redis缓存中,同时设置这个key的有效时间比正常有效时间更短一些。

4.Redis缓存击穿

击穿:热点数据缓存过期,此时如果出现高并发访问会导致全去访问数据库了造成击穿

实际开发中,考虑redis所在服务器中内存压力,都会设置key的有效时间。一定会出现键值对过期的情况。如果正好key过期了,此时出现大量并发访问,这些访问都会去访问数据库,这种情况称为缓存击穿。

解决办法:

热点数据永不过期

互斥锁,互斥锁要设置个超时时间防止一直占用,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

加分布式锁。出现key过期只允许一个线程去访问数据库,访问到后重新加入缓存,其他线程就从缓存中获取数据了。

5.Redis缓存雪崩

在一段时间内,出现大量缓存数据失效,这段时间内数据库的访问频率骤增,这种情况称为缓存雪崩。

解决办法:

均匀设置过期时间

互斥锁

后台更新缓存

6.Reids简介

Redis是以Key-Value形式进行存储的NoSQL数据库。

Redis是使用C语言进行编写的。

平时操作的数据都在内存中,效率特高,所以多把Redis当做缓存工具使用,也常用于分布式锁。

7.Redis 集群原理

集群搭建完成后由集群节点平分(不能平分时,前几个节点多一个槽)16384个槽。客户端可以访问集群中任意节点。所以在写代码时都是需要把集群中所有节点都配置上。当向集群中新增或查询一个键值对时,会对Key进行Crc16算法得出一个小于16384值,判断值在哪个节点上,然后就操作哪个节点。

8.Spring Cache 原理

Spring Cache是由Spring-context.jar提供的功能。对缓存工具的通用业务进行了分装。Spring Cache按照严格的顺序依次寻找Generic、JCache 、EhCache 2.x、Hazelcast、Infinispan、Couchbase、Redis、Caffeine、Simple缓存工具的管理类实例(CacheManage)。只要寻找到就不在继续寻找。而是使用对于的缓存管理实例。

以我做过的项目举例,在项目中使用的是Redis缓存工具。只需要把spring-boot-starter-data-redis启动器添加到项目中。Spring Cache就会使用Redis作为缓存工具(因为依赖中包含了RedisCacheManager)。RedisCacheManager构造方法要求有RedisCacheConfiguraion,RedisCacheConfiguraion中定义的RedisTemplate的key为String,value为JDK序列化器,所以使用Spring Cache结合Redis时注意存放对象时,类必须序列化综合说明Spring Cache结合Redis的执行原理。

Spring Cache扫描容器中包含了RedisCacheManager实例。讲会使用Redis作为缓存工具。

在执行业务方法时,如果方法上包含@Cacheable将会判断是否存在key,如果存在key直接获取Redis中数据作为方法返回结果。如果不存在key则执行方法后,把方法返回值缓存到Redis中,最后返回返回值。

如果执行业务方法时,方法上面包含@CacheEvit将会在执行完成方法后删除缓存数据。

如果执行业务方法时,方法上面包含@CachePut,每次都会执行方法体,并且把方法返回值缓存到Redis中。

9.Reids是单线程的,但他为什么这么快?

1.完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。

2.数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的。

3.采用单线程,避免了不必要的上下文切换和竞争操作,也不存在多线程或者多线程导致的切换而消耗CPU,不用去考虑各种锁问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。

4.使用多路I/O复用模型,非阻塞IO。(多路:多个网络连接 复用:复用同一个线程)

5.使用底层模型不同。

二.RabbitMQ

1.RabbitMQ原理?

RabbitMQ是由Erlang语言编写的基于AMQP的消息中间件。

客户端应用程序向RabbitMQ发送消息Message,在Message会包含路由键Routing Key,交换器Exchange接收到消息Message后会根据交换器类型Exchange Type解决把消息如何发送给绑定的队列Queue中,如果交换器类型是Direct这个消息只放入到路由键对应的队列中,如果是topic交换器消息放入到routing key匹配的多个队列中,如果是fanout交换器消息会放入到所有绑定到交换器的队列中。等放入到队列中之后RabbitMQ的事情就结束了。剩下的事情是由Consumer进行完成,Consumer一直在监听队列,当队列里面有消息就会把消息取出,取出后根据程序的逻辑对消息进行处理。以上这些就是RabbitMQ的运行原理。

2.RabbitMQ支持的交换器类型都有哪些?

Direct Exchange:直连交换器(默认)。通过路由键明确指定存储消息的一个队列。

Fanout Exchange:扇形交换器。把消息发送给所有绑定的队列。

Topic Exchange:主题交换器。按照路由规则,把消息发送给多个队列。

Header Exchange:首部交换器。比Direct多了一些头部消息,平时使用较少。

3.MQ如何防止消息重复消费(幂等性)?

1、对于需要保存到数据库的数据,我们可以设置某条数据的某个值,比如订单号之类的,设置一个唯一索引,这样的话即使重复消费也不会生效数据

2、乐观锁,也就是我们每次插入一条数据或者更新的时候判断某个版本号是不是与预期一样,如果不是,那么就不进行操作

3、使用redis进行存储,保留我们消费过的数据的每个特征,然后每次操作数据的时候先去redis进行判断,如果存在的话,那么这条数据就是重复消费的,然后我们可以丢弃或者做其他处理。

4.如何保证rabbitmq的消息的顺序性?

生产者发送多条消息到一个队列中,这多条消息具有先后顺序,这个队列只有一个消费者去消费消息。消费者在消费信息的时候不去直接消费消息,而是将消息保存在内存队列中。根据消息的关键值(例如订单ID)进行哈希操作,将关键值相同的消息(一组需要保证顺序的消息)发送到相同的内存队列中的里面,一个线程只去一个内存队列中取信息。这样就保证了消息的顺序性。(首先生产者发送消息到队列中是有序的,然后消费者消费时不直接消费消息,而是把消息放在内存队列中,然后根据主键id进行哈希操作,把主键id相同的消息发送到相同的内存队列中,一个线程只去一个内存取消息)。

5.如何处理消息堆积?

见另外两篇

主要措施就是:增加消费端、堆积的消息没用的话直接舍弃、已经堆积了的有用的消息通过临时增加服务器创建临时方法把消息快速消费掉。

三.数据库及SQL优化

1.请说一下MySQL支持几种存储引擎?

答:MySQL支持的引擎主要有以下两种:

MyISAM:MyISAM是ISAM的扩展格式引擎。它在ISAM的基础增加了索引和字段管理等大量功能,还具有表格锁定的机制用于优化多个并发的读写操作。MyISAM更加强调了数据读取操作,所以在WEB开发中使用非常广泛。MyISAM的缺点是不支持事务,且当数据比较多的时候写操作的效率会很低。

InnoDB:InnoDB现在是MySQL默认的数据库引擎,它是一种为巨大数据量而生的最大性能设计,它支持事务,支持多版本读取,而且增加了行级锁机制,此外还支持外键。

以上存储引擎各适用于什么情况?

        如果是读多写少的项目,可以考虑使用MyISAM,MYISAM索引和数据是分开的,而且其索引是压缩的,可以更好地利用内存。压缩后的索引也能节约一些磁盘空间。MYISAM拥有全文索引的功能,这可以极大地优化LIKE查询的效率。

        如果你的应用程序要使用事务,或者是大数据量毫无疑问你要选择INNODB引擎。

2.请说一下MySQL支持的日志类型有哪些?

错误日志(回滚等):错误日志记录MySQL运行过程中较为严重的警告和错误信息,以及MySQL启动和关闭的详细信息。

二进制日志(主从):包含所有更新数据(新增、删除、修改、改表等)SQL 信息的记录。

通用查询日志(记录查询等信息):通用查询日志是记录建立的客户端连接和执行的语句。

慢查询日志:记录所有执行时间超过long_query_time秒的所有查询或不适用于索引的查询

3.请说一下如何进行SQL优化/SQL索引优化?

优化MySQL主要从两个方向:硬件级别数据库级别

从硬件级别来讲可以从磁盘读写、CPU、CPU缓存来去优化。

而从数据库级别来讲可以考虑表结构是否正确,正确的设置索引达到查询高效,对于不同情况选择不同的存储引擎(),每张表是否具有适当的行格式,应用程序是否使用适当的锁策略,索引缓存区域使用的大小是否正确这些来去进行Mysql的优化。而SQL 优化包含在数据库级别优化中。

我们平常所说的SQL优化就是指优化SQL语句和索引,比如常规的调优思路有查看slow-log,分析slow-log,分析出查询慢的语句。按照一定优先级,进行一个一个的排查所有慢语句。分析top sql,进行explain调试,查看语句执行时间。调整索引或语句本身。

4.请说一下MySQL数据架构/SQL命令流程

  1. 客户端向服务器端发送SQL命令
  2. 服务器端连接模块连接并验证
  3. 缓存模块解析SQL为Hash并与缓存中Hash表对应。如果有结果直接返回结果,如果没有对应继续向下执行
  4. 解析器解析SQL为解析树,如果出现错误,报SQL解析错误。如果正确,向下传递
  5. 预处理器对解析树继续处理,处理成新的解析树。
  6. 优化器根据开销自动选择最优执行计划,生成执行计划
  7. 执行器执行执行计划,访问存储引擎接口
  8. 存储引擎访问物理文件并返回结果
  9. 如果开启缓存,缓存管理器把结果放入到查询缓存中。
  10. 返回结果给客户端。

四、Java基础

1.final,finally和finalize的区别

一、final :

  1、final 可以修饰类,不能被继承
  2、修饰方法,不能被重写
  3、修饰变量,只能赋值一次

二、finally:

finally是try语句中的一个语句体,不能单独使用,用来释放资源

三、finalize:

finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。

2.String、StringBuffer和StringBuilder的区别

String:不可变字符序列(字符串常量),String声明的是不可变对象,当修改对象的时候就会创建一个新对象;

StringBuffer:可变字符序列、可以在原对象上进行修改,不会创建新对象,把所有修改数据的方法都加上synchronized,所以线程安全,但是效率低;

StringBuilder:可变字符序列、可以在原对象上进行修改,不会创建新对象,效率高、线程不安全;

String str="i"与String str=new String("i")一样吗?(扩展)

不一样,因为内存的分配方式不一样。String  str="i"的方式,Java 虚拟机会将其分配到常量池中,如果常量池中有"1",就返回"的地址,如果没有就创建"1",然后返回"i"的地址;而String str=new String(")则会被分到堆内存中新开辟一块空间。

3.= =和equals的区别

“==”是关系运算符,equals()是方法,同时他们的结果都返回布尔值;

“==”使用情况如下:

a) 基本类型,比较的是值

b) 引用类型,比较的是地址

c) 不能比较没有父子关系的两个对象

equals()方法使用如下:

a) 系统类一般已经覆盖了equals(),比较的是内容。

b) 用户自定义类如果没有覆盖equals(),将调用父类的equals (比如是Object),而Object的equals的比较是地址(return (this == obj);)

c) 用户自定义类需要覆盖父类的equals()

4.JavaBean需要符合哪些条件

符合一定条件的java类称为JavaBean

JavaBean需要符合以下四点

(1)类是public

(2)属性是private

(3)提供getter/setter方法 (get/set)

(4)提供无参构造器 

5.Java中的4P访问权限有哪些,请问你想使用权限修饰谁?

private package protected public

如果修饰类,使用package和public

如果修饰类的属性或方法,使用private package protected public

6.常见的五种异常类型

1.Java.lang.NullPointerException  空指针异常

2. java.lang.ClassNotFoundException  类型转换异常

3. java.lang.ArithmeticException  数学运算异常

4. java.lang.ArrayIndexOutOfBoundsException  数组下标越界

5. java.lang.IllegalArgumentException 方法参数错误

6. java.lang.IllegalAccessException 没有访问权限

7.请说一下java中的集合分

集合包中最常用的有Collection和Map两个接口的实现类。

Collection用于存放单列对象,Map用于存放Key-Value形式的键值对(双列对象),在Collection中常用的又分为两种类型的接口,List他的特点是有序可重复和Set他的特点是无序不可重复。List常用的实现类有ArrayList,LinkedList。

ArrayList是基于数组方式实现的。LinkedList是基于链表实现的。

Set常用的实现类有Hashset基于哈希表实现。

Map常用的实现类有HashMap基于数组链表加红黑树实现。

8.Java基本数据类型初始值

byte              (byte)0

short             (short)0

int                  0

long               0L

float               0.0f

double           0.0d

boolean        false

char              '/uoooo'(null)

9.请说一下Java跨平台原理

我们都知道java具有一个强大的功能,就是java具有一次编译到处运行的特点,而支持java这一特点的原因是虚拟机jvm的存在,我们在使用java文件的时候,需要把源文件通过编译器编译成字节码文件,然后将字节码文件通过jvm翻译成可以识别的机器码.我们可能使用的是不同的平台,其翻译的结果可能不同,但是jvm是运行在操作系统上的,是一样的,,与硬件没有直接的关系,这就支持了java代码实现了java代码的”一次编译,到处运行”.

类装载器ClassLoader:用来装载.class文件

执行引擎:执行字节码,或者执行本地方法

运行时数据区:方法区、堆、Java栈、程序计数器、本地方法栈

10.请说一下Java中GC

答:GC全称垃圾回收器,是Java的一个核心技术。它通过引用计数法和可达性分析来判断需要回收的对象。当CPU空闲的时候GC会自动回收,堆区存储空间满了之后也会进行垃圾回收,此外我们也可以通过调用System.gc()来进行垃圾回收。垃圾回收的算法有四个,分别是:标记-清除算法、复制算法、标记-整理算法和分代收集算法。

11.RESTful接口

restful其实就是一套编写接口的协议,协议规定如何编写以及如何设置返回值、状态码等信息。

12.Servlet的生命周期

简单的Servlet生命周期解释:

Servlet生命周期可被定义为从创建直到毁灭的整个过程。

以下是Servlet遵循的过程:

Servlet通过调用init ()方法进行初始化。

Servlet调用service()方法来处理客户端的请求。

Servlet通过调用destroy()方法终止(结束) 。

最后,Servlet 是由JVM的垃圾回收器进行垃圾回收的。.

13.重写和重载的区别

重载发生在一个类中,同名的方法如果有不同的参数列表,如参数个数不同,参数类型不同就是重载,重写是发生在子类和父类之间,要求子类被重写的方法要和父类被重写的方法有相同的返回值类型。

14.BIO、NIO、 AIO有什么区别?

BIO: Block IO同步阻塞式IO,就是我们平常使用的传统IO,它的特点是模式简单使用方便,并发处理能力低。

NIO: New IO同步非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel (通道)通讯,实现了多路复用

AIO: Asynchronous IO是NIO的升级,也叫NIO2,实现了异步非堵塞IO,异步IO的操作基于事件和回调机制

15.请解释什么是值传递和引用传递?

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。如果量很大的话,效率就会低,而引用传递是直接把内存地址传过去。

五、Spring

1.MyBatis的运行原理

在 MyBatis 运行开始时需要先通过 Resources 加载全局配置文件.下面 需要实例化 SqlSessionFactoryBuilder 构建器.帮助 SqlSessionFactory 接 口实现类 DefaultSqlSessionFactory. 在实例化 DefaultSqlSessionFactory 之前需要先创建 XmlConfigBuilder 解析全局配置文件流,并把解析结果存放在 Configuration 中.之后把 Configuratin 传递给 DefaultSqlSessionFactory.到此 SqlSessionFactory 工 厂创建成功. 由 SqlSessionFactory 工厂创建 SqlSession. 每次创建 SqlSession 时,都需要由 TransactionFactory 创建 Transaction 对象,同时还需要创建 SqlSession 的执行器 Excutor,最后实例化 DefaultSqlSession,传递给 SqlSession 接口. 根据项目需求使用 SqlSession 接口中的 API 完成具体的事务操作. 如果事务执行失败,需要进行 rollback 回滚事务. 如果事务执行成功提交给数据库.关闭 SqlSession 到此就是 MyBatis 的运行原理

MyBatis的运行原理

当运行MyBatis框架时,会先通过Resources去加载MyBaits配置文件,把加载到的文件流交给XMLConfigBuilder去解析,XMLConfigBuilder会把解析后的结果封装到Configuration中同时传递给DefaultSqlSessionFactory构造方法进行实例化SqlSessionFactory,再由SqlSessionFactory工厂对象创建SqlSession对象,中间每个SqlSession创建过程都会由Mybatis自动创建Transaction对象及Executor,最后实例化 DefaultSqlSession,传递给 SqlSession 接口

之后Mybatis会加载指定包下所有接口和映射文件实现接口绑定,每个接口都是通过动态代理实例化的。 在具体的业务中调用接口的某个方法时,调用了对应的SQL当事务执行结束后,需要commit事务.如果在提交事务过程出现异常,需要Rollback回滚事务.最后需要关闭SglSession对象.以上就是Mybatis的运行原理。

2.SpringMVC运行原理

(1)客户端(浏览器)发送请求,直接请求到 DispatcherServlet。

(2)DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。

(3)解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。

(4)HandlerAdapter 会根据 Handler 来调用真正的处理器来处理请求,并处理相应的业务逻辑。

(5)处理器处理完业务后,会给DispatcherServlet 返回一个 ModelAndView 对象。

(6)DispatcherServlet 将ModelAndView 传给ViewResolver 视图解析器。

ViewResolver解析后返回具体的View

(7)DispaterServlet对返回的View进行视图渲染,再去响应用户。

当用户发起请求后,执行DiapacherServlet,DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。HandlerAdapter会根据Handler 调用具体的HandlerMethod,当HandlerMethod执行完成后会返回ModelAndView,会被ViewResovler视图解析器解析,解析后返回具体的View,DiapacherServlet对返回的View进行视图渲染.最终响应给客户端浏览器.(给面试官这样讲)

3.Spring Boot运行原理以及加载顺序

运行原理:答:SpringBoot项目中都有一个带有@SpringBootApplication注解的启动类,该启动类中的SpringApplication.run()方法会加载@SpirngBootApplication注解,而该注解包含了三大主要功能,也就是三个注解,分别是:

第一个:@SpringBootConfiguration,这个注解相当于Spring基础框架中的@Configuration注解,他可以将此类定义为Spring框架的配置类。

第二个:@EnableAutoConfiguration,这个注解可以将spring-boot-autoconfigurer.jar和xxx-autoconfigurer.jar加载到spring容器中,然后就可以自动启动命名为XxxAutoConfiguration.java这样的配置类。

第三个:@ComponentScan,这个注解可以扫描@SpringBootApplication所在启动类的同包及其子包中的组件类。组件类就是添加了@Repository、@Service、@Controller或@Component注解的类。

加载顺序:默认先去读取项目config目录下的配置文件,而且properties文件比yml文件优先级高所以先去读取config/application.properties然后是config/application.yml。然后去加载项目根目录下的配置文件也是先读取application.properties,再去读取application.yml,接下来去类路径下的config中读取,先加载resources/config/application.properties,后加载resources/config/application.yml

最后去类路径下去读取,先读取resources/application.properties后读取resources/application.yml。除了spring boot中application配置文件以外,还有spring cloud中bootstrap配置文件,Spring Boot默认不加载bootstrap配置文件,需要在项目中引用spring-cloud-context才能使用,bootstrap只能存放在classpath下(即resources中),支持bootstrap.properties和bootstrap.yml两种写法,properties的优先级高于yml。如果在resource中有bootstrap和application配置文件,bootstrap配置文件高于application配置文件。application配置文件内容可以替换/覆盖bootstrap配置文件内容。

4.请说Spring中的IOC和DI

IOC即控制反转,是一种设计思想,把对于对象的创建,初始化,销毁等工作交给spring容器来做,spring容器来控制对象的生命周期。他的一个常用的实现方式是DI依赖注入(DI是IOC的一种实现方式),DI依赖注入就是Spring容器根据对象之间的依赖关系完成对象的创建以及组装的过程。

Spring Ioc中文名称叫做控制反转。以前由程序员手动实例化对象的过程交给Spring容器进行管理。程序中绝大多数对象都由Spring 容器进行统一管理后,可以在程序中所有被Spring容器管理的实例方便的取出其他所管理的对象,这个过程也成为DI依赖注入,所以平时多把IoC和DI放在一起说。在很多地方都在大量应用Spring IoC这个特性。例如分层开发中每层类对象由Spring 容器管理,在其他层可以方便注入对象。还有在Spring Boot中整合其他技术时也使用IoC特性,只要导入Spring Boot启动器,可以把示例注入到Spring 容器中,在代码中可以直接注入对应的实例等等。所以只要使用Spring Framework或Spring Boot时,Spring IoC是必不可少的。

5.请说Spring中的AOP

AOP就是面向切面编程。其本质是通过预编译和运行时动态代理方式实现程序功能统一维护。通过AOP技术,我们可以对程序的业务逻辑(主要是service层)进行隔离增强,从而提高程序的可重用性。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

6.请说一下Spring

Spring是一个轻量级框架, 能够简化企业级应用开发,减少代码量;

Spring的核心框架是AOP (面向切面)与IOC (控制反转),IOC说白了就是跟容器要对象,其中DI,依赖注入就是给属性赋值; AOP就是根据动态代理,分为切面,切入点和通知。Spring还提供对jdbc的轻量级封装;还提供声明式事物。Spring还根据MVC设计模式开发出SpringMVC框架。

7.aop的几种实现方式

1、aspectj静态代理,编译期间将aspectj(切面)织入Java字节码中,运行时就是增强之后的aop对象。

2、jdk动态代理,支持对接口的代理。Proxy类利用InvocationHandler接口动态创建一个符合某一接口的实例,生成目标类的代理对象。

3、cglib动态代理,支持对类的代理(该类无需实现接口)。利用Enhancer工具类返回目标对象的子类对象(即代理对象),实现.MethodIntercepter接口,重写intercept方法中通过反射调用目标方法并添加增强代码,实现aop。补充: Spring Aop包括jdk动态代理和cglib动态代理。

8.Bean的生命周期

实例化 -> 属性赋值 -> 初始化 -> 销毁

9.SpringSession的原理

答:

当客户端访问服务端的时候,服务端会判断客户端的cookie中是否携带了session键值对

如果cookie中没有session键值对就会创建Session对象,然后把这个Session对象保存在redis数据库中;每个Session对象都包含了创建时间creationTime、最大存活时间键maxInactiveInteval和最后访问时间lastAccessTime三组键值对。

如果cookie中有session键值对,就根据session键值对的value拼接成spring:session:sessons:value去redis中取出对应的Session对象;然后根据Session对象的lastAccessTiime属性和maxInactiveInterval属性来判断Session是否过期。

如果Session已经过期就重新创建一个Session对象,并再次保存到redis数据库中。

如果Session没有过期,就将lastAccessTime属性更新。

六、分布式和微服务

1.Dubbo的实现原理

服务容器Container 负责启动加载运行服务提供者Provider。根据Provider配置的文件根据协议发布服务 , 完成服务的初始化.Provider在启动时,根据配置中的Registry地址连接Registry,将Provider的服务信息发布到Registry,在Registry注册自己提供的服务。Consumer在启动时,根据消费者XML配置文件中的服务引用信息,连接到Registry,向Registry订阅自己所需的服务。Registry根据服务订阅关系,返回Provider地址列表给Consumer,如果有变更,Registry会推送最新的服务地址信息给Consumer。

2.fastdfs运行原理

客户端去请求fastdfs中的tracker值,tracker进行负载均衡,把相关内容去转发到对应的storage中,storage中分为不同组,分为逻辑组,根据逻辑组去找具体真实内容,把真实内容返回去。

3.请说一下Solr搜索数据的原理

Solr是基于lucene的全文搜索服务器,易于加入到Web应用程序中,用户根据搜索条件去进行搜索,该条件根据他的类型去进行判断,如果该类型需要拆词,他会把这个类型进行拆词,然后根据拆词后的值和索引进行比较如果和索引中的词匹配的话就会去把数据查出来。

4、请说一下什么是微服务

微服务是由单一应用程序构成的小服务,拥有自己的进程与轻量化处理,服务依业务功能设计(微服务一个业务一个项目),以全自动方式部署,与其他服务使用 HTTP协议通讯。

1.微服务是架构。2.微服务中项目都成为服务。3.微服务拆分颗粒度为业务。4.微服务中服务和服务之间使用HTTP协议通信。5.微服务和Docker结合使用更方便。6.微服务是分布式架构的一种。

5、RPC原理

首先需要有处理网络连接通讯的模块,负责连接建立、管理和消息的传输。其次需要有编解码的模块,因为网络通讯都是传输的字节码,需要将我们使用的对象序列化和反序列化。剩下的就是客户端和服务器端的部分,服务器端暴露要开放的服务接口,客户调用服务接口的一个代理实现,这个代理实现负责收集数据、编码并传输给服务器然后等待结果返回。

七、常见算法

1.快速排序算法

1.从数列中挑出一个元素,称为 “基准”(pivot);

2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

代码:import java.util.Arrays;

public class TestQuickSort {

        private static int partition(int[] arr, int low, int high) {

                //指定左指针i和右指针j

                int i = low;

                int j= high;

                //将第一个数作为基准值。挖坑

                int x = arr[low];

                //使用循环实现分区操作

                while(i<j){//5  8

                        //1.从右向左移动j,找到第一个小于基准值的值 arr[j]

                        while(arr[j]>=x && i<j){

                                j--;

                        }

                        //2.将右侧找到小于基准数的值加入到左边的(坑)位置, 左指针想中间移动一个位置i++

                        if(i<j){

                                arr[i] = arr[j];

                                i++;

                        }

                        //3.从左向右移动i,找到第一个大于等于基准值的值 arr[i]

                        while(arr[i]<x && i<j){

                                i++;

                        }

                        //4.将左侧找到的打印等于基准值的值加入到右边的坑中,右指针向中间移动一个位置 j--

                        if(i<j){

                                arr[j] = arr[i];

                                j--;

                        }

                }

                //使用基准值填坑,这就是基准值的最终位置

                arr[i] = x;//arr[j] = y;

                //返回基准值的位置索引

                return i; //return j;

        }

        private static void quickSort(int[] arr, int low, int high) {//???递归何时结束

                if(low < high){

                        //分区操作,将一个数组分成两个分区,返回分区界限索引

                        int index = partition(arr,low,high);

                        //对左分区进行快排

                        quickSort(arr,low,index-1);

                        //对右分区进行快排

                        quickSort(arr,index+1,high);

                }

        }

        public static void quickSort(int[] arr) {

                int low = 0;

                int high = arr.length-1;

                quickSort(arr,low,high);

        }

        

        public static void main(String[] args) {

                //给出无序数组

                int arr[] = {72,6,57,88,60,42,83,73,48,85};

        //输出无序数组

        System.out.println(Arrays.toString(arr));

        //快速排序

        quickSort(arr);

        //partition(arr,0,arr.length-1);

        //输出有序数组

        System.out.println(Arrays.toString(arr));

        }

}

2.冒泡排序

1.比较相邻的元素。如果第一个比第二个大,就交换它们两个;

2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;

3.针对所有的元素重复以上的步骤,除了最后一个;

4.重复步骤1~3,直到排序完成。

代码:public class BubbleSort {

    public static void bubbleSort(int[] arr) {

        // 空数组 或 只有一个元素的数组,则什么都不做。

        if (arr == null || arr.length == 1) return;

        // 外层循环表示趟数。

        for (int i = 0; i < arr.length - 1; i++) {

            // 默认有序,如果发生交换说明无序。

            boolean isSorted = true;

            // j 表示 要比较元素对的第一个。

            for (int j = 0; j < arr.length - 1 - i; j++) {

                // 不能写 >= ,否则不稳定。

                if (arr[j] > arr[j + 1]) {

                    int temp = arr[j];

                    arr[j] = arr[j + 1];

                    arr[j + 1] = temp;

                    // 发生交换了,说明无序。

                    isSorted = false;

                }

            }

            // 如果没有发生交换,则数组已经有序,结束冒泡。

            if (isSorted)   return;

            // 把每一趟排序的结果也输出一下。

            System.out.print("第 "+ (i+1) + " 趟: ");

            print(arr);

        }

    }

    public static void main(String[] args) {

        int[] arr = {6, 9, 1, 4, 5, 8, 7, 0, 2, 3};

        System.out.print("排序前:  ");

        print(arr);

        bubbleSort(arr);

        System.out.print("排序后:  ");

        print(arr);

    }

    // 打印数组

    public static void print(int[] arr) {

        if (arr == null)    return;

         for(int i : arr) {

            System.out.print(i + " ");

        }

        System.out.println();

    }

}

3.选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

代码:/** 2  * 选择排序

 3  * @param array 待排序数组

 4  * @return 已排序数组 5  

*/ 6 public static int[] selectionSort(int[] array) { 

7     int len = array.length;

 8     // 如果数组长度为0或者1,都是不用排序直接返回

 9     if(len == 0 || len == 1) {

10         return array;

11     }

12     for(int i = 0; i < len - 1; i++) {

13         int minIdx = i;

14         for(int j = i + 1; j < len; j++) {

15             // 找到最小的数

16             if(array[minIdx] > array[j]) {

17                 // 保存最小数的索引

18                 minIdx = j;

19             }

20         }

21         // 如果一轮比较下来都没有变更最小值的索引则无需调换顺序

22         if(minIdx != i) {

23             int tmp = array[i];

24             array[i] = array[minIdx];

25             array[minIdx] = tmp;

26         }

27     }

28     return array;

29 }

4.折半查找

前提:查找表必须使用顺序存储结构;其次,查找表必须按关键字大小有序排列。

首先取中间数据项,然后作比较,如果数据项比待查关键字大,那么就取数据项前半部分中间数据项在进行比较,直至查到该关键字.

/**

* 寻找排好数组中的一个值

*

* @param array 要查找的数组

* @param value 查找的值

* @param left 左边界,这个值必须位于数组长度区间内

* @param right 右边界,这个值必须位于数组长度区间内

* @return 找到的值在数组中的位置,如果没找到就返回-1

*

*/

static int binarySearch(int[] array,int value, int left,int right){

if(left>right){ //退出条件

  return -1; //没有找到指定元素

}

int mid =(left + right) >>>1 ;//相当于mid=(left + right)/2

if(array[mid] == value){

  return mid;

}else if (array[mid] > value){

  //递归调用查找左边

  return binarySearch(array,value,left,mid-1);

}else {

  //递归调用查找右边

  return binarySearch(array,value,mid+1,right);

}

}

static int binarySearch(int[] array,int value, int left,int right){

 int low = left;//开始位置

 int high = right -1; //结束位置

 while(low <= high){

   int mid =(low + high)>>>1; //相当于mid = (low + right)/2

   int midVal = array[mid]; //取中间值

   if(midVal < value){ //中间值小于要查找的关键字比较

     low = mid +1;

   }else if(midVal > value){ //中间值大于要查找的关键字比较

     high = mid -1;

   }else {

     return mid; //查找成功,返回找到的位置

   }

 }

 return -(low+1); //没找到,返回负值

}

八、常用设计模式

1.请手写一下单例设计模式

/**

 * 饿汉式的单例模式

 * 在类加载的时候创建单例实例,而不是等到第一次请求实例的时候的时候创建

* 1、私有 的无参数构造方法Singleton(),避免外部创建实例

 * 2、私有静态属性instance

 * 3、公有静态方法getInstance()

通过单例模式设计的方法创建的类在当前进程中只有一个实例。

确保某一个类在整个项目中只有一个实例,并且自行创建实例化对象,并向整个系统提供这个实例。

 */

public class Singleton {

private static Singleton instance = new Singleton();

private Singleton(){ }

public static Singleton getInstance(){

return instance;

}

}

/**

 * 懒汉式的单例模式

 *在类加载的时候不创建单例实例,只有在第一次请求实例的时候的时候创建,使用了synchronized所以是线程安全。

*/

public class Singleton {

private static Singleton instance;

private Singleton(){ }

/**

 * 多线程情况的单例模式,避免创建多个对象

*/

public static Singleton getInstance(){

if(instance ==null){//避免每次加锁,只有第一次没有创建对象时才加锁

synchronized(Singleton.class){//加锁,只允许一个线程进入

if(instance==null){ //只创建一次对象

instance = new Singleton();

}

}

}

return instance;

}

}

2.工厂设计模式

工厂模式分为简单工厂模式和工厂方法模式,简单工厂模式是只有一个工厂类,这个类里面有很多的方法,每一个方法的返回值就是一个对象,你需要什么对象,就调用什么方法。工厂方法模式是每一个实体类都有一个工厂,所以有很多的工厂,每一个工厂里面的返回值就是对应的一个对象,所以,想要使用什么对象,直接创建对应的工厂类,调用里面的方法就可以得到。

3.策略模式

九、多线程

1.进程和线程?


2.并发(concurrency)和并行(parallellism)?


3.java中的++操作符线程安全么?


4.Java里面一般用以下几种机制保证线程安全?


5.Synchronized加在方法上和加在对象上的区别?


6.Sleep()和wait()有什么区别?


7.synchronized关键字 


8.volatile关键字


9.获取多线程的几种方式?

10.为什么要使用线程池?

十、集合

1.请说一下说一下HashMap的实现原理?

HashMap基于Hash算法实现的,在JDK1.8以前底层是数组和单项链表的结合体1.8以后多了红黑树。当添加元素时先把k,v封装到node对象中,底层调用hashCode方法算出hash值,得到数组下标。

  1. 如果数组下标没有任何元素则添加成功。
  2. 如果数组下标位置有链表,则调用equals方法,若返回FALSE,则添加到链表末端。若有一个equals方法返回了TRUE,则覆盖。
  3. 当计算出的hash值相同时,我们称之为hash冲突,HashMap 的做法是用链表和红黑树存储相同hash值的value。当hash冲突的个数比较少时,使用链表否则使用红黑树。

查询元素同理先通过key的hashcode方法计算出hash值,得到数组下标,再调用key的equals方法与链表上的节点比对,若返回true,则该元素就是要查找的元素。

Hashcode是一个本地方法,返回对象的地址值。

hashset

2.请说一下说一下HashMap的扩容机制?

使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新Entry数组里。

3.请说一下说一下HashMap和HashTable的区别?

答:

HashTable是继承的Dictionary类,而HashMap是继承的AbstractMap类。。

HashTable线程安全,而HashMap线程不安全

HashTable的key和value都不允许为null,而HashMap的key和value允许为null。

4.请ArrayList和LinkedList的区别?

ArrayList和LinkedList两者都实现了List接口,但是它们之间有些不同。

ArrayList是由Array所支持的基于一个索引的数据结构,所以它提供对元素的随机访问,复杂度为O(1),但LinkedList存储一系列的节点数据,每个节点都与前一个和下一个节点相连接。所以,尽管有使用索引获取元素的方法,内部实现是从起始点开始遍历,遍历到索引的节点然后返回元素,时间复杂度为O(n),比ArrayList要慢。

与ArrayList相比,在LinkedList中插入、添加和删除一个元素会更快,因为在一个元素被插入到中间的时候,不会涉及改变数组的大小,或更新索引。

LinkedList比ArrayList消耗更多的内存,因为LinkedList中的每个节点存储了前后节点的引用。

5.说一下java中concurrent包的作用

在Java里有一个包:java.util.concurrent包,这组开发包是从JDK1.5的时候开始添加到JDK系统之中的,主要目的是进行高并发访问的处理,也就是说通过这个程序实现的开发包都将基于线程池的高速操作完成,而对于线程池一共有四种:任意扩张的线程池、定长线程池、线程调度池、单线程池。

6.HashMap底层实现

HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。HashMap基于Hash算法实现的,我们通过put(key,value)存储,get(key)来获取。 当传入key时,HashMap会根据key. hashCode()计算出hash值,根据hash值将value保存在bucket里。当计算出的hash值相同时,我们称之为hash冲突,HashMap 的做法是用链表和红黑树存储相同hash值的value。当hash冲突的个数比较少时,使用链表否则使用红黑树。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。

十一、项目总结

1.请说一下后台用户认证功能的业务逻辑

后台系统管理模块中包含了权限设置。在实现时前端页面时提供了两个请求参数,分别叫做用户名username和密码password。服务器端接收请求后会交给Spring Security 的UsernamePasswordAuthenticationFilter进行过滤,过滤完成后交给自定义登录逻辑UserDetailsService实现类进行处理,处理过程中直接使用配置类中配置的密码解析器对请求密码进行处理和并和数据库密码进行匹配,如果匹配成功进行登录,返回模板信息status=200,如果失败返回模板信息status=400

  1. 请说一下后台商品管理模块中新增商品的业务实现

整个后台使用EasyUI进行显示数据,在页面的左侧使用EasyUITree进行树状菜单显示,其中有商品管理模块,在商品模块中包含了商品新增,商品查询,规格参数等功能,在做商品新增时,需要填写商品的全部相关信息,其中包含很多内容,比如使用kindeditor做页面的显示,然后根据页面信息完成填写,在选择商品类目时,页面会根据商品类目显示规格参数模板信息,在完成信息添加后,页面会将数据向发送给服务器端,此时相当于多表新增,所有的事务都封装到provider中,在consumer中去调用provider中的方法,当对MySQL的数据操作完成后,此处需要同步缓存,此处的同步缓存是借助RabbitMQ去完成的,在这里使用RabbitMQ中的Fanout交换器去完成的,首先自定义了一个Fanout交换器,然后将多个队列绑定到Fanout交换器上,其中需要绑定同步Solr的队列和同步Redis的队列,在MySQL数据新增成功后同时会向Fanout交换器中发送新增数据相关内容的消息,然后在两个队列中去分别编写同步Solr和同步Redis的业务,同步Solr我是通过HttpClient调用当前项目中一个专门去操作Solr的Search项目去进行同步的,他会把相关的商品信息通过HttpClient发送给Search项目,Search项目接收到消息后同步到Solr中,另外一个是队列是同步到Redis,这里使用Spring Data进行数据同步,同步到Redis中。

  1. 请说一下后台CMS模块

我负责的CMS模块中主要分为两大模块,内容分类管理,内容管理,在内容分类管理中会在页面显示一个树状菜单,这个树状菜单是EasyUI的一个异步加载特性去分层进行查询,所以在服务器端只需要提供一个根据主键做查询的功能就可以了。显示完成以后,使用EasyUI的一个上下文菜单,其中包含了新增,重命名,删除,在添加的功能业务中,添加完成后只需要判断当前节点的父节点是否是父节点如果不是父节点就改成父节点,如果是直接把当前节点添加进去即可,添加的时候判断是否重名,如果重名则返回添加失败。重命名功能中要求修改后名字和修改前名子不一样,而且修改后的名字和当前父节点下的节点名字不能重名。在删除功能中使用递归算法实现的,删除某个节点当前节点下及所有其他子节点的逻辑状态都由1正常改为2删除状态同时如果当前节点删除后,还需要去判断当前节点父节点是否已经是没有其他正常状态子节点如果是的话还需要把他的isparent属性设置为false。

在内容管理中,主要是根据分类显示内容,默认情况是分页显示全部内容,当我点击左边菜单中分类时,他会显示这个分类下的所有内容,我在对内容进行新增,修改,删除的时候使用了RabbitMQ。当新增,修改,删除成功的时候会向RabbitMQ中发送消息,Consumer一直在监听队列,当队列里面有消息就会把消息取出,根据消息的内容,通过程序的逻辑去修改Redis缓存中的数据,使用户访问缓存中的数据,不用去频繁访问数据库,提高了用户体验度。

  1. 请说一下前台中门户模块中商品分类导航功能业务实现

我负责的前台门户模块中商品分类导航会在前台页面显示一个树状菜单,我在服务器端提供了根据父节点id查询当前父节点下所有商品分类的功能,在这里我使用了递归算法判断是否是父节点如果是父节点就根据主键id继续查询,然后拼接成前台要的字符串数据返回。由于数据量较大对数据库性能不友好,所以我使用spring cache和redis给商品分类导航加入缓存功能。

  1. 请说一下前台中用户登录业务逻辑

用户在点击登陆后,会将账号密码发送到服务器端,服务器端根据传进来的参数判断账号的类型,如果使用普通账号登陆则去数据库中查找账号密码是否正确,如果使用手机号作为账号登陆,则去数据库中查找手机号密码是否正确,如果使用邮箱作为账号登陆,则去数据库中查找邮箱密码是否正确。如果正确则登陆成功并跳转到登陆之前的页面,如果失败则返回登陆失败。

  1. 请说前台购物车模块业务逻辑

添加购物车时分为临时购物车和用户购物车,临时购物车和用户购物车的区别在于操作Redis的key,临时购物车的key是存储到cookie中,每次请求时有个携带一个自定义的数据,里面是一个UUID,通过这个UUID从Redis中去取,而用户购物车的key是一个固定字符串+用户主键去redis中去取,取到redis的key以后,根据redis的key去把用户购物车中的数据给拿出来,用户购物车数据是放到一个大Map中,根据Map判断里面的商品,如果商品已经存在直接修改数量,如果商品不存在则去向Map中添加商品数量,此处为了减少Myqsql操作直接获取的是redis的缓存,直接从商品详情缓存里面把商品相关信息取到,这样能提升整个程序的效率。

在购物车界面修改商品数量的时候,也是通过临时购物车或者用户购物车存储到redis中的key去获取商品信息,直接修改商品数量,如果要删除商品,则直接将redis中的key删除即可。

用户登陆之后,会通过HttpClient去调用购物车服务的一个接口,传递两个参数,一个是购物车的key一个是临时购物车的key传递完成后,根据传入的两个key值分别从redis中取出用户购物车数据和临时购物车数据,然后将临时购物车中的数据放入用户购物车中,最后将临时购物车数据和cookie数据删掉。

  1. 请说商品详情模块的实现

在前台商品详情服务中,通过商品主键id去查询商品的相关信息并返回给浏览器,为了使商品详情查询的速度快一些,在查询完商品信息后延迟一秒再根据商品的主键id去查询商品描述。在点击商品规格参数的时候,页面发送商品的主键id像服务器端查询商品规格参数。

  1. 请说商品搜索模块的实现

我负责的商品搜索模块是通过solr实现的,首先我创建了前台所要的数据和solr中要查询业务字段的实体类,通过solrtemplate设置一些查询条件去solr的索引数据中查询然后给前台返回结果,如果后台执行了修改数据操作,则通过rabbitmq发送消息将修改后的数据同步到solr索引中去。

  1. 请说创建订单的实现

在创建订单的时候服务器会判断用户有没有登陆,如果未登陆则拦截请求,然后重定向到登陆页面进行登陆,登陆成功后,创建订单,这时向服务器端发送查询商品的请求,判断商品货存充不充足,如果货存不足,则返回无货且不能提交订单,如果货存充足,提交订单的时候,会向服务器端发送新增订单的请求,为了防止高并发下库存出现负数情况,此处使用RabbitMQ队列功能进行排队,新增订单是向多个表中完成新增,新增的同时,修改数据库中商品的数量,都新增成功返回1,并且去Redis中删除购物车中的商品数据,如果有一个新增失败则抛出异常,进行回滚。创建订单成功显示成功页面,否则显示创建失败页面。

十二、自我介绍

   面试官你好,我叫***,今年毕业于**大学的**专业。我做过的java项是********,这个项目是是*******,其中使用的技术栈有Spring , MyBatis-Plus , SpringMVC ,MySQL,Dubbo , RabbitMQ ,Solr+ Redis ,JSP,FastDFS ,SpringSecurity , Spring Boot , Nginx

十三、面试总结

1.事务四个特性

原子性:强调事务的不可分割。

一致性:事务执行前后的数据的完整性保持一致。

隔离性:一个事务的执行过程中,不应该受到其他事务的干扰。

持久性:事务一旦结束,数据就持久到数据库中。

2.事务的四个隔离级别

1.读取未提交内容:一个事务可以查看到未提交的内容,常产生脏读问题

2.读取提交内容:一个事务只能查看已提交的内容,常产生不可重复读问题。

3.可重读:同一事务的多个实例并发读取数据时得到统一结果。

MqSQL的默认事务隔离级别。常产生幻读问题。

4.可串行化:最高隔离级别,给事务上共享锁,同时只能有一个事务操作,解决幻读问题,会导致大量超时和锁的竞争问题。

3.TCP的三次握手

所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:

图2 TCP三次握手
  (1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
  (2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
  (3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

4.OSI结构

物理层,数据链路层,网络层,传输层,会话层,表示层,应用层。

5.http和https的区别

端口不同,https 443 http是80

连接方式不同,http连接没有状态,https是根据http和ssl共同加密传输和身份证的协议

https具有安全性

https传输一般是需要申请证书。

6.java1.8的新特性

Lambda表达式

新的日期API

引入optional

使用base64

接口的默认方法和静态方法

新增方法引用格式

新增Stream类

支持并行数组

对并发类(Concurency)的扩展

7.数组和链表的区别

1、数组是-种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。最大的特点就是支持随机访问,但插入、删除操作也因此变得比较低效,平均情况时间复杂度为0 (n) 。在平时的业务开发中,我们可以直接使用编程语言提供的容器类,但是,如果是特别底层的开发,直接使用数组可能会更合适。

2、链表它并不需要一块连续的内存空间,它通过”指针”将一组零散的内存,空间可扩容,比较常用的是单链表,双链表和循环链表。和数组相比,链表更适合插入、删除操作频繁的场景,查询的时间复杂度较高。不过,在具体软件开发中,要对数组和链表的各种性能进行对比,综合来选择使用两者中的哪一个。

8.使用RabbitMQ的好处

解耦:系统间耦合性太高强  优点:将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改

       写入                     -------------系统B

系统A---------消息队列-------------系统C   订阅

                                   -------------系统D

异步:一些非必要的业务逻辑以同步的方式运行,太耗费时间

优点:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

削峰:并发量大的时候,所以的请求直接怼到数据库,造成数据库连接异常。

优点:系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。

9.自定义异常

      (1)编写类,继承Exception或RuntimeException

      (2)调用父类的构造方法 [一带参,一无参即可]

10.Nginx是怎么实现负载均衡的

1、轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

2、weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的
情况。

3、上述方式存在一个问题就是说,在负载均衡系统中,假如用户在某台服务器上登录了,那么该用户第二次请求的时候,因为我们是负载均衡系统,每次请求都会重新定位到服务器集群中的某一个,那么已经登录某一个服务器的用户再重新定位到另一个服务器,其登录信息将会丢失,这样显然是不妥的。

我们可以采用ip_hash指令解决这个问题,如果客户已经访问了某个服务器,当用户再次访问时,会将该请求通过哈希算法,自动定位到该服务器。

每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
4、fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。

5、url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

11.Zookeeper和Eureka的区别

在Zookeeper集群中,Zookeeper的数据保证的是一致性的。当Leader出现问题时,整个Zookeeper不可用,需要花费30~120s来进行重新选择leader,当leader选举成功以后才能进行访问整个Zookeeper集群。通过这点也可以看出Zookeeper是强一致性的,集群所有节点必须能通信,才能用集群。虽然这样集群数据安全了,但是可用性大大降低了。

在Eureka集群中所有的节点都是保存完整的信息的,当Eureka Client向Eureka中注册信息时,如果发现节点不可用,会自动切换到另一台Eureka Sever,也就是说整个集群中即使只有一个Eureka可用,那么整个集群也是可用的。

12.请说说对springMVC的理解

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

13.请说说对springBoot的理解

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

14.请说说对springCloud的理解

spring cloud 是一系列框架的有序集合。spring开发配置太复杂。springboot简化了spring项目开发。而springboot虽然开发服务容易,服务管理却不好整,所以出来了cloud来管理服务;Springcloud大概有接近二十个组件,常用的有服务发现注册Eureka 、配置中心config、服务网关Zuul 、负载均衡Ribbon (feign包含了他)、断路器Hystrix (feign包含了他)。

由于是微服务架构,由多个单体项目组成,就会有各自的通信地址,所以我们用服务发现注册Eureka来管理通信地址,配置中心config来管理配置文件并放到gitee上去保存,服务发现注册包含了服务端EurekaServer和客户端EurekaClient,(可以讲心跳机制),微服务EurekaServer在启动时会将通信地址发给注册中心EurekaClient,形成通信列表,也就是服务注册;微服务从注册中心获取一份微服务通信地址,自己向别的微服务通过通信地址找到的该微服务通信地址基于HTTP发起请求叫做服务发现;

当有多个微服务时,根据业务需求可能会有多个服务者,也就是功能一样的两个模块。需要对他们发起的请求进行处理,所以我们需要负债均衡负载均衡Ribbon,通过负载均衡来控制请求的分发,负载均衡常用算法有轮询(公平,两个都一样),随机,加权轮询(哪个厉害点就多给点,能者多劳)。

但是一个服务出现延迟时,所有的请求都阻塞在依赖的服务,高并发的依赖失败时如果没有隔离措施,当前应用服务就有被拖垮的风险也就是雪崩,所以我们需要用到断路器Hystrix,将出现问题的服务通过熔断、降级等手段隔离开来,这样不影响整个系统的主业务,所以维护起来更方便。具体方式有:资源隔离(线程池隔离和信号量隔离),熔断(熔断,半熔断,闭合),降级(服务熔断后返回一个预先设定好的托底数据);

当服务都能正常运行时,我们不能让所有都来访问,要有一个保安来拦截不该进来的请求,比如爬虫和其余不合法的请求,所以我们用到了服务网关Zuul,zuul还能自定义过滤器,比如自定义一个accesstoken,不带accesstoken的不让访问。有效的保证了服务的安全性。

15.请说说对spring Security的原理

  1. 用户在浏览器中随意输入一个URL
  2. Spring Security 会判断当前是否已经被认证(登录)如果已经认证,正常访问URL。如果没有被认证跳转到loginPage()对应的URL中,显示登录页面。
  3. 用户输入用户名和密码点击登录按钮后,发送登录url
  4. 如果url和loginProcessingUrl()一样才执行登录流程。否则需要重新认证。
  5. 执行登录流程时首先被UsernamePasswordAuthenticationFilter进行过滤,取出用户名和密码,放入到容器中。根据usernameParameter和passwordParameter进行取用户名和密码,如果没有配置这两个方法,默认为请求参数名username和password
  6. 执行自定义登录逻辑UserDetailsService的实现类。判断用户名是否存在和数据库中,如果不存在,直接抛出UsernameNotFoundException。如果用户名存在,把从数据库中查询出来的密码通过org.springframework.security.core.userdetails.User传递给Spring Security。Spring Security根据容器中配置的Password encoder示例把客户端传递过来的密码和数据库传递过来的密码进行匹配。如果匹配成功表示认证成功。
  7. 如果登录成功,跳转到successForwardUrl(转发)/successHandler(自己控制跳转方式)/defaultSuccessUrl(重定向)配置的URL
  8. 如果登录失败,跳转到failureForwardUrl/failureHandler/failureUrl

16.Java的三大特性,各个的优点

封装:隐藏对象内部特性和行为

好处:保护内部的状态,禁止对象之间的不良交互提高模块化提高了代码的可用性和可维护性。

继承:从父类获取字段和方法的能力

好处:提供了代码的重用性,可以在不修改基类的情况下给现存的类添加新特性。

多态:给不同的底层数据类型做相同的接口展示

好处:一个多态类型上的操作可以应用到其他类型的值上面

17.Java为什么不支持多继承

若子类继承的父类中拥有相同的成员变量,子类在引用该变量的时候无法判别使用那个父类的成员变量。

若一个子类继承多个父类拥有相同的方法,同时子类并未覆盖该方法,那么调用该方法时将无法确定调用哪个父类的方法。

18.zookeeper的底层原理

1.zookeeper的核心是原子广播,这个机制保证了各个server之间的数据同步。实现这个机制的协议是Zab协议,Zab协议有两种分别是恢复模式(选主)和广播模式(同步),当服务器启动或者领导者奔溃之后Zab就进入了恢复模式,当leader领导者选举出来之后且大多数server和leadr完成状态同步之后。恢复模式就关闭了。广播模式保证了leader和server之间的状态同步。

2.为了保证顺序的一致性。zookeeper采用了递增的事务id(zxid)来标志事务,所有的提议在被提出的时候就加上了zxid,具体实现中是通过64位的数组,高32位是epoch用来表示与leader关系是否改变,每次一个leader选举出来且大多数Server都会产生一个新的epoch,标识当前属于哪个leader的统治时期,低32位是用来用来递增计数。
(zxid(64位数字)=高32+低32位=leader统治时期+计数器)

3.每个server的工作过程中有三种状态
(1)looking,当前server不知道leader是谁?正在搜寻
(2)leading:选举出来的leader
(3)following:leader已经选举出来了,当前server与leader进行同步。当前是server角色是learner学习者。

2.zookeeper的读写机制
zookeeper是由一个server或者多个server组成的。一个集群中zookeeper通过自己的选举投票机制保证只有一个leader和多个follower。每个server保存一分数据副本。全局的数据副本一致,分布式读写。更新请求转发,有leader实时。
 

19.Eueaka的底层原理

Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,则使用 Eureka Client 连接到 Eureka Server 并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。Spring Cloud 的一些其他子模块(例如 Gateway)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的业务逻辑

服务注册:将服务信息注册进入注册中心
服务发现:从服务注册中心上获取服务信息
实质:存key服务名 取key value调用地址

20.springmvc的主要组件

(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等等)

21.springmvc的优点

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

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

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

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

22.springboot的注解

启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。

@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

@ComponentScan:Spring组件扫描。

23.spring中bean的作用域

① singleton

使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。

② prototype

使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。

③ request

该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。

④ session

该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。

⑤ global-session

该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。

24.Mybatis的一级缓存和二级缓存(作用域)?

Mybatis的一级缓存是指Session缓存,一级缓存的作用域默认是SqlSession。Mybatis默认开启一级缓存。

在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;第二次以后则直接去一级缓存中取。当执行的SQL查询中间发生了增删改的操作,mybatis会把SqlSession的缓存清空。

一级缓存失效的情况

        SqlSession不同;
        SqlSession相同,查询条件不同。因为缓存条件不同,缓存中还没有数据。
        SqlSession相同,在两次相同查询条件中间执行过增删改操作。
       SqlSession相同,手动清空了一级缓存。(如果SqlSession去执行commit操作(执行插入。更新、删除),清空SqlSession中的一级缓存,这样做的目的是为了让缓存中存储的是最新的消息,避免脏读。)
Mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个nameSpace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。一个会话,查询一条数据,这个数据会被放在当前会话的一级缓存中;如果会话被关闭了,一级缓存汇总的数据会被保存到二级缓存。新的会话查询信息就会参照二级缓存。

二级缓存的使用原则

只能在一个命名空间下使用二级缓存。由于二级缓存中的数据是基于nameSpace的,即不同nameSpace中的数据互不干扰。在多个nameSpace中若均存在对同一个表的操作,那么这多个nameSpace中的数据可能就会出现不一致现象。
在单表上使用二级缓存。如果一个表与其他表有关联关系,那么就非常有可能存在多个nameSpace对同一数据的操作。而不同nameSpace中的数据互不干扰,所以就有可能出现多个nameSpace中的数据不一致现象。
查询多于修改时使用二级缓存。在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。


三、mybatis有二级缓存,为什么还要用redis?
mybatis一级缓存作用域是session,session在commit之后缓存就消失了。
mybatis二级缓存作用域是sessionFactory,该缓存是以nameSpace为单位的(也就是一个Mapper.xml文件),不同nameSpace下操作互不影响。
所有对数据表的改变操作都会刷新缓存,但是一般不用二级缓存。例如,在UserMapper.xml中有大多数针对User表的操作,但是在另外一个XXXMapper.xml中,还有针对user单表的操作,这会导致user在两个命名空间下的数据不一致。
如果UserMapper.xml中做了刷新缓存的操作,在XXXMapper.xml中缓存依然有效,如果针对user单表查询,使用缓存的结果可能会不正确,读到脏数据。
redis很好的解决了这个问题,而且比之一、二级缓存的更好,redis可以搭建在其他服务器上,缓存容量可扩展,redis可以灵活的使用在需要的缓存数据上。
 

25.spring的常用注解

@Component

主要用于将java类注入到spring框架中,相当于在XML配置文件中的

<bean id=”xxx” class=”xxx”/>

@Autowired

它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作(DI依赖注入工作)

@Qualifier

指定注入Bean的名称

@Scope

来显式指定Bean作用范围

@Controller

用于对Controller实现类注解

@Service

用于对Service实现类注解

@Repository

用于对Dao实现类注解

26.spring的事务

spring的事务是一个统一的模型
(1)指定要使用的事务管理器实现类,使用<bean>
(2)指定哪些类,哪些方法需要加入事务的功能
(3)指定方法需要的事务的隔离级别、传播行为、超时时间。

27.hashmap和concurrentHashMap的区别

concurrentHashMap线程安全 速度慢 分段锁 不是整体锁

hashMap线程不安全 速度快

还有Hashtable 线程安全,速度最慢 因为是整体的锁,基本被淘汰了

concurrentHashMap 适合用于并发编程,其实就是在要求线程安全的时候用它

因为hashmap虽然效率高但是线程不安全,hashtable线程安全缺效率太低’

28.说一下悲观锁和乐观锁

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到他拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程)

乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。

29.Http常见的状态码

200 – 请求成功

301 – 资源(网页等)被永久转移到其它URL

404 – 请求的资源(网页等)不存在

500 – 内部服务器错误

TTP状态码共分为5种类型:

分类分类描述

1**信息,服务器收到请求,需要请求者继续执行操作

2**成功,操作被成功接收并处理

3**重定向,需要进一步的操作以完成请求

4**客户端错误,请求包含语法错误或无法完成请求

5**服务器错误,服务器在处理请求的过程中发生了错误

30.Mybatis中#和$的区别

1、#{}是占位符        ${}是拼接符

2、#{} 为参数占位符 ,即sql 预编译;${} 为字符串替换,即 sql 拼接

3、变量替换后,#{} 对应的变量自动加上单引号 '',${} 对应的变量不会加上单引号 ''

4、#{} 能防止sql 注入,${} 不能防止sql 注入

31.SpringMVC中常用注解

@RequestMapping

RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。

@PathVariable

用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数

@requestParam

@requestParam主要用于在SpringMVC后台控制层获取参数

@ResponseBody 自动将返回值转换为json字符串

32.sleep和wait的区别

Sleep是Thread类中的静态方法

Wait是object类中的方法

Sleep方法不释放锁

Wait方法释放锁

Sleep方法是用来暂停线程的

Wait方法是用来线程交互的的

Sleep方法用完以后线程会自动苏醒

Wait方法用完以后不会自动苏醒需要调用它的notify方法或者notifyAll方法去唤醒(notify随机唤醒一个wait线程 notifyAll唤醒所有wait线程)

33.为什么使用并发编程

1.带来性能上的提升

2.他能提高程序的容错率

3.方便我们进行业务的拆分

34.FastDFS好处

采用分布式文件系统可以将多个地点的文件系统通过网络连接起来,组成一个文件系统网络,结点之间通过网络进行通信,一台文件系统的存储和传输能力有限,我们让文件在多台计算机上存储,通过多台计算共同传输。

为什么要使用fastDFS呢?
​ 上边介绍的NFS、GFS都是通用的分布式文件系统,通用的分布式文件系统的优点的是开发体验好,但是系统复杂性高、性能一般,而专用的分布式文件系统虽然开发体验性差,但是系统复杂性低并且性能高。fastDFS非常适合存储图片等那些小文件,fastDFS不对文件进行分块,所以它就没有分块合并的开销,fastDFS网络通信采用socket,通信速度很快。

客户端去请求fastdfs中的tracker值,tracker进行负载均衡,把相关内容去转发到对应的storage中,storage中分为不同组,分为逻辑组,根据逻辑组去找具体真实内容,把真实内容返回去。

35.转发和重定向的区别

转发

1.转发的操作是在服务器内部进行的跳转,整个过程中没有产生新的请求.

2.因为整个跳转过程中,没有产生新的请求,所以可以实现数据的共享.

3.因为转发的操作是咋子服务器内部进行的跳转,整个过程浏览器并不知道,所以浏览器中的URL地址不会改变.

重定向

1.在重定向的整个过程中,产生了新的请求

2.因为在整个重定向中产生了新的请求,所以无法实现,重定向前后的数据共享

3.由于整个过程浏览器是可知的,所以URL地址是重定向以后的地址.

36.堆(heap)和栈(stack)和方法区

栈(先进后出)多个线程是多个栈,每个线程都有自己的栈区域。栈帧中存放的是局部变量。或者说栈中存放的是基本数据类型和堆中对象的引用。

栈中有栈帧(main方法和他调用的方法的区域),在栈帧中有局部变量表,操作数栈(程序在运行过程中,操作数临时存放的区域),动态链接,方法出口(返回到调用这个方法的那个位置去)。

堆中存放的是new出来的对象。

堆线程共享,栈线程私有。堆空间大于栈。

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。

方法区(类加载子系统将字节码文件加载到方法区中)存放常量,静态变量和类元信息(类的组成信息)。

37.JVM

答:JVM是java虚拟机,他的内存结构主要分为三部分,类加载子系统,运行时数据区,执行引擎。在运行时数据区中又分为堆,栈,方法区,本地方法栈,程序计数器。如果要说JVM的原理,就不得不谈到Java程序执行的原理。首先,我们写好的Java源代码会被编译器编译成.class字节码文件;然后当我们运行java程序时,JVM的类加载器就会将字节码文件加载到它的运行时存储区中;接下来JVM会找到main方法作为程序执行的入口开始执行程序。在整个运行过程中,CPU会在空闲时进行垃圾回收。

38.类装载的执行过程

类装载分为以下 5 个步骤:

· 加载:根据查找路径找到相应的 class 文件然后导入;

· 检查:检查加载的 class 文件的正确性;

· 准备:给类中的静态变量分配内存空间;

· 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;

· 初始化:对静态变量和静态代码块执行初始化工作。

39.Java中有哪些引用类型

· 强引用:发生 gc 的时候不会被回收。

· 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。

· 弱引用:有用但不是必须的对象,在下一次GC时会被回收。

· 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

40.说一下序列化和反序列化

  • 概念:
  • 序列化:把对象转换为字节序列的过程称为对象的序列化。
  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
  • 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象,这个相反的过程又称为反序列化。
  • 什么时候需要序列化?
  • a. 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
  • b. 当你想用套接字在网络上传送对象的时候;
  • c. 当你想通过RMI传输对象的时候;(远程方法调用)
  • 总结
  • a. ObjectOutputStream代表对象输出流:它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
  • b. ObjectInputStream代表对象输入流:它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回;
  • c. transient 修饰的属性,是不会被序列化的;
  • d. 静态static的属性,是不会被序列化的;

41.说一下接口和抽象方法的区别

1、一个类继承抽象类需要实现其中的抽象方法,否则该类还是需要被声明为抽象类,当一个类实现了接口,就必须实现其所有方法。

2、接口相比于抽象类更抽象,抽象类中可以定义构造器,可以有抽象方法和实现方法,接口则没有构造方法,且方法全是抽象方法。

3、接口中的方法默认是public的,抽象类的方法没有限制

4、接口中定义的成员变量实际上都是常量(public static final),抽象类中的成员变量没有限制

5、抽象类不一定要有抽象方法,而有抽象方法的类必须声明为抽象类

42.说一下对AOP的理解和依赖注入的方式

1. 接口注入

2. Setter方法注入

3. 构造方法注入

43.说一下for和foreach的区别

1.在长度固定的情况下for循环效率比foreach高

2.Foreach适用于只是进行集合或数组遍历,for则在较复杂的循环中效率更高

3.如果对集合中的值进行修改,就要用for循环,foreach不能用于增加,删除等复杂的操作。

44.session和cookie的区别

1,存储的位置不同

Session:服务端
Cookie:客户端

存储的数据格式不同

Session:value为对象,Object类型
Cookie:value为字符串,如果我们存储一个对象,这个时候,就需要将对象转换为JSON

存储的数据大小

Session:受服务器内存控制
Cookie:一般来说,最大为4k

生命周期不同

Session:服务器端控制,默认是30分钟,注意,当用户关闭了浏览器,session并不会消失

Cookie:客户端控制,其实是客户端的一个文件,分两种情况
1,默认的是会话级的cookie,这种随着浏览器的关闭而消失,比如保存sessionId的cookie
2,非会话级cookie,通过设置有效期来控制,比如这种“7天免登录”这种功能,
就需要设置有效期,setMaxAge

cookie跟session之间的联系

http协议是一种无状态协议,服务器为了记住用户的状态,我们采用的是Session的机制
而Session机制背后的原理是,服务器会自动生成会话级的cookie来保存session的标识.

45.AOP中的日志

通过注解+切面类的方式 来进行相关的定义,我们只需要再所需的业务方法上加入该注解就能进行日志记录,可以再需要的方法加上注解来进行记录日志,比较灵活。切面类也可以根据自己的需求来实现相关功能,

46.创建对象的四种方式

1.使用new关键字创建对象

2.使用反射创建对象

(1)使用Class类的forName方法

(2)使用ClassLoader类的loadClass方法

(3)使用Constructor类的newInstance方法创建对象

3.调用Clone方法创建对象

4.使用反序列化创建对象

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值