2022年面试,整理全网初、中、高级常见 Java 面试题

面试题答案见微信小程序 “Java 精选面试题”,3000 + 道面试题。内容持续更新中包含基础、集合、并发、JVM、Spring、Spring MVC、Spring Boot、Spring Cloud、Dubbo、MySQL、Redis、MyBaits、Zookeeper、Linux、数据结构与算法、项目管理工具、消息队列、设计模式、Nginx、常见 BUG 问题、网络编程等。

————————————————

面向对象编程有哪些特征?

一、抽象和封装

类和对象体现了抽象和封装

抽象就是解释类与对象之间关系的词。类与对象之间的关系就是抽象的关系。一句话来说明:类是对象的抽象,而对象则是类得特例,即类的具体表现形式。

封装两个方面的含义:一是将有关数据和操作代码封装在对象当中,形成一个基本单位,各个对象之间相对独立互不干扰。二是将对象中某些属性和操作私有化,已达到数据和操作信息隐蔽,有利于数据安全,防止无关人员修改。把一部分或全部属性和部分功能(函数)对外界屏蔽,就是从外界(类的大括号之外)看不到,不可知,这就是封装的意义。

二、继承

面向对象的继承是为了软件重用,简单理解就是代码复用,把重复使用的代码精简掉的一种手段。如何精简,当一个类中已经有了相应的属性和操作的代码,而另一个类当中也需要写重复的代码,那么就用继承方法,把前面的类当成父类,后面的类当成子类,子类继承父类,理所当然。就用一个关键字 extends 就完成了代码的复用。

三、多态

没有继承就没有多态,继承是多态的前提。虽然继承自同一父类,但是相应的操作却各不相同,这叫多态。由继承而产生的不同的派生类,其对象对同一消息会做出不同的响应。 JDK、JRE、JVM 之间有什么关系?

1、JDK

JDK (Java development Toolkit),JDK 是整个 Java 的核心,包括了 Java 的运行环境(Java Runtime Environment),一堆的 Java 工具(Javac,java,jdb 等)和 Java 基础的类库(即 Java API 包括 rt.jar).

Java API 是 Java 的应用程序的接口,里面有很多写好的 Java class,包括一些重要的结构语言以及基本图形,网络和文件 I/O 等等。

2、JRE

JRE(Java Runtime Environment),Java 运行环境。在 Java 平台下,所有的 Java 程序都需要在 JRE 下才能运行。只有 JVM 还不能进行 class 的执行,因为解释 class 的时候,JVM 需调用解释所需要的类库 lib。JRE 里面有两个文件夹 bin 和 lib,这里可以认为 bin 就是 JVM,lib 就是 JVM 所需要的类库,而 JVM 和 lib 合起来就称 JRE。

JRE 包括 JVM 和 JAVA 核心类库与支持文件。与 JDK 不同,它不包含开发工具 ----- 编译器,调试器,和其他工具。

3、JVM

JVM:Java Virtual Machine(Java 虚拟机)JVM 是 JRE 的一部分,它是虚拟出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM 有自己完善的硬件构架,入处理器,堆栈,寄存器等,还有相应的指令系统。

JVM 是 Java 实现跨平台最核心的部分,所有的 Java 程序会首先被编译为 class 的类文件,JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用。Java 面对不同操作系统使用不同的虚拟机,一次实现了跨平台。JVM 对上层的 Java 源文件是不关心的,它关心的只是由源文件生成的类文件 如何使用命令行编译和运行 Java 文件?

编译和运行 Java 文件,需了解两个命令:

1)javac 命令:编译 java 文件;使用方法: javac Hello.java ,如果不出错的话,在与 Hello.java 同一目录下会生成一个 Hello.class 文件,这个 class 文件是操作系统能够使用和运行的文件。

2)java 命令: 作用:运行.class 文件;使用方法:java Hello, 如果不出错的话,会执行 Hello.class 文件。注意:这里的 Hello 后面不需要扩展名。

新建文件,编写代码如下:

public class Hello{


public static void main(String[] args){



System.out.println("Hello world,欢迎关注微信公众号“Java精选”!");


}
}

文件命名为 Hello.java,注意后缀为 “java”。

打开 cmd,切换至当前文件所在位置,执行 javac Hello.java,该文件夹下面生成了一个 Hello.class 文件

输入 java Hello 命令,cmd 控制台打印出代码的内容 Hello world,欢迎关注微信公众号 “Java 精选”! 说说常用的集合有哪些?

Map 接口和 Collection 接口是所有集合框架的父接口

Collection 接口的子接口包括:Set 接口和 List 接口。

Set 中不能包含重复的元素。List 是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。

Map 接口的实现类主要有:HashMap、Hashtable、ConcurrentHashMap 以及 TreeMap 等。Map 不能包含重复的 key,但是可以包含相同的 value。根据键得到值,对 map 集合遍历时先得到键的 set 集合,对 set 集合进行遍历,得到相应的值。

Set 接口的实现类主要有:HashSet、TreeSet、LinkedHashSet 等

List 接口的实现类主要有:ArrayList、LinkedList、Stack 以及 Vector 等

Iterator,所有的集合类,都实现了 Iterator 接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:

hasNext () 是否还有下一个元素

next () 返回下一个元素

remove () 删除当前元素 进程与线程之间有什么区别?

进程是系统中正在运行的一个程序,程序一旦运行就是进程。

进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程与进程的一个主要区别是,统一进程内的一个主要区别是,同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。

线程是进程的一个实体,是进程的一条执行路径。

线程是进程的一个特定执行路径。当一个线程修改了进程的资源,它的兄弟线程可以立即看到这种变化。 什么是 JVM?

Java 程序的跨平台特性主要是指字节码文件可以在任何具有 Java 虚拟机的计算机或者电子设备上运行,Java 虚拟机中的 Java 解释器负责将字节码文件解释成为特定的机器码进行运行。

因此在运行时,Java 源程序需要通过编译器编译成为.class 文件。

众所周知 java.exe 是 java class 文件的执行程序,但实际上 java.exe 程序只是一个执行的外壳,它会装载 jvm.dll(windows 下,下皆以 windows 平台为例,linux 下和 solaris 下其实类似,为:libjvm.so),这个动态连接库才是 java 虚拟机的实际操作处理所在。

JVM 是 JRE 的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

JVM 有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java 语言最重要的特点就是跨平台运行。

使用 JVM 就是为了支持与操作系统无关,实现跨平台。所以,JAVA 虚拟机 JVM 是属于 JRE 的,而现在我们安装 JDK 时也附带安装了 JRE (当然也可以单独安装 JRE)。 什么是事务?

事务(transaction)是指数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)。

通俗的说就是事务可以作为一个单元的一组有序的数据库操作。如果组中的所有操作都成功,则认为事务成功,即使只有一个操作失败,事务也不成功。如果所有操作完成,事务则提交,其修改将作用于所有其他数据库进程。如果一个操作失败,则事务将回滚,该事务所有操作的影响都将取消。 MySQL 事务都有哪些特性?

事务的四大特性:

1 、原子性

事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

2 、一致性

事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。

3 、隔离性

一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

4 、持续性

也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。 MyBatis 是什么框架?

MyBatis 框架是一个优秀的数据持久层框架,在实体类和 SQL 语句之间建立映射关系,是一种半自动化的 ORM 实现。其封装性要低于 Hibernate,性能优秀,并且小巧。

ORM 即对象 / 关系数据映射,也可以理解为一种数据持久化技术。

MyBatis 的基本要素包括核心对象、核心配置文件、SQL 映射文件。

数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。 什么是 Redis?

redis 是一个高性能的 key-value 数据库,它是完全开源免费的,而且 redis 是一个 NOSQL 类型数据库,是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案,是一个非关系型的数据库。但是,它也是不能替代关系型数据库,只能作为特定环境下的扩充。

redis 是一个以 key-value 存储的数据库结构型服务器,它支持的数据结构类型包括:字符串(String)、链表(lists)、哈希表(hash)、集合(set)、有序集合(Zset)等。为了保证读取的效率,redis 把数据对象都存储在内存当中,它可以支持周期性的把更新的数据写入磁盘文件中。而且它还提供了交集和并集,以及一些不同方式排序的操作。 什么是 Spring 框架?

Spring 中文翻译过来是春天的意思,被称为 J2EE 的春天,是一个开源的轻量级的 Java 开发框架, 具有控制反转(IoC)和面向切面(AOP)两大核心。Java Spring 框架通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

Spring 框架不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。

1)IOC 控制反转

对象创建责任的反转,在 Spring 中 BeanFacotory 是 IOC 容器的核心接口,负责实例化,定位,配置应用程序中的对象及建立这些对象间的依赖。XmlBeanFacotory 实现 BeanFactory 接口,通过获取 xml 配置文件数据,组成应用对象及对象间的依赖关系。

Spring 中有 3 中注入方式,一种是 set 注入,另一种是接口注入,另一种是构造方法注入。

2)AOP 面向切面编程

AOP 是指纵向的编程,比如两个业务,业务 1 和业务 2 都需要一个共同的操作,与其往每个业务中都添加同样的代码,通过写一遍代码,让两个业务共同使用这段代码。

Spring 中面向切面编程的实现有两种方式,一种是动态代理,一种是 CGLIB,动态代理必须要提供接口,而 CGLIB 实现是由 = 有继承。 什么是 Spring MVC 框架?

Spring MVC 属于 Spring FrameWork 的后续产品,已经融合在 Spring Web Flow 中。

Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。

使用 Spring 可插入 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用 Spring 中的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(已基本淘汰),Struts2(老项目还在使用或已重构)等。

通过策略接口,Spring 框架是高度可配置的且包含多种视图技术,如 JavaServer Pages(JSP)技术、Velocity、Tiles、iText 和 POI 等。

Spring MVC 框架并不清楚或限制使用哪种视图,所以不会强迫开发者只使用 JSP 技术。

Spring MVC 分离了控制器、模型对象、过滤器以及处理程序对象的角色,这种分离让它们更容易进行定制。 什么是 Spring Boot 框架?

Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。

Spring Boot 框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

2014 年 4 月发布第一个版本的全新开源的 Spring Boot 轻量级框架。它基于 Spring4.0 设计,不仅继承了 Spring 框架原有的优秀特性,而且还通过简化配置来进一步简化了 Spring 应用的整个搭建和开发过程。

另外 Spring Boot 通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。 什么是 Spring Cloud 框架?

Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。

Spring Cloud 并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

Spring Cloud 的子项目,大致可分成两大类:

一类是对现有成熟框架 “Spring Boot 化” 的封装和抽象,也是数量最多的项目;

第二类是开发一部分分布式系统的基础设施的实现,如 Spring Cloud Stream 扮演的是 kafka, ActiveMQ 这样的角色。

对于快速实践微服务的开发者来说,第一类子项目已经基本足够使用,如:

1)Spring Cloud Netflix 是对 Netflix 开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST 客户端、请求路由等;

2)Spring Cloud Config 将配置信息中央化保存,配置 Spring Cloud Bus 可以实现动态修改配置文件;

3)Spring Cloud Bus 分布式消息队列,是对 Kafka, MQ 的封装;

4)Spring Cloud Security 对 Spring Security 的封装,并能配合 Netflix 使用;

5)Spring Cloud Zookeeper 对 Zookeeper 的封装,使之能配置其它 Spring Cloud 的子项目使用;

6)Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于 Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能。注意的是从 2.x 起,官方不会继续开源,若需要使用 2.x,风险还是有的。但是我觉得问题并不大,eureka 目前的功能已经非常稳定,就算不升级,服务注册 / 发现这些功能已经够用。consul 是个不错的替代品,还有其他替代组件,后续篇幅会有详细赘述或关注微信公众号 “Java 精选”,有详细替代方案源码分享。 Spring Cloud 框架有哪些优缺点?

Spring Cloud 优点:

1)服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率,每个模块可以独立开发和部署、代码耦合度低;

2)可以更精准的制定优化服务方案,提高系统的可维护性,每个服务可以单独进行部署,升级某个模块的时候只需要单独部署对应的模块服务即可,效率更高;

3)微服务架构采用去中心化思想,服务之间采用 Restful 等轻量级通讯,比 ESB 更轻量,模块专一性提升,每个模块只需要关心自己模块所负责的功能即可,不需要关心其他模块业务,专一性更高,更便于功能模块开发和拓展;

4)技术选型不再单一,由于每个模块是单独开发并且部署,所以每个模块可以有更多的技术选型方案,如模块 1 数据库选择 mysql,模块 2 选择用 oracle 也是可以的;

5)适于互联网时代,产品迭代周期更短。系统稳定性以及性能提升,由于微服务是几个服务共同组成的项目或者流程,因此相比传统单一项目的优点就在于某个模块提供的服务宕机过后不至于整个系统瘫痪掉,而且微服务里面的容灾和服务降级机制也能大大提高项目的稳定性;从性能而言,由于每个服务是单独部署,所以每个模块都可以有自己的一套运行环境,当某个服务性能低下的时候可以对单个服务进行配置或者代码上的升级,从而达到提升性能的目的。

Spring Cloud 缺点:

1)微服务过多,治理成本高,不利于维护系统,服务之间接口调用成本增加,相比以往单项目的时候调用某个方法或者接口可以直接通过本地方法调用就能够完成,但是当切换成微服务的时候,调用方式就不能用以前的方式进行调试、目前主流采用的技术有 http api 接口调用、RPC、WebService 等方式进行调用,调用成本比单个项目的时候有所增加;

2)分布式系统开发的成本高(容错,分布式事务等)对团队挑战大

2)独立的数据库,微服务产生事务一致性的问题,由于各个模块用的技术都各不相同、而且每个服务都会高并发进行调用,就会存在分布式事务一致性的问题;

3)分布式部署,造成运营的成本增加、相比较单个应用的时候,运营人员只需要对单个项目进行部署、负载均衡等操作,但是微服务的每个模块都需要这样的操作,增加了运行时的成本;

4)由于整个系统是通过各个模块组合而成的,因此当某个服务进行变更时需要对前后涉及的所有功能进行回归测试,测试功能不能仅限于当个模块,增加了测试难度和测试成本;

总体来说优点大过于缺点,目前看来 Spring Cloud 是一套非常完善的微服务框架,目前很多企业开始用微服务,Spring Cloud 的优势是显而易见的。 什么是消息队列?

MQ 全称为 Message Queue 消息队列(MQ)是一种应用程序对应用程序的通信方法。

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

MQ 是消费生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中的消息。

消息生产者只需要把消息发布到 MQ 中而不用管谁来获取,消息消费者只管从 MQ 中获取消息而不管是谁发布的消息,这样生产者和消费者双方都不用清楚对方的存在。 

目前在生产环境,使用较多的消息队列有 ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ 等。 消息队列有哪些应用场景?

列举消息队列场景,异步处理,应用解耦,流量削锋,日志处理和消息通讯五个场景。

1、异步处理场景

用户注册后,需要发注册邮件和注册短信。传统的做法有两种

1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。

2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。

按照以上描述,用户的响应时间相当于是注册信息写入数据库的时间,也就是 50 毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是 50 毫秒。因此采用消息队列的方式,系统的吞吐量提高到每秒 20 QPS。比串行提高了 3 倍,比并行提高了两倍。

2、应用解耦场景

用户购买物品下单后,订单系统需要通知库存系统。传统的做法是订单系统调用库存系统的接口。该模式的缺点:

1)假如库存系统无法访问,则订单减库存将失败,从而导致订单失败;

2)订单系统与库存系统耦合;

如何解决以上问题呢?

订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。

库存系统:订阅下单的消息,采用拉 / 推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。

假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后订单系统写入消息队列就不再关心其他的后续操作,从而实现订单系统与库存系统的应用解耦。

3、流量削锋场景

流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中被广泛应用。

秒杀活动,一般会因为流量并发过大,导致流量暴增,应用宕机,从而为解决该问题,一般在应用前端加入消息队列。

控制活动的人数,缓解短时间内高流量压垮应用。当用户的请求,服务器接收后,先写入消息队列。如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面,而其秒杀业务根据消息队列中的请求信息,再做后续处理。

4、日志处理场景

日志处理是指将消息队列用在日志处理中,比如 Kafka 的应用,解决大量日志传输的问题。日志采集客户端,负责日志数据采集,定时写受写入 Kafka 队列,而 Kafka 消息队列,负责日志数据的接收,存储和转发,日志处理应用订阅并消费 kafka 队列中的日志数据。

5、消息通讯

消息通讯是指消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。 什么是 Linux 操作系统?

Linux 全称 GNU/Linux,是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 的多用户、多任务、支持多线程和多 CPU 的操作系统。

伴随着互联网的发展,Linux 得到了来自全世界软件爱好者、组织、公司的支持。它除了在服务器方面保持着强劲的发展势头以外,在个人电脑、嵌入式系统上都有着长足的进步。使用者不仅可以直观地获取该操作系统的实现机制,而且可以根据自身的需要来修改完善 Linux,使其最大化地适应用户的需要。 

Linux 不仅系统性能稳定,而且是开源软件。其核心防火墙组件性能高效、配置简单,保证了系统的安全。

在很多企业网络中,为了追求速度和安全,Linux 不仅仅是被网络运维人员当作服务器使用,甚至当作网络防火墙,这是 Linux 的一大亮点。

Linux 具有开放源码、没有版权、技术社区用户多等特点,开放源码使得用户可以自由裁剪,灵活性高,功能强大,成本低。尤其系统中内嵌网络协议栈,经过适当的配置就可实现路由器的功能。这些特点使得 Linux 成为开发路由交换设备的理想开发平台。 什么是数据结构?

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

数据结构(data structure)是带有结构特性的数据元素的集合,它研究的是数据的逻辑结构和数据的物理结构以及它们之间的相互关系,并对这种结构定义相适应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。

简而言之,数据结构是相互之间存在一种或多种特定关系的数据元素的集合,即带 “结构” 的数据元素的集合。“结构” 就是指数据元素之间存在的关系,分为逻辑结构和存储结构。

数据的逻辑结构和物理结构是数据结构的两个密切相关的方面,同一逻辑结构可以对应不同的存储结构。算法的设计取决于数据的逻辑结构,而算法的实现依赖于指定的存储结构。

数据结构的研究内容是构造复杂软件系统的基础,它的核心技术是分解与抽象。

通过分解可以划分出数据的 3 个层次;再通过抽象,舍弃数据元素的具体内容,就得到逻辑结构。类似地,通过分解将处理要求划分成各种功能,再通过抽象舍弃实现细节,就得到运算的定义。

上述两个方面的结合可以将问题变换为数据结构。这是一个从具体(即具体问题)到抽象(即数据结构)的过程。

然后,通过增加对实现细节的考虑进一步得到存储结构和实现运算,从而完成设计任务。这是一个从抽象(即数据结构)到具体(即具体实现)的过程。 什么是设计模式?

设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路,通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性好。使用设计模式最终的目的是实现代码的高内聚和低耦合。

高内聚低耦合是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。

目的是使程序模块的可重用性、移植性大大增强。

通常程序结构中各模块的内聚程度越高,模块间的耦合程度就越低。

内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事,它描述的是模块内的功能联系;耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 什么是 Zookeeper?

ZooKeeper 由雅虎研究院开发,ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,后来托管到 Apache,是 Hadoop 和 Hbase 的重要组件。

ZooKeeper 是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务。

ZooKeeper 的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

ZooKeeper 包含一个简单的原语集,提供 Java 和 C 的接口。

ZooKeeper 代码版本中,提供了分布式独享锁、选举、队列的接口,代码在 $zookeeper_home\src\recipes。其中分布锁和队列有 Java 和 C 两个版本,选举只有 Java 版本。

于 2010 年 11 月正式成为 Apache 的顶级项目。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

分布式应用程序可以基于 ZooKeeper 实现数据发布与订阅、负载均衡、命名服务、分布式协调与通知、集群管理、Leader 选举、分布式锁、分布式队列等功能。 应用服务 8080 端口被意外占用如何解决?

1)按键盘 WIN+R 键,打开后在运行框中输入 “CMD” 命令,点击确定。

2)在 CMD 窗口,输入 “netstat -ano” 命令,按回车键,即可查看所有的端口占用情况。

3)找到本地地址一览中类似 “0.0.0.0:8080” 信息,通过此列查看 8080 端口对应的程序 PID。

4)打开任务管理器,详细信息找到对应的应用 PID(若不存在通过设置可以调出来),右键结束任务即可。 什么是 Dubbo 框架?

Dubbo(读音 [ˈdʌbəʊ])是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。

Dubbo 提供了六大核心能力:面向接口代理的高性能 RPC 调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。

Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

核心组件

Remoting: 网络通信框架,实现了 sync-over-async 和 request-response 消息机制;

RPC: 一个远程过程调用的抽象,支持负载均衡、容灾和集群功能;

Registry: 服务目录框架用于服务的注册和服务事件发布和订阅。 什么是 Maven?

Maven 即为项目对象模型(POM),它可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具软件。

Maven 除了以程序构建能力为特色之外,还提供高级项目管理工具。

