面试总结
JDK1.8的新特性
-
default关键字
接口中可以定义默认实现方法和静态方法,通常我们认为接口里只能有抽象方法,不能有任何方法发的实现,但是使用default修饰的方法可以在接口里自定义内容。
-
Lambda表达式
-
函数式接口
-
方法与构造参数引用
-
局部变量限制
-
Date Api更新
-
流
-
对Map的数据结构进行了优化
为HashMap加入了红黑树,使得查询更加的快速。
extends 和super 泛型限定符
总结:上界不存,下界不取
-
<? extends >:上界限定符
只能输入限定类型本身与其子类
-
<? super>:下界限定符
只能输入限定类型本身与其父类
#==&equals
- ==
- 作用与基本数据类型的变量,比较的是值是否相等。
- 作用与引用类型的变量,比较的是所指向对象的地址。
- equals
- equals继承自Object类。
- 只能比较引用类型的变量。
- 对equals方法进行了重写,比较的是所指向的对象的内容。
- 没有对equals方法进行重写,比较的是引用类型变量所指向的对象的地址。
String&StringBuffer&StringBuilder
- 运行速度:StringBuilder>>StringBuffer>>String
- String:速度最慢,字符串常量,不可变。
- StringBuffer:速度中等,字符串变量,可变,线程安全,适用于多线程。
- StringBuilder:速度最快,字符串变量,可变,线程不安全,适用于单线程。
#HashMap&HashTable&ConcurrentHashMap
-
HashTable
- 数据结构:数组+链表
- 初始大小:11 Size
- 自动扩容:newSize = oldSize * 2 + 1
- 键值均不能为null
- 线程安全(修改数据时锁住整个HashTable)
- 效率低
-
HashMap
-
数据结构:
- JDK1.7:数组+链表
- JDK1.8:数组+链表(红黑树)
-
初始大小:16 Size
-
自动扩容:newSize = oldSize * 2(每次扩容后原来数组中的元素需要重新计算排列)
-
允许一个null键和多个null值
-
线程不安全
-
效率高
-
-
ConcurrentHashMap
- 数据结构:分段数组+链表
- 线程安全
- 使用分段锁技术:通过把整个Map分为N个段,可以提供相同的线程安全,但是可以提升N倍的效率,默认提升16倍。
-
哈希冲突
若干Key的哈希值按数组大小取模后,如果落在同一个槽点上,就会组成一条Entry链,在查找时通过遍历Entry链上的每个元素使用equals()方法进行比较。
-
加载因子
为了降低哈希冲突的概率,默认当HashMap中的键值对数量达到数组长度的75%,就会触发自动扩容。
-
空间换时间
如果希望加快查询速度,可以降低加载因子,增大初始大小,以降低哈希冲突的概率。
-
共有属性
- 容量(capacity):哈希表中的桶数量
- 初始化容量(initial capacity):创建哈希表时的桶的数量(HashMap可以在初始化时指定初始化容量)
- 尺寸(Size):当前哈希表中的记录数量
- 负载因子(load factor):负载因子 = 尺寸/容量。
ProPerties
Properties(Java.util.Properties)主要用于读取Java的配置文件。
- properties继承自Hashtable
集合之List和Set
Tomcat容器
JDBC操作步骤
- 加载驱动
- 创建并获取数据库连接
- 创建jdbc statement对象
- 设置sql语句
- 设置sql语句参数
- 通过statement执行sql并获取结果
- 对sql结果进行处理
- 释放资源
Servlet
9大内置对象:
- out 输出流对象
- request 请求对象
- response 响应对象
- config 配置对象
- session 会话对象
- page 页面对象
- pageContext 页面上下文对象
- appliction 应用程序对象
- Eexception 异常对象
4大域:
- page 页面域
- request 请求域
- session 会话域
- application 全局作用域
实现会话跟踪的技术有哪些?
-
使用Cookie
向客户端发送Cookie:
Cookie c =new Cookie("name","value"); //创建Cookie c.setMaxAge(60*60*24); //设置最大时效,此处设置的最大时效为一天 response.addCookie(c); //把Cookie放入到HTTP响应中
从客户端读取Cookie:
String name ="name"; Cookie[]cookies =request.getCookies(); if(cookies !=null){ for(int i= 0;i<cookies.length;i++){ Cookie cookie =cookies[i]; if(name.equals(cookis.getName())) //something is here. //you can get the value cookie.getValue(); } }
-
URL重写
-
隐藏的表单域
<input type="hidden" name ="session" value="..."/>
优点:cookie被禁用依然可用
缺点:所有页面必须是表单提交之后的结果
-
HttpSession
Java中常用的23种设计模式(单例/工厂/代理)
-
单例设计模式
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
-
具体实现:
-
饿汉式单例
-
懒汉式单例
-
定义一个静态方法返回这个唯一对象。
-
将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
-
在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
-
-
饿汉模式/立即加载:
- 立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”),常见的实现办法就是直接new实例化。
public class Singleton { // 将自身实例化对象设置为一个属性,并用static、final修饰 private static final Singleton instance = new Singleton(); // 构造方法私有化 private Singleton() { } // 静态方法返回该实例 public static Singleton getInstance() { return instance; } }
- 优点:
- 实现起来简单,没有多线程同步问题。
- 缺点:
- 当类SingletonTest被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。
-
懒汉模式/延迟加载:
- 延迟加载就是调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”),常见的实现方法就是在get方法中进行new实例化。
public class Singleton { // 将自身实例化对象设置为一个属性,并用static修饰 private static Singleton instance; // 构造方法私有化 private Singleton() {} // 静态方法返回该实例 public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
- 优点:
- 实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
- 缺点:
- 在多线程环境中,这种实现方法是完全错误的,根本不能保证单例的状态。
-
-
工厂设计模式
工厂设计模式,就是用来生产对象的,在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则,如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦
-
简单工厂
-
定义:一个工厂方法,依据传入的参数,生成对应的产品对象。
-
角色:
1、抽象产品
2、具体产品
3、具体工厂
4、产品使用者 -
使用说明:先将产品类抽象出来,比如,苹果和梨都属于水果,抽象出来一个水果类Fruit,苹果和梨就是具体的产品类,然后创建一个水果工厂,分别用来创建苹果和梨。
-
水果接口
public interface Fruit { void whatIm(); }
-
实现类 苹果
public class Apple implements Fruit { @Override public void whatIm() { //苹果 } }
-
实现类 梨
public class Pear implements Fruit { @Override public void whatIm() { //梨 } }
-
工厂类 水果工厂
public class FruitFactory { public Fruit createFruit(String type) { if (type.equals("apple")) {//生产苹果 return new Apple(); } else if (type.equals("pear")) {//生产梨 return new Pear(); } return null; } }
-
产品试用者
FruitFactory mFactory = new FruitFactory(); Apple apple = (Apple) mFactory.createFruit("apple");//获得苹果 Pear pear = (Pear) mFactory.createFruit("pear");//获得梨
-
总结:以上的这种方式,每当我想添加一种水果,就必然要修改工厂类,这显然违反了开闭原则,亦不可取;所以简单工厂只适合于产品对象较少,且产品固定的需求,对于产品变化无常的需求来说显然不合适。
-
-
工厂方法
-
定义:将工厂提取成一个接口或抽象类,具体生产什么产品由子类决定;
-
角色:
抽象产品类
具体产品类
抽象工厂类
具体工厂类 -
使用说明:和上例中一样,产品类抽象出来,这次我们把工厂类也抽象出来,生产什么样的产品由子类来决定。
-
水果接口 苹果类和梨类 代码和上例一样 。
-
工厂接口
public interface FruitFactory { Fruit createFruit();//生产水果 }
-
苹果工厂
public class AppleFactory implements FruitFactory { @Override public Fruit createFruit() { return new Apple(); } }
-
梨工厂
public class PearFactory implements FruitFactory { @Override public Fruit createFruit() { return new Pear(); } }
-
使用
AppleFactory appleFactory = new AppleFactory(); PearFactory pearFactory = new PearFactory(); Apple apple = (Apple) appleFactory.createFruit();//获得苹果 Pear pear = (Pear) pearFactory.createFruit();//获得梨
-
总结:以上这种方式,虽然解耦了,也遵循了开闭原则,但是问题根本还是没有解决啊,换汤没换药,如果我需要的产品很多的话,需要创建非常多的工厂,所以这种方式的缺点也很明显。
-
-
抽象工厂
-
定义:为创建一组相关或者是相互依赖的对象提供的一个接口,而不需要指定它们的具体类。
-
角色:和工厂方法一样
-
使用说明:抽象工厂和工厂方法的模式基本一样,区别在于,工厂方法是生产一个具体的产品,而抽象工厂可以用来生产一组相同,有相对关系的产品;重点在于一组,一批,一系列;举个例子,假如生产小米手机,小米手机有很多系列,小米note、红米note等;假如小米note生产需要的配件有825的处理器,6英寸屏幕,而红米只需要650的处理器和5寸的屏幕就可以了。
-
cpu接口和实现类
public interface Cpu { void run(); class Cpu650 implements Cpu { @Override public void run() { //625 也厉害 } } class Cpu825 implements Cpu { @Override public void run() { //825 处理更强劲 } } }
-
屏幕接口和实现类
public interface Screen { void size(); class Screen5 implements Screen { @Override public void size() { //5寸 } } class Screen6 implements Screen { @Override public void size() { //6寸 } } }
-
工厂接口
public interface PhoneFactory { Cpu getCpu();//使用的cpu Screen getScreen();//使用的屏幕 }
-
具体工厂实现类:小米手机工厂
public class XiaoMiFactory implements PhoneFactory { @Override public Cpu getCpu() { return new Cpu.Cpu825();//高性能处理器 } @Override public Screen getScreen() { return new Screen.Screen6();//6寸大屏 } }
-
具体工厂实现类:红米手机工厂
public class HongMiFactory implements PhoneFactory { @Override public Cpu getCpu() { return new Cpu.Cpu650();//高效处理器 } @Override public Screen getScreen() { return new Screen.Screen5();//小屏手机 } }
-
总结:以上例子可以看出,抽象工厂可以解决一系列的产品生产的需求,对于大批量,多系列的产品,用抽象工厂可以更好的管理和扩展。
-
- 三种工厂方式总结:
1、对于简单工厂和工厂方法来说,两者的使用方式实际上是一样的,如果对于产品的分类和名称是确定的,数量是相对固定的,推荐使用简单工厂模式;
2、抽象工厂用来解决相对复杂的问题,适用于一系列、大批量的对象生产;
-
数据库事务
-
数据库事务的特点
一持隔原
- 一致性:一个事务开始前与结束后,数据库的完整性没有被破坏,只是从一种一致性状态转换为了另外一种一致性状态。就像转账汇款,虽然数据发生了变化,但总量不变。
- 持久性:在事务完成后,该事务对数据库所做的更改会被持久的保存在数据库中。
- 隔离性:多个事务同时并发访问数据库的同一数据时,在没有提交事务之前,这个操作对其他事务是不可见的。
- 原子性:一个事务中的所有操作,要么全部完成,要么全部失败回滚,不会在中间结束。
-
事务并发处理带来的问题
- 脏读<<不可重复读**(Orcale默认隔离级别)<<可重复读(Mysql默认隔离级别)**<<幻读
- 脏读:操作事务未提交,被读取到了未提交的数据值。
- 不可重复读:在同一个事务中读取同一个数据两次,两次读取到的数据值不一样(事务执行期间发生了修改操作)。
- 幻读:在同一个事务中读取同一个数据两次,两次读取到的数据条数不一样(事务执行期间发生了增删操作)。
-
隔离级别
- 读未提交<<读已提交(Orcale默认隔离级别)<<可重复读(Mysql默认隔离级别)<<序列化
- 读未提交(RU):不能避免。
- 读已提交(RC):可以避免脏读。
- 可重复读(RR):可以避免脏读和不可重复读。
- 序列化(SL):可以避免脏读和不可重复读以及幻读。
多线程
简介
- java程序是多线程的 ,其采用了更传统的顺序语言的基础上提供了对多线程的支持,并且每个任务开启时都会在内存空间中有自己的地址去执行,因此任务之间根本不能相互干涉,并且其也没有相互干涉的必要性,所以安全性较高.
- 我理解为在主方法中(也就是主线程)开启另一个线程,使得cup执行程序是在多个线程之间高速度切换,以完成多任务,足以满足多用户的需求.
- 基本的线程机制:并发编程使我们可以将程序划分为多个分离的,独立运行的任务.通过使用多线程机制,这些独立任务(在Java中也称为子任务或子线程)中的每一个都将由执行线程来驱动.一个线程就是在进程种的一个单一的顺序控制流,因此单个线程可以拥有多个并发执行任务.就像是每个任务都是有自己的cup一样,但其实底层的机制是切分cup的时间,单个cup快速且随机的执行不同的任务.
创建线程的方式
- 继承Thread类
- 实现Runnable接口
- 实现callable接口
- 线程池创建
线程的生命周期
-
线程的6种状态
-
新建(New)
当新创建出来一个线程,但是这个线程还没有开始运行时。
-
可运行(Runnable)
一旦调用了start方法,则该线程处于可运行状态,在这个状态下,它有可能在运行,也有可能没有运行,具体取决于是否抢到CPU的使用权。
-
被阻塞(Blocked)
当一个线程视图获取一个内部对象的锁时,而该锁被其他对象持有,这时就进入了被阻塞状态。
-
等待(Waiting)
当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。在调用 Object.wait 方法或 Thread.join 方法, 或者是等待 java,util.concurrent 库中的 Lock 或 Condition 时, 就会出现这种情况。实际上,被阻塞状态
与等待状态是有很大不同的。 -
计时等待(Timed waiting)
有几个方法有一个超时参数。调用它们导致线程进人计时等待( timed waiting ) 状态。这一状态将一直保持到超时期满或者接收到适当的通知。带有超时参数的方法有Thread.sleep 和 Object.wait、Thread.join、 Lock,tryLock 以及 Condition.await 的计时版。
-
终止(Terminated)
- 线程因如下两个原因之一而被终止:
- 因为 run 方法正常退出而自然死亡。
- 因为一个没有捕获的异常终止了 run 方法而意外死亡。
-
线程安全
分布式事务
分布式锁
线程池
ThreadPoolExport
线程池创建方式有几种
Redis数据库
Redis是一个用C语言开发,并且开源的Key-Value数据库。是一种NoSql(非关系型)数据库。
-
数据类型
- String(字符串)
- 一个key对应一个Value
- 最大存储512MB
- List(列表)
- 字符串列表,按插入顺序排列
- HaSh(哈希)
- 是一个键值对的集合
- 适合存储对象
- Set(集合)
- 无序排列的字符串
- 不可重复
- SortedSet(有序集合)
- 有序排列的字符串
- 不可重复
- String(字符串)
-
数据持久化
-
快照(RDB):存储数据
RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。
体积小,恢复快,但数据完整性和一致性不高。
-
日志文件(AOF):存储(写)命令
AOF :Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
体积大,恢复慢,数据完整性高。
-
混合模式(4.0版本支持):RDB+AOF
先以RDB模式存储全量数据,再已AOF存储新增数据。
-
-
Redis的并发竞争问题如何解决?
- Redis是单进程单线程模式
- 客户端角度:创建连接池并使用内部锁。
- 服务器角度:使用setnx命令
-
什么是缓存穿透?
缓存穿透就是当有很大的并发量在缓存中没有查到数据而去数据库查询,就会给数据库造成很大7的压力,这就叫缓存穿透。
关键词:缓存value为空;并发量很大去访问DB。
-
造成的原因。
业务代码出现问题,或数据出现问题,或者是一些恶意攻击。
-
解决方案。
如果一个查询返回的数据为空,不管是数据不存在还是系统故障,我们仍然把这个结果进行缓存,但是它的过期时间会很短最长不超过5分钟。
-
-
什么时缓存击穿
缓存击穿即使在某一时间节点有大量的并发查询去查询同一条数据,而这条数据刚好过期,所以去查数据库,从而会对数据库造成很大的压力。
关键词:缓存没有查到,从而大量请求去查询同一条数据。
-
造成的原因
某一Key刚好过期,但是却刚好有大量请求来查询这条数据。
-
解决方案
互斥锁
设置热点数据永不过期。
-
-
什么是缓存雪崩?
雪崩就是当有很大的并发量去请求缓存时,造成了缓存服务器的崩溃,从而使大量请求去查询数据库,使得数据库也可能会挂掉,这就叫做雪崩。
关键词:缓存挂掉,大量并发去访问数据库,从而使数据库也可能会挂掉。
-
造成的原因
当在某一个时间节点有大量的缓存失效,这样在失效的时候,大量数据访问就会直接访问数据库,给数据库造成很大的压力。
-
解决方案
- 设置Redis集群来达到高可用。
- 使用互斥锁
- 不同的Key设置不同的过期时间,使过期时间尽量的平均一些。
- 设置永远不过期。
- 资源保护,可以做各种资源的线程池隔离,从而保护主线程池。
-
-
什么是Redis,简述它的优缺点?
- Redis本质上是一个Key-Value类型的内存数据库,整个系统都加载在内存中进行工作,并且可以定期将数据保存在硬盘中进行持久化。因为Redis是直接操作的内存,所以Redis的性能非常好,每秒可以处理10W次的读写操作。而且Redis支持的数据类型也比较多,而且他的单个Value最大可以存储512M的数据,所以它可以实现很多有用的功能。而且Redis可以使用expire设置数据的过期时间。Redis的主要缺点就是数据库的容量要收物理内存容量的限制,不能用作海量数据的存储,所以它适合一些数据量比较小,但是需要高性能操作的场景。
-
MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?
Redis的内存数据集数量达到一定大小,它会自动执行数据淘汰策略。
-
Redis有哪些适合的场景?
-
会话缓存
-
全页缓存
-
队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
-
排行榜/计数器
-
发布/订阅
-
-
说说Redis哈希槽的概念?
Redis集群没有一致性的Hash,而是引入了哈希槽的概念,redis集群一共有16384个哈希槽,每个key通过对16384取模来确定放在哪个槽,集群的每个节点都负责一部分槽。
-
Redis集群的主从复制模型是怎样的?
每个节点都会有N-1个复制品。
-
Redis集群会有写操作丢失吗?为什么?
redis不能保证数据的一致性,所以可能发生写操作丢失。
-
Redis集群之间是如何复制的?
异步复制。
-
Redis集群最大节点个数是多少?
16384个。
-
Redis集群如何选择数据库?
Redis集群目前无法做数据库选择,默认在0数据库。
-
怎么理解Redis事务?
事务是一个单独的隔离操作,事务中的命令都会序列化,按顺序的执行,事务在执行过程中不会被其他指令所打断。
Mysql数据库
-
引擎
-
Sql优化
-
如何定位并优化慢Sql
- 根据慢日志定位慢Sql
show variables like '%quer%'; set global slow_query_log = on; # 开启慢Sql日志记录 set global long_query_time = 1; #定义慢Sql的超时时间 单位:(秒)
- 使用
explain
工具分析Sql- 在需要分析的Sql前方加上
explain
字段。
- 在需要分析的Sql前方加上
-
-
修改Sql或尽量让Sql走索引
-
show profile分析
-
索引
- 创建索引的方式:
- 添加PRIMARY KEY(主键索引)
mysql>ALTER TABLE table_name ADD PRIMARY KEY (column) - 添加UNIQUE(唯一索引)
mysql>ALTER TABLE table_name ADD UNIQUE (column ) - 添加**INDEX(**普通索引)
mysql>ALTER TABLE table_name ADD INDEX index_name (column) - 添加FULLTEXT(全文索引)
mysql>ALTER TABLE table_name ADD FULLTEXT ( column) - 添加多列索引
mysql>ALTER TABLE table_name ADD INDEX index_name(column1,column2,column3)
- 添加PRIMARY KEY(主键索引)
- 为什么使用索引?
- 避免全表扫描,提升查询效率。
- 什么样的信息可以成为索引?
- 主键,唯一键,普通键等。
- 索引的数据结构
- 二叉树
- B树
-
B+树(主要)
- 磁盘读写代价更低
- 查询效率更加稳定
- 更有利于对数据库的扫描
- Hash结构
- 密集索引:每个搜索码值都对应一个索引值
- 稀疏索引:只为某些搜索码值建立了索引值
- 联合索引的原则
- 最左匹配原则
- 创建索引的方式:
-
数据库三大范式
- 第一范式就是无重复的域(字段)。
- 第二范式就是在第一范式的基础上属性完全依赖于主键。
- 第三范式的目标就是确保表中各列与主键列直接相关,而不是间接相关。
MongoDb数据库
- MongoDb数据类型:
- 字符串
- 整数
- 布尔类型
- 双精度浮点数
- 数组
- 时间戳
- 对象
- Null
- 日期
- 对象Id
- 二进制数据
- 代码
- 正则表达式
MyBatis框架
Spring框架
-
AOP(面向切面编程)
-
IOC(控制反转)
-
DI(依赖注入)
-
事务的传播行为
7种 指定事务如何传播
-
配置
-
执行流程
SpringMVC框架
-
什么是Spring MVC ?简单介绍下你对springMVC的理解?
-
Spring MVC是一个基于Java的,并且实现了MVC设计模式的一种轻量级Web框架,通过把Model,Veiw,Controller分离,将Web层进行解耦,使得开发人员简化开发,减少出错。
-
Spring MVC执行流程
- 用户通过url请求前端控制器DispatcherServlet,控制器会过滤出哪些url可以访问,哪些不能访问,这就是url-pattern的作用,同时会加载Spring MVC.xml配置文件;
- 前端控制器会找到处理器映射器,请求获取处理器。
- 处理器映射器通过url找到具体的处理器,生成处理器对象并给处理器对象加上拦截器,一起返回给前端控制器。
- 前端控制器拿到处理器后,通过处理器适配器访问处理器,并执行处理器。
- 处理器会返回一个ModelAndView对象给处理器适配器。
- 处理器适配器把处理器给他的ModelAndView返回给前端控制器。
- 前端控制器请求视图解析器去进行解析。
- 视图解析器返回解析完的视图对象给前端控制器。
- 视图渲染,通过把ModelAndView存入request域中,用来加载数据。
- 前端控制器响应用户。
-
Springmvc的优点:
- 可以和Spring框架集成(Aop,Ioc)
- 清晰的角色分配,前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。
- 可以支持各种视图技术,不止JSP。
- 支持各种请求资源的映射策略。
-
Spring MVC的主要组件?
(1)前端控制器 DispatcherServlet(不需要程序员开发)
作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
(2)处理器映射器HandlerMapping(不需要程序员开发)
作用:根据请求的URL来查找Handler
(3)处理器适配器HandlerAdapter
注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。
(4)处理器Handler(需要程序员开发)
(5)视图解析器 ViewResolver(不需要程序员开发)
作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
(6)视图View(需要程序员开发jsp)
View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
-
SpringMVC怎么样设定重定向和转发的?
- 转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"
- 重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"
-
SpringMVC怎么和AJAX相互调用的?
- 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 :
-
加入Jackson.jar
-
在配置文件中配置json的映射
-
在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。
-
如何解决POST请求中文乱码问题,GET的又如何处理呢?
- 解决post请求乱码问题:
- 在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;
- get请求中文参数出现乱码解决方法有两个:
- 修改tomcat配置文件添加编码与工程编码一致
- 另外一种方法对参数进行重新编码:
- 解决post请求乱码问题:
-
Spring MVC的异常处理 ?
- 可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。
-
SpringMvc的控制器是不是单例模式,如果是,有什么问题,怎么解决?
- 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。
-
SpringMVC常用的注解有哪些?
- @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
- @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
- @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
-
SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代?
- 一般用@Conntroller注解,表示是表现层,不能用别的注解代替。
-
如果在拦截请求中,我想拦截get方式提交的方法,怎么配置?
- 可以在@RequestMapping注解里面加上method=RequestMethod.GET。
-
怎样在方法里面得到Request,或者Session?
- 直接在方法的形参中声明request,SpringMvc就自动把request对象传入。
-
如果想在拦截的方法里面得到从前台传入的参数,怎么得到?
- 直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。
-
如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象?
- 直接在方法中声明这个对象,SpringMvc就自动会把属性赋值到这个对象里面。
-
SpringMvc中函数的返回值是什么?
- 返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的,但一般用String比较好。
-
SpringMvc用什么对象从后台向前台传递数据的?
- 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到。
-
怎么样把ModelMap里面的数据放入Session里面?
- 可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。
-
SpringMvc里面拦截器是怎么写的:
-
有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可:
<!-- 配置SpringMvc的拦截器 --> <mvc:interceptors> <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --> <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean> <!-- 只针对部分请求拦截 --> <mvc:interceptor> <mvc:mapping path="/modelMap.do" /> <bean class="com.zwp.action.MyHandlerInterceptorAdapter" /> </mvc:interceptor> </mvc:interceptors>
-
-
注解原理:
- 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
SpringBoot
SpringCloud
SpringData-Jpa
SpringData-Redis
SpringData-Mysql
SpringTask
Hibernate
-
Hibernate一级缓存与二级缓存的区别
- 主要区别为作用范围不同
- 一级缓存Session级别,当这个Session关闭后这个缓存就不存在了。
- 二级缓存是SessionFactory级别的,这种缓存在整个应用服务器中都有效。
-
Hibernate中**get()和load()**方法的区别
- Get()是立即加载
- Load()是延迟加载
fastDFS
CORS跨域
SSO单点登录
实现方式:Spring Security + CAS
单点登录流程:
- 浏览器发送请求,请求资源。
- Spring Security 拦截请求
- Spring Security 判断有无权限,如果首次访问没有权限,则携带请求链接与参数重定向到CAS服务器
- CAS服务器返回登录页面,用户使用账户密码进行登录
- 用户登录成功,CAS使用原始请求链接携带Server Tiket发送请求
- Spring Security 拦截请求,判断有无权限,有权限,放行请求
Solr搜索
- 搜索原理
- 分词器
ActiveMQ消息中间件
服务与服务之间进行异步通信。
- 为什么要使用ActiveMQ
- 服务间的解耦
- 消息生产者与消息消费者间的异步处理
- 进行流量削峰
- 消息通讯
- 应用场景:
- 聊天室:使用点对点与订阅模式实现。
- 秒杀活动:因为秒杀活动会在一瞬间流量暴增,所以可以将MQ部署在应用前端进行流量控制。
- ActiveMQ的使用
- 点对点模式
- 一个消息只能被一个消费者接收。
- 消息一旦被消费就会消失。
- 如果消息没有被消费,就会一直等待。
- 多个消费者监听同一个生产者,消息只能被消费一次,先到先得。
- 订阅模式
- 一个消息可以被多个消费者接收。
- 消费者只能消费在它订阅之后产生的消息。
- 问题
-
问:Active MQ的消息是被顺序接受的吗?如果不是,如何确保消息会被顺序接收?
答:不是被顺序接收,如果需要顺序接受可以给每一个消息加上时间戳,让消息具有时序性.
Zookeeper&Dubbo
ThreadLocal
JVM虚拟机
JVM是一个内存中的虚拟机
- JVM架构
- Class Loader(类加载器):加载class文件到内存。
- Execution Engine(命令解释器):对命令进行解析。
- Native Interface(本地接口):融合不同语言的的原生库为Java所用。
- Runtime Data Area(内存模型):JVM内存结构模型。
-
反射机制:java的反射机制是在运行状态中可以获取任意一个类或者任意一个对象的任意属性和任意方法,这种动态调用对象的功能称为Java语言的反射机制。
-
Class Loader:ClassLoader可以将class文件里的数据加载到JVM虚拟机。
-
双亲委派模型:
-
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即
ClassNotFoundException
),子加载器才会尝试自己去加载。 -
为什么使用双亲委派机制加载类:
- 避免重复加载相同的字节码文件
-
-
JVM内存模型(JDK8)
-
线程私有:
- 程序计数器(PCR)
- 当前线程所执行字节码行号指示器(逻辑)。
- 改变计数器的值来获取下一行需要执行的字节码指令。
- 和线程是一对一的关系,也就是”线程私有“。
- 只对Java方法技术,如果是Native方法则计数器的值为Undefined。
- 不会发生内存泄漏。
- 虚拟机栈(stack)
- Java方法执行的内存模型。
- 包含多个栈帧。
- 本地方法栈
- 与虚拟机栈相似,主要作用于标注了Native的方法。
- 程序计数器(PCR)
-
线程共享:
- 元空间(MetaSpace)
- 元空间与永久代的区别
- 元空间使用本地内存,永久代使用JVM的内存
- 元空间与永久代的区别
- Java堆(Heap)
- 对象实例的分配区域
- GC管理的主要区域
- 元空间(MetaSpace)
-
JVM三大性能调优参数-Xms -Xmx -Xss的含义?
- -Xms:初始Java堆的大小
- -Xmx:Java堆能达到的最大值
- -Xss:规定了每个线程虚拟机栈(堆栈)的大小
-
Java内存模型中堆和栈的区别?
- 管理方式:栈自动释放,堆需要GC处理
- 空间大小:栈比堆小
- 碎片相关:栈产生的碎片远小于堆
- 分配方式:栈支持静态和动态分配,而堆仅支持动态分配
- 效率:栈的效率比堆高
-
-
Java垃圾回收机制
-
对象被判定为垃圾的标准——这个东西没有了价值
-
引用计数算法——判断对象的引用数量。
-
通过判断对象的引用数量来决定对象是否可以被回收。
-
每个对象实例都有一个引用计数器,被引用 +1 ,引用结束 -1 。
-
任何引用计数器为0的对象实例都可以被当作垃圾收集。
**优点:**执行效率高,程序执行受影响小。
缺点:无法检测出循环引用情况,会导致内存泄漏。
-
可达性分析算法——通过判断对象引用链的可达性是否可达来决定对象是否可以被回收。
- 可以作为GC Root的对象
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法区中的常量引用对象
- 方法区中的类静态属性引用
- 本地方法栈中JNI(Native)的引用对象
- 活跃线程的引用对象
- 可以作为GC Root的对象
-
-
谈谈你了解的垃圾回收算法
-
标记—清除算法(Mark and Sweep)
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存
总结:会造成内存碎片化
-
复制算法(Copying)—— 适合新生代
- 分为对象面和空闲面
- 对象在对象面上创建
- 存活的对象被从对象面复制到空闲面
- 将对象面所有对象那个内存清除
总结:
-
解决碎片化问题。
-
顺序分配内存,简单高效。
-
适用于对象存活率低的场景。
-
标记—整理算法(Compacting)——适合老年代
- 标记:从根集合进行扫描,对存活对象进行标记。
- 清除:移动所有存活对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。
总结:
- 避免内存的不连续执行
- 不用设置两块内存互换
- 适用于存活率高的场景
-
分代收集算法(Generational Collector )
-
垃圾回收算法的组合拳
-
按照对象生命周期的不同划分区域,以采用不同的垃圾回收算法
-
目的:提高JVM的回收效率
-
JDK7:年轻代,老年代,永久代
-
JDK8:
- 年轻代
- 存活率低:复制算法
- 老年代
- 存活率高:标记—整理算法
- 年轻代
-
GC的分类
-
Minor GC
发生在年轻代的垃圾回收动作,使用复制算法。
-
Full GC
发生在老年代的垃圾回收动作,在执行之前也会执行Minor GC,使用标记清理算法或标记整理算法。
-
-
年轻代:尽可能的快速回收掉那些生命周期短的对象(所占堆空间:1/3)
- 使用复制算法回收。
- 一个Eden区【8/10内存】
- 两个Survior区(一个From区【1/10内存】,一个To区【1/10内存】)
-
对象如何晋升到老年代
- 经历一定Minor GC后依然存活的对象(默认为15次)
- Seurvivor区中存放不下的对象
- 新生成的大对象
-
常用的调优参数
- XX: SurvivorRatio:Eden和 Survivor的比值,默认8:1
- XX:NewRatio:老年代和年轻代內存大小的比例
- XX: MaxTenuring Threshold:对象从年轻代晋升到老生代经过GC次数的最大阈值
-
老年代:存放生命周期较长的对象(所占堆空间:2/3)
- 使用标记清理算法或标记整理算法回收。
- 触发Full GC的条件
- 老年代空间不足
- 永久代空间不足
- 调用 System. gc()
- CMS GC时出现 promotion failed, concurrent mode failure
- Minor go晋升到老年代的平均大小大于老年代的剩余空间
- 使用RMI来进行RPC或管理的JDK应用,每小时执行1次Full GC
-
-
-
Java的异常处理
Error和Exception的区别
- Error:程序无法执行处理的错误,编译器也不做检查
- Exception:程序可以处理的异常,补货后可以恢复
- RuntimeException:不可预知的,程序应该自行避免
- 非RuntimeException:可以预知的编译期异常
- 总结:前者是程序无法处理的错误,后者是可以处理的异常
从责任角度看:
- Error属于JVM应该承担的责任;
- Runtime Exception是程序应该承担的责任;
- Checked Exception是Java编译器应该承担的责任。
常见Error以及Exception
-
Runtime Exception:
- NullPointerException - 空指针引用异常
- ClassCastException - 强制类型转换异常
- IllegalArgumentException - 传递非法参数异常
- IndexOutOfBoundsException - 数组索引越界异常
- NumberFormatException - 数字格式异常
-
非Runtime Exception:
- ClassNotFoundExcepton - 找不到指定class的异常
- IOExcrption - IO操作异常
-
Error:
-
NoClassDefoundError - 找不到class定义的异常
成因:
- 类依赖的class或者jar不存在
- 类文件存在,但是在不同的域中
- 大小写问题,javac编译时无视大小写,很有可能编译出来的class文件与需要的不一样
-
*Error - 深递归导致栈被耗尽而抛出的异常
-
OutOfMemoryError - 内存溢出异常
-
Java的异常处理机制
-
抛出异常:创建异常对象,交由运行时系统处理
-
捕获异常:寻找合适的异常处理器处理异常,否则终止运行
Java异常的处理原则
- 具体明确:抛岀的异常应能通过异常类名和 message准确说明异常的类型和产生异常的原因
- 提早抛出:应尽可能早的发现并抛出异常,便于精确定位问题
- 延迟捕获:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常
高效主流的异常处理框架
-
设计一个通用的继承自RuntimeException的异常类来统一处理
-
其余异常都统一转译为上述异常AppException
-
在Catch之后,抛出上述异常的子类,并提供注意定位的信息
-
由前端接收AppException做统一处理
Spring中的所有异常都可以用org. springframework.core.Nested. RuntimeException来表示
上一篇: 计算机网路面试题