由于 Maven 的缺省构建规则有较高的可重用性,所以常常用两三行 Maven 构建脚本就可以构建简单的项目。

由于 Maven 面向项目的方法,许多 Apache Jakarta 项目发文时使用 Maven,而且公司项目采用 Maven 的比例在持续增长,相比较 Gradle,在之后的篇幅中会说明,欢迎大家关注微信公众号 “Java 精选”。

Maven 这个单词来自于意第绪语(犹太语),意为知识的积累,最初在 Jakata Turbine 项目中用来简化构建过程。

当时有一些项目(有各自 Ant build 文件),仅有细微的差别,而 JAR 文件都由 CVS 来维护。于是希望有一种标准化的方式构建项目,一个清晰的方式定义项目的组成,一个容易的方式发布项目的信息,以及一种简单的方式在多个项目中共享 JARs。 应用层中常见的协议都有哪些?

应用层协议(application layer protocol)定义了运行在不同端系统上的应用程序进程如何相互传递报文。

应用层协议

1)DNS:一种用以将域名转换为 IP 地址的 Internet 服务,域名系统 DNS 是因特网使用的命名系统,用来把便于人们使用的机器名字转换为 IP 地址。

现在顶级域名 TLD 分为三大类:国家顶级域名 nTLD;通用顶级域名 gTLD;基础结构域名。

域名服务器分为四种类型:根域名服务器;顶级域名服务器;本地域名服务器;权限域名服务器。

2)FTP:文件传输协议 FTP 是因特网上使用得最广泛的文件传送协议。FTP 提供交互式的访问,允许客户指明文件类型与格式,并允许文件具有存取权限。

基于客户服务器模式,FTP 协议包括两个组成部分,一是 FTP 服务器,二是 FTP 客户端,提供交互式的访问面向连接,使用 TCP/IP 可靠的运输服务,主要功能:减少 / 消除不同操作系统下文件的不兼容性 。

3)telnet 远程终端协议:telnet 是一个简单的远程终端协议,它也是因特网的正式标准。又称为终端仿真协议。

4)HTTP:超文本传送协议,是面向事务的应用层协议,它是万维网上能够可靠地交换文件的重要基础。http 使用面向连接的 TCP 作为运输层协议,保证了数据的可靠传输。

5)电子邮件协议 SMTP:即简单邮件传送协议。SMTP 规定了在两个相互通信的 SMTP 进程之间应如何交换信息。SMTP 通信的三个阶段:建立连接、邮件传送、连接释放。

6)POP3:邮件读取协议,POP3 (Post Office Protocol 3) 协议通常被用来接收电子邮件。

7)远程登录协议 (Telnet):用于实现远程登录功能。

8)SNMP:简单网络管理协议。由三部分组成:SNMP 本身、管理信息结构 SMI 和管理信息 MIB。SNMP 定义了管理站和代理之间所交换的分组格式。SMI 定义了命名对象类型的通用规则,以及把对象和对象的值进行编码。MIB 在被管理的实体中创建了命名对象,并规定类型。 Java 中的关键字都有哪些?

1)48 个关键字:abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while。

2)2 个保留字(目前未使用,以后可能用作为关键字):goto、const。

3)3 个特殊直接量(直接量是指在程序中通过源代码直接给出的值):true、false、null。 Java 中基本类型都有哪些?

Java 的类型分成两种,一种是基本类型,一种是引用类型。其中 Java 基本类型共有八种。

基本类型可以分为三大类:字符类型 char,布尔类型 boolean 以及数值类型 byte、short、int、long、float、double。

数值类型可以分为整数类型 byte、short、int、long 和浮点数类型 float、double。

JAVA 中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或操作系统的改变而改变。实际上《Thinking in Java》一书作者,提到 Java 中还存在另外一种基本类型 void,它也有对应的包装类 java.lang.Void,因为 Void 是不能 new,也就是不能在堆里面分配空间存对应的值,所以将 Void 归成基本类型,也有一定的道理。

8 种基本类型表示范围如下:

byte:8 位,最大存储数据量是 255,存放的数据范围是 - 128~127 之间。

short:16 位,最大数据存储量是 65536,数据范围是 - 32768~32767 之间。

int:32 位,最大数据存储容量是 2 的 32 次方减 1,数据范围是负的 2 的 31 次方到正的 2 的 31 次方减 1。

long:64 位,最大数据存储容量是 2 的 64 次方减 1,数据范围为负的 2 的 63 次方到正的 2 的 63 次方减 1。

float:32 位,数据范围在 3.4e-45~1.4e38,直接赋值时必须在数字后加上 f 或 F。

double:64 位,数据范围在 4.9e-324~1.8e308,赋值时可以加 d 或 D 也可以不加。

boolean:只有 true 和 false 两个取值。

char:16 位,存储 Unicode 码,用单引号赋值。 为什么 Map 接口不继承 Collection 接口?

1)Map 提供的是键值对映射(即 Key 和 value 的映射),而 Collection 提供的是一组数据并不是键值对映射。

2)若果 Map 继承了 Collection 接口,那么所实现的 Map 接口的类到底是用 Map 键值对映射数据还是用 Collection 的一组数据呢?比如平常所用的 hashMap、hashTable、treeMap 等都是键值对,所以它继承 Collection 是完全没意义,而且 Map 如果继承 Collection 接口的话,违反了面向对象的接口分离原则。

接口分离原则:客户端不应该依赖它不需要的接口。

另一种定义是类间的依赖关系应该建立在最小的接口上。

接口隔离原则将非常庞大、臃肿的接口拆分成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。

接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署,让客户端依赖的接口尽可能地小。

3)Map 和 List、Set 不同,Map 放的是键值对,List、Set 存放的是一个个的对象。说到底是因为数据结构不同,数据结构不同,操作就不一样,所以接口是分开的,还是接口分离原则。 Collection 和 Collections 有什么区别?

java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection 接口在 Java 类库中有很多具体的实现。

Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式。其直接继承接口有 List 与 Set。

Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set

java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于 Java 的 Collection 框架。 堆和栈的概念,它们有什么区别和联系?

在说堆和栈之前,我们先说一下 JVM(虚拟机)内存的划分:

Java 程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java 虚拟机运行时也是要开辟空间的。JVM 运行时在内存中开辟一片内存区域,启动时在自己的内存区域中进行更细致的划分,因为虚拟机中每一片内存处理的方式都不同,所以要单独进行管理。

JVM 内存的划分有五片:

1)寄存器;

2)本地方法区;

3)方法区;

4)栈内存;

5)堆内存。

重点来说一下堆和栈:

栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for 循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。

堆内存:存储的是数组和对象(其实数组就是对象),凡是 new 建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java 有垃圾回收机制不定时的收取。

比如主函数里的语句 int [] arr=new int [3]; 在内存中是怎么被定义的:

主函数先进栈,在栈中定义一个变量 arr, 接下来为 arr 赋值,但是右边不是一个具体值,是一个实体。实体创建在堆里,在堆里首先通过 new 关键字开辟一个空间,内存在存储数据的时候都是通过地址来体现的,地址是一块连续的二进制,然后给这个实体分配一个内存地址。数组都是有一个索引,数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化(这是堆内存的特点,未初始化的数据是不能用的,但在堆里是可以用的,因为初始化过了,但是在栈里没有),不同的类型初始化的值不一样。所以堆和栈里就创建了变量和实体:

那么堆和栈是怎么联系起来的呢?

刚刚说过给堆分配了一个地址,把堆的地址赋给 arr,arr 就通过地址指向了数组。所以 arr 想操纵数组时,就通过地址,而不是直接把实体都赋给它。这种我们不再叫他基本数据类型,而叫引用数据类型。称为 arr 引用了堆内存当中的实体。可以理解为 c 或 “c++” 的指针,Java 成长自 “c++” 和 “c++” 很像,优化了 “c++”

如果当 int [] arr=null;

arr 不做任何指向,null 的作用就是取消引用数据类型的指向。

当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当做一个垃圾,在不定时的时间内自动回收,因为 Java 有一个自动回收机制,(而 “c++” 没有,需要程序员手动回收,如果不回收就越堆越多,直到撑满内存溢出,所以 Java 在内存管理上优于 “c++”)。自动回收机制(程序)自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候收不一定。

所以堆与栈的区别很明显:

1)栈内存存储的是局部变量而堆内存存储的是实体;

2)栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

3)栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。 Class.forName 和 ClassLoader 有什么区别?

在 java 中对类进行加载可以使用 Class.forName () 和 ClassLoader。 ClassLoader 遵循双亲委派模型,最终调用启动类加载器的类加载器,实现的功能是 “通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到 JVM 中。

Class.forName () 方法实际上也是调用的 ClassLoader 来实现的。

通过分析源码可以得出:最后调用的方法是 forName () 方法,方法中的第 2 个参数默认设置为 true,该参数表示是否对加载的类进行初始化,设置为 true 时会对类进行初始化,这就意味着会执行类中的静态代码块以及对静态变量的赋值等操作。

也可以自行调用 Class.forName (String name, boolean initialize,ClassLoader loader) 方法手动选择在加载类的时候是否要对类进行初始化。

JDK 源码中对参数 initialize 的描述是:if { @code true} the class will be initialized,大概意思是说:当值为 true,则加载的类将会被初始化。 为什么要使用设计模式?

1)设计模式是前人根据经验总结出来的,使用设计模式,就相当于是站在了前人的肩膀上。

2)设计模式使程序易读。熟悉设计模式的人应该能够很容易读懂运用设计模式编写的程序。

3)设计模式能使编写的程序具有良好的可扩展性,满足系统设计的开闭原则。比如策略模式,就是将不同的算法封装在子类中,在需要添加新的算法时,只需添加新的子类,实现规定的接口,即可在不改变现有系统源码的情况下加入新的系统行为。

4)设计模式能降低系统中类与类之间的耦合度。比如工厂模式,使依赖类只需知道被依赖类所实现的接口或继承的抽象类,使依赖类与被依赖类之间的耦合度降低。

5)设计模式能提高代码的重用度。比如适配器模式,就能将系统中已经存在的符合新需求的功能代码兼容新的需求提出的接口 。

6)设计模式能为常见的一些问题提供现成的解决方案。

7)设计模式增加了重用代码的方式。比如装饰器模式,在不使用继承的前提下重用系统中已存在的代码。 为什么 String 类型是被 final 修饰的?

1、为了实现字符串池

final 修饰符的作用:final 可以修饰类,方法和变量,并且被修饰的类或方法,被 final 修饰的类不能被继承,即它不能拥有自己的子类,被 final 修饰的方法不能被重写, final 修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。

String 为什么要被 final 修饰主要是为了” 安全性 “和” 效率 “的原因。

final 修饰的 String 类型,代表了 String 不可被继承,final 修饰的 char [] 代表了被存储的数据不可更改性。虽然 final 修饰的不可变,但仅仅是引用地址不可变,并不代表了数组本身不会改变。

为什么保证 String 不可变呢?

因为只有字符串是不可变,字符串池才有可能实现。字符串池的实现可以在运行时节约很多 heap 空间,不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么 String interning 将不能实现,反之变量改变它的值,那么其它指向这个值的变量值也会随之改变。

如果字符串是可变,会引起很严重的安全问题。如数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接或在 socket 编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则改变字符串指向的对象值,将造成安全漏洞。

2、为了线程安全

因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

3、为了实现 String 可创建 HashCode 不可变性

因为字符串是不可变的,所以在它创建的时候 HashCode 就被缓存了,不需要重新计算。使得字符串很适合作为 Map 键值对中的键,字符串的处理速度要快过其它的键对象。这就是 HashMap 中的键往往都使用字符串。 ​final 关键字的基本用法?

在 Java 中 final 关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面从这三个方面来了解一下 final 关键字的基本用法。

1、修饰类

当用 final 修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用 final 进行修饰。final 类中的成员变量可以根据需要设为 final,但是要注意 final 类中的所有成员方法都会被隐式地指定为 final 方法。

在使用 final 修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为 final 类。

2、修饰方法

下面这段话摘自《Java 编程思想》第四版第 143 页:

“使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的 Java 版本中,不需要使用 final 方法进行这些优化了。“

因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为 final 的。即父类的 final 方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。

final 修饰的方法表示此方法已经是 “最后的、最终的” 含义,亦即此方法不能被重写(可以重载多个 final 修饰的方法)。此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中 final 修饰的方法同时访问控制权限为 private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与 final 的矛盾,而是在子类中重新定义了新的方法。(注:类的 private 方法会隐式地被指定为 final 方法。)

3、修饰变量

final 成员变量表示常量,只能被赋值一次,赋值后值不再改变。 当 final 修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果 final 修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final 要求值,即地址的值不发生变化。

final 修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

当函数的参数类型声明为 final 时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。 如何理解 final 关键字?

1)类的 final 变量和普通变量有什么区别?

当用 final 作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且 final 变量一旦被初始化赋值之后,就不能再被赋值了。

2)被 final 修饰的引用变量指向的对象内容可变吗?

引用变量被 final 修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的

3)final 参数的问题

在实际应用中,我们除了可以用 final 修饰成员变量、成员方法、类,还可以修饰参数、若某个参数被 final 修饰了,则代表了该参数是不可改变的。如果在方法中我们修改了该参数,则编译器会提示你:

The final local variable i cannot be assigned. It must be blank and not using a compound assignment。

java 采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。 ArrayList 和 LinkedList 有什么区别?

1)ArrayList 是 Array 动态数组的数据结构,LinkedList 是 Link 链表的数据结构,此外,它们两个都是对 List 接口的实现。前者是数组队列,相当于动态数组;后者为双向链表结构,也可当作堆栈、队列、双端队列。

2)当随机访问 List 时(get 和 set 操作),ArrayList 比 LinkedList 的效率更高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。

3)当对数据进行增加和删除的操作时(add 和 remove 操作),LinkedList 比 ArrayList 的效率更高,因为 ArrayList 是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

4)从利用效率来看,ArrayList 自由性较低,因为它需要手动设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而 LinkedList 自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。

5)ArrayList 主要控件开销在于需要在 List 列表预留一定空间;而 LinkList 主要控件开销在于需要存储结点信息以及结点指针信息。 HashMap 和 HashTable 有什么区别?

Hashtable 是线程安全,而 HashMap 则非线程安全。

Hashtable 所有实现方法添加了 synchronized 关键字来确保线程同步,因此相对而言 HashMap 性能会高一些,平时使用时若无特殊需求建议使用 HashMap,在多线程环境下若使用 HashMap 需要使用 Collections.synchronizedMap () 方法来获取一个线程安全的集合。

HashMap 允许使用 null 作为 key,不过建议还是尽量避免使用 null 作为 key。HashMap 以 null 作为 key 时,总是存储在 table 数组的第一个节点上。而 Hashtable 则不允许 null 作为 key。

HashMap 继承了 AbstractMap,HashTable 继承 Dictionary 抽象类,两者均实现 Map 接口。

HashMap 的初始容量为 16,Hashtable 初始容量为 11,两者的填充因子默认都是 0.75。

HashMap 扩容时是当前容量翻倍即:capacity*2,Hashtable 扩容时是容量翻倍 + 1 即:capacity*2+1。

HashMap 和 Hashtable 的底层实现都是数组 + 链表结构实现。 线程的生命周期包括哪几个阶段?

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态。当线程启动以后,它不能一直占用着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。

线程的生命周期包含 5 个阶段,包括:新建、就绪、运行、阻塞、死亡。

新建(new Thread)

当创建 Thread 类的一个实例(对象)时,此线程进入新建状态(未被启动)。

就绪(runnable)

线程已经被启动,正在等待被分配给 CPU 时间片,也就是说此时线程正在就绪队列中排队等候得到 CPU 资源

运行(running)

线程获得 CPU 资源正在执行任务(run () 方法),此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入,线程将一直运行到结束。

堵塞(blocked)

由于某种原因导致正在运行的线程让出 CPU 并暂停自己的执行,即进入堵塞状态。

正在睡眠:用 sleep (long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

正在等待:调用 wait () 方法。(调用 motify () 方法回到就绪状态)

被另一个线程所阻塞:调用 suspend () 方法。(调用 resume () 方法恢复)

死亡(dead) 当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

自然终止:正常运行 run () 方法后终止

异常终止:调用 stop () 方法让一个线程终止运行 Thread 类中的 start () 和 run () 方法有什么区别?

Thread 类中通过 start () 方法来启动一个线程,此时线程处于就绪状态,可以被 JVM 来调度执行,在调度过程中,JVM 通过调用 Thread 类的 run () 方法来完成实际的业务逻辑,当 run () 方法结束后,此线程就会终止,所以通过 start () 方法可以达到多线程的目的。

如果直接调用线程类的 run () 方法,会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,即 start () 方法呢能够异步的调用 run () 方法,但是直接调用 run () 方法确实同步的,无法达到多线程的目的。 notify 和 notifyAll 有什么区别?

Java 中提供了 notify () 和 notifyAll () 两个方法来唤醒在某些条件下等待的线程。

当调用 notify () 方法时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。

当调用 notifyAll () 方法时,等待该锁的所有线程都会被唤醒,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁。

如果线程调用了对象的 wait () 方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll () 方法(唤醒所有 wait 线程)或 notify () 方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

也就是说,调用了 notify 后只要一个线程会由等待池进入锁池,而 notifyAll 会将该对象等待池内的所有线程移动到锁池中,等待锁竞争优先级高的线程竞争到对象锁的概率大,若某线程没有竞争到该对象锁,它将会留在锁池中,唯有线程再次调用 wait () 方法,才会重新回到等待池中。

而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,释放掉该对象锁,此时锁池中的线程会继续竞争该对象锁。

因此,notify () 和 notifyAll () 之间的关键区别在于 notify () 只会唤醒一个线程,而 notifyAll () 方法将唤醒所有线程。 什么是乐观锁,什么是悲观锁?

乐观锁

乐观锁的意思是乐观思想,即认为读多写少,遇到并发写的可能性低,每次获取数据时都认为不会被修改,因此不会上锁,但是在更新操作时会判断有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读比较写的操作。

Java 中的乐观锁基本上都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,反之失败。

悲观锁

悲观锁的意思是悲观思想,即认为写多,遇到并发写的可能性高,每次获取数据时都认为会被修改,因此每次在读写数据时都会上锁,在读写数据时就会 block 直到拿到锁。

Java 中的悲观锁就是 Synchronized、AQS 框架下的锁则是先尝试 CAS 乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。 Java 中 volatile 关键字有什么作用?

Java 语言提供了弱同步机制,即 volatile 变量,以确保变量的更新通知其他线程。

volatile 变量具备变量可见性、禁止重排序两种特性。

volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。

volatile 变量的两种特性:

变量可见性

保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的值对于其他线程是可以立即获取的。

禁止重排序

volatile 禁止了指令重排。比 sychronized 更轻量级的同步锁。在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此 volatile 变量是一种比 sychronized 关键字更轻量级的同步机制。

volatile 适合场景:一个变量被多个线程共享,线程直接给这个变量赋值。

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。如果计算机有多个 CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程可以拷贝到不同 CPU cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

适用场景

值得说明的是对 volatile 变量的单次读 / 写操作可以保证原子性的,如 long 和 double 类型变量,但是并不能保证 “i++” 这种操作的原子性,因为本质上 i++ 是读、写两次操作。在某些场景下可以代替 Synchronized。但是,volatile 的不能完全取代 Synchronized 的位置,只有在一些特殊的场景下,才能适用 volatile。

总体来说,需要必须同时满足下面两个条件时才能保证并发环境的线程安全:

1)对变量的写操作不依赖于当前值(比如 “i++”),或者说是单纯的变量赋值(boolean flag = true)。

2)该变量没有包含在具有其他变量的不变式中,也就是说,不同的 volatile 变量之间,不 能互相依赖。只有在状态真正独立于程序内其他内容时才能使用 volatile。 Spring 中常用的注解包含哪些?

1)声明 bean 的注解

@Component 组件,没有明确的角色

@Service 在业务逻辑层使用(service 层)

@Repository 在数据访问层使用(dao 层)

@Controller 在展现层使用,控制器的声明(C * 上使用)

2)注入 bean 的注解

@Autowired:由 Spring 提供

@Inject:由 JSR-330 提供

@Resource:由 JSR-250 提供

都可以注解在 set 方法和属性上,推荐注解在属性上(一目了然,少写代码)。

3)java 配置类相关注解

@Configuration 声明当前类为配置类,相当于 xml 形式的 Spring 配置(类上使用)

@Bean 注解在方法上,声明当前方法的返回值为一个 bean,替代 xml 中的方式(方法上使用)

@Configuration 声明当前类为配置类,其中内部组合了 @Component 注解,表明这个类是一个 bean(类上使用)

@ComponentScan 用于对 Component 进行扫描,相当于 xml 中的(类上使用)

@WishlyConfiguration 为 @Configuration 与 @ComponentScan 的组合注解,可以替代这两个注解

4)切面(AOP)相关注解

Spring 支持 AspectJ 的注解式切面编程。

@Aspect 声明一个切面(类上使用)

使用 @After、@Before、@Around 定义建言(advice),可直接将拦截规则(切点)作为参数。

@After 在方法执行之后执行(方法上使用)

@Before 在方法执行之前执行(方法上使用)

@Around 在方法执行之前与之后执行(方法上使用)

@PointCut 声明切点

在 java 配置类中使用 @EnableAspectJAutoProxy 注解开启 Spring 对 AspectJ 代理的支持(类上使用)

5)@Bean 的属性支持

@Scope 设置 Spring 容器如何新建 Bean 实例(方法上使用,得有 @Bean)

其设置类型包括:

Singleton (单例,一个 Spring 容器中只有一个 bean 实例,默认模式),

Protetype (每次调用新建一个 bean),

Request (web 项目中,给每个 http request 新建一个 bean),

Session (web 项目中,给每个 http session 新建一个 bean),

GlobalSession(给每一个 global http session 新建一个 Bean 实例)

@StepScope 在 Spring Batch 中还有涉及

@PostConstruct 由 JSR-250 提供,在构造函数执行完之后执行,等价于 xml 配置文件中 bean 的 initMethod

@PreDestory 由 JSR-250 提供,在 Bean 销毁之前执行,等价于 xml 配置文件中 bean 的 destroyMethod

6)@Value 注解

@Value 为属性注入值(属性上使用)

7)环境切换

@Profile 通过设定 Environment 的 ActiveProfiles 来设定当前 context 需要使用的配置环境。(类或方法上使用)

@Conditional Spring4 中可以使用此注解定义条件话的 bean,通过实现 Condition 接口,并重写 matches 方法,从而决定该 bean 是否被实例化。(方法上使用)

8)异步相关

@EnableAsync 配置类中,通过此注解开启对异步任务的支持,叙事性 AsyncConfigurer 接口(类上使用)

@Async 在实际执行的 bean 方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要 @EnableAsync 开启异步任务)

9)定时任务相关

@EnableScheduling 在配置类上使用,开启计划任务的支持(类上使用)

@Scheduled 来申明这是一个任务,包括 cron,fixDelay,fixRate 等类型(方法上,需先开启计划任务的支持)

10)@Enable * 注解说明

注解主要用来开启对 xxx 的支持。

@EnableAspectJAutoProxy 开启对 AspectJ 自动代理的支持

@EnableAsync 开启异步方法的支持

@EnableScheduling 开启计划任务的支持

@EnableWebMvc 开启 Web MVC 的配置支持

@EnableConfigurationProperties 开启对 @ConfigurationProperties 注解配置 Bean 的支持

@EnableJpaRepositories 开启对 SpringData JPA Repository 的支持

@EnableTransactionManagement 开启注解式事务的支持

@EnableTransactionManagement 开启注解式事务的支持

@EnableCaching 开启注解式的缓存支持

11)测试相关注解

@RunWith 运行器,Spring 中通常用于对 JUnit 的支持

@ContextConfiguration 用来加载配置 ApplicationContext,其中 classes 属性用来加载配置类 Spring MVC 中常用的注解包含哪些?

@EnableWebMvc 在配置类中开启 Web MVC 的配置支持,如一些 ViewResolver 或者 MessageConverter 等,若无此句,重写 WebMvcConfigurerAdapter 方法(用于对 SpringMVC 的配置)。

@Controller 声明该类为 SpringMVC 中的 Controller

@RequestMapping 用于映射 Web 请求,包括访问路径和参数(类或方法上)

@ResponseBody 支持将返回值放在 response 内,而不是一个页面,通常用户返回 json 数据(返回值旁或方法上)

@RequestBody 允许 request 的参数在 request 体中,而不是在直接连接在地址后面。(放在参数前)

@PathVariable 用于接收路径参数,比如 @RequestMapping (“/hello/{name}”) 申明的路径,将注解放在参数中前,即可获取该值,通常作为 Restful 的接口实现方法。

@RestController 该注解为一个组合注解,相当于 @Controller 和 @ResponseBody 的组合,注解在类上,意味着,该 Controller 的所有方法都默认加上了 @ResponseBody。

@ControllerAdvice 通过该注解,我们可以将对于控制器的全局配置放置在同一个位置,注解了 @Controller 的类的方法可使用 @ExceptionHandler、@InitBinder、@ModelAttribute 注解到方法上,

这对所有注解了 @RequestMapping 的控制器内的方法有效。

@ExceptionHandler 用于全局处理控制器里的异常

@InitBinder 用来设置 WebDataBinder,WebDataBinder 用来自动绑定前台请求参数到 Model 中。

@ModelAttribute 本来的作用是绑定键值对到 Model 里,在 @ControllerAdvice 中是让全局的 @RequestMapping 都能获得在此处设置的键值对。 为什么说 MyBatis 是半自动 ORM 映射?

ORM 是 Object 和 Relation 之间的映射,包括 Object->Relation 和 Relation->Object 两方面。Hibernate 是个完整的 ORM 框架,而 MyBatis 完成的是 Relation->Object,也就是其所说的 Data Mapper Framework。

JPA 是 ORM 映射标准,主流的 ORM 映射都实现了这个标准。MyBatis 没有实现 JPA,它和 ORM 框架的设计思路不完全一样。MyBatis 是拥抱 SQL,而 ORM 则更靠近面向对象,不建议写 SQL,实在要写需用框架自带的类 SQL 代替。MyBatis 是 SQL 映射而不是 ORMORM 映射,当然 ORM 和 MyBatis 都是持久层框架。

最典型的 ORM 映射是 Hibernate,它是全自动 ORM 映射,而 MyBatis 是半自动的 ORM 映射。Hibernate 完全可以通过对象关系模型实现对数据库的操作,拥有完整的 JavaBean 对象与数据库的映射结构来自动生成 SQL。而 MyBatis 仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写 SQL 来实现和管理。

Hibernate 数据库移植性远大于 MyBatis。Hibernate 通过它强大的映射结构和 HQL 语言,大大降低了对象与数据库(oracle、mySQL 等)的耦合性,而 MyBatis 由于需要手写 SQL,因此与数据库的耦合性直接取决于程序员写 SQL 的方法,如果 SQL 不具通用性而用了很多某数据库特性的 SQL 语句的话,移植性也会随之降低很多,成本很高。 main 方法中 args 参数是什么含义?

java 中 args 即为 arguments 的缩写,是指字符串变量名,属于引用变量,属于命名,可以自定义名称也可以采用默认值,一般习惯性照写。

String [] args 是 main 函数的形式参数,可以用来获取命令行用户输入进去的参数。

1)字符串变量名 (args) 属于引用变量,属于命名,可以自定义名称。

2)可以理解成用于存放字符串数组,若去掉无法知晓"args"声明的变量是什么类型。

3)假设 public static void main 方法,代表当启动程序时会启动这部分;

4)String [] args 是 main 函数的形式参数,可以用来获取命令行用户输入进去的参数。

5)java 本身不存在不带 String args [] 的 main 函数,java 程序中去掉 String args [] 会出现错误。 什么是高内聚、低耦合?

内聚关注模块内部的元素结合程度,耦合关注模块之间的依赖程度。

1)内聚性

又称块内联系。指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一个模块内各元素(语名之间、程序段之间)联系的越紧密,则它的内聚性就越高。

所谓高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。

2)耦合性

也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。

对于低耦合,粗浅的理解是:一个完整的系统,模块与模块之间,尽可能的使其独立存在。也就是说,让每个模块,尽可能的独立完成某个特定的子功能。模块与模块之间的接口,尽量的少而简单。如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。这样有利于修改和组合。 Spring Boot 框架的优缺点?

Spring Boot 优点

1)创建独立的 Spring 应用程序

Spring Boot 以 jar 包的形式独立运行,使用 java -jar xx.jar 命令运行项目或在项目的主程序中运行 main 方法。

2)Spring Boot 内嵌入 Tomcat,Jetty 或者 Undertow,无序部署 WAR 包文件

Spring 项目部署时需要在服务器上部署 tomcat,然后把项目打成 war 包放到 tomcat 中 webapps 目录。

Spring Boot 项目不需要单独下载 Tomcat 等传统服务器,内嵌容器,使得可以执行运行项目的主程序 main 函数,让项目快速运行,另外,也降低对运行环境的基本要求,环境变量中有 JDK 即可。

3)Spring Boot 允许通过 maven 工具根据需要获取 starter

Spring Boot 提供了一系列的 starter pom 用来简化我们的 Maven 依赖,通过这些 starter 项目就能以 Java Application 的形式运行 Spring Boot 项目,而无需其他服务器配置。

starter pom:

https://docs.spring.io/spring-boot/docs/1.4.1.RELEASE/reference/htmlsingle/#using-boot-starter

4)Spring Boot 尽可能自动配置 Spring 框架

Spring Boot 提供 Spring 框架的最大自动化配置,使用大量自动配置,使得开发者对 Spring 的配置减少。

Spring Boot 更多的是采用 Java Config 的方式,对 Spring 进行配置。

5)提供生产就绪型功能,如指标、健康检查和外部配置

Spring Boot 提供了基于 http、ssh、telnet 对运行时的项目进行监控;可以引入 spring-boot-start-actuator 依赖,直接使用 REST 方式来获取进程的运行期性能参数,从而达到监控的目的,比较方便。

但是 Spring Boot 只是微框架,没有提供相应的服务发现与注册的配套功能、监控集成方案以及安全管理方案,因此在微服务架构中,还需要 Spring Cloud 来配合一起使用,可关注微信公众号 “Java 精选”,后续篇幅会针对 Spring Cloud 面试题补充说明。

5)绝对没有代码生成,对 XML 没有要求配置

Spring Boot 优点

1)依赖包太多,一个 spring Boot 项目就需要很多 Maven 引入所需的 jar 包

2)缺少服务的注册和发现等解决方案

3)缺少监控集成、安全管理方案 Spring Boot 核心注解都有哪些?

1)@SpringBootApplication*

用于 Spring 主类上最最最核心的注解,自动化配置文件,表示这是一个 SpringBoot 项目,用于开启 SpringBoot 的各项能力。

相当于 @SpringBootConfigryation、@EnableAutoConfiguration、@ComponentScan 三个注解的组合。

2)@EnableAutoConfiguration

允许 SpringBoot 自动配置注解,开启这个注解之后,SpringBoot 就能根据当前类路径下的包或者类来配置 Spring Bean。

如当前路径下有 MyBatis 这个 Jar 包,MyBatisAutoConfiguration 注解就能根据相关参数来配置 Mybatis 的各个 Spring Bean。

3)@Configuration

Spring 3.0 添加的一个注解,用来代替 applicationContext.xml 配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在的类来进行注册。

4)@SpringBootConfiguration

@Configuration 注解的变体,只是用来修饰 Spring Boot 的配置而已。

5)@ComponentScan

Spring 3.1 添加的一个注解,用来代替配置文件中的 component-scan 配置,开启组件扫描,自动扫描包路径下的 @Component 注解进行注册 bean 实例放到 context (容器) 中。

6)@Conditional

Spring 4.0 添加的一个注解,用来标识一个 Spring Bean 或者 Configuration 配置文件,当满足指定条件才开启配置

7)@ConditionalOnBean

组合 @Conditional 注解,当容器中有指定 Bean 才开启配置。

8)@ConditionalOnMissingBean

组合 @Conditional 注解,当容器中没有值当 Bean 才可开启配置。

9)@ConditionalOnClass

组合 @Conditional 注解,当容器中有指定 Class 才可开启配置。

10)@ConditionalOnMissingClass

组合 @Conditional 注解,当容器中没有指定 Class 才可开启配置。

11)@ConditionOnWebApplication

组合 @Conditional 注解,当前项目类型是 WEB 项目才可开启配置。

项目有以下三种类型:

① ANY:任意一个 Web 项目

② SERVLET: Servlet 的 Web 项目

③ REACTIVE :基于 reactive-base 的 Web 项目

12) @ConditionOnNotWebApplication

组合 @Conditional 注解,当前项目类型不是 WEB 项目才可开启配置。

13)@ConditionalOnProperty

组合 @Conditional 注解,当指定的属性有指定的值时才可开启配置。

14)@ConditionalOnExpression

组合 @Conditional 注解,当 SpEl 表达式为 true 时才可开启配置。

15)@ConditionOnJava

组合 @Conditional 注解,当运行的 Java JVM 在指定的版本范围时才开启配置。

16)@ConditionalResource

组合 @Conditional 注解,当类路径下有指定的资源才开启配置。

17)@ConditionOnJndi

组合 @Conditional 注解,当指定的 JNDI 存在时才开启配置。

18)@ConditionalOnCloudPlatform

组合 @Conditional 注解,当指定的云平台激活时才可开启配置。

19)@ConditiomalOnSingleCandidate

组合 @Conditional 注解,当制定的 Class 在容器中只有一个 Bean,或者同时有多个但为首选时才开启配置。

20)@ConfigurationProperties

用来加载额外的配置 (如.properties 文件),可用在 @Configuration 注解类或者 @Bean 注解方法上面。可看一看 Spring Boot 读取配置文件的几种方式。

21)@EnableConfigurationProperties

一般要配合 @ConfigurationProperties 注解使用,用来开启 @ConfigurationProperties 注解配置 Bean 的支持。

22)@AntoConfigureAfter

用在自动配置类上面,便是该自动配置类需要在另外指定的自动配置类配置完之后。如 Mybatis 的自动配置类,需要在数据源自动配置类之后。

23)@AutoConfigureBefore

用在自动配置类上面,便是该自动配置类需要在另外指定的自动配置类配置完之前。

24)@Import

Spring 3.0 添加注解,用来导入一个或者多个 @Configuration 注解修饰的配置类。

25)@IMportReSource

Spring 3.0 添加注解,用来导入一个或者多个 Spring 配置文件,这对 Spring Boot 兼容老项目非常有用,一位内有些配置文件无法通过 java config 的形式来配置 Spring Boot 的目录结构是怎样的?

1、代码层的结构

根目录:com.springboot

1)工程启动类 (ApplicationServer.java) 置于 com.springboot.build 包下

2)实体类 (domain) 置于 com.springboot.domain

3)数据访问层 (Dao) 置于 com.springboot.repository

4)数据服务层 (Service) 置于 com,springboot.service, 数据服务的实现接口 (serviceImpl) 至于 com.springboot.service.impl

5)前端控制器 (Controller) 置于 com.springboot.controller

6)工具类 (utils) 置于 com.springboot.utils

7)常量接口类 (constant) 置于 com.springboot.constant

8)配置信息类 (config) 置于 com.springboot.config

9)数据传输类 (vo) 置于 com.springboot.vo

2、资源文件的结构

根目录:src/main/resources

1)配置文件 (.properties/.json 等) 置于 config 文件夹下

2)国际化 (i18n) 置于 i18n 文件夹下

3)spring.xml 置于 META-INF/spring 文件夹下

4)页面以及 js/css/image 等置于 static 文件夹下的各自文件下 Spring Boot 需要独立的容器运行吗?

Spring Boot 项目可以不需要,内置了 Tomcat/Jetty 等容器,默认 Tomcat。

Spring Boot 不需要独立的容器就可以运行,因为在 Spring Boot 工程发布的 jar 文件里已经包含了 tomcat 插件的 jar 文件。

Spring Boot 运行时创建 tomcat 对象实现 web 服务功能,另外也可以将 Spring Boot 编译成 war 包文件放到 tomcat 中运行。 Spring Boot 运行方式有哪几种?

1)直接执行 main 方法运行,通过 IDE 工具运行 Application 这个类的 main 方法

2)使用 Maven 插件 spring-boot-plugin 方式启动,在 Spring Boot 应用的根目录下运行 mvn spring-boot:run

3)使用 mvn install 生成 jar 后通过 java -jar 命令运行 Spring Boot 自动配置原理是什么?

Spring Boot 的启动类中使用了 @SpringBootApplication 注解,里面的 @EnableAutoConfiguration 注解是自动配置的核心,注解内部使用 @Import (AutoConfigurationImportSelector.class)(class 文件用来哪些加载配置类)注解来加载配置类,并不是所有的 bean 都会被加载,在配置类或 bean 中使用 @Condition 来加载满足条件的 bean。

@EnableAutoConfiguration 给容器导入 META-INF/spring.factories 中定义的自动配置类,筛选有效的自动配置类。每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能 Spring Boot 热部署有几种方式?

1)spring-boot-devtools

通过 Springboot 提供的开发者工具 spring-boot-devtools 来实现,在 pom.xml 引用其依赖。

然后在 Settings→Build→Compiler 中将 Build project automatically 勾选上,最后按 ctrl+shift+alt+/ 选择 registy,将 compiler.automake.allow.when.app.running 勾选。

2)Spring Loaded

Spring 官方提供的热部署程序,实现修改类文件的热部署

下载 Spring Loaded(项目地址 https://github.com/spring-projects/spring-loaded)

添加运行时参数:-javaagent:C:/springloaded-1.2.5.RELEASE.jar –noverify

3)JRebel

收费的一个热部署软件,安装插件使用即可。 MyBatis 中 $ 和 # 传参有什么区别?

1)“#” 符号将传入的数据当成一个字符串并将传入的数据加上双引号。

如:order by #{userId},如果传入的值是 1,那么解析成 sql 时的值为 order by "1",如果传入的值是 userId,则解析成的 sql 为 order by "userId"。

2)“$” 符号将传入的数据直接显示生成在 sql 语句中。

如:order by ${userId},如果传入的值是 1,那么解析成 sql 时的值为 order by 1, 如果传入的值是 userId,则解析成的 sql 为 order by userId。

3)“#” 符号能够很大程度防止 sql 注入,而 “$” 符号无法防止 sql 注入。

4)“$” 符号方式一般用于传入数据库对象,例如传入表名。

5)一般能用 “#” 符号的就别用 “$” 符号

6)MyBatis 排序时使用 order by 动态参数时需要注意使用 “$” 符号而不是 “#” 符号。 MyBatis 如何实现分页?

1)相对原始方法,使用 limit 分页,需要处理分页逻辑:

MySQL 数据库使用 limit,如:

select * from table limit 0,10; -- 返回 0-10 行

Oracle 数据库使用 rownum,如:

从表 Sys_option(主键为 sys_id) 中从第 10 条记录开始检索 20 条记录,语句如下:

SELECT * FROM (SELECT ROWNUM R,t1.* From Sys_option where rownum < 30 ) t2 Where t2.R >= 10

2)拦截 StatementHandler,其实质还是在最后生成 limit 语句。

3)使用 PageHelper 插件,目前比较常见的方法。 MyBatis 如何获取自动生成的主键 id?

数据插入时获得主键值分为两种情况:支持主键自增数据库和不支持主键自增。

1)对于支持自动生成主键的数据库,如 Mysql、sqlServer,可以通过 Mybatis 元素 useGeneratedKeys 返回当前插入数据主键值到输入类中。

<insert id="insertTest" useGeneratedKeys="true" keyProperty="id" 
 parameterType="com.kq.domain.IdentityTest">
        insert into identity_test(name)
        values(#{name,jdbcType=VARCHAR})
</insert>

当执行此条插入语句以后,实体类 IdentityTest 中的 Id 会被当前插入数据的主键自动填充。

2)对于不支持自动生成主键的数据库。Oracle、DB2 等,可以用元素 selectKey 回当前插入数据主键值到输入类中。(同时生成一个自定义的随机主键)

<insert id="insertTest" useGeneratedKeys="true" keyProperty="id" 
 parameterType="com.kq.domain.IdentityTest">
 <selectKey keyProperty="id" resultType="String" order="BEFORE">
        SELECT  REPLACE(UUID(),'-','')  
  </selectKey>
        insert into identity_test(name)
        values(#{name,jdbcType=VARCHAR})
</insert>

当执行此条插入语句以后,实体类 IdentityTest 中的 Id 也会被当前插入数据的主键自动填充。 TCP 和 UDP 协议有什么区别?

1)基于连接

TCP 是面向连接的协议,而 UDP 是无连接的协议。即 TCP 面向连接;UDP 是无连接的,即发送数据之前不需要建立连接。

2)可靠性和有序性

TCP 提供交付保证(Tcp 通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输),无差错,不丢失,不重复,且按序到达,也保证了消息的有序性。该消息将以从服务器端发出的同样的顺序发送到客户端,尽管这些消息到网络的另一端时可能是无序的。TCP 协议将会为你排好序。

UDP 不提供任何有序性或序列性的保证。UDP 尽最大努力交付,数据包将以任何可能的顺序到达。

TCP 的逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道。

3)实时性

UDP 具有较好的实时性,工作效率比 TCP 高,适用于对高速传输和实时性有较高的通信或广播通信。

4)协议首部大小

TCP 首部开销 20 字节;UDP 的首部开销小,只有 8 个字节。

5)运行速度

TCP 速度比较慢,而 UDP 速度比较快,因为 TCP 必须创建连接,以保证消息的可靠交付和有序性,毕竟 TCP 协议比 UDP 复杂。

6)拥塞机制

UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低,对实时应用很有用,如 IP 电话,实时视频会议等。

7)流模式(TCP)与数据报模式 (UDP) TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文的 。

8)资源占用

TCP 对系统资源要求较多,UDP 对系统资源要求较少。

TCP 被认为是重量级的协议,而与之相比,UDP 协议则是一个轻量级的协议。因为 UDP 传输的信息中不承担任何间接创造连接,保证交货或秩序的的信息。这也反映在用于承载元数据的头的大小。

9)应用

每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信 。基于 UDP 不需要建立连接,所以且适合多播的环境,UDP 是大量使用在游戏和娱乐场所。 Integer 类型值是 0 ,为什么!= '' 无法执行?

开发微信小程序 “Java 精选面试题” 后台管理系统时,遇到根据状态判断是或否发布。

MySQL 数据库中设计数据库表,其中某字段 status 使用 tinyint 数据类型,当修改状态的时候,赋值 status 属性的值为 0,用于改变状态记录试题库中发布情况。

但是通过 Debug 模式查看 Controller 控制层明显已经获取到 status 等于 0,但是在执行到 MyBatis 中 xml 文件 SQL 语句时,总是无法赋值成功,xml 配置如下:

<update id="updateWarehouse" parameterType="Warehouse">


update t_warehouse 


<set>



<if test="title != null and title != ''">title = #{title},</if>



<if test="code != null and code != ''">code = #{code},</if>



<if test="content != null and content != ''">content = #{content},</if>



<if test="status != null and status != ''">status = #{status},</if>



<if test="parentId != null and parentId != ''">parentId = #{parentId}</if>


</set>


where id = #{id}
</update>

分析:

通过分析表面上没有任何传参问题,通过上网查询 MyBatis 相关资料,终于弄明白什么原因。 此行代码中

<if test="status != null and status != ''">status = #{status},</if>

and status != '',MyBatis 中传参 status 的值为 0 时,因为数据类型为 Integer 类型,判断为 false 值。

MyBatis 认定 0 = '' 的,因此判断 status != '' 为 false,这导致修改试题信息时状态值无法改变为 0。

正确写法:

<update id="updateWarehouse" parameterType="Warehouse">


update t_warehouse 


<set>



<if test="title != null and title != ''">title = #{title},</if>



<if test="code != null and code != ''">code = #{code},</if>



<if test="content != null and content != ''">content = #{content},</if>



<if test="status != null">status = #{status},</if>



<if test="parentId != null and parentId != ''">parentId = #{parentId}</if>


</set>


where id = #{id}
</update>

MySQL 的索引有哪些设计原则?

选择唯一性索引

唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录

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

某个字段用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为该字段建立索引,可以提高整个表的查询速度

限制索引的数量

索引的数量并不是越多越好,每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。

修改表时,索引过多会使得更新表速度变慢

尽量使用字段数据量少的索引

若索引的字段数据量过长,会导致查询的速度变慢

如:对一个 char (200) 类型的字段进行全文检索需要的时间肯定比对 char (10) 类型的字段需要的时间更多

排序、分组和联合字段建立索引

使用 order by、group by、distinct 和 union 等操作的字段进行排序操作会浪费很多时间。若为其建立索引,可以有效的避免排序操作

尽量使用前缀索引

索引字段的值很长,最好使用值的前缀来索引

如:text 和 blog 类型的字段,进行全文检索会浪费时间。若只检索字段的部分若干个字符,可以提高检索速度

删除不使用或者很少使用的索引

表中数据被批量更新或数据的使用方式被改变后,原有的一些索引可能不再需要。应当定期清理这些索引

小表不创建索引 (超过 200w 数据的表,创建索引)

包含很多列且不需要搜索非空值时可以考虑不建索引

被用来过滤记录的字段创建索引

primary key 字段,系统自动创建主键的索引 unique key 字段,系统自动创建对应的索引 foreign key 约束所定义的作为外键的字段 在查询中用来连接表的字段 用作为排序 (order by 的字段) 的字段

创建索引必须考虑数据的操作方式,原则是内容变动少,经常被用于查询的字段创建索引,对于经常性变动的表而言,则需要谨慎创建必要的索引 为什么要使用自增 ID 作为主键?

1、若定义主键 (PRIMARY KEY),InnoDB 会选择主键作为聚集索引,反之未定义主键,则 InnoDB 会选择第一个不包含 NULL 值的唯一索引作为主键索引。

没有唯一索引,则 InnoDB 会选择内置 6 字节长的 ROWID 作为隐含的聚集索引 (ROWID 随着行记录的写入而主键递增,这个 ROWID 不像 ORACLE 的 ROWID 那样可引用,是隐含的)。

2、数据记录本身被存于主索引(一颗 B+Tree)的叶子节点上,这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放。

每当有一条新的记录插入时,MySQL 会根据其主键将其插入适当的节点和位置,若页面达到装载因子(InnoDB 默认为 15/16),则开辟一个新的页(节点)。

3、若表使用自增主键,则每次插入新的记录就会顺序添加到当前索引节点的后续位置,当写满一页就会自动开辟一个新的页。

4、若使用非自增主键,因为每次插入主键的值都近似于随机,所以每次新纪录都要被插到现有索引页得中间某个位置,此时 MySQL 将为新记录插到合适位置而移动数据,甚至可能被回写到磁盘上而从缓存中清除掉,此时又要从磁盘上读回来,这将增大了开销。同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续需通过 OPTIMIZE TABLE 来重建表并优化填充页面。 Linux 如何切换用户?

Linux 系统切换用户的命令是 su

su 的含义是 (switch user) 切换用户的缩写。

通过 su 命令,可以从普通用户切换到 root 用户,也可以从 root 用户切换到普通用户。

<font color="#dd0000"> 注:从普通用户切换到 root 用户需要密码,从 root 用户切换到普通用户不需要密码。</font>

使用 SecureCRT 工具连接终端,由普通用户切换到 root 用户。

在终端输入 su 命令然后回车,要求输入密码(linux 终端输入的密码不显示)输入密码后回车进入 root 用户。

在终端输入 su root 然后回车,也可以进入 root 用户或 su - root 回车,也可以切换 root 用户。针对 su root 与 su - root 区别之后的篇幅会阐述,欢迎关注微信公众号 “Java 精选”,送免费资料。 su root 和 su - root 有什么区别?

su 后面不加用户是默认切到 root,su 是不改变当前变量;su - 是改变为切换到用户的变量。

简单来说就是 su 只能获得 root 的执行权限,不能获得环境变量,而 su - 是切换到 root 并获得 root 的环境变量及执行权限。

语法:

$ su [user_name]

su 命令可以用来更改你的用户 ID 和组 ID。su 是 switch user 或 set user id 的一个缩写。

此命令是用于开启一个子进程,成为新的用户 ID 和赋予存取与这个用户 ID 关联所有文件的存取权限。

出于安全的考虑,在实际转换身份时,会被要求输入这个用户帐号的密码。

如果没有参数,su 命令将转换为 root(系统管理员)。root 帐号有时也被称为超级用户,因为这个用户可以存取系统中的任何文件。

当必须要提供 root 密码,想要回到原先的用户身份,可以不使用 su 命令,只需使用 exit 命令退出使用 su 命令而生成的新的对话进程。

$ su – username

当使用命令 su username 时,对话特征和原始的登录身份一样。

如果需要对话进程拥有转换后的用户 ID 一致的特征,使用短斜杠命令: su – username。 Linux 怎么切换目录?

1)使用 pwd 命令查看一下当前所在的目录

2)切换文件目录使用的 cd 命令。

切换到根目录命令如下:

cd /

3)根目录下使用 ls 命令查看该目录下有哪些文件,使用用绝对路径的方式进入所需目录,比如进入 usr 目录,命令如下:

cd /usr

4)若进入文件下一层级目录,可以使用相对路径的方式切换目录。比如目录结构如下:

usr/local/other

在当前 usr 目录下使用命令

cd ./local

进入 usr 目录下的 local 目录。此命令和使用绝对路径的方式区别在于,前面多了个 ".",这个 "." 代表的是当前目录。

5)若要回到上一级目录,则可以使用命令

cd ../ 

Dubbo 支持哪些协议,推荐用哪种?

dubbo 协议(推荐使用) 单一 TCP 长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及服务消费者远大于提供者的情况。

缺点是 Hessian 二进制序列化,不适合传送大数据包的服务

rmi 协议

采用 JDK 标准的 rmi 协议实现,传输参数和返回参数对象需要实现 Serializable 接口。

使用 java 标准序列化机制,使用阻塞式短连接,传输数据包不限,消费者和提供者个数相当。

多个短连接,TCP 协议传输,同步传输,适用常规的远程服务调用和 rmi 互操作。

缺点是在依赖低版本的 Common-Collections 包,java 反序列化存在安全漏洞,需升级 commons-collections3 到 3.2.2 版本或 commons-collections4 到 4.1 版本。

webservice 协议

基于 WebService 的远程调用协议 (Apache CXF 的 frontend-simple 和 transports-http) 实现,提供和原生 WebService 的互操作。

多个短连接,基于 HTTP 传输,同步传输,适用系统集成和跨语言调用。

http 协议

基于 Http 表单提交的远程调用协议,使用 Spring 的 HttpInvoke 实现,对传输数据包不限,传入参数大小混合,提供者个数多于消费者。

缺点是不支持传文件,只适用于同时给应用程序和浏览器 JS 调用。

hessian 协议

集成 Hessian 服务,基于底层 Http 通讯,采用 Servlet 暴露服务,Dubbo 内嵌 Jetty 作为服务器实现,可与 Hession 服务互操作。

通讯效率高于 WebService 和 Java 自带的序列化。

适用于传输大数据包 (可传文件),提供者比消费者个数多,提供者压力较大。

缺点是参数及返回值需实现 Serializable 接口,自定义实现 List、Map、Number、Date、Calendar 等接口

thrift 协议

协议:对 thrift 原生协议的扩展添加了额外的头信息,使用较少,不支持传 null 值。

memcache 协议

基于 memcached 实现的 RPC 协议。

redis 协议

基于 redis 实现的 RPC 协议。 Dubbo 默认使用什么注册中心,还有别的选择吗?

推荐使用 Zookeeper 作为注册中心,还有 Redis、Multicast、Simple 注册中心,但不推荐。 为什么 Redis 需把数据放到内存中? 

若不将数据读到内存中,磁盘 I/O 速度会严重影响 Redis 的性能。

Redis 具有快速和数据持久化的特征。

Redis 为了提高读写时的速度将数据读到内存中,并通过异步的方式将数据写入磁盘。

注:若设置最大使用内存,则数据已有记录数达到内存限值后不能继续插入新值。

为什么内存读取比磁盘读取数据速度快?

1)内存是电器元件,利用高低电平存储数据,而磁盘是机械元件,电气原件速度超快,而磁盘由于在每个磁盘块切换时磁头会消耗很多时间,说白了就是 IO 时间长,两者性能没发比较。

2)磁盘的数据进行操作时也是读取到内存中交由 CPU 进行处理操作,因此直接放在内存中的数据,读取速度肯定快很多。

Zookeeper 怎么保证主从节点的状态同步?

Zookeeper 的核心是原子广播机制,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。

Zab 协议有两种模式,分别是恢复模式和广播模式。

恢复模式

当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。

状态同步保证了 leader 和 server 具有相同的系统状态。

广播模式

一旦 leader 已经和多数的 follower 进行了状态同步后,它就可以开始广播消息了,即进入广播状态。

此时当一个 server 加入 ZooKeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。

待到同步结束,它也参与消息广播。

ZooKeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的 followers 支持。 Dubbo 停止更新了吗?

Dubbo 是阿里巴巴内部使用的分布式业务框架,于 2012 年由阿里巴巴开源。

由于 Dubbo 在阿里巴巴内部经过广泛的业务验证,在很短时间内,Dubbo 就被许多互联网公司所采用,并产生了许多衍生版本,如网易,京东,新浪,当当等等。

由于阿里巴巴内部策略的调整变化,在 2014 年 10 月 Dubbo 停止维护。随后部分互联网公司公开了自行维护的 Dubbo 版本,比较著名的如当当 DubboX,新浪 Motan 等。

在 2017 年 9 月,阿里宣布重启 Dubbo 项目,并决策在未来对开源进行长期持续的投入。随后 Dubbo 开始了密集的更新,并将搁置三年以来大量分支上的特性及缺陷快速修正整合。

在 2018 年 2 月,阿里巴巴将 Dubbo 捐献给 Apache 基金会,Dubbo 成为 Apache 孵化器项目。 为什么选用 Maven 进行构建?

1)Maven 是一个优秀的项目构建工具。

使用 Maven 可以比较方便的对项目进行分模块构建,这样在开发或测试打包部署时,会大大的提高效率。

2)Maven 可以进行依赖的管理。

使用 Maven 可以将不同系统的依赖进行统一管理,并且可以进行依赖之间的传递和继承。

Maven 可以解决 jar 包的依赖问题,根据 JAR 包的坐标去自动依赖 / 下载相关 jar,通过仓库统一管理 jar 包。

多个项目 JAR 包冗余,使用 Maven 解决一致性问题。

4)屏蔽开发工具之间的差异,例如:IDE,Eclipse,maven 项目可以无损导入其他编辑器。 Maven 规约是什么?

src/main/java 存放项目的类文件(后缀.java 文件,开发源代码)

src/main/resources 存放项目配置文件,若没有配置文件该目录可无,如 Spring、Hibernate、MyBatis 等框架配置文件

src/main/webapp 存放 web 项目资源文件(web 项目需要)

src/test/java 存放所有测试类文件(后缀.java 文件,测试源代码)

src/test/resources 测试配置文件,若没有配置文件该目录可无

target 文件编译过程中生成的后缀.class 文件、jar、war 等

pom.xml maven 项目核心配置文件,管理项目构建和依赖的 Jar 包

Maven 负责项目的自动化构建,以编译为例,Maven 若果自动进行编译,需要知道 Java 的源文件保存位置,通过这些规约,不用开发者手动指定位置,Maven 就可以清晰的知道相关文件所在位置,从而完成自动编译。

遵循 **“约定>>> 配置 >>> 编码”**。即能进行配置的不要去编码指定,能事先约定规则的不要去进行配置。这样既减轻了工作量,也能防止编译出错。 Maven 常用命令有哪些?

1)mvn clean

清理输出目录默认 target/

2)mvn clean compline

编译项目主代码,默认编译至 target/classes 目录下

3)mvn clean install

maven 安装,将生成的 JAR 包文件复制到本地 maven 仓库中,其他项目可以直接使用这个 JAR 包

4)mvn clean test

maven 测试,但实际执行的命令有:

clean:clean
resource:resources
compiler:compile
resources:testResources
compiler:testCompile

maven 执行 test 前,先自动执行项目主资源处理,主代码编译,测试资源处理,测试代码编译等工作

测试代码编译通过之后默认在 target/test-calsses 目录下生成二进制文件,随后执行 surefile:test 任务运行测试,并输出测试报告,显示运行多少次测试,失败成功等。

5)mvn celan package

maven 打包,maven 会在打包之前默认执行编译,测试等操作,打包成功之后默认输出在 target / 目录中

6)mvn help:system

打印出 java 系统属性和环境变量。

7)echo %MAVEN_HOME%:

查看 maven 安装路径。

8)mvn deploy

在整合或发布环境下执行,将终版本的包拷贝到远程的 repository,使得其他的开发者或者工程可以共享。

9)mvn

检查是否安装了 maven。

10)mvn dependency:list

查看当前项目中的已解析依赖

11)mvn dependency:tree

查看当前项目的依赖树

12)mvn dependency:analyse

查看当前项目中使用未声明的依赖和已声明但未使用的依赖 什么是链式存储结构?

链接存储结构的含义是在计算机中用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),它不要求逻辑上相邻的元素在物理位置上也相邻。

链式存储结构的优点主要是插入和删除简单,前提条件是知道操作位置,时间复杂度是 O (1),如果不知道操作位置则要定位元素,时间复杂度为 O (n),没有容量的限制,可以使用过程中动态分配的内存空间,不用担心溢出问题,但是它并不能实现随机读取,同时空间利用率不高。 说说几种常见的排序算法和复杂度?

1)快速排序

原理:快速排序采用的是一种分治的思想,通过依次排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

选定一个合适的值(理想情况下选择中间值最好,但实际中一般使用数组第一个值),称为 “枢轴”(pivot)。

基于这个值将数组分为两部分,较小的分在左边,较大的分在右边,如此一轮下来,这个枢轴的位置一定在最终位置上。

对两个子数组分别重复上述过程,直到每个数组只有一个元素,排序完成。

复杂度:O (n)

特点:快速排序是平时最常使用的一种排序算法,因它速度快,效率高的一种排序算法。

2)冒泡排序

原理:冒泡排序采用的事两个相邻的值进行比较,将值大的交换到右侧。就是逐一比较交换,进行内外两次循环,外层循环为遍历所有数值,逐个确定每个位置,内层循环为确定位置后遍历所有后续没有确定位置的数字,与该位置的值进行比较,只要比该位置的值小,就位置交换。

复杂度:O (n^2),最佳时间复杂度为 O (n)

特点:冒泡排序在实际开发中使用比较少,更适合数据量比较少的场景,因其效率比较低,但逻辑简单,方便记忆。

3)直接插入排序

原理:直接插⼊排序是从第二个数字开始,逐个取出,插入到之前排好序的数组中。

复杂度:O (n^2),最佳时间复杂度为 O (n)

4)直接选择排序

原理:直接选择排序是从第一个位置开始遍历位置,找到剩余未排序的数组中最小值,将最小值做交换位置。

复杂度:O (n^2)

特点:类似冒泡排序其逻辑简单,但效率低,适合少量数据排序。 Java 递归遍历目录下的所有文件?

import java.io.File;

public class ListFiles {


public static void listAll(File directory) {



if(!(directory.exists() && directory.isDirectory())) {




throw new RuntimeException("目录不存在");



}



File[] files = directory.listFiles();







for (File file : files) {




System.out.println(file.getPath() + file.getName());




if(file.isDirectory()) {





listAll(file);




}



}


}





public static void main(String[] args) {



File directory = new File("E:\\Program Files (x86)");



listAll(directory);


}
}

JSP 获取 ModelAndView 传参数据问题?

Idea 开发工具自动创建的 web.xml 约束太低,导致无法正常获取数据,需要把 web.xml 约束的信息调整一下,参考如下:

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

Linux 运行 SQL 语句文件报错?

原因分析:Linux 下 MySQL 版本不兼容导致的。

解决办法:把文件中所有的 utf8mb4_0900_ai_ci 替换为 utf8_general_ci 以及 utf8mb4 替换为 utf8 类型。 如何解决 Linux 显示中文乱码问题?

在 Linux 中通过 locale 来设置程序运行的不同语言环境,locale 由 ANSI C 提供支持。locale 的命名规则为_.,如 zh_CN.GBK,zh 代表中文,CN 代表大陆地区,GBK 表示字符集。

修改 /etc/locale.conf 文件的内容

LANG="zh_CN.UTF-8"

执行命令,使修改文件立刻生效

source /etc/locale.conf

IDEA 中 Maven 项目无法自动识别 pom.xml?

方式一

File->Settings->Build,Excecution,Deployment->Build Tools->Maven->Ignored Files

查看是否存在 maven pom 被勾选,去掉勾选即可。

方式二

右键项目 pom.xml 文件,选择 “add as maven project”, 自动导入 pom 所依赖的 jar 包。

刷新 Maven 配置

右键单击项目,在弹出菜单中选择 Maven->Reimport 菜单项。IDEA 将通过网络自动下载相关依赖,并存放在 Maven 的本地仓库中。

或者将 Maven 的刷新设置为自动,单击 File|Setting 菜单项,打开 Settings 选项卡,在左侧的目录树中展开 Maven 节点,勾选 Import Maven projects automatically 选择项。 面向过程与面向对象有什么区别?

面向过程   

性能相比面向对象高,因其类调用时需要实例化,开销比较大,消耗资源,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。

面向对象

易维护、复用以及扩展,由于面向对象有封装、继承、多态性等特征,可以设计出低耦合、高内聚的系统,使得更加灵活,易于维护。 Java 编程语言有哪些特点?

1)简单易学;

2)面向对象(封装,继承,多态);

3)平台无关性(Java 虚拟机实现平台无关性);

4)可靠性;

5)安全性;

6)支持多线程;

7)支持网络编程并方便易用;

8)编译与解释并存。 重载和重写有什么区别?

重载(Overload) 是指让类以统一的方式处理不同类型数据的一种手段,实质表现就是多个具有不同的参数个数或者不同类型的同名函数,存在于同一个类中,返回值类型不同,是一个类中多态性的一种表现。

调用方法时通过传递不同参数个数和参数类型来决定具体使用哪个方法的多态性。

重写(Override) 是指父类与子类之间的多态性,实质就是对父类的函数进行重新定义。

如果子类中定义某方法与其父类有相同的名称和参数则该方法被重写,需注意的是子类函数的访问修饰权限不能低于父类的。

如果子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,如需父类中原有的方法则可使用 super 关键字。 静态方法和实例方法有什么不同?

静态方法和实例方法的区别主要体现在两个方面:

其一在外部调用静态方法时,可以使用 "类名。方法名" 的方式,也可以使用 "对象名。方法名" 的方式而实例方法只能试用后面这种方式。也就是说,调用静态方法可以无需创建对象进行实例化。

其二静态方法在访问本类的成员时,只允许访问静态成员也就是静态成员变量和静态方法,而不允许访问实例成员变量和实例方法,实例方法是没有这个限制的。 == 和 equals 两者有什么区别?

使用 == 比较

用于对比基本数据类型的变量,是直接比较存储的 “值” 是否相等;

用于对比引用类型的变量,是比较的所指向的对象地址。

使用 equals 比较

equals 方法不能用于对比基本数据类型的变量;

如果没对 Object 中 equals 方法进行重写,则是比较的引用类型变量所指向的对象地址,反之则比较的是内容。 HashMap 是怎么扩容的?

当 HashMap 中元素个数超过数组大小 * loadFactor 时,需进行数组扩容。

loadFactor 默认值为 0.75,默认情况下,数组大小为 16,HashMap 中元素个数超过 16 * 0.75=12 的时,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以能够预选知道 HashMap 中元素的个数,应该预设数组的大小,可以有效的提高 HashMap 的性能。

假设有 1000 个元素 new HashMap (1000),理论上来讲 new HashMap (1024) 更合适,不过上面已经提过即使是 1000 个元素,HashMap 也会自动设置为 1024。但是 new HashMap (1024),而 0.75*1024 <1000, 为了可以 0.75 * size >1000,必须 new HashMap (2048),避免了 resize 的问题。

总结:

添加元素时会检查容器当前元素个数。当 HashMap 的容量值超过临界值 (默认 16 * 0.75=12) 时扩容。HashMap 将会重新扩容到下一个 2 的指数幂(16->32->64)。调用 resize 方法,定义长度为新长度 (32) 的数组,然后对原数组数据进行再 Hash。注意的是这个过程比较损耗性能。 JDK1.8 和 JDK1.7 中 ArrayList 的初始容量多少?

JDK1.7 下 ArrayList () 初始化后的默认长度是 10,源码如下:

//无参构造方法
public ArrayList() {
        this(10);
}

public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
}

通过上述源代码可以看出,默认的构造方法中直接指定数组长度为 10,同时调用重载的构造方法,创建了长度为 10 的一个数组。

JDK1.8 下 ArrayList () 初始化后的默认长度是 0,源码如下:

//无参构造方法
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

构造方法中静态类型的数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {} 是个空数组,数组长度为 0,因此 JDK1.8 下的 ArrayList () 初始化后默认的数组长度为 0。

Arrays.asList () 有什么使用限制?

1)Arrays.asList () 方法不适用于基本数据类型

byte
short
int
long
float
double
boolean

2)Arrays.asList () 方法把数组与列表链接起来,当更新其中之一时,另一个自动更新。

3)Arrays.asList () 方法不支持 add 和 remove 方法。 Set 为什么是无序的?

Set 系列集合添加元素无序的根本原因是底层采用哈希表存储元素。

JDK1.8 以下版本:哈希表 = 数组 + 链表 + (哈希算法)

JDK1.8 及以上版本:哈希表 = 数组 + 链表 + 红黑树 + (哈希算法)

当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

Comparable 和 Comparator 有什么区别?

Comparable 接口出自 java.lang 包,它有一个 compareTo (Object obj) 方法用来排序。

Comparator 接口出自 java.util 包,它有一个 compare (Object obj1, Object obj2) 方 法用来排序。

一般对集合使用自定义排序时,需要重写 compareTo () 方法或 compare () 方法。

当需要对某一个集合实现两种排序方式,比如一个用户对象中的姓名和身份证分别采用一种排序方法。 方式一:重写 compareTo () 方法实现姓名、身份证排序

方式二:使用自定义的 Comparator 方法实现姓名、身份证排序

方法三:使用两个 Comparator 来实现姓名、身份证排序

其中方式二代表只能使用两个参数的形式 Collections.sort ()。

Collections 是一个工具类,sort 是其中的静态方法,是用来对 List 类型进行排序的,它有两种参数形式:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}
public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

HashMap 中如何实现同步?

HashMap 可以使用如下代码实现:

Map map = Collections.synchronizedMap(new HashMap());

来达到同步的效果。

具体而言,该方法会返回一个同步的 Map 集合,这个 Map 封装了底层 HashMap 的所有方法,使得底层的 HashMap 可以在多线程的环境中也能够保证安全性。 List、Set、Map 三者有什么区别?

List

存储数据允许不唯一集合,可以有多个元素引用相同的对象且是有序的对象。

Set

不允许重复的集合,不存在多个元素引用相同的对象。

Map

使用键值对存储方式。Map 维护与 Key 有关联的值。两个 Key 可以引用相同的对象,但 Key 不能重复,比如 Key 是 String 类型,但也可以是任何对象。 多线程实现的方式有几种?

1)继承 Thread 类创建线程

Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。

启动线程的唯一方法是通过 Thread 类的 start () 实例方法。

start () 方法将启动一个新线程,并执行 run () 方法。

这种方式实现多线程比较简单,通过自己的类直接继承 Thread,并重写 run () 方法,就可以启动新线程并执行自己定义的 run () 方法。

2)实现 Runnable 接口创建线程

如果自己的类已经继承了两一个类,就无法再继承 Thread,因此可以实现一个 Runnable 接口

3)实现 Callable 接口,通过 FutureTask 包装器来创建 Thread 线程

4)使用 <code>ExecutorService</code>、<code>Callable</code>、<code>Future</code > 实现有返回结果的线程

<code>ExecutorService</code>、<code>Callable</code>、<code>Future</code > 三个接口实际上都是属于 Executor 框架。

在 JDK1.5 中引入的新特征,不需要为了得到返回值而大费周折。主要需要返回值的任务必须实现 Callable 接口;不需要返回值的任务必须实现 Runnabel 接口。

执行 Callable 任务后,可以获取一个 Future 对象,在该对象上调用 get () 方法可以获取到 Callable 任务返回的 Object 了。注意的是 get () 方法是阻塞的,线程没有返回结果时,该方法会一直等待。 什么是线程局部变量?

ThreadLocal 并非是一个线程本地实现版本,它并不是一个 Thread,而是 thread local variable(线程局部变量)。也许把它命名为 ThreadLocalVar 更合适。

线程局部变量(ThreadLocal)功能非常简单,就是为每一个使用该变量的线程都提供了一个变量值副本,是 Java 中一种较为特殊的线程绑定机制,使得每一个线程都独立地改变所具有的副本,而不会和其他线程的副本冲突。 Java 中常见的阻塞队列有哪些?

ArrayBlockingQueue

最典型的有界队列,其内部是用数组存储元素的,利用 ReentrantLock 实现线程安全,使用 Condition 来阻塞和唤醒线程。

LinkedBlockingQueue

内部用链表实现 BlockingQueue。如果不指定它的初始容量,那么它容量默认就为整型的最大值 Integer.MAX_VALUE,由于这个数非常大,通常不可能放入这么多的数据,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限。

SynchronousQueue

相比较其他,最大的不同之处在于它的容量为 0,所以没有地方暂存元素,导致每次存储或获取数据都要先阻塞,直至有数据或有消费者获取数据。

PriorityBlockingQueue

支持优先级的无界阻塞队列,可以通过自定义类实现 compareTo () 方法来指定元素排序规则或初始化时通过构造器参数 Comparator 来指定排序规则。需主要的是插入队列的对象必须是可以比较大小的值,否则会抛出 ClassCastException 异常。

DelayQueue

具有 “延迟” 的功能。队列中的可以设定任务延迟多久之后执行,比如 “30 分钟后未付款自动取消订单” 等需要执行的场景。 创建线程池的有几种方式?

newCachedThreadPool

创建一个可缓存的线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,如果没有可回收线程,则新建线程。

newFixedThreadPool

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

newScheduledThreadPool

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

newSingleThreadExecutor

创建一个单线程化的线程池,它只会唯一的工作线程来执行任务,保证所有任务按照指定执行。 查看文件内容有哪些命令?

vi 文件名

编辑方式查看,可以修改文件

cat 文件名

显示全部文件内容

tail 文件名

仅查看文件尾部信息,可以指定查看行数

head 文件名

仅查看头部,可以指定查看行数

more 文件名

分页显示文件内容

less 文件名

与 more 相似,可以往前翻页 命令中可以使用哪几种通配符?

“?”

可以替代任意单个字符。

“*”

可以替代任意多个字符。

方括号 “[charset]”

可以替代 charset 集中的任何单个字符,比如 [a-z],[abABC] 根据文件名搜索文件有哪些命令?

find <指定目录> < 指定条件 > < 指定动作 >,

whereis 加参数与文件名

locate 只加文件名 bash shell 中 hash 命令有什么作用?

Linux 中 hash 命令管理着一个内置的哈希表,记录了已执行过命令的完整路径,用该命令可以打印出所使用过的命令以及执行的次数。 Linux 中进程有哪几种状态?

1)不可中断状态:进程处于睡眠状态,此时的进程不可中断,进程不响应异步信号。

2)暂停状态 / 跟踪状态:向进程发送一个 SIGSTOP 信号,它就会因响应信号而进入 TASK_STOPPED 状态;当进程正在被跟踪时,它处于 TASK_TRACED 这个特殊的状态。正在被跟踪指的是进程暂停下来,等待跟踪它的进程对它进行操作。

3)就绪状态:在 run_queue 队列中的状态

4)运行状态:在 run_queue 队列中的状态

5)可中断睡眠状态:处于这个状态的进程因为等待某事件的发生,比如等待 socket 连接等,而被挂起

6)zombie 状态(僵尸):父进程没有通过 wait 系列的系统调用,直接将子进程的 task_struct 也释放掉。

7)退出状态 Integer 和 int 两者有什么区别?

Integer 是 int 的包装类,默认值是 null;int 是基本数据类型,默认值是 0;

Integer 变量必须实例化后才能使用;int 变量不需要;

Integer 实际是对象的引用,指向此 new 的 Integer 对象;int 是直接存储数据值。

分析总结

1)Integer 与 new Integer 不相等。new 出来的对象被存放在堆,而非 new 的 Integer 常量则在常量池,两者内存地址不同,因此判断是 false。

2)两个值都是非 new Integer,如果值在 - 128,127 区间,则是 true,反之为 false。

这是因为 java 在编译 Integer i2 = 128 时,被翻译成:

Integer i2 = Integer.valueOf(128);

而 valueOf () 函数会对 - 128 到 127 之间的数进行缓存。

3)两个都是 new Integer,两者判断为 false,内存地址不同。

4)int 和 Integer 对比不管是否 new 对象,两者判断都是 true,因为会把 Integer 自动拆箱为 int 再去比。 什么是 Java 内部类?

内部类是指把 A 类定义在另一个 B 类的内部。

例如:把类 User 定义在类 Role 中,类 User 就被称为内部类。

class Role {
    class User {
    }
}

1、内部类的访问规则 ​ 1)可以直接访问外部类的成员,包括私有

​2)外部类要想访问内部类成员,必须创建对象

2、内部类的分类

​1)成员内部类

​2)局部内部类

​3)静态内部类

​4)匿名内部类 常用的垃圾收集器有哪些?

Serial 收集器(新生代)

Serial 即串行,以串行的方式执行,是单线程的收集器。它只会使用一个线程进行垃圾收集工作,GC 线程工作时,其它所有线程都将停止工作。

作为新生代垃圾收集器的方式:-XX:+UseSerialGC

ParNew 收集器(新生代)

Serial 收集器的多线程版本,需注意的是 ParNew 在单核环境下性能比 Serial 差,在多核条件下有优势。

作为新生代垃圾收集器的方式:-XX:+UseParNewGC

Parallel Scavenge 收集器(新生代)

多线程的收集器,目标是提高吞吐量(吞吐量 = 运行用户程序的时间 / (运行用户程序的时间 + 垃圾收集的时间))。

作为新生代垃圾收集器的方式:-XX:+UseParallelGC

Serial Old 收集器(老年代)

Serial 收集器的老年代版本。

作为老年代垃圾收集器的方式:-XX:+UseSerialOldGC

Parallel Old 收集器(老年代)

Parallel Scavenge 收集器的老年代版本。

作为老年代垃圾收集器的方式:-XX:+UseParallelOldGC

CMS 收集器(老年代)

CMS(Concurrent Mark Sweep),收集器几乎占据着 JVM 老年代收集器的半壁江山,它划时代的意义就在于垃圾回收线程几乎能做到与用户线程同时工作。

作为老年代垃圾收集器的方式:-XX:+UseConcMarkSweepGC

G1 收集器(新生代 + 老年代)

面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。关注微信公众号 Java 精选,内涵视频资料、开源项目、源码分析等等。

作为老年代垃圾收集器的方式:-XX:+UseG1GC 生产环境中应用的 JVM 参数有哪些?

-server -Xms6000M -Xmx6000M -Xmn500M -XX:PermSize=500M -XX:MaxPermSize=500M -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0 -Xnoclassgc -XX:+DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=90 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log 什么情况下会发生栈内存溢出?

栈溢出 (StackOverflowError)

栈是线程私有的,他的生命周期与线程相同,每个方法在执行时会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口灯信息。局部变量表又包含基本数据类型,对象引用类型。

若线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常。

若虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。 常用的 JVM 调优配置参数有哪些?

假设参数为 - Xms20m -Xmx20m -Xss256k

XX 比 X 的稳定性更差,并且版本更新不会进行通知和说明。

1、-Xms

s 为 strating,表示堆内存起始大小

2、-Xmx

x 为 max,表示最大的堆内存,一般来说 - Xms 和 - Xmx 的设置为相同大小,当 heap 自动扩容时,会发生内存抖动,影响程序的稳定性

3、-Xmn

n 为 new,表示新生代大小,-Xss 规定了每个线程虚拟机栈(堆栈)的大小。

4、-XX:SurvivorRator=8

表示堆内存中新生代、老年代和永久代的比为 8:1:1

5、-XX:PretenureSizeThreshold=3145728

表示当创建(new)的对象大于 3M 的时候直接进入老年代

6、-XX:MaxTenuringThreshold=15

表示当对象的存活的年龄(minor gc 一次加 1)大于多少时,进入老年代

7、-XX:-DisableExplicirGC

表示是否(+ 表示是,- 表示否)打开 GC 日志 什么是类加载器?

类加载器是指把类文件加载到虚拟机中,通过一个类的全限定名(包名 + 类型)来获取描述该类的二进制字节流。

类加载器是 Java 语言的一项创新,最开始是为了满足 Java Applet 的需求而设计的。

类加载器目前在层次划分、程序热部署和代码加密等领域被广泛使用。 类加载器分为哪几类?

JVM 默认提供了系统类加载器(JDK1.8),包括如下:

Bootstrap ClassLoader(系统类加载器)

Application ClassLoader(应用程序类加载器)

Extension ClassLoader(扩展类加载器)

Customer ClassLoader(自定义加载器) 可以自定义一个 java.lang.String 吗?

答案是可以的,但是不能被加载使用。

这个主要是因为加载器的委托机制,在类加载器的结构图中,BootStrap 是顶层父类,ExtClassLoader 是 BootStrap 类的子类,ExtClassLoader 是 AppClassLoader 的父类。当使用 java.lang.String 类时,Java 虚拟机会将 java.lang.String 类的字节码加载到内存中。

加载某个类时,优先使用父类加载器加载需要使用的类。加载自定义 java.lang.String 类,其使用的加载器是 AppClassLoader,根据优先使用父类加载器原理,AppClassLoader 加载器的父类为 ExtClassLoader,这时加载 String 使用的类加载器是 ExtClassLoader,但类加载器 ExtClassLoader 在 jre/lib/ext 目录下并不能找到自定义 java.lang.String 类。

然后使用 ExtClassLoader 父类的加载器 BootStrap,父类加载器 BootStrap 在 JRE/lib 目录的 rt.jar 找到了 String.class,将其加载到内存中。 MyBatis 实现批量插入数据的方式有几种?

MyBatis 实现批量插入数据的方式有几种?

1、MyBatis foreach 标签

foreach 主要用在构建 in 条件,在 SQL 语句中进行迭代一个集合。

foreach 元素的属性主要有 item,index,collection,open,separator,close。

item 表示集合中每一个元素进行迭代时的别名 index 指定一个名字,用于表示在迭代过程中,每次迭代到的位置 open 表示该语句以什么开始 separator 表示在每次进行迭代之间以什么符号作为分隔符 close 表示以什么结束

collection 必须指定该属性,在不同情况下,值是不同的,主要体现 3 种情况:

若传入单参数且参数类型是 List 时,collection 属性值为 list

若传入单参数且参数类型是 array 数组时,collection 的属性值为 array

若传入参数是多个时,需要封装成 Map

具体用法如下:

<insert id="insertForeach" parameterType="java.util.List" useGeneratedKeys="false">


insert into t_userinfo


(name, age, sex) values


<foreach collection="list" item="item" index="index" separator=",">



(#{item.name},#{item.age},#{item.sex})


</foreach>



</insert>

2、MyBatis ExecutorType.BATCH

Mybatis 内置 ExecutorType,默认是 simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交 sql。关注微信公众号 Java 精选,内涵视频资料、开源项目、源码分析等等。

batch 模式会重复使用已经预处理的语句,并批量执行所有更新语句。但 batch 模式 Insert 操作时,在事务没有提交前,是无法获取到自增的 id。 什么是自动装箱?什么是自动拆箱?

自动装箱是指将基本数据类型重新转化为对象。

public class Test {  


public static void main(String[] args) {  



Integer num = 9;


}  
}  

num = 9 的值是属于基本数据类型,原则上不能直接赋值给对象 Integer。但是在 JDK1.5 版本后就可以进行这样的声明自动将基本数据类型转化为对应的封装类型,成为对象后可以调用对象所声明的方法。

自动拆箱是指将对象重新转化为基本数据类型。

public class Test {  


public static void main(String[] args) {  



// 声明Integer对象



Integer num = 9;



// 隐含自动拆箱



System.out.print(num--);


}  
}

由于对象不能直接进行运算,而是需要转化为基本数据类型后才能进行加减乘除。

// 装箱
Integer num = 10;
// 拆箱
int num1 = num;

一级缓存和二级缓存有什么区别?

一级缓存

Mybatis 一级缓存是指 SQLSession

一级缓存的作用域是 SQlSession

Mabits 默认开启一级缓存

同一个 SqlSession 中,执行相同的 SQL 查询时第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。

当执行 SQL 时两次查询中间发生了增删改的操作,则 SQLSession 的缓存会被清空。

每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中。

Mybatis 的内部缓存使用一个 HashMap,其中 key 为 hashcode+statementId+sql 语句,Value 为查询出来的结果集映射成的 java 对象。

SqlSession 执行 insert、update、delete 等操作 commit 后会清空该 SQLSession 缓存。

二级缓存

二级缓存是 mapper 级别的,Mybatis 默认是没有开启二级缓存的。

第一次调用 mapper 下的 SQL 去查询用户的信息,查询到的信息会存放到 mapper 对应的二级缓存区域。

第二次调用 namespace 下的 mapper 映射文件中,相同的 sql 去查询用户信息,会去对应的二级缓存内取结果。 Redis 支持那些数据类型?

String 字符串

命令: set key value

string 类型是二进制安全的,它可以包含任何数据,如图片或序列化对象等。

string 类型是 Redis 最基本的数据类型,一个键最大能存储 512MB。

Hash(哈希)

命令: hmset name key1 value1 key2 value2

Redis hash 是一个键值(key=>value)对集合。

Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

List(列表)

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

命令: lpush name value

key 对应 list 的头部添加字符串元素

命令: rpush name value

key 对应 list 的尾部添加字符串元素

命令: lrem name index

key 对应 list 中删除 count 个和 value 相同的元素

命令: llen name

返回 key 对应 list 的长度

Set(集合)

命令: sadd name value

Redis 的 Set 是 string 类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

zset(sorted set:有序集合)

命令: zadd name score value

Redis zset 和 set 一样也是 string 类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

zset 的成员是唯一的,但分数(score)却可以重复。 什么是 Redis 持久化?Redis 有哪几种持久化方式?

Redis 持久化是指把内存的数据写到磁盘中去,防止服务因宕机导致内存数据丢失。

Redis 提供了两种持久化方式:RDB(默认)和 AOF。

RDB 是 Redis DataBase 缩写,功能核心函数 rdbSave(生成 RDB 文件)和 rdbLoad(从文件加载内存)两个函数

AOF 是 Append-only file 缩写,每当执行服务器(定时)任务或者函数时 flushAppendOnlyFile 函数都会被调用,这个函数执行以下两个工作。

AOF 写入与保存

WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件

SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。 什么是缓存穿透?如何避免?

缓存穿透是指一般的缓存系统,都是按照 key 去缓存查询,如果不存在对应的 value,就应该去后端系统查找(比如 DB)。

一些恶意的请求会故意查询不存在的 key,导致请求量大,造成后端系统压力大,这就是做缓存穿透。

如何避免?

1)对查询结果为空的情况也进行缓存,缓存时间设置短一点或 key 对应的数据 insert 后清理缓存。

2)对一定不存在的 key 进行过滤,可以把所有可能存在的 key 放到一个大的 Bitmap 中,查询时通过 bitmap 过滤。 什么是缓存雪崩?何如避免?

缓存雪崩是指当缓存服务器重启或大量缓存集中在某一个时间段内失效,这样在失效时,会给后端系统带来很大压力,导致系统崩溃。

如何避免?

1)在缓存失效后,使用加锁或队列的方式来控制读数据库写缓存的线程数量。如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。

2)实现二级缓存方式,A1 为原始缓存,A2 为拷贝缓存,A1 失效时,切换访问 A2,A1 缓存失效时间设置为短期,A2 设置为长期。

3)不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀。 MyBatis 是否支持延迟加载?其原理是什么?

Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载。

association 指的就是一对一 collection 指的就是一对多查询

在 Mybatis 配置文件中,启用延迟加载配置参数

lazyLoadingEnabled=true。

原理:使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法。

比如调用 a.getB ().getName (),拦截器 invoke () 方法发现 a.getB () 是 null 值,就会单独发送事先保存好的查询关联 B 对象的 SQL 语句,先查询出 B,然后再调用 a.setB (b) 赋值,最后再调用 a.getB ().getName () 方法就有值了。几乎所有的包括 Hibernate、Mybatis,支持延迟加载的原理都是一样的。 如何解决 MyBatis 转义字符的问题?

xml 配置文件使用转义字符

SELECT * FROM test WHERE crate_time &lt;= #{crate_time} AND end_date &gt;= #{crate_time}

xml 转义字符关系表

字符 转义 备注
< &lt; 小于号
> &gt; 大于号
& &amp;
' &apos; 单引号
" &quot; 双引号

注意:XML 中只有”<” 和”&” 是非法的,其它三个都是合法存在的,使用时都可以把它们转义了,养成一个良好的习惯。

转义前后的字符都会被 xml 解析器解析,为了方便起见,使用

<![CDATA[...]]>

来包含不被 xml 解析器解析的内容。标记所包含的内容将表示为纯文本,其中... 表示文本内容。

但要注意的是:

1)此部分不能再包含”]]>”;

2)不允许嵌套使用;

3)”]]>” 这部分不能包含空格或者换行。 Zookeeper 是如何保证事务的顺序一致性的?

Zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被提出的时候加上了 zxid。

zxid 实际上是一个 64 位的数字,高 32 位是 epoch 用来标识 leader 的周期。

如果有新的 leader 产生出来,epoch 会自增,低 32 位用来递增计数。

当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。

Zookeeper 有哪几种部署模式?

部署模式:单机模式、伪集群模式、集群模式。 Zookeeper 集群最少要几台服务器,什么规则?

集群最少要 3 台服务器,集群规则为 2N+1 台,N>0,即 3 台。 Zookeeper 有哪些典型应用场景?

Zookeeper 是一个典型的发布 / 订阅模式的分布式数据管理与协调框架,开发者可以使用它来进行分布式数据的发布和订阅。

通过对 Zookeeper 中丰富的数据节点进行交叉使用,配合 Watcher 事件通知机制,可以非常方便的构建一系列分布式应用中年都会涉及的核心功能。应用场景如下:

1)数据发布 / 订阅

2)负载均衡

3)命名服务

4)分布式协调 / 通知

5)集群管理

6)Master 选举

7)分布式锁

8)分布式队列 Paxos 和 ZAB 算法有什么区别和联系?

相同点

两者都存在一个类似于 Leader 进程的角色,由其负责协调多个 Follower 进程的运行;

Leader 进程都会等待超过半数的 Follower 做出正确的反馈后,才会将一个提案进行提交;

ZAB 协议中每个 Proposal 中都包含一个 epoch 值来代表当前的 Leader 周期,而 Paxos 中名字为 Ballot。

不同点

Paxos 是用来构建分布式一致性状态机系统,而 ZAB 用来构建高可用的分布式数据主备系统(Zookeeper)。 Zookeeper 中 Java 客户端都有哪些?

Java 客户端:

1)zk 自带的 zkclient

2)Apache 开源的 Curator Zookeeper 集群支持动态添加服务器吗?

动态添加服务器等同于水平扩容,而 Zookeeper 在这方面的表现并不是太好。两种方式:

1)全部重启

关闭所有 Zookeeper 服务,修改配置之后启动。不影响之前客户端的会话。

2)逐一重启

在过半存活即可用的原则下,一台机器重启不影响整个集群对外提供服务。这是比较常用的方式,在 Zookeeper 3.5 版本开始支持动态扩容。 Zookeeper 和 Nginx 的负载均衡有什么区别?

Zookeeper 的负载均衡是可以调控,而 Nginx 的负载均衡只是能调整权重,其他可控的都需要自己写 Lua 插件;但是 Nginx 的吞吐量比 Zookeeper 大很多,具体按业务应用场景选择用哪种方式。 Zookeeper 节点宕机如何处理?

Zookeeper 本身是集群模式,官方推荐不少于 3 台服务器配置。Zookeeper 自身保证当一个节点宕机时,其他节点会继续提供服务。

假设其中一个 Follower 宕机,还有 2 台服务器提供访问,因为 Zookeeper 上的数据是有多个副本的,并不会丢失数据;

如果是一个 Leader 宕机,Zookeeper 会选举出新的 Leader。

Zookeeper 集群的机制是只要超过半数的节点正常,集群就可以正常提供服务。只有 Zookeeper 节点挂得太多,只剩不到一半节点提供服务,才会导致 Zookeeper 集群失效。

Zookeeper 集群节点配置原则

3 个节点的 cluster 可以挂掉 1 个节点(leader 可以得到 2 节 > 1.5)。

2 个节点的 cluster 需保证不能挂掉任何 1 个节点(leader 可以得到 1 节 <=1)。 Socket 前后端通信是如何实现服务器集群?

假设有两台 A 服务器服务 S1 和 B 服务器服务 S2,应用服务 S0

首先你接收客户端(浏览器)请求的服务肯定是单点(一对一)连接,之后交给应用服务 S0 处理分配给两台服务器 A 和 B 上的服务 S1 和 S2。

分配原则可以按一定的策略,如计数器等,按当前活跃连接数来决定分给哪台服务器。也可以更科学的按两台服务器实际处理的数据量来分配,因为有些连接可能一直空闲。

如果两台服务器上的服务通信正常且数据库能够承受压力,访问请求并不是太多的情况下,可以考虑使用数据库传递消息,反之可以考虑使用 Redis 缓存技术。

如果需要即时传递消息,在其中一个服务器上的服务 S1 查找不到,把消息发给另外一台服务器上的服务 S2 或把消息存储至数据库或缓存当中,然后通知其他服务有生产消息。类似 MQ 处理方式可参考 ActiveMQ。 为什么要用 Redis 而不用 Map、Guava 做缓存?

缓存可以划分为本地缓存和分布式缓存。

以 Java 为例,使用自带 Map 或者 Guava 类实现的是本地缓存,主要特点是轻量以及快速,它们的生命周期会随着 JVM 的销毁而结束,且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

使用 Redis 或 Memcached 等被称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,具有一致性,但是需要保持 Redis 或 Memcached 服务的高可用。 Redis 是单线程的吗?为什么这么快?

Redis 是单线程的,至于为什么这么快主要是因如下几方面:

1)Redis 是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在 IO 上,所以读取速度快。

2)Redis 使用的是非阻塞 IO、IO 多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。

3)Redis 采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。

4)Redis 避免了多线程的锁的消耗。

5)Redis 采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大 为什么使用消息队列?

消息队列中间件有很多,使用的场景广泛。主要解决的核心问题是削峰填谷、异步处理、模块解耦。 RabbitMQ 有几种广播类型?

direct(默认方式):最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,默认采取轮询的方式进行消息发送。

headers:与 direct 类似,只是性能很差,此类型几乎用不到。

fanout:分发模式,把消费分发给所有订阅者。

topic:匹配订阅模式,使用正则匹配到消息队列,能匹配到的都能接收到。 Kafka 的分区策略有哪些?

ProducerRecord 内部数据结构:

-- Topic (名字) -- PartitionID (可选) -- Key [( 可选 ) -- Value

提供三种构造函数形参:

ProducerRecord(topic, partition, key, value)
ProducerRecord(topic, key, value)
ProducerRecord(topic, value)

第一种分区策略:指定分区号,直接将数据发送到指定的分区中。

第二种分区策略:没有指定分区号,指定数据的 key 值,通过 key 取上 hashCode 进行分区。

第三种分区策略:没有指定分区号,也没有指定 key 值,直接轮循进行分区。

第四种分区策略:自定义分区。 RabbitMQ 有哪些重要组件?

ConnectionFactory (连接管理器):应用程序与 RabbitMQ 之间建立连接的管理器

Channel (信道):消息推送使用的通道

Exchange (交换器):用于接受、分配消息

Queue (队列):用于存储生产者的消息

RoutingKey (路由键):用于把生产者的数据分配到交换器上

BindKey (绑定键):用于把交换器的消息绑定到队列上 RabbitMQ 有哪些重要角色?

生产者:

消息的生产者,负责创建和推送数据到消息服务器。

消费者:

消息的接收者,用于处理数据和确认消息。

代理:

RabbitMQ 本身,用于扮演传递消息的角色,本身并不生产消息。 RabbitMQ 如何保证消息顺序性?

RabbitMQ 保证消息的顺序性方式有两种:

1)拆分多个 queue,每个 queue 对应一个 consumer(消费者),就是多一些 queue。

2)一个 queue,对应一个 consumer(消费者),然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。 如何保证消息消费的幂等性?

1、利用数据库的唯一约束实现幂等

比如将订单表中的订单编号设置为唯一索引,创建订单时,根据订单编号就可以保证幂等

2、去重表

根据数据库的唯一性约束类似的方式来实现。其实现大体思路是:首先在去重表上建唯一索引,其次操作时把业务表和去重表放在同个本地事务中,如果出现重现重复消费,数据库会抛唯一约束异常,操作就会回滚。

3、利用 redis 的原子性

每次操作都直接 set 到 redis 里面,然后将 redis 数据定时同步到数据库中。

4、多版本(乐观锁)控制

此方式多用于更新的场景下。其实现的大体思路是:给业务数据增加一个版本号属性,每次更新数据前,比较当前数据的版本号是否和消息中的版本一致,如果不一致则拒绝更新数据,更新数据的同时将版本号 + 1。

5、状态机机制

此方式多用于更新且业务场景存在多种状态流转的场景。

6、token 机制

生产者发送每条数据时,增加一个全局唯一 ID,这个 ID 通常是业务的唯一标识。在消费者消费时,需要验证这个 ID 是否被消费过,如果没消费过,则进行业务处理。处理结束后,在把这个 ID 存入 Redis,同时设置状态为已消费。如果已经消费过,则清除 Redis 缓存的这个 ID。 Kafka 消费者如何取消订阅?

在 KafkaConsumer 类中使用 unsubscribe () 方法来取消主题的订阅。该方法可以取消如下的订阅方法:

subscribe (Collection); 方式实现的订阅 subscribe (Pattern); 方式实现的订阅 assign () 方式实现的订阅

取消订阅使用代码如下:

consumer.unsubscribe();

将 subscribe (Collection) 或 assign () 中的集合参数设置为空集合,也可以实现取消订阅,以下三种方式都可以取消订阅:

consumer.unsubscribe();
consumer.subscribe(new ArrayList<String>());
consumer.assign(new ArrayList<TopicPartition>());

设计模式有多少种,都有哪些设计模式?

Java 有 23 种设计模式

设计模式总体分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式。 设计模式的六大原则是什么?

1)开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2)里氏代换原则(Liskov Substitution Principle)

里氏代换原则 (Liskov Substitution Principle LSP) 面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对 “开 - 闭” 原则的补充。实现 “开 - 闭” 原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3)依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。

4)接口隔离原则(Interface Segregation Principle)

大概意思是指使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

5)迪米特法则(最少知道原则)(Demeter Principle)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6)合成复用原则(Composite Reuse Principle)

原则是尽量使用合成 / 聚合的方式,而不是使用继承。 什么是单例模式?

单例模式的定义就是确保某一个类仅有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式分为:懒汉式单例、饿汉式单例、登记式单例三种。

单例模式特点:

1) 单例类只能有一个实例。

2) 单例类必须自己创建自己的唯一实例。

3) 单例类必须给所有其他对象提供这一实例。

单例模式的优点是内存中只有一个对象,节省内存空间;避免频繁的创建销毁对象,可以提高性能;避免对资源的多重占用,简化访问;为整个系统提供一个全局访问点。

单例模式的缺点是不适用于变化频繁的对象;滥用单利将带来一些问题,如为了节省资源将数据库连接池对象设计为的单利类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失。

从名字上来说饿汉和懒汉,饿汉就是类一旦加载,就把单例初始化完成,保证 getInstance 的时候单例是已经被初始化,而懒汉比较懒,只有当调用 getInstance 的时候,才回去初始化这个单例。

1、懒汉式单例,具体代码如下:

package com.yoodb;
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
    //私有的无参构造方法
    private Singleton(){
         
    }
    //注意,这里没有final
    private static Singleton single = null;
    //静态工厂方法
    public synchronized  static Singleton getInstance(){
        if(single == null){
            single = new Singleton();
        }
        return single;
    }
}

2、饿汉式单例,具体代码如下:

package com.yoodb;
 
public class Singleton {
    //私有的无参构造方法
    private Singleton(){
         
    }
    //自动实例化
    private final static Singleton single = new Singleton();
    //静态工厂方法
    public static synchronized Singleton getInstance(){
        return single;
    }
}

3、登记式单例,具体代码如下:

package com.yoodb;
 
import java.util.HashMap;
import java.util.Map;
 
public class Singleton {
    public static Map<String,Singleton> map = new HashMap<String,Singleton>();
    static{
        //将类名注入下次直接获取
        Singleton single = new Singleton();
        map.put(single.getClass().getName(),single);
    }
    //保护式无参构造方法
    protected Singleton(){}
     
    public static Singleton getInstance(String name){
        if(name == null){
            name = Singleton.class.getName(); 
        }
        if(map.get(name) == null){
            try {
                map.put(name, Singleton.class.newInstance());
            } catch (InstantiationException | IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
     
    public String create() {      
return "创建一个单例!";      
    }  
     
    public static void main(String[] args) {
         Singleton single = Singleton.getInstance(null);  
         System.out.println(single.create());  
    }
}

单例模式中饿汉式和懒汉式有什么区别?

1、线程安全

饿汉式是线程安全的,可以直接用于多线程而不会出现问题,懒汉式就不行,它是线程不安全的,如果用于多线程可能会被实例化多次,失去单例的作用。

2、资源加载

饿汉式在类创建的同时就实例化一个静态对象,不管之后会不会使用这个单例,都会占据一定的内存资源,相应的在调用时速度也会更快。

懒汉式顾名思义,会延迟加载,在第一次使用该单例时才会实例化对象出来,第一次掉用时要初始化,如果要做的工作比较多,性能上会有些延迟,第一次调用之后就和饿汉式。 单例模式都有哪些应用场景?

1)需要生成唯一序列;

2)需要频繁实例化然后销毁代码的对象;

3)有状态的工具类对象;

4)频繁访问数据库或文件的对象。

例如:项目中读取配置文件、数据库连接池参数、spring 中的 bean 默认是单例、线程池(threadpool)、缓存(cache)、日志参数等程序的对象。

需要注意的事这些场景只能有一个实例,如果生成多个实例,就会导致许多问题产生,如:程序行为异常、资源使用过量或数据不一致等。 什么是线程安全?

如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的且其他变量的值也和预期的是一样的,就是线程安全的。

或者也可以理解成一个类或程序所提供的接口对于线程来说是原子操作,多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说不用考虑同步的问题,那就是线程安全的。 Spring 框架中使用了哪些设计模式?

Spring 框架中使用大量的设计模式,下面列举比较有代表性的:

代理模式

AOP 能够将那些与业务无关(事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度有利于可拓展性和可维护性。

单例模式

Spring 中 bean 的默认作用域是单例模式,在 Spring 配置文件中定义 bean 默认为单例模式。

模板方法模式

模板方法模式是一种行为设计模式,用来解决代码重复的问题,如 RestTemplate、JmsTemplate、JpaTemplate。

包装器设计模式

Spring 根据不同的业务访问不同的数据库,能够动态切换不同的数据源。

观察者模式

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

工厂模式

Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。 Spring MVC 执行流程是什么?

1)客户端发送请求到前端控制器 DispatcherServlet。

2)DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。

3)处理器映射器找到具体的处理器,根据 xml 配置、注解进行查找,生成处理器对象及处理器拦截器,并返回给 DispatcherServlet。

4)DispatcherServlet 调用 HandlerAdapter 处理器适配器。

5)HandlerAdapter 经过适配调用具体的后端控制器 Controller。

6)Controller 执行完成返回 ModelAndView。

7)HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。

8)DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。

9)ViewReslover 解析后返回具体 View。

10)DispatcherServlet 根据 View 进行渲染视图,将模型数据填充至视图中。

11)DispatcherServlet 响应客户端。

组件说明

DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。

HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。

ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel 等。 Spring MVC 如何解决请求中文乱码问题?

解决 GET 请求中文乱码问题

方式一:每次请求前使用 encodeURI 对 URL 进行编码。

方式二:在应用服务器上配置 URL 编码格式,在 Tomcat 配置文件 server.xml 中增加 URIEncoding="UTF-8",然后重启 Tomcat 即可。

<ConnectorURIEncoding="UTF-8" 
    port="8080"  maxHttpHeaderSize="8192"  maxThreads="150" 
    minSpareThreads="25"  maxSpareThreads="75"connectionTimeout="20000" 



    disableUploadTimeout="true" URIEncoding="UTF-8" />

解决 POST 请求中文乱码问题

方式一:在 request 解析数据时设置编码格式:

request.setCharacterEncoding("UTF-8");

方式二:使用 Spring 提供的编码过滤器

在 web.xml 文件中配置字符编码过滤器,增加如下配置:

<!--编码过滤器-->                                                                                                                
<filter>                                                                


<filter-name>encodingFilter</filter-name>                           


<filter-class>                                                      



org.springframework.web.filter.CharacterEncodingFilter          


</filter-class>                                                     


<!-- 开启异步支持-->                                               


<async-supported>true</async-supported>                             


<init-param>                                                        



<param-name>encoding</param-name>                               



<param-value>utf-8</param-value>                                


</init-param>                                                       
</filter>                                                               
<filter-mapping>                                                        


<filter-name>encodingFilter</filter-name>                           


<url-pattern>/*</url-pattern>                                       
</filter-mapping>     

该过滤器的作用就是强制所有请求和响应设置编码格式:

request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");

Spring MVC 请求转发和重定向有什么区别?

请求转发是浏览器发出一次请求,获取一次响应,而重定向是浏览器发出 2 次请求,获取 2 次请求。

请求转发是浏览器地址栏未发生变化,是第 1 次发出的请求,而重定向是浏览器地址栏发生变化,是第 2 次发出的请求。

请求转发称为服务器内跳转,重定向称为服务器外跳转。

请求转发是可以获取到用户提交请求中的数据,而重定向是不可以获取到用户提交请求中的数据,但可以获取到第 2 次由浏览器自动发出的请求中携带的数据。

请求转发是可以将请求转发到 WEB-INF 目录下的(内部)资源,而重定向是不可以将请求转发到 WEB-INF 目录下的资源。 Spring MVC 中系统是如何分层?

表现层(UI):数据的展现、操作页面、请求转发。

业务层(服务层):封装业务处理逻辑。

持久层(数据访问层):封装数据访问逻辑。

各层之间的关系:MVC 是一种表现层的架构,表现层通过接口调用业务层,业务层通过接口调用持久层,当下一层发生改变,不影响上一层的数据。 如何开启注解处理器和适配器?

在配置文件中(一般命名为 springmvc.xml 文件)通过开启配置:

<mvc:annotation-driven>

来实现注解处理器和适配器的开启。 Spring MVC 如何设置重定向和转发?

在返回值前面加 “forward:” 参数,可以实现转发。

"forward:getName.do?name=Java精选"

在返回值前面加 “redirect:” 参数,可以实现重定向。

"redirect:https://blog.yoodb.com"
Spring MVC 中函数的返回值是什么?

Spring MVC的返回值可以有很多类型,如String、ModelAndView等,但事一般使用String比较友好。
@RequestMapping 注解用在类上有什么作用?

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

用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。

比如

```java
@Controller
@RequestMapping("/test/")
public class TestController{

}

启动的是本地服务,默认端口是 8080,通过浏览器访问的路径就是如下地址:

http://localhost:8080/test/

Spring MVC 控制器是单例的吗?

默认情况下是单例模式,在多线程进行访问时存在线程安全的问题。

解决方法可以在控制器中不要写成员变量,这是因为单例模式下定义成员变量是线程不安全的。

通过 @Scope (value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) 或 @Scope ("prototype") 可以实现多例模式。但是不建议使用同步,因为会影响性能。

使用单例模式是为了性能,无需频繁进行初始化操作,同时也没有必要使用多例模式。 RequestMethod 可以同时支持 POST 和 GET 请求访问吗?

POST 请求访问

@RequestMapping(value="/wx/getUserById",method = RequestMethod.POST)

GET 请求访问

@RequestMapping(value="/wx/getUserById/{userId}",method = RequestMethod.GET)

同时支持 POST 和 GET 请求访问

@RequestMapping(value="/subscribe/getSubscribeList/{customerCode}/{sid}/{userId}")

RequestMethod 常用的参数

GET(SELECT):从服务器查询,在服务器通过请求参数区分查询的方式。

POST(CREATE):在服务器新建一个资源,调用 insert 操作。

PUT(UPDATE):在服务器更新资源,调用 update 操作。

DELETE(DELETE):从服务器删除资源,调用 delete 语句。 Spring 依赖注入有几种实现方式?

1)Constructor 构造器注入:通过将 @Autowired 注解放在构造器上来完成构造器注入,默认构造器参数通过类型自动装配。

public class Test {  
    private UserInterface user;  
    @Autowired 
    private Test(UserInterface user) {  
        this.user = user;  
    }  
}

2)Field 接口注入:通过将 @Autowired 注解放在构造器上来完成接口注入。

@Autowired //接口注入  
private IUserService userService;  

3)Setter 方法参数注入:通过将 @Autowired 注解放在方法上来完成方法参数注入。

public class Test {



private User user;

    @Autowired
    public void setUser(User user) {  
        this.user = user;  
    }  
    public String getuser() {  
        return user;  
    }  
}

Spring 可以注入 null 或空字符串吗?

完全可以 Spring 支持哪几种 bean 作用域?

1)singleton:默认,每个容器中只有一个 bean 的实例,单例的模式由 BeanFactory 自身来维护。

2)prototype:为每一个 bean 请求提供一个实例。

3)request:为每一个网络请求创建一个实例,在请求完成后,bean 会失效并被垃圾回收器回收。

4)session:与 request 范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean 会随之失效。

5)global-session:全局作用域,global-session 和 Portlet 应用相关。

当应用部署在 Portlet 容器中工作时,它包含很多 portlet。

如果想要声明让所有的 portlet 共用全局的存储变量的话,那么这全局变量需要存储在 global-session 中。

全局作用域与 Servlet 中的 session 作用域效果相同。 JDK1.8 中 ConcurrentHashMap 不支持空键值吗?

首先明确一点 HashMap 是支持空键值对的,也就是 null 键和 null 值,而 ConcurrentHashMap 是不支持空键值对的。

查看一下 JDK1.8 源码,HashMap 类部分源码,代码如下:

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
}
static final int hash(Object key) {


int h;


return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap 在调用 put () 方法存储数据时会调用 hash () 方法来计算 key 的 hashcode 值,可以从 hash () 方法上得出当 key==null 时返回值是 0,这意思就是 key 值是 null 时,hash () 方法返回值是 0,不会再调用 key.hashcode () 方法。

ConcurrentHashMap 类部分源码,代码如下:

public V put(K key, V value) {


return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {


if (key == null || value == null) throw new NullPointerException();


int hash = spread(key.hashCode());


int binCount = 0;


for (Node<K,V>[] tab = table;;) {



Node<K,V> f; int n, i, fh;



if (tab == null || (n = tab.length) == 0)




tab = initTable();



else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {




if (casTabAt(tab, i, null,







 new Node<K,V>(hash, key, value, null)))





break;                   // no lock when adding to empty bin



}



else if ((fh = f.hash) == MOVED)




tab = helpTransfer(tab, f);



else {




V oldVal = null;




synchronized (f) {





if (tabAt(tab, i) == f) {






if (fh >= 0) {







binCount = 1;







for (Node<K,V> e = f;; ++binCount) {








K ek;








if (e.hash == hash &&









((ek = e.key) == key ||









 (ek != null && key.equals(ek)))) {









oldVal = e.val;









if (!onlyIfAbsent)










e.val = value;









break;








}








Node<K,V> pred = e;








if ((e = e.next) == null) {









pred.next = new Node<K,V>(hash, key,















  value, null);









break;








}







}






}






else if (f instanceof TreeBin) {







Node<K,V> p;







binCount = 2;







if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,














   value)) != null) {








oldVal = p.val;








if (!onlyIfAbsent)









p.val = value;







}






}





}




}




if (binCount != 0) {





if (binCount >= TREEIFY_THRESHOLD)






treeifyBin(tab, i);





if (oldVal != null)






return oldVal;





break;




}



}


}


addCount(1L, binCount);


return null;
}

ConcurrentHashmap 在调用 put () 方法时调用了 putVal () 方法,而在该方法中判断 key 为 null 或 value 为 null 时抛出空指针异常 NullPointerException。

ConcurrentHashmap 是支持并发的,当通过 get () 方法获取对应的 value 值时,如果指定的键为 null,则为 NullPointerException,这主要是因为获取到的是 null 值,无法分辨是 key 没找到 null 还是有 key 值为 null。 Spring 有哪些不同的通知类型?

通知(advice)是在程序中想要应用在其他模块中横切关注点的实现。

Advice 主要有 5 种类型:

@Before 注解使用 Advice

前置通知(BeforeAdvice):在连接点之前执行的通知(advice),除非它抛出异常,否则没有能力中断执行流。

@AfterReturning 注解使用 Advice

返回之后通知(AfterRetuningAdvice):如果一个方法没有抛出异常正常返回,在连接点正常结束之后执行的通知(advice)。

@AfterThrowing 注解使用 Advice

抛出(异常)后执行通知(AfterThrowingAdvice):若果一个方法抛出异常来退出的话,这个通知(advice)就会被执行。

@After 注解使用 Advice

后置通知(AfterAdvice):无论连接点是通过什么方式退出的正常返回或者抛出异常都会执行在结束后执行这些通知(advice)。

注解使用 Advice

围绕通知(AroundAdvice):围绕连接点执行的通知(advice),只有这一个方法调用。这是最强大的通知(advice)。 Spring AOP 连接点和切入点是什么?

连接点(Joint Point)

连接点是指一个应用执行过程中能够插入一个切面的点,可以理解成一个方法的执行或者一个异常的处理等。

连接点可以是调用方法、抛出异常、修改字段等时,切面代码可以利用这些点插入到应用的正规流程中。使得程序执行过程中能够应用通知的所有点。

在 Spring AOP 中一个连接点总是代表一个方法执行。如果在这些方法上使用横切的话,所有定义在 EmpoyeeManager 接口中的方法都可以被认为是一个连接点。

切入点(Point cut)

切入点是一个匹配连接点的断言或者表达式,如果通知定义了 “什么” 和 “何时”,那么切点就定义了 “何处”。

通知(Advice)与切入点表达式相关联,切入点用于准确定位,确定在什么地方应用切面通知。

例如表达式

execution(* EmployeeManager.getEmployeeById(...))

可以匹配 EmployeeManager 接口的 getEmployeeById ()。

Spring 默认使用 AspectJ 切入点表达式,由切入点表达式匹配的连接点概念是 AOP 的核心。 Spring AOP 代理模式是什么?

代理模式是使用非常广泛的设计模式之一。

代理模式是指给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是生活中常见的房产中介。

Spring AOP 是基于代理实现的。

Spring AOP 代理是一个由 AOP 框架创建的用于在运行时实现切面协议的对象。

Spring AOP 默认为 AOP 代理使用标准的 JDK 动态代理,这使得任何接口(或者接口的集合)可以被代理。

Spring AOP 也可以使用 CGLIB 代理,如果业务对象没有实现任何接口那么默认使用 CGLIB。 Spring 框架有哪些特点?

1)方便解耦,简化开发

通过 Spring 提供的 IoC 容器,可以将对象之间的依赖关系交由 Spring 进行控制,避免编码所造成的过度耦合。使用 Spring 可以使用户不必再为单实例模式类、属性文件解析等底层的需求编码,可以更专注于上层的业务逻辑应用。

2)支持 AOP 面向切面编程

通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松完成。

3)支持声明事物

通过 Spring 可以从单调繁琐的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

4)方便程序测试

使用非容器依赖的编程方式进行几乎所有的测试工作,通过 Spring 使得测试不再是高成本的操作,而是随手可做的事情。Spring 对 Junit4 支持,可以通过注解方便的测试 Spring 程序。

5)方便便捷集成各种中间件框架

Spring 可以降低集成各种中间件框架的难度,Spring 提供对各种框架如 Struts,Hibernate、Hessian、Quartz 等的支持。

6)降低 Java EE API 使用难度

Spring 对很多 Java EE API 如 JDBC、JavaMail、远程调用等提供了一个简易的封装层,通过 Spring 的简易封装,轻松实现这些 Java EE API 的调用。 Spring 是由哪些模块组成的?

Core module

Bean module

Context module

Expression Language module

JDBC module

ORM module

OXM module

Java Messaging Service(JMS) module

Transaction module

Web module

Web-Servlet module

Web-Struts module

Web-Portlet module Spring 提供几种配置方式设置元数据?

Spring 配置元数据可以采用三种方式,可以混合使用。

1)基于 XML 的配置元数据

使用 XML 文件标签化配置 Bean 的相关属性。

属性 描述 对应注解
class 此项必填,指定要创建 Bean 的类 (全路径)
id 全局唯一 指定 bean 的唯一标示符
name 全局唯一 指定 bean 的唯一标示符 @Bean 的 name 属性
scope 创建 bean 的作用域 @Scope
singleton 是否单例 @Scope(value=SCOPE_SINGLETON)
depends-on 用来表明依赖关系 @DependsOn
depends-check 依赖检查
autowire 自动装配 默认 NO @Bean 的 autowire 属性
init-method 对象初始化后调用的方法 @Bean 的 initMethod 属性
destroy-method 对象销毁前调用的方法 @Bean 的 destroyMethod
lazy-init 容器启动时不会初始化,只有使用时初始化 @Lazy
primary 容器中有多个相同类型的 bean 时,autowired 时优先使用 primary=true @Primary
factory-method 工厂创建对象的方法
factory-bean 工厂 bean

2)基于注解的配置元数据

@Component  标识一个被Spring管理的对象
@Respository  标识持久层对象
@Service         标识业务层对象
@Controller      标识表现层对象

Spring Boot 项目中 Contrller 层、Service 层、Dao 层均采用注解的方式,在对应类上加相对应的 @Controller,@Service,@Repository,@Component 等注解。

需要注意的是使用 @ComponentScan 注解,若是 Spring Boot 框架项目,扫描组件的默认路径与 Application.class 同级包。

3)基于 Java 的配置元数据

默认情况下,方法名即为 Bean 名称。Java 使用 Configuration 类配置 bean,</bean> 对应 Spring 注解 @Bean。

目前常用的方式是第二种和第三种,也经常结合使用。

HTTP1.0 和 HTTP1.1 有什么区别?

1)长连接(Persistent Connection)

HTTP1.0 规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,服务器完成请求处理后立即断开 TCP 连接,服务器不跟踪每个客户也不记录过去的请求。

HTTP1.1 支持长连接,在请求头中有 Connection:Keep-Alive。在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟。

2)节省带宽

HTTP1.0 中存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象传输过去,并且不支持断点续传功能。

HTTP1.1 支持只发送 header 信息,不携带其他任何 body 信息,如果服务器认为客户端有权限请求服务器,则返回 100 状态码,客户端接收到 100 状态码后把请求 body 发送到服务器;如果返回 401 状态码,客户端无需发送请求 body 节省带宽。

3)HOST 域

HTTP1.0 没有 host 域,HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。

HTTP1.1 的请求消息和响应消息都支持 host 域,且请求消息中若是 host 域会报告 400 Bad Request 错误。一台物理服务器上可以同时存在多个虚拟主机(Multi-homed Web Servers),并且它们可以共享一个 IP 地址。

4)缓存处理

HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准。

HTTP1.1 引入了更多的缓存控制策略如 Entity tag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供选择的缓存头来控制缓存策略。

5)错误通知管理

HTTP1.1 中新增 24 个错误状态响应码,比如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。 HTTP1.1 和 HTTP2.0 有什么区别?

1)多路复用

HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP1.1 大了好几个数量级。

HTTP1.1 可以建立多个 TCP 连接来支持处理更多并发的请求,但是创建 TCP 连接本身也是有开销的。

2)头部数据压缩

HTTP1.1 中 HTTP 请求和响应都是由状态行、请求 / 响应头部、消息主体三部分组成。

一般而言,消息主体都会经过 gzip 压缩或本身传输的就是压缩过后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输。

随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 等不会频繁变动的内容,完全是一种浪费资源的体现。

HTTP1.1 不支持 header 数据的压缩,而 HTTP2.0 使用 HPACK 算法对 header 的数据进行压缩,压缩的数据体积小,在网络上传输更快。

3)服务器推送

服务端推送是一种在客户端请求前发送数据的机制。

网页中使用了许多资源:HTML、样式表、脚本、图片等,在 HTTP1.1 中这些资源每一个都必须明确地请求,这是一个很慢的过程。

浏览器从获取 HTML 开始,然后在它解析和评估页面时获取更多的资源,因为服务器必须等待浏览器做每一个请求,网络经常是空闲和未充分使用的。

HTTP2.0 引入了 server push,允许服务端推送资源给浏览器,在浏览器明确请求前,不用客户端再次创建连接发送请求到服务器端获取,客户端可以直接从本地加载这些资源,不用再通过网络。 Spring Boot 支持哪几种内嵌容器?

Spring Boot 支持的内嵌容器有 Tomcat(默认)、Jetty、Undertow 和 Reactor Netty(v2.0+),借助可插拔(SPI)机制的实现,开发者可以轻松进行容器间的切换。 什么是 Spring Boot Stater?

Spring Boot 在配置上相比 Spring 要简单许多,其核心在于 Spring Boot Stater。

Spring Boot 内嵌容器支持 Tomcat、Jetty、Undertow 等应用服务的 starter 启动器,在应用启动时被加载,可以快速的处理应用所需要的一些基础环境配置。

starter 解决的是依赖管理配置复杂的问题,可以理解成通过 pom.xml 文件配置很多 jar 包组合的 maven 项目,用来简化 maven 依赖配置,starter 可以被继承也可以依赖于别的 starter。

比如 spring-boot-starter-web 包含以下依赖:

org.springframework.boot:spring-boot-starter
org.springframework.boot:spring-boot-starter-tomcat
org.springframework.boot:spring-boot-starter-validation
com.fasterxml.jackson.core:jackson-databind
org.springframework:spring-web
org.springframework:spring-webmvc

starter 负责配与 Sping 整合相关的配置依赖等,使用者无需关心框架整合带来的问题。

比如使用 Sping 和 JPA 访问数据库,只需要项目包含 spring-boot-starter-data-jpa 依赖就可以完美执行。 Spring Boot Stater 有什么命名规范?

1)Spring Boot 官方项目命名方式

前缀:spring-boot-starter-,其中是指定类型的应用程序名称。

格式:spring-boot-starter-{模块名}

举例:spring-boot-starter-web、spring-boot-starter-jdbc

2)自定义命名方式

后缀:*-spring-boot-starter

格式:{模块名}-spring-boot-starter

举例:mybatis-spring-boot-starter Spring Boot 启动器都有哪些?

Spring Boot 在 org.springframework.boot 组下提供了以下应用程序启动器:

名称 描述
spring-boot-starter 核心启动器,包括自动配置支持,日志记录和 YAML
spring-boot-starter-activemq 使用 Apache ActiveMQ 的 JMS 消息传递启动器
spring-boot-starter-amqp 使用 Spring AMQP 和 Rabbit MQ 的启动器
spring-boot-starter-aop 使用 Spring AOP 和 AspectJ 进行面向方面编程的启动器
spring-boot-starter-artemis 使用 Apache Artemis 的 JMS 消息传递启动器
spring-boot-starter-batch 使用 Spring Batch 的启动器
spring-boot-starter-cache 使用 Spring Framework 的缓存支持的启动器
spring-boot-starter-data-cassandra 使用 Cassandra 分布式数据库和 Spring Data Cassandra 的启动器
spring-boot-starter-data-cassandra-reactive 使用 Cassandra 分布式数据库和 Spring Data Cassandra Reactive 的启动器
spring-boot-starter-data-couchbase 使用 Couchbase 面向文档的数据库和 Spring Data Couchbase 的启动器
spring-boot-starter-data-couchbase-reactive 使用 Couchbase 面向文档的数据库和 Spring Data Couchbase Reactive 的启动器
spring-boot-starter-data-elasticsearch 使用 Elasticsearch 搜索和分析引擎以及 Spring Data Elasticsearch 的启动器
spring-boot-starter-data-jdbc 使用 Spring Data JDBC 的启动器
spring-boot-starter-data-jpa 将 Spring Data JPA 与 Hibernate 结合使用的启动器
spring-boot-starter-data-ldap 使用 Spring Data LDAP 的启动器
spring-boot-starter-data-mongodb 使用 MongoDB 面向文档的数据库和 Spring Data MongoDB 的启动器
spring-boot-starter-data-mongodb-reactive 使用 MongoDB 面向文档的数据库和 Spring Data MongoDB Reactive 的启动器
spring-boot-starter-data-neo4j 使用 Neo4j 图形数据库和 Spring Data Neo4j 的启动器工具
spring-boot-starter-data-r2dbc 使用 Spring Data R2DBC 的启动器
spring-boot-starter-data-redis 使用 Redis 键值数据存储与 Spring Data Redis 和 Lettuce 客户端的启动器
spring-boot-starter-data-redis-reactive 将 Redis 键值数据存储与 Spring Data Redis Reacting 和 Lettuce 客户端一起使用的启动器
spring-boot-starter-data-rest 使用 Spring Data REST 在 REST 上公开 Spring 数据存储库的启动器
spring-boot-starter-freemarker 使用 FreeMarker 视图构建 MVC Web 应用程序的启动器
spring-boot-starter-groovy-templates 使用 Groovy 模板视图构建 MVC Web 应用程序的启动器
spring-boot-starter-hateoas 使用 Spring MVC 和 Spring HATEOAS 构建基于超媒体的 RESTful Web 应用程序的启动器
spring-boot-starter-integration 使用 Spring Integration 的启动器
spring-boot-starter-jdbc 结合使用 JDBC 和 HikariCP 连接池的启动器
spring-boot-starter-jersey 使用 JAX-RS 和 Jersey 构建 RESTful Web 应用程序的启动器。的替代品 spring-boot-starter-web
spring-boot-starter-jooq 使用 jOOQ 访问 SQL 数据库的启动器。替代 spring-boot-starter-data-jpa 或 spring-boot-starter-jdbc
spring-boot-starter-json 读写 JSON 启动器
spring-boot-starter-jta-atomikos 使用 Atomikos 的 JTA 交易启动器
spring-boot-starter-mail 使用 Java Mail 和 Spring Framework 的电子邮件发送支持的启动器
spring-boot-starter-mustache 使用 Mustache 视图构建 Web 应用程序的启动器
spring-boot-starter-oauth2-client 使用 Spring Security 的 OAuth2 / OpenID Connect 客户端功能的启动器
spring-boot-starter-oauth2-resource-server 使用 Spring Security 的 OAuth2 资源服务器功能的启动器
spring-boot-starter-quartz 启动器使用 Quartz Scheduler
spring-boot-starter-rsocket 用于构建 RSocket 客户端和服务器的启动器
spring-boot-starter-security 使用 Spring Security 的启动器
spring-boot-starter-test 用于使用包括 JUnit Jupiter,Hamcrest 和 Mockito 在内的库测试 Spring Boot 应用程序的启动器
spring-boot-starter-thymeleaf 使用 Thymeleaf 视图构建 MVC Web 应用程序的启动器
spring-boot-starter-validation 初学者,可将 Java Bean 验证与 Hibernate Validator 结合使用
spring-boot-starter-web 使用 Spring MVC 构建 Web(包括 RESTful)应用程序的启动器。使用 Tomcat 作为默认的嵌入式容器
spring-boot-starter-web-services 使用 Spring Web Services 的启动器
spring-boot-starter-webflux 使用 Spring Framework 的反应式 Web 支持构建 WebFlux 应用程序的启动器
spring-boot-starter-websocket 使用 Spring Framework 的 WebSocket 支持构建 WebSocket 应用程序的启动器

除应用程序启动器外,以下启动程序还可用于添加生产环境上线功能: | 名称 | 描述 | |-|-| |spring-boot-starter-actuator | 使用 Spring Boot Actuator 的程序,该启动器提供了生产环境上线功能,可帮助您监视和管理应用程序 |

Spring Boot 还包括以下启动程序,如果想排除或替换启动器,可以使用这些启动程序: | 名称 | 描述 | |-|-| |spring-boot-starter-jetty | 使用 Jetty 作为嵌入式 servlet 容器的启动器。替代 spring-boot-starter-tomcat| |spring-boot-starter-log4j2 | 使用 Log4j2 进行日志记录的启动器。替代 spring-boot-starter-logging| |spring-boot-starter-logging | 使用 Logback 进行日志记录的启动器。默认记录启动器 | |spring-boot-starter-reactor-netty | 启动器,用于将 Reactor Netty 用作嵌入式反应式 HTTP 服务器。| |spring-boot-starter-tomcat | 启动器,用于将 Tomcat 用作嵌入式 servlet 容器。默认使用的 servlet 容器启动器 spring-boot-starter-web| |spring-boot-starter-undertow | 使用 Undertow 作为嵌入式 servlet 容器的启动器。替代 spring-boot-starter-tomcat|

Spring Cloud 断路器的作用是什么?

分布式架构中断路器模式的作用基本类似的,当某个服务单元发生故障,类似家用电器发生短路后,通过断路器的故障监控,类似熔断保险丝,向调用方返回一个错误响应,不需要长时间的等待。这样就不会出现因被调用的服务故障,导致线程长时间占用而不释放,避免了在分布式系统中故障的蔓延。 Spring Cloud 核心组件有哪些?

Eureka:服务注册与发现,Eureka 服务端称服务注册中心,Eureka 客户端主要处理服务的注册与发现。

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

Ribbon:负载均衡,服务间发起请求时基于 Ribbon 实现负载均衡,从一个服务的多台机器中选择一台。

Hystrix:提供服务隔离、熔断、降级机制,发起请求是通过 Hystrix 提供的线程池,实现不同服务调用之间的隔离,避免服务雪崩问题。

Zuul:服务网关,前端调用后端服务,统一由 Zuul 网关转发请求给对应的服务。 Spring Cloud 如何实现服务的注册?

1、当服务发布时,指定对应的服务名,将服务注册到注册中心,比如 Eureka、Zookeeper 等。

2、注册中心加 @EnableEurekaServer 注解,服务使用 @EnableDiscoveryClient 注解,然后使用 Ribbon 或 Feign 进行服务直接的调用发现。

服务发现组件的功能

1)服务注册表

服务注册表是一个记录当前可用服务实例的网络信息的数据库,是服务发现机制的核心。

服务注册表提供查询 API 和管理 API,使用查询 API 获得可用的服务实例,使用管理 API 实现注册和注销;

2)服务注册

3)健康检查 什么是 Spring Cloud Config?

Spring Cloud Config 为分布式系统中的外部配置提供服务器和客户端支持,可以方便的对微服务各个环境下的配置进行实时更新、集中式管理。

Spring Cloud Config 分为 Config Server 和 Config Client 两部分。

Config Server 负责读取配置文件,并且暴露 Http API 接口,Config Client 通过调用 Config Server 的接口来读取配置文件。 Spring Cloud Eureka 自我保护机制是什么?

1)假设某一时间某个微服务宕机了,而 Eureka 不会自动清除,依然对微服务的信息进行保存。

2)在默认的情况系,Eureka Server 在一定的时间内没有接受到微服务的实例心跳(默认为 90 秒),Eureka Server 将会注销该实例。

3)但是在网络发生故障时微服务在 Eureka Server 之间是无法通信的,因为微服务本身实例是健康的,此刻本不应该注销这个微服务。那么 Eureka 自我保护模式就解决了这个问题。

4)当 Eureka Server 节点在短时间内丢失过客户端时包含发生的网络故障,那么节点就会进行自我保护。

5)一但进入自我保护模式,Eureka Server 就会保护服务注册表中的信息,不再删除服务注册表中的数据注销任何微服务。

6)当网络故障恢复后,这个 Eureka Server 节点会自动的退出自我保护机制。

7)在自我保护模式中 Eureka Server 会保护服务注册表中的信息,不在注销任何服务实例,当重新收到心跳恢复阀值以上时,这个 Eureka Server 节点就会自动的退出自我保护模式,这种设计模式是为了宁可保留错误的服务注册信息,也不盲目的注销删除任何可能健康的服务实例。

8)自我保护机制就是一种对网络异常的安全保护实施,它会保存所有的微服务,不管健康或不健康的微服务都会保存,而不会盲目的删除任何微服务,可以让 Eureka 集群更加的健壮、稳定。

9)在 Eureka Server 端可取消自我保护机制,但是不建议取消此功能。 常用的并发工具类有哪些?

CountDownLatch 闭锁

CountDownLatch 是一个同步计数器,初始化时传入需要计数线程的等待数,可能是等于或大于等待执行完的线程数。调用多个线程之间的同步或说起到线程之间的通信(不是互斥)一组线程等待其他线程完成工作后在执行,相当于加强的 join。

CyclicBarrier 栅栏

CyclicBarrier 字面意思是栅栏,是多线程中重要的类,主要用于线程之间互相等待的问题,初始化时传入需要等待的线程数。

作用:让一组线程达到某个屏障被阻塞直到一组内最后一个线程达到屏蔽时,屏蔽开放,所有被阻塞的线程才会继续运行。

Semophore 信号量

semaphore 称为信号量是操作系统的一个概念,在 Java 并发编程中,信号量控制的是线程并发的数量。

作用:semaphore 管理一系列许可每个 acquire () 方法阻塞,直到有一个许可证可以获得,然后拿走许可证,每个 release 方法增加一个许可证,这可能会释放一个阻塞的 acquire () 方法,然而并没有实际的许可保证这个对象,semaphore 只是维持了一个可获取许可的数量,主要控制同时访问某个特定资源的线程数量,多用在流量控制。

Exchanger 交换器

Exchange 类似于交换器可以在队中元素进行配对和交换线程的同步点,用于两个线程之间的交换。

具体来说,Exchanger 类允许两个线程之间定义同步点,当两个线程达到同步点时,它们交换数据结构,因此第一个线程的数据结构进入到第二个线程当中,第二个线程的数据结构进入到第一个线程当中。 并发和并行有什么区别?

并行(parallellism)是指两个或者多个事件在同一时刻发生,而并发(parallellism)是指两个或多个事件在同一时间间隔发生。

并行是在不同实体上的多个事件,而并发是在同一实体上的多个事件。

并行是在一台处理器上同时处理多个任务(Hadoop 分布式集群),而并发在多台处理器上同时处理多个任务。 JSP 模版引擎如何解析 ${} 表达式?

目前开发中已经很少使用 JSP 模版引擎,JSP 虽然是一款功能比较强大的模板引擎,并被广大开发者熟悉,但它前后端耦合比较高。

其次是 JSP 页面的效率没有 HTML 高,因为 JSP 是同步加载。而且 JSP 需要 Tomcat 应用服务器部署,但不支持 Nginx 等,已经快被时代所淘汰。

JSP 页面中使用 ${表达式} 展示数据,但是页面上并没有显示出对应数据,而是把 ${表达式} 当作纯文本显示。

原因分析:这是由于 jsp 模版引擎默认会无视 EL 表达式,需要手动设置 igNoreEL 为 false。

<%@ page isELIgnored="false" %>

什么是服务熔断?什么是服务降级?

熔断机制

熔断机制是应对雪崩效应的一种微服务链路保护机制。

当某个微服务不可用或者响应时间过长时会进行服务降级,进而熔断该节点微服务的调用,快速返回 “错误” 的响应信息。

当检测到该节点微服务调用响应正常后恢复调用链路。

在 Spring Cloud 框架里熔断机制通过 Hystrix 实现,Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是 5 秒内调用 20 次,如果失败,就会启动熔断机制。

服务降级

服务降级一般是从整体负荷考虑,当某个服务熔断后,服务器将不再被调用,此时客户端可以准备一个本地 fallback 回调,返回一个缺省值。这样做目的是虽然水平下降,但是是可以使用,相比直接挂掉要强很多。

Spring Boot 和 Spring Cloud 之间有什么联系?

Spring Boot 是 Spring 推出用于解决传统框架配置文件冗余,装配组件繁杂的基于 Maven 的解决方案,旨在快速搭建单个微服务。

Spring Cloud 专注于解决各个微服务之间的协调与配置,整合并管理各个微服务,为各个微服务之间提供配置管理、服务发现、断路器、路由、事件总线等集成服务。

Spring Cloud 是依赖于 Spring Boot,而 Spring Boot 并不依赖与 Spring Cloud。

Spring Boot 专注于快速、方便的开发单个的微服务个体,Spring Cloud 是关注全局的服务治理框架。 你都知道哪些微服务技术栈?

微服务描述 技术名称
服务开发 Springboot、Spring、SpringMVC
服务配置与管理 Netflix 公司的 Archaius、阿里的 Diamond 等
服务注册与发现 Eureka、Consul、Zookeeper 等
服务调用 REST、RPC、gRPC
服务熔断器 Hystrix、Envoy 等
负载均衡 Ribbon、Nginx 等
服务接口调用(客户端调用服务发简单工具) Feign 等
消息队列 kafka、RabbitMQ、ActiveMQ 等
服务配置中心管理 SpringCloudConfig、Chef 等
服务路由(API 网关) Zuul 等
服务监控 Zabbix、Nagios、Metrics、Spectator 等
全链路追踪 Zipkin、Brave、Dapper 等
服务部署 Docker、OpenStack、Kubernetes 等
数据流操作开发包 SpringCloud Stream(封装与 Redis,Rabbit、Kafka 等发送接收消息)
事件消息总线 Spring Cloud Bus
接口和抽象类有什么区别?
比较方面 抽象类说明 接口说明
默认方法 抽象类可以有默认的方法实现 JDK1。8 之前版本,接口中不存在方法的实现
实现方式 子类使用 extends 关键字来继承抽象类。如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现 子类使用 implements 来实现接口,需要提供接口中所有声明的实现。
构造器 抽象类中可以有构造器 接口中不能
和正常类区别 抽象类不能被实例化 接口则是完全不同的类型
访问修饰符 抽象方法可以有 public、protected、default 等修饰 接口默认是 public,不能使用其他修饰符
多继承 一个子类只能存在一个父类 一个子类可以存在多个接口
添加新方法 抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 如果往接口中添加新方法,则子类中需要实现该方法

什么是线程死锁?

线程死锁是指多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

产生死锁必须具备以下四个条件:

互斥条件:该资源任意一个时刻只由一个线程占用;

请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源持有不释放;

不剥夺条件:线程已获得的资源,在末使用完之前不能被其他线程强行剥夺,只有使用完毕后才释放资源;

循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 如何避免线程死锁?

只需要破坏产生死锁的四个条件中任意一个就可以避免线程死锁,但是互斥条件是没有办法破坏的,因为锁的意义就是想让线程之间存在资源互斥访问。

1)破坏请求与保持条件,一次性申请所有的资源;

2)破坏不剥夺条件,占用部分资源的线程进一步申请其他资源时如果申请不到,使其主动释放占有的资源;

3)破坏循环等待条件,按序申请资源来预防线程死锁,按某一顺序申请资源,释放资源则反序释放。 父类中静态方法能否被子类重写?

父类中静态方法不能被子类重写。

重写只适用于实例方法,不能用于静态方法,而且子类当中含有和父类相同签名的静态方法,一般称之为隐藏。

public class A {





public static String a = "这是父类静态属性";





public static String getA() {



return "这是父类静态方法";


}



}
public class B extends A{





public static String a = "这是子类静态属性";





public static String getA() {



return "这是子类静态方法";


}





public static void main(String[] args) {



A a =  new B();



System.out.println(a.getA());


}
}

如上述代码所示,如果能够被重写,则输出的应该是 “这是子类静态方法”。与此类似的是,静态变量也不能被重写。如果想要调用父类的静态方法,应该使用类来直接调用。

什么是不可变对象?有什么好处?

不可变对象是指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象。

比如 String、Integer 及其它包装类。

不可变对象最大的好处是线程安全。 静态变量和实例变量有什么区别?

静态变量:独立存在的变量,只是位置放在某个类下,可以直接类名加点调用静态变量名使用。并且是项目或程序一启动运行到该类时就直接常驻内存。不需要初始化类再调用该变量。用关键字 static 声明。静态方法也是同样,可以直接调用。

实例变量:相当于该类的属性,需要初始化这个类后才可以调用。如果这个类未被再次使用,垃圾回收器回收后这个实例也将不存在了,下次再使用时还需要初始化这个类才可以再次调用。

1)存储区域不同:静态变量存储在方法区属于类所有,实例变量存储在堆当中;

2)静态变量与类相关,普通变量则与实例相关;

3)内存分配方式不同。

4)生命周期不同。

需要注意的是从 JDK1.8 开始用于实现方法区的 PermSpace 被 MetaSpace 取代。 Object 类都有哪些公共方法?

1)equals(obj);

判断其他对象是否 “等于” 此对象。

2)toString();

表示返回对象的字符串。通常,ToString 方法返回一个 “以文本方式表示” 此对象的字符串。结果应该是一个简洁但信息丰富的表达,很容易让人阅读。建议所有子类都重写此方法。

**3)getClass (); ** 返回此对象运行时的类型。

4)wait();

表示当前线程进入等待状态。

5)finalize();

用于释放资源。

6)notify();

唤醒在该对象上等待的某个线程。

7)notifyAll();

唤醒在该对象上等待的所有线程。

8)hashCode();

返回对象的哈希代码值。用于哈希查找,可以减少在查找中使用 equals 的次数,重写 equals 方法一般都要重写 hashCode 方法。这个方法在一些具有哈希功能的 Collection 中用到。

9)clone();

实现对象的浅复制,实现 Cloneable 接口才可以调用这个方法,否则抛出 CloneNotSupportedException 异常。 Java 创建对象有哪几种方式?

创建对象 构造方法说明
使用 new 关键字 调用构造方法
使用 Class 类的 newInstance 方法 调用构造方法
使用 Constructor 类的 newInstance 方法 调用构造方法
使用 clone 方法 没有调用构造方法
使用反序列化 没有调用构造方法
a==b 与 a.equals (b) 有什么区别?

a==b 与 a.equals (b) 有什么区别?

假设 a 和 b 都是对象

a==b 是比较两个对象内存地址,当 a 和 b 指向的是堆中的同一个对象才会返回 true。

a.equals (b) 是比较的两个值内容,其比较结果取决于 equals () 具体实现。

多数情况下需要重写这个方法,如 String 类重写 equals () 用于比较两个不同对象,但是包含的字母相同的比较:

public boolean equals(Object obj) {


if (this == obj) {// 相同对象直接返回true



return true;


}


if (obj instanceof String) {



String anotherString = (String)obj;



int n = value.length;



if (n == anotherString.value.length) {




char v1[] = value;




char v2[] = anotherString.value;




int i = 0;




while (n-- != 0) {





if (v1[i] != v2[i])






return false;





i++;




}




return true;



}


}


return false;
}

Object 中 equals () 和 hashcode () 有什么联系?

Java 的基类 Object 提供了一些方法,其中 equals () 方法用于判断两个对象是否相等,hashCode () 方法用于计算对象的哈希码。equals () 和 hashCode () 都不是 final 方法,都可以被重写(overwrite)。

hashCode () 方法是为对象产生整型的 hash 值,用作对象的唯一标识。

hashCode () 方法常用于基于 hash 的集合类,如 Hashtable、HashMap 等,根据 Java 规范使用 equal () 方法来判断两个相等的对象,必须具有相同的 hashcode。

将对象放入到集合中时,首先要判断放入对象的 hashcode 是否已经存在,不存在则直接放入集合。

如果 hashcode 相等,然后通过 equal () 方法判断要放入对象与集合中的其他对象是否相等,使用 equal () 判断不相等,则直接将该元素放入集合中,反之不放入集合中。 hashcode () 中可以使用随机数字吗?

hashcode () 中不可以使用随机数字不行,这是因为对象的 hashcode 值必须是相同的。 Java 中 & 和 && 有什么区别?

& 是位操作

&& 是逻辑运算符

逻辑运算符具有短路特性,而 & 不具备短路特性。

来看一下代码执行结果:

public class Test{

    static String name;
 
    public static void main(String[] args){
        if(name!=null & name.equals("")){
            System.out.println("ok");
        }else{
            System.out.println("error");
        }
    }
}

执行结果:

Exception in thread "main" java.lang.NullPointerException


at com.jingxuan.JingXuanApplication.main(JingXuanApplication.java:25)

上述代码执行时抛出空指针异常,若果 & 替换成 &&,则输出日志是 error。 一个 .java 类文件中可以有多少个非内部类?

一个.java 类文件中只能出现一个 public 公共类,但是可以有多个 default 修饰的类。如果存在两个 public 修饰的类时,会报如下错误:

The public type Test must be defined in its own file

Java 中如何正确退出多层嵌套循环?

1)使用 lable 标签和 break 方式;

lable 是跳出循环标签。

break lable; 是跳出循环语句。

当执行跳出循环语句时会跳出循环标签下方循环的末尾后面。

lable:
for(int i=0;i<3;i++){


for(int j=0;j<3;j++){



System.out.println(i);



if(i == 2) {




break lable;



}






}
}

上述代码在执行过程中,当 i=2 时,执行跳出循环语句,控制台只输出 i=0 和 i=1 的结果,执行继续 for 循环后面的代码。

0
0
0
1
1
1
2
执行for后面的程序代码

2)通过在外层循环中添加标识符,比如定义布尔类型 bo = false,当 bo=true 跳出循环语句。 浅拷贝和深拷贝有什么区别?

浅拷贝是指被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

深拷贝是指被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,并且不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。 Java 中 final 关键字有哪些用法?

Java 代码中被 final 修饰的类不可以被继承。

Java 代码中被 final 修饰的方法不可以被重写。

Java 代码中被 final 修饰的变量不可以被改变,如果修饰引用类型,那么表示引用类型不可变,引用类型指向的内容可变。

Java 代码中被 final 修饰的方法,JVM 会尝试将其内联,以提高运行效率。

Java 代码中被 final 修饰的常量,在编译阶段会存入常量池中。 String s = new String ("abc"); 创建了几个 String 对象?

String s = new String ("abc"); 创建了 2 个 String 对象。

一个是字符串字面常数,在字符串常量池中。

一个是 new 出来的字符串对象,在堆中。 String 和 StringBuffer 有什么区别?

String 是不可变对象,每次对 String 类型进行操作都等同于产生了一个新的 String 对象,然后指向新的 String 对象。因此尽量避免对 String 进行大量拼接操作,否则会产生很多临时对象,导致 GC 开始工作,影响系统性能。

StringBuffer 是对象本身操作,而不是产生新的对象。因此在有大量拼接的情况下,建议使用 StringBuffer。

String 是线程不安全的,而 StringBuffer 是线程安全的。

需要注意是 Java 从 JDK5 开始,在编译期间进行了优化。如果是无变量的字符串拼接时,那么在编译期间值都已经确定了的话,javac 工具会直接把它编译成一个字符常量。比如:

String str = "关注微信公众号" + "“Java精选”" + ",面试经验、专业规划、技术文章等各类精品Java文章分享!";

在编译期间会直接被编译成如下:

String str = "关注微信公众号“Java精选”,面试经验、专业规划、技术文章等各类精品Java文章分享!";

Java 中 3*0.1 == 0.3 返回值是什么?

3*0.1==0.3 返回值是 false

这是由于在计算机中浮点数的表示是误差的。所以一般情况下不进行两个浮点数是否相同的比较。而是比较两个浮点数的差点绝对值,是否小于一个很小的正数。如果条件满足,就认为这两个浮点数是相同的。

System.out.println(3*0.1 == 0.3);
System.out.println(3*0.1);

System.out.println(4*0.1==0.4);
System.out.println(4*0.1);

执行结果如下:

false
0.30000000000000004
true
0.4

分析:3*0.1 的结果是浮点型,值是 0.30000000000000004,但是 4*0.1 结果值是 0.4。这个是二进制浮点数算法的计算原因。

a=a+b 和 a+=b 有什么区别吗?

+= 操作符会进行隐式自动类型转换,a+=b 隐式的将相加操作结果类型强制转换为持有结果的类型,而 a=a+b 则不会自动进行类型转换。

byte a = 127;
byte b = 127;
a = a + b;
a += b;

a = a + b; 编译报错:

Type mismatch: cannot convert from int to byte

Java 中线程阻塞都有哪些原因?

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),Java 提供了大量方法来支持阻塞。

方法名 方法说明
sleep() sleep () 方法允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到 CPU 时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep () 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止
suspend () 和 resume () 两个方法配套使用,suspend () 使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume () 被调用,才能使得线程重新进入可执行状态。典型地,suspend () 和 resume () 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume () 使其恢复。
yield() yield () 使当前线程放弃当前已经分得的 CPU 时间,但不使当前线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield () 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程
wait () 和 notify () 两个方法配套使用,wait () 使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify () 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify () 被调用。

感谢阅读,更多的java课程学习路线,笔记,面试等架构资料,需要的同学可以私信我(资料)即可免费获取!

 

猜你喜欢

转载自blog.csdn.net/q66562636/article/details/125561603