Java面试题总结(含答案)

目录

一、Java基础

JDK 、 JRE 、JVM有什么区别和联系?

== 和 equals 的区别是什么?

两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

final 在 java 中有什么作用?

Java的基础类型有哪些?String属于基础的数据类型吗?

java 中操作字符串都有哪些类?它们之间有什么区别?

String str="i"与 String str=new String(“i”)一样吗?

String 类的常用方法都有那些?

如何将字符串反转?

接口和抽象类有哪些区别?

java 中 IO 流分为几种?

BIO、NIO、AIO 有什么区别?

Files的常用方法都有哪些?

二、容器

java 容器都有哪些?

Collection 和 Collections 有什么区别?

List、Set、Map 之间的区别是什么?

HashMap 和 Hashtable 有什么区别?

如何决定使用 HashMap 还是 TreeMap?

说一下 HashMap 、HashSet的实现原理?

ArrayList 和 LinkedList 的区别是什么?

如何实现数组和 List 之间的转换?

ArrayList 和 Vector 的区别是什么?

Array 和 ArrayList 有何区别?

在 Queue 中 poll()和 remove()有什么区别?

哪些集合类是线程安全的?

迭代器 Iterator 是什么?

Iterator 怎么使用?有什么特点?

Iterator 和 ListIterator 有什么区别?

怎么确保一个集合不能被修改?

三、多线程

并行和并发有什么区别?

线程和进程的区别?

守护线程是什么?

创建线程有哪几种方式?

说一下 Runnable 和 Callable 有什么区别?

线程有哪些状态?

sleep() 和 wait() 有什么区别?

notify()和 notifyAll()有什么区别?

线程的 run()和 start()有什么区别?

创建线程池有哪几种方式?

线程池都有哪些状态?

 线程池中 submit()和 execute()方法有什么区别?

在 java 程序中怎么保证多线程的运行安全?

什么是死锁?怎么防止死锁?

ThreadLocal 是什么?有哪些使用场景?

说一下 synchronized 底层实现原理?

synchronized 和 volatile 的区别是什么?

synchronized 和 Lock 有什么区别?

synchronized 和 ReentrantLock 区别是什么?

说一下 atomic 的原理?

四、反射

什么是反射?

什么是 java 序列化?什么情况下需要序列化?

动态代理是什么?怎么实现动态代理?有哪些应用?

五、对象拷贝

为什么要使用克隆?

如何实现对象克隆?

深拷贝和浅拷贝区别是什么?

六、Java Web

jsp 和 servlet 有什么区别?

jsp 有哪些内置对象?作用分别是什么?

说一下 jsp 的 4 种作用域?

session 和 cookie 有什么区别?

说一下 session 的工作原理?

如果客户端禁止 cookie 能实现 session 还能用吗?

如何避免 sql 注入?

什么是 XSS 攻击,如何避免?

什么是 CSRF 攻击,如何避免?

七、异常

throw 和 throws 的区别?

final、finally、finalize 有什么区别?

try-catch-finally 中哪个部分可以省略?

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

常见的异常类有哪些?

八、网络 

http 响应码 301 和 302 代表的是什么?有什么区别?

forward 和 redirect 的区别?

简述 tcp 和 udp的区别?

tcp 为什么要三次握手,两次不行吗?为什么?

说一下tcp的三次握手和四次挥手?

说一下 tcp 粘包是怎么产生的?

OSI 的七层模型都有哪些?

get 和 post 请求有哪些区别?

如何实现跨域?

九、设计模式

说一下你熟悉的设计模式?

设计模式的六大原则

简单工厂和抽象工厂有什么区别?

常用设计模式 Java 实现----单例模式

常用设计模式 Java 实现----工厂模式

十、Spring/Spring MVC

为什么要使用 spring?

解释一下什么是 aop(切面)?

解释一下什么是 ioc?

spring 有哪些主要模块?

spring 常用的注入方式有哪些?

spring 中的 bean 是线程安全的吗?

spring 自动装配 bean 有哪些方式?

spring 事务实现方式有哪些?

说一下 spring 的事务隔离?

说一下 spring mvc 运行流程?

@RequestMapping 的作用是什么?

@Autowired 的作用是什么?

十一、Spring Boot/Spring Cloud

什么是 spring boot?

为什么要用 spring boot?

springBoot常用注解及解释

spring boot 核心配置文件是什么?

spring boot 有哪些方式可以实现热部署?

jpa 和 hibernate 有什么区别?

什么是 spring cloud?

spring cloud 断路器的作用是什么?

spring cloud 的核心组件有哪些?

十二、Mybatis

mybatis 中 #{}和 ${}的区别是什么?

mybatis 有几种分页方式?

RowBounds 是一次性查询全部结果吗?为什么?

mybatis 逻辑分页和物理分页的区别是什么?

mybatis 是否支持延迟加载?延迟加载的原理是什么?

说一下 mybatis 的一级缓存和二级缓存?

mybatis 和 hibernate 的区别有哪些?

mybatis 有哪些执行器(Executor)?

mybatis 分页插件的实现原理是什么?

mybatis 如何编写一个自定义插件?

十三、MySql

数据库的三范式是什么?

一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几(8)?

如何获取当前数据库版本?

说一下 ACID 是什么?

char 和 varchar 的区别是什么?

float 和 double 的区别是什么?

mysql 的内连接、左连接、右连接有什么区别?

mysql 索引是怎么实现的?

怎么验证 mysql 的索引是否满足需求?

说一下数据库的事务隔离?

说一下 mysql 常用的引擎?

 说一下 mysql 的行锁和表锁?

说一下乐观锁和悲观锁?

mysql 问题排查都有哪些手段?如何做 mysql 的性能优化??

十四、Redis

redis 是什么?都有哪些使用场景?

redis 有哪些功能?

redis 和 memecache 有什么区别?

redis 为什么是单线程的?

什么是缓存穿透和雪崩?怎么解决?

redis 支持的数据类型有哪些?

redis 支持的 java 客户端都有哪些?

jedis 和 redisson 有哪些区别?

怎么保证缓存和数据库数据的一致性?

redis 持久化有几种方式?

redis 怎么实现分布式锁?

redis 分布式锁有什么缺陷?

redis 如何做内存优化?

redis 淘汰策略有哪些?

redis 常见的性能问题有哪些?该如何解决?

十五、JVM

说一下 jvm 的主要组成部分?及其作用?

说一下 jvm 运行时数据区?

说一下堆栈的区别?

队列和栈是什么?有什么区别?

说一下类加载的执行过程?

怎么判断对象是否可以被回收?

java 中都有哪些引用类型?

说一下 jvm 有哪些垃圾回收器?

详细介绍一下 CMS 垃圾回收器?

新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

简述分代垃圾回收器是怎么工作的?

说一下 jvm 调优的工具?

常用的 jvm 调优的参数都有哪些?

十六、Linux(常用命令)


一、Java基础

JDK 、 JRE 、JVM有什么区别和联系?

  1. JDK :英文名称(Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
  2. JRE :英文名称(Java Runtime Environment),Java 运行时环境。它主要包含两个部分,jvm 的标准实现Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。
  3. JVM :英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,jvm Java 能够跨平台的核心,具体的下文会详细说明。

这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM

== 和 equals 的区别是什么?

== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用

equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是是否相等

两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

答案是 不一定!

hashCode()相等的两个对象他们的equal()不一定相等。

equal()相等的两个对象他们的hashCode()肯定相等。

final 在 java 中有什么作用?

final作为Java中的关键字可以用于三个地方。用于修饰、类属性方法

特征:凡是引用final关键字的地方皆不可修改!

(1)修饰类:表示该类不能被继承;

(2)修饰方法:表示方法不能被重写;

(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

Java的基础类型有哪些?String属于基础的数据类型吗?

基本数据类型:(共有8种)

整型:byteshortintlong;长度分别为1,2,4,8;

浮点型:floatdouble;长度分别为 4,8;

字符型:char;长度为 2;

布尔型:boolean;长度为 1。

注:除了以上8种数据类型,剩下的全部都是引用数据类型(strintg属于引用类型)。

java 中操作字符串都有哪些类?它们之间有什么区别?

StringStringBufferStringBuilder

String :是不可变的对象,每次操作都会生成的 String 对象,然后将指针指向新的 String 对象。

StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

StringBuffer线程安全,同步锁(synchronized),多线程仍可以保证数据安全

StringBuilder线程不安全,多线程无法保证数据安全

String str="i"与 String str=new String(“i”)一样吗?

答:不一样

因为内存的分配方式不一样。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“i”)方式,则会被分到堆内存中。

String 类的常用方法都有那些?

和长度有关

返回类型

方法名

作用

int

length()

得到一个字符串的字符个数

和数组有关

返回类型

方法名

作用

byte[]

getByte()

将一个字符串转换成字节数组

char[]

toCharArray()

将一个字符串转换成字符数组

String[]

split(String)

将一个字符串按照指定内容分割开

和判断有关

返回类型

方法名

作用

boolean

equals()

判断两个字符串的内容是否一样

boolean

equalsIsIgnoreCase(String)

忽略太小写的比较两个字符串的内容是否一样

boolean

contains(String)

判断一个字符串里面是否包含指定的内容

和改变内容有关

返回类型

方法名

作用

String

toUpperCase()

将一个字符串全部转换成大写

String

toLowerCase()

将一个字符串全部转换成小写

String

replace(String,String)

将某个内容全部替换成指定内容

String

substring(int,int)

从下标x截取到下标y-1对应的元素

String

trim()

去除一个字符串的前后空格

和位置有关

返回类型

方法名

作用

char

charAt(int)

得到指定下标位置对应的字符

int

indexOf(String)

得到指定内容第一次出现的下标

int

lastIndexOf(String)

得到指定内容最后一次出现的下标

如何将字符串反转?

public static String reverse4(String s) {
  return new StringBuffer(s).reverse().toString();
}

接口和抽象类有哪些区别?

抽象类是什么:

抽象类不能创建实例,它只能作为父类被继承。抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类的随意性。

如果一个类中有抽象方法则这个类一定是抽象类

抽象类中的方法不一定是抽象方法

抽象类不不能使用final修饰  抽象类是用于被继承的,final修饰的类不可修改,不可继承

(1)抽象类可以有构造方法,接口中不能有构造方法。

(2)抽象类中可以有普通成员变量,接口中没有普通成员变量

(3)抽象类中可以包含静态方法,接口中不能包含静态方法

(4) 一个类可以实现多个接口,但只能继承一个抽象类。

(5)接口可以被多重实现,抽象类只能被单一继承

(6)如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法

java 中 IO 流分为几种?

IO流的分类

(1)按照数据的流向:

输入流输出流

(2)按照流数据的格式:

字符流字节流

(3)按照流数据的包装过程:

节点流(低级流)处理流(高级流)

最基本的几种进行简单介绍

•InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

•OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

IO流图表

BIO、NIO、AIO 有什么区别?

BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。

NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。

AIO ( Asynchronous I/O):异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。

进程中的IO调用步骤大致可以分为以下四步:

  1. 进程向操作系统请求数据 ;
  2. 操作系统把外部数据加载到内核的缓冲区中;
  3. 操作系统把内核的缓冲区拷贝到进程的缓冲区 ;
  4. 进程获得数据完成自己的功能 ;
  5. 当操作系统在把外部数据放到进程缓冲区的这段时间(即上述的第二,三步),如果应用进程是挂起等待的,那么就是同步IO,反之,就是异步IO,也就是AIO 。

Files的常用方法都有哪些?

  • Files. exists():检测文件路径是否存在。
  • Files. createFile():创建文件。
  • Files. createDirectory():创建文件夹。
  • Files. delete():删除一个文件或目录。
  • Files. copy():复制文件。
  • Files. move():移动文件。
  • Files. size():查看文件个数。
  • Files. read():读取文件。
  • Files. write():写入文件。

二、容器

java 容器都有哪些?

JAVA中的容器类主要分为两大类,一类是Map类,一类是Collection类,他们有一个共同的父接口Iterator,它提供基本的遍历,删除元素操作。Iterator还有一个子接口LinkIterator,它提供双向的遍历操作。

Collection 和 Collections 有什么区别?

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

Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于JavaCollection框架

List、Set、Map 之间的区别是什么?

List有序集合、元素可重复ArrayList基于数组实现的有序集合可以存储多个nullLinkedList基于链表实现的有序集合可以存储多个null

Set无序集合、元素不可重复LinkHashSet按照插入排序可以存储一个null-------SortSet可排序----------HashSet无序可以存储一个null

Map键值对集合、储存键、值和之间的映射,Key无序,唯一Value不要求有序,允许重复。hashmap 、linkedhashmap  key与value均可以为null.  treemap  key不可以为null,value可以为null.  hashtable、concurrenthashmap key与value均不能为null.

HashMap 和 Hashtable 有什么区别?

HashMap允许键和值是null,而Hashtable则不允许键或者值是null。

Hashtable是同步的,而HashMap不是,所以HashMap更适用于单线程环境,Hashtable则适用于多线程环境。

如何决定使用 HashMap 还是 TreeMap?

如果你需要得到一个有序的结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候我们会使用HashMap。

说一下 HashMap 、HashSet的实现原理?

HashMap 的实现原理:

HashMap是基于Hash算法实现的,

我们通过put(key,value)存储数据,通过get(key)来获取数据

当传入key时,HashMap会根据Key.hashCode()计算出Hash值,根据Hash值将value保存在bucket里 。

当计算出相同的Hash值时,我们称之为Hash冲突,HashMap 的做法是用链表和红黑树存储相同Hash值的value,

当hash冲突的个数比较少时,使用链表存储,

否则使用红黑树。

HashSet 的实现原理:

HashSet是基于HashMap实现的,HashSet 底层使用HashMap来保存所有元素,

因此HashSet 的实现比较简单,相关HashSet 的操作,基本上都是直接调用底层HashMap的相关方法来完成,HashSet不允许有重复的值,并且元素是无序的。

ArrayList 和 LinkedList 的区别是什么?

数据结构实现

ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。

随机访问效率

ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。

增加和删除效率

在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

  综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList

如何实现数组和 List 之间的转换?

数组转 List ,使用 JDK 中 java.util.Arrays 工具类的 asList 方法

public static void testArray2List() {
	String[] strs = new String[] {"aaa", "bbb", "ccc"};
	List<String> list = Arrays.asList(strs);
	for (String s : list) {
		System.out.println(s);
	}
}

List 转数组,使用 List 的toArray方法。无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象

public static void testList2Array() {
	List<String> list = Arrays.asList("aaa", "bbb", "ccc");
	String[] array = list.toArray(new String[list.size()]);
	for (String s : array) {
		System.out.println(s);
	}
}

ArrayList 和 Vector 的区别是什么?

  • Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
  • vector是线程(Thread)同步(Synchronized)的,所以它也是线程安全的,而Arraylist是线程异步(ASynchronized)的,是不安全的。如果不考虑到线程的安全因素,一般用Arraylist效率比较高。
如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。

Array 和 ArrayList 有何区别?

  • Array类型的变量在声明的同时必须进行实例化(至少得初始化数组的大小),而ArrayList可以只是先声明。(int[] array = new array[3];或 int[] array = {1,2,3};)
  • Array只能存储同构的对象,而ArrayList可以存储异构的对象。同构的对象是指类型相同的对象,若声明为int[]的数组就只能存放整形数据,string[]只能存放字符型数据,但声明为object[]的数组除外。而ArrayList可以存放任何不同类型的数据
  • Array是始终是连续存放的,而ArrayList的存放不一定连续。
  • Array对象的初始化必须只定指定大小,且创建后的数组大小是固定的,而ArrayList的大小可以动态指定,其大小可以在初始化时指定,也可以不指定,也就是说该对象的空间可以任意增加。
  • Array不能够随意添加和删除其中的项,而ArrayList可以在任意位置插入和删除项。

在 Queue 中 poll()和 remove()有什么区别?

队列的两种实现方式
1offer()add()的区别
add()和offer()都是向队列中添加一个元素。但是如果想在一个满的队列中加入一个新元素,调用 add() 方法就会抛出一个 unchecked 异常,而调用 offer() 方法会返回 false。可以据此在程序中进行有效的判断!
2peek()element()的区别
peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,调用element()方法会抛出NoSuchElementException异常。
3poll()remove()的区别
poll()和remove()都将移除并且返回对头,但是在poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。

哪些集合类是线程安全的?

Vector

Stack

Hashtable

java.util.concurrent包下所有的集合类

   ArrayBlockingQueue、ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque...

迭代器 Iterator 是什么?

迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

Java中的Iterator功能比较简单,并且只能单向移动:

(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

(2) 使用next()获得序列中的下一个元素。

(3) 使用hasNext()检查序列中是否还有元素。

(4) 使用remove()将迭代器新返回的元素删除。

Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

Iterator 怎么使用?有什么特点?

public class TestIterator {
       static List<String> list = new ArrayList<String>();
    static {
        list.add("111");
        list.add("222");
        list.add("333");
    }
    public static void main(String[] args) {
        testIteratorNext();
        System.out.println();
        testForEachRemaining();
        System.out.println();
        testIteratorRemove();
    }
    //使用 hasNext 和 next遍历 
    public static void testIteratorNext() {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String str = iterator.next();
            System.out.println(str);
        }
    }
    //使用 Iterator 删除元素 
    public static void testIteratorRemove() {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String str = iterator.next();
            if ("222".equals(str)) {
                iterator.remove();
            }
        }
        System.out.println(list);
    }
    //使用 forEachRemaining 遍历
    public static void testForEachRemaining() {
        final Iterator<String> iterator = list.iterator();
        iterator.forEachRemaining(new Consumer<String>() {
            public void accept(String t) {
                System.out.println(t);
            }   
        });
    }
}

Iterator 和 ListIterator 有什么区别?

一.相同点

都是迭代器,当需要对集合中元素进行遍历不需要干涉其遍历过程时,这两种迭代器都可以使用。

二.不同点

1.使用范围不同,Iterator可以应用于所有的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型。

2.ListIterator有add方法,可以向List中添加对象,而Iterator不能。

3.ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。

4.ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

5.都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。

怎么确保一个集合不能被修改?

1. Collections. unmodifiableCollection(Collection c) 方法
List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        Collection<Integer> readOnlyList = Collections.unmodifiableCollection(list);
        readOnlyList.add(4); // 会报错
2. 使用Arrays.asList创建的集合
List<Integer> integers = Arrays.asList(11, 22, 33, 44);
        integers.add(55);

三、多线程

并行和并发有什么区别?

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。

你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时。

并行的关键是你有同时处理多个任务的能力。

所以我认为它们最关键的点就是:是否是『同时』

线程和进程的区别?

1、首先是定义

进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。

线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。

2、一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。

3、线程是一种轻量级的进程,与进程相比,线程给操作系统带来侧创建、维护、和管理的负担要轻,意味着线程的代价或开销比较小。

守护线程是什么?

守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,

1、守护线程,比如垃圾回收线程,就是最典型的守护线程。

2、用户线程,就是应用程序里的自定义线程。

创建线程有哪几种方式?

主要有三种:

    继承 Thread 重写 run 方法;

    实现Runnable接口,重写 run 方法;

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

    实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。

说一下 Runnable 和 Callable 有什么区别?

1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果

2、Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

线程有哪些状态?

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

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权

   即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

   阻塞的情况分三种:

①.等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,

   必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,

②.同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

③.其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时,或者I/O处理完毕时,线程重新转入就绪状态。

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

sleep() 和 wait() 有什么区别?

sleep:Thread类中定义的方法,表示线程休眠,会自动唤醒;

  wait:Object中定义的方法,需要手工调用notify()或者notifyAll()方法。

  sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

  sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果),调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。

notify()和 notifyAll()有什么区别?

notify()和notifyAll()都是用来用来唤醒调用wait(方法进入等待锁资源队列的线程,区别在于:

notify()

唤醒正在等待此对象监视器的单个线程。 如果有多个线程在等待,则选择其中一个随机唤醒(由调度器决定),唤醒的线程享有公平竞争资源的权利

notifyAll()

唤醒正在等待此对象监视器的所有线程,唤醒的所有线程公平竞争资源

线程的 run()和 start()有什么区别?

  1. start()方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码;
  2. run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码;

创建线程池有哪几种方式?

java中创建线程池的方式一般有两种:

    通过Executors工厂方法创建

通过new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)自定义创建

线程池都有哪些状态?

线程池的生命周期,总共有五种状态

    RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;

    SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);

    STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;

    TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。

    TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。

进入TERMINATED的条件如下:线程池不是RUNNING状态;

              线程池状态不是TIDYING状态或TERMINATED状态;

     如果线程池状态是SHUTDOWN并且workerQueue为空;

              workerCount为0;

              设置TIDYING状态成功。
线程池的生命周期流程图

 线程池中 submit()和 execute()方法有什么区别?

1、接收的参数不一样。exucute只能执行实现Runnable接口的线程,submit可以执行实现Runnable接口或Callable接口的线程

2、submit有返回值,而execute没有

在 java 程序中怎么保证多线程的运行安全?

线程的安全性问题体现在:

    原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性

    可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到

    有序性:程序执行的顺序按照代码的先后顺序执行

导致原因:

    缓存导致的可见性问题

    线程切换带来的原子性问题

    编译优化带来的有序性问题

解决办法:

    JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题

    synchronized、volatile、LOCK,可以解决可见性问题

    Happens-Before 规则可以解决有序性问题

Happens-Before 规则如下:

    程序次序规则:在一个线程内,按照程序控制流顺序,书写在前面的操作先行发生于书写在后面的操作

    管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作

    volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作

    线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作

    线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测

    线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生

    对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始

多线程锁的升级原理是什么?

锁的级别从低到高:

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
偏向锁轻量级锁轻量级锁
适用场景只有一个线程进入同步块虽然很多线程,但是没有冲突:多条线程进入同步块,但是线程进入时间错开因而并未争抢锁

发生了锁争抢的情况:多条线程进入同步块并争用锁

本质取消同步操作CAS操作代替互斥同步互斥同步
优点不阻塞,执行效率高(只有第一次获取偏向锁时需要CAS操作,后面只是比对ThreadId)不会阻塞

不会空耗CPU

缺点适用场景太局限。若竞争产生,会有额外的偏向锁撤销的消耗长时间获取不到锁空耗阻塞,上下文切换,重量级操作,消耗操作系统资源

什么是死锁?怎么防止死锁?

什么是死锁?
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
死锁产生的4个必要条件?
  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
解决死锁的基本方法
  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

ThreadLocal 是什么?有哪些使用场景?

ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。

经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

说一下 synchronized 底层实现原理?

synchronized 和 volatile 的区别是什么?

名称

内存可见性

指令重排序

原子性

线程是否阻塞

synchronized

可见

会重排序

可以保证

会阻塞

volatile

可见

不会重排序

无法保证

不会阻塞

synchronized 和 Lock 有什么区别?

  • 实现层面不一样。synchronized 是 Java 关键字,JVM层面 实现加锁和释放锁;Lock 是一个接口,在代码层面实现加锁和释放锁
  • 是否自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要再 finally {} 代码块显式地中释放锁
  • 是否一直等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
  • 获取锁成功是否可知。synchronized 无法得知是否获取锁成功;Lock 可以通过 tryLock 获得加锁是否成功
  • 功能复杂性。synchronized 加锁可重入、不可中断、非公平;Lock 可重入、可判断、可公平和不公平、细分读写锁提高效率

synchronized 和 ReentrantLock 区别是什么?

  • synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
  • synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
  • synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
  • synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
  • synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放

说一下 atomic 的原理?

Atomic通过CAS(Compare And Wwap)乐观锁机制-自旋锁(它的实现很简单,就是用一个预期的值和内存的值进行比较,如果两个值相等,就用预期的值替换内存中的值,并返回true。否则,返回false)保证原子性,【通过降低锁粒度(多段锁)增加并发性能。这一点是java8做出的改进】从而避免synchronized的高开销,执行效率大为提升。

四、反射

什么是反射?

Java 反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java 的反射机制。

什么是 java 序列化?什么情况下需要序列化?

序列化:将 Java 对象转换成字节流的过程。

反序列化:将字节流转换成 Java 对象的过程。

当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。

序列化的实现:

类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。

注意事项:

    某个类可以被序列化,则其子类也可以被序列化

    声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据反序列化读取序列化对象的顺序要保持一致

动态代理是什么?怎么实现动态代理?有哪些应用?

动态代理:

当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。

Java 中实现动态的方式:

  • JDK 中的动态代理 
  • Java类库 CGLib

应用场景:

  • 统计每个 api 的请求耗时
  • 统一的日志输出
  • 校验被调用的 api 是否已经登录和权限鉴定
Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程

五、对象拷贝

为什么要使用克隆?

想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了。

克隆分浅克隆和深克隆,浅克隆后的对象中非基本对象和原对象指向同一块内存,因此对这些非基本对象的修改会同时更改克隆前后的对象。深克隆可以实现完全的克隆,可以用反射的方式或序列化的方式实现。

如何实现对象克隆?

1). 实现 Cloneable 接口并重写 Object 类中的 clone()方法;

2). 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆

深拷贝和浅拷贝区别是什么?

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,

深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。

浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。

深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。

六、Java Web

jsp 和 servlet 有什么区别?

Servlet

    一种服务器端的Java应用程序

    由 Web 容器加载和管理

    用于生成动态 Web 内容

    负责处理客户端请求

Jsp

    是 Servlet 的扩展,本质上还是 Servlet

    每个 Jsp 页面就是一个 Servlet 实例

    Jsp 页面会被 Web 容器编译成 Servlet,Servlet 再负责响应用户请求  

区别

    Servlet 适合动态输出 Web 数据和业务逻辑处理,对于 html 页面内容的修改非常不方便;Jsp 是在 Html 代码中嵌入 Java 代码,适合页面的显示

    内置对象不同,获取内置对象的方式不同

jsp 有哪些内置对象?作用分别是什么?

jsp共有以下9个内置对象:

    1.request  客户端请求,此请求会包含GET/POST请求的参数

    2.response  网页传回客户端的回应

    3.pageContext  网页的属性是在这里管理

    4.session  请求有关的会话期

    5.application  servlet正在执行的内容

    6.out  用来传送回应的输出

    7.config  servlet的架构部分

    8.page  jsp页面网页本身

    9.exception  针对错误网页,未捕捉的例外
作用分别是什么?分别有什么方法?

request表示HttpServletRequest对象。它包含了有关浏览器请求的信息,并且提供了几个用于获取cookie,header和session数据的有用的方法。

response表示HttpServletResponse对象,并提供了几个用于设置送回浏览器的响应的方法(如cookies,头信息等)。

out对象是javax.jsp.JspWriter的一个实例,并提供了几个方法使你能用于向浏览器回送输出结果。

pageContext表示一个javax.servlet.jsp.PageContext对象,它是用于方便存取各种范围的名字空间,servlet相关的对象API,并且包装了通用的servlet相关功能的方法。

session表示一个请求的javax.servlet.http.HttpSession对象,Session

可以存储用户的状态信息。

application表示一个javax.servlet.ServletContext对象,这有助于查询有关servlet引擎和servlet环境的信息。

config表示一个javax.servlet.ServletConfig对象,该对象用于存取servlet实例的初始化参数。

page表示从该页面产生的一个servlet实例。

说一下 jsp 的 4 种作用域?

page:代表与一个页面相关的对象和属性。

request:代表与客户端发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件;需要在页面显示的临时数据可以置于此作用域。

session:代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。

application:代表与整个 Web 应用程序相关的对象和属性,它实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域。

session 和 cookie 有什么区别?

1、存储位置不同

cookie的数据信息存放在客户端浏览器上。

session的数据信息存放在服务器上。

2、存储容量不同

单个cookie保存的数据<=4KB,一个站点最多保存20个Cookie。

对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置session删除机制。

3、存储方式不同

cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据。

session中能够存储任何类型的数据,包括且不限于string,integer,list,map等。

4、隐私策略不同

cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的。

session存储在服务器上,对客户端是透明对,不存在敏感信息泄漏的风险。

5、有效期上不同

开发可以通过设置cookie的属性,达到使cookie长期有效的效果。

session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session不能达到长期有效的效果。

6、服务器压力不同

cookie保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie是很好的选择。

session是保管在服务器端的,每个用户都会产生一个session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。

7、浏览器支持不同

假如客户端浏览器不支持cookie

  cookie是需要客户端浏览器支持的,假如客户端禁用了cookie,或者不支持cookie,则会话跟踪会失效。关于WAP上的应用,常规的cookie就派不上用场了。

  运用session需要使用URL地址重写的方式。一切用到session程序的URL都要进行URL地址重写,否则session会话跟踪还会失效。

假如客户端支持cookie

  cookie既能够设为本浏览器窗口以及子窗口内有效,也能够设为一切窗口内有效。

  session只能在本窗口以及子窗口内有效。

8、跨域支持上不同

cookie支持跨域名访问。

session不支持跨域名访问。

说一下 session 的工作原理?

session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。

如果客户端禁止 cookie 能实现 session 还能用吗?

一般默认情况下,在会话中,服务器存储 session 的 sessionid 是通过 cookie 存到浏览器里。

如果浏览器禁用了 cookie,浏览器请求服务器无法携带 sessionid,服务器无法识别请求中的用户身份,session失效。

但是可以通过其他方法在禁用 cookie 的情况下,可以继续使用session。

  • 通过url重写,把 sessionid 作为参数追加的原 url 中,后续的浏览器与服务器交互中携带 sessionid 参数。
  • 服务器的返回数据中包含 sessionid,浏览器发送请求时,携带 sessionid 参数。
通过 Http 协议其他 header 字段,服务器每次返回时设置该 header 字段信息,浏览器中 js 读取该 header 字段,请求服务器时,js设置携带该 header 字段。

如何避免 sql 注入?

  1. (简单又有效的方法)使用PreparedStatement
  2. 使用正则表达式过滤传入的参数
  3. 字符串过滤
  4. jsp中调用该函数检查是否包函非法字符
  5. JSP页面判断代码

什么是 XSS 攻击,如何避免?

XSS 攻击原理
攻击者往 web 页面里插入恶意的 HTML 代码(Javascript、css、html 标签等),当用户浏览该页面时,嵌入其中的 HTML 代码会被执行,从而达到恶意攻击用户的目的。如盗取用户 cookie 执行一系列操作,破坏页面结构、重定向到其他网站等。
预防思路
  1. web 页面中可由用户输入的地方,如果对输入的数据转义、过滤处理
  2. 后台输出页面的时候,也需要对输出内容进行转义、过滤处理(因为攻击者可能通过其他方式把恶意脚本写入数据库)
  3. 前端对 html 标签属性、css 属性赋值的地方进行校验

什么是 CSRF 攻击,如何避免?

什么是 CSRF 攻击(跨站请求攻击)
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
防御措施

1.检查Referer字段 

2.添加校验token

七、异常

throw 和 throws 的区别?

throws:用来声明一个方法可能产生的所有异常,不做任何处理而是将异常往上传,谁调用我我就抛给谁。

  用在方法声明后面,跟的是异常类名

  可以跟多个异常类名,用逗号隔开

  表示抛出异常,由该方法的调用者来处理

  throws表示出现异常的一种可能性,并不一定会发生这些异常

throw:则是用来抛出一个具体的异常类型。

  用在方法体内,跟的是异常对象名

  只能抛出一个异常对象名

  表示抛出异常,由方法体内的语句处理

  throw则是抛出了异常,执行throw则一定抛出了某种异常 

final、finally、finalize 有什么区别?

final (可以修饰 类 方法 变量):......

finally(跟try…except连用):不关有没有异常都会执行。

finalize(在java.lang.Object里定义的):这个方法在gc启动,该对象被回收的时候被调用

try-catch-finally 中哪个部分可以省略?

catch 和 finally 语句块可以省略其中一个

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

finally 一定会执行,即使是 catch 中 return 了,catch 中的 return 会等 finally 中的代码执行完之后,才会执行。

常见的异常类有哪些?

Throwable 是异常的根类。

Throwable 包含子类 错误-Error 和 异常-Exception 。

Exception 又分为 一般异常和运行时异常 RuntimeException。

运行时异常不需要代码显式捕获处理。

八、网络 

http 响应码 301 和 302 代表的是什么?有什么区别?

301 表示被请求 url 永久转移到新的 url;

302 表示被请求 url 临时转移到新的 url。

301 搜索引擎会索引新 url 和新 url 页面的内容;302 搜索引擎可能会索引旧 url 和 新 url 的页面内容。

302 的返回码可能被别人利用,劫持你的网址。因为搜索引擎索引他的网址,他返回 302 跳转到你的页面。

forward 和 redirect 的区别?

是servlet种的两种主要的跳转方式。forward又叫转发,redirect叫做重定向。

两者的区别总结:

1. 从地址栏显示来说:

        1)forword是服务器内部的重定向,服务器直接访问目标地址的 url网址,把里面的东西读取出来,但是客户端并不知道,因此用forward的话,客户端浏览器的网址是不会发生变化的。

        2)redirect是服务器根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,所以地址栏显示的是新的地址。

2. 从数据共享来说:

        1)由于在整个定向的过程中用的是同一个request,因此forward会将request的信息带到被重定向的jsp或者servlet中使用。即可以共享数据

        2)redirect不能共享

3. 从运用的地方来说

        1)forword 一般用于用户登录的时候,根据角色转发到相应的模块

        2)redirect一般用于用户注销登录时返回主页面或者跳转到其他网站

4. 从效率来说:

        1)forword效率高,而redirect效率低

5. 从本质来说:

        forword转发是服务器上的行为,而redirect重定向是客户端的行为

简述 tcp 和 udp的区别?

tcp是面向连接的协议,也就是说,在收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来。使用TCP协议传输数据,TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。当数据从A端传到B端后,B端会发送一个确认包(ACK包)给A端,告知A端数据我已收到!

UDP协议就没有这种确认机制,这就是为什么说TCP协议可靠,UDP协议不可靠,提供这种可靠服务,会加大网络带宽的开销,因为“虚拟信道”是持续存在的,同时网络中还会出现大量的ACK和FIN包。TCP协议提供了可靠的数据传输,但是其拥塞控制、数据校验、重传机制的网络开销很大,不适合实时通信,所以选择开销很小的UDP协议来传输数据。UDP协议是无连接的数据传输协议并且无重传机制,会发生丢包、收到重复包、乱序等情况。

区别:

1:TCP基于连接,UDP基于无连接。

2:TCP对系统资源要求高,UDP少。

3:TCP是基于字节流的,UDP是数据报文模式。

4:TCP复杂,UDP简单。

tcp 为什么要三次握手,两次不行吗?为什么?

        两次握手只能保证单向连接是畅通的。

Step1:A -> B : 你好,B。

Step2:A <- B : 收到。你好,A。

这样的两次握手过程, A 向 B 打招呼得到了回应,即 A 向 B 发送数据,B 是可以收到的。

但是 B 向 A 打招呼,A 还没有回应,B 没有收到 A 的反馈,无法确保 A 可以收到 B 发送的数据。

    只有经过第三次握手,才能确保双向都可以接收到对方的发送的 数据

Step3:A -> B : 收到,B。

这样 B 才能确定 A 也可以收到 B 发送给 A 的数据。

说一下tcp的三次握手和四次挥手?

三次握手过程:

第一次握手:tcp客户端发送一个SYN包序号是j(seq=j),客户端进入SYN_send状态,等服务器确认;

第二次握手:服务器收到客户端发送的这个SYN包,向客户端发送一个ACK确认包(ack=j+1),同时还发送一个SYN包(seq=k)。

第三次握手:客户端收收到SYN包和ACK包之后,再向服务器发送一个ACK确认包(ack=k+1) 发送完建立连接成功
四次挥手过程:

tcp连接时全双工的,需要每一个方向都单独进行关闭,首先执行的一方将执行主动关闭,另一方执行被动关闭

(1)第一次分手: tcp客户端发送一个FIN包(seq=j)

(2)第二次分手:服务器收到FIN包(seq=j)后,发回一个ACK确认包(ack=j+1)

(3)第三次分手:服务器发送一个FIN包(seq=k)到客户端

(4)第四次分手:客户端收到服务器发送的FIN包(seq=K)后,发送ACK包(ack=k+1)断开连接

 第二次握手和第三次握手之间需要等待一段时间确认数据全部接收完毕,才执行三握

说一下 tcp 粘包是怎么产生的?

1、什么是 tcp 粘包?

发送方发送的多个数据包,到接收方缓冲区首尾相连,粘成一包,被接收。

2、原因

TCP 协议默认使用 Nagle 算法可能会把多个数据包一次发送到接收方。

应用程读取缓存中的数据包的速度小于接收数据包的速度,缓存中的多个数据包会被应用程序当成一个包一次读取。

3、处理方法

  1. 发送方使用 TCP_NODELAY 选项来关闭 Nagle 算法
  2. 数据包增加开始符和结束,应用程序读取、区分数据包。
在数据包的头部定义整个数据包的长度,应用程序先读取数据包的长度,然后读取整个长度的包字节数据,保证读取的是单个包且完整。

OSI 的七层模型都有哪些?

OSI七层模型

功能

数据格式

对应的网络协议

对应的网络协议

应用层

提供为应用软件而设的接口,以设置与另一应用软件之间的通信

数据ATPU

DNS、HTTP、FTP、IMAP4、POP3、SSH、TELNET…

应用层

表达层

把数据转换为能与接收者的系统格式兼容并适合传输的格式

数据ATPU

会话层

负责在数据传输中设置和维护计算机网络中两台计算机之间的通信连接

数据ATPU

传输层

把传输表头(TH)加至数据以形成数据包。传输表头包含了所使用的协议等发送信息

数据组织成数据段Segment

TCPUDP、PPTP、TLS/SSL…

传输层

网络层

决定数据的路径选择和转寄,将网络表头(NH)加至数据包,以形成分组。网络表头包含了网络数据

分割和重新组合数据包Packet

IP(v4·v6)、ICMP(v6)、IGMP、Ipsec…

网络层

数据链路层

负责网络寻址、错误侦测和改错。当表头和表尾被加至数据包时,会形成帧。数据链表头(DLH)是包含了物理地址和错误侦测及改错的方法。数据链表尾(DLT)是一串指示数据包末端的字符串

将比特信息封装成数据帧Frame

Wi-Fi(IEEE 802.11)、ARP、WiMAX(IEEE 802.16)、PPP、PPPoE、L2TP…

数据链路层

物理层

在局部局域网上传送数据帧(data frame),它负责管理计算机通信设备和网络媒体之间的互通。包括了针脚、电压、线缆规范、集线器、中继器、网卡、主机接口卡等

传输比特(bit)流

get 和 post 请求有哪些区别?

  1. GET把参数包含在URL中,POST通过request body传递参数
  2. GET请求会被浏览器主动cache,而POST不会,除非手动设置。
  3. GET请求在URL中传送的参数是有长度限制的,而POST么有。
  4. GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
  5. GET请求只能进行url编码,而POST支持多种编码方式。
  6. GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
  7. 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  8. GET参数通过URL传递,POST放在Request body中。
  9. GET在浏览器回退时是无害的,而POST会再次提交请求。
  10. GET产生的URL地址可以被Bookmark,而POST不可以。

GET和POST还有一个重大区别:

GET产生一个TCP数据包;POST产生两个TCP数据包。

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

因为POST需要两步,时间上消耗的要多一点,GET比POST更有效!

1. GET与POST都有自己的语义,不能随便混用。

2. 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。

3. 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

如何实现跨域?

跨域:当浏览器执行脚本时会检查是否同源,只有同源的脚本才会执行,如果不同源即为跨域。

  1. 这里的同源指访问的协议、域名、端口都相同。
  2. 同源策略是由 Netscape 提出的著名安全策略,是浏览器最核心、基本的安全功能,它限制了一个源中加载脚本与来自其他源中资源的交互方式。
  3. Ajax 发起的跨域 HTTP 请求,结果被浏览器拦截,同时 Ajax 请求不能携带与本网站不同源的 Cookie。
  4. <script> <img> <iframe> <link> <video> <audio> 等带有 src 属性的标签可以从不同的域加载和执行资源。

如当使用 ajax 提交非同源的请求时,浏览器就会阻止请求。提示

Access to XMLHttpRequest at '...' from origin '...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

如何实现跨域请求呢?

1、jsonp(实现原理请百度)

利用了 script 不受同源策略的限制

缺点:只能 get 方式,易受到 XSS攻击

2、CORS(Cross-Origin Resource Sharing),跨域资源共享

当使用XMLHttpRequest发送请求时,如果浏览器发现违反了同源策略就会自动加上一个请求头 origin;

后端在接受到请求后确定响应后会在后端在接受到请求后确定响应后会在 Response Headers 中加入一个属性 Access-Control-Allow-Origin;

浏览器判断响应中的 Access-Control-Allow-Origin 值是否和当前的地址相同,匹配成功后才继续响应处理,否则报错

缺点:忽略 cookie,浏览器版本有一定要求

3、代理跨域请求

前端向发送请求,经过代理,请求需要的服务器资源

缺点:需要额外的代理服务器

4、Html5 postMessage 方法

允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本、多窗口、跨域消息传递

缺点:浏览器版本要求,部分浏览器要配置放开跨域限制

5、修改 document.domain 跨子域

相同主域名下的不同子域名资源,设置 document.domain 为 相同的一级域名

缺点:同一一级域名;相同协议;相同端口

6、基于 Html5 websocket 协议

websocket 是 Html5 一种新的协议,基于该协议可以做到浏览器与服务器全双工通信,允许跨域请求

缺点:浏览器一定版本要求,服务器需要支持 websocket 协议

7、document.xxx + iframe

通过 iframe 是浏览器非同源标签,加载内容中转,传到当前页面的属性中

缺点:页面的属性值有大小限制

九、设计模式

说一下你熟悉的设计模式?

创建型模式,共五种:

  1. 工厂方法模式
  2. 抽象工厂模式
  3. 单例模式
  4. 建造者模式
  5. 原型模式

结构型模式,共七种:

  1. 适配器模式
  2. 装饰器模式
  3. 代理模式
  4. 外观模式
  5. 桥接模式
  6. 组合模式
  7. 享元模式

行为型模式,共十一种:

  1. 策略模式
  2. 模板方法模式
  3. 观察者模式
  4. 迭代子模式
  5. 责任链模式
  6. 命令模式
  7. 备忘录模式
  8. 状态模式
  9. 访问者模式
  10. 中介者模式
  11. 解释器模式

设计模式的六大原则

总原则:开闭原则(Open Close Principle

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。

1、单一职责原则

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

2、里氏替换原则(Liskov Substitution Principle

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

3、依赖倒转原则(Dependence Inversion Principle

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

4、接口隔离原则(Interface Segregation Principle

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

5、迪米特法则(最少知道原则)(Demeter Principle

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

6、合成复用原则(Composite Reuse Principle

原则是尽量首先使用合成/聚合的方式,而不是使用继承。

简单工厂和抽象工厂有什么区别?

    简单工厂模式
是由一个工厂对象创建产品实例,简单工厂模式的工厂类一般是使用静态方法,通过不同的参数的创建不同的对象实例可以生产结构中的任意产品,不能增加新的产品
    抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需制定他们具体的类,生产多个系列产品生产不同产品族的全部产品,不能新增产品,可以新增产品族
 

常用设计模式 Java 实现----单例模式

单例模式

什么是单例?

保证一个类只会创建一个实例

应用场景:

    Winodw 任务管理器

    Windows 回收站

    网站的在线人数计数器

    应用程序的日志应用

    Web 应用配置对象的读取

    数据库连接池

    线程池

    文件系统

    HttpApplication

运行时动态获取类信息

/**
 * 饿汉式:
 *  类初始化时,会立即加载对象,线程天生安全,效率高
 */
public class User {
    // 存在方法区,不会回收
    private static User user = new User();
    private User() {
    }
    public User getInstance() {
        return user;
    }
/**
 * 懒汉式:
 *  类初始化时,不会真正创建对象,只有使用时才真正创建对象
 */
public class User {
    private static User user;
    private User() {
    }
    public static synchronized User getInstance() {
        if (user == null)
            user = new User();
        return user;
}}
/**
 * 静态内部类:
 *  兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射***)
 */
public class User {
    private User(){
    }
    static class SingletonClassInstance{
        private static final User user = new User();
    }
    public static User getInstance() {
        return SingletonClassInstance.user;
    }}
/**
 * 枚举:
 *  枚举天生就是单例,从JVM提供保障单例,避免反射,缺点没有延迟加载
 */
public class User {
    private User() {
    }
    public static User getInstance() {
        return SingletonUserEnum.INSTANCE.getInstance();
    }
    static enum SingletonUserEnum{
        INSTANCE;
        private User user;
        private SingletonUserEnum() {
            user = new User();
        }
        public User getInstance() {
            return this.user;
        }
    }}
/**
 * 双重检验锁:
 *  线程安全的单例模式
 */
public class User {
    private String userName;
    private volatile static User3 user3;
    private User(){
    }
    public User getInstance(){
        if (user == null) {
            synchronized (this) {
                if (user == null){
                    user = new User();
                }
            }
        }
        return user;
    }}
如何防止反射漏洞***
  1. 在类里面增加一个 flag,初始值为 false,创建对象后更改为 true,如果为 true,就抛出异常
  2. 如何选择单例创建方式?
  3. 需要延迟加载,选择静态内部类、懒汉式
  4. 不需要延迟加载,选择枚举类、饿汉式
  5. 多线程应用首选双重检验锁

常用设计模式 Java 实现----工厂模式

工厂模式

什么是工厂模式?

实现了创建者和调用者分离,工厂模式分为简单工厂、工厂方法、抽象工厂模式

应用场景:

  1. 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方
  2. 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时

设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口

优缺点:

  • 优点:符合开闭原则,添加子类时只需要添加对应的工厂类即可,而不用修改原有的工厂类,每个对象的创建逻辑都在对应的工厂类中,代码逻辑清晰,易于维护。
  • 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
UML类图

 工厂方法实现:

/**
*定义一个Factory接口,里面提供了一个getCar()方法,具体的创建逻辑由其实现类去完成。
*/
public interface Factory {
  
    public Car getCar();
}
/**
*Factory接口的具体实现类,用于创建对应的对象
*/
public class AudiFactory implements Factory {
  
    public Car getCar() {
        return new Audi();
    }
}
/**
*Factory接口的具体实现类,用于创建对应的对象
*/
public class BmwFactory implements Factory {
  
    public Car getCar() {
        return new Bmw();
    }
}
/**
*测试类
*/
public class FuncFactoryTest {
  
    public static void main(String[] args) {
        Car car1 = new AudiFactory().getCar();
        System.out.println(car1.getName());
         
        Car car2 = new BmwFactory().getCar();
        System.out.println(car2.getName());
    }
}
抽象工厂模式

什么是抽象工厂模式?

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂

该超级工厂又称为其他工厂的工厂,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类,每个生成的工厂都能按照工厂模式提供对象

应用场景:

QQ 换皮肤,一整套一起换

生成不同操作系统的程序

优缺点:

  • 优点:可以创建系列产品,方便切换使用的产品系列。
  • 缺点:扩展产品族较为麻烦,不光要添加所有的抽象产品实现,还要添加产品族对应的工厂;另外添加产品也不简单,要添加抽象产品,所有的产品族实现,还要对原先的工厂实现添加工厂方法以生产新产品。
UML类图

 这个案例我们就以生产电脑为例,众所周知目前电脑的CPU有两大品牌:Intel和AMD。我们就以此为例,如果电脑选用Intel系列,就要用Intel的CPU和主板等,如果用AMD系列,就用AMD系列的零部件。

 下面的两个接口就是我们上面提到的抽象产品角色

//CPU
public interface CPU {
  
    public String getName();
}
//主板
public interface MainBoard {
  
    public String getName();
}

下面的四个类就是我们上面提到的具体产品角色。首先是Intel系列产品

public class IntelCPU implements CPU {
  
    public String getName() {
        return "IntelCPU";
    }
  
}
public class IntelMainBoard implements MainBoard{
  
    public String getName() {
        return "IntelMainBoard";
    }
  
}

然后是AMD系列产品:

public class AMDCPU implements CPU {
  
    public String getName() {
        return "AMDCPU";
    }
  
}
public class AMDMainBoard implements MainBoard{
  
    public String getName() {
        return "AMDMainBoard";
    }
  
}

下面的Factory就是抽象工厂角色,提供了生产CPU和主板的抽象方法。

public interface Factory {
    //生产CPU
    public CPU createCPU();
    //生产主板
    public MainBoard createMainBoard();
}

然后是具体的工厂类角色,分别生产不同系列的产品。

//Intel系列产品工厂
public class IntelFactory implements Factory {
  
    public CPU createCPU() {
        return new IntelCPU();
    }
  
    public MainBoard createMainBoard() {
        return new IntelMainBoard();
    }
  
}
//AMD系列产品工厂
public class AMDFactory implements Factory {
  
    public CPU createCPU() {
        return new AMDCPU();
    }
  
    public MainBoard createMainBoard() {
        return new AMDMainBoard();
    }
  
}

测试类如下:

public class FactoryTest {
  
    public static void main(String[] args) {
        Factory intel = new IntelFactory();
        System.out.println(intel.createCPU().getName());
        System.out.println(intel.createMainBoard().getName());
         
        Factory amd = new AMDFactory();
        System.out.println(amd.createCPU().getName());
        System.out.println(amd.createMainBoard().getName());
    }
}

运行结果为:

IntelCPU
IntelMainBoard
AMDCPU
AMDMainBoard

抽象工厂终极改进(反射+配置文件+简单工厂)
上面也说过抽象工厂的缺点是扩展产品族比较麻烦,我们对上面的抽象工厂做个改进,使其在添加产品族的时候更简单一些。我们引入简单工厂,但是简单工厂扩展的时候要添加判断条件,所以我们可以通过反射+配置文件去解决这个问题。
UML图

改进后的代码如下所示:Configuration类用于读取配置文件(没有具体去实现) 

type=Intel
packageName=com.wkp.design.pattern.factory.abst
public class SimpleFactory {
  
    static class Configuration{
        public static String get(String key){
            String value="";//TODO 读取配置文件得到value
            return value;
        }
    }
     
    //通过配置文件读取产品族类型及包名
    private static final String type=Configuration.get("type");
    private static final String packageName=Configuration.get("packageName");
     
    //生产CPU
    public CPU createCPU() throws Exception{
        return (CPU)Class.forName(packageName+"."+type+"CPU").newInstance();
    }
    //生产主板
    public MainBoard createMainBoard() throws Exception{
        return (MainBoard)Class.forName(packageName+"."+type+"MainBoard").newInstance();
    }
}

测试代码如下

public class SimpleFactoryTest {
  
    public static void main(String[] args) throws Exception {
        SimpleFactory factory = new SimpleFactory();
        System.out.println(factory.createCPU().getName());
        System.out.println(factory.createMainBoard().getName());
    }
}

我们看到,在调用的时候客户端完全不用管用的是Intel还是AMD,这样如果想切换产品族的话,只需要修改配置文件即可,非常的方便。

十、Spring/Spring MVC

为什么要使用 spring?

spring 是一个开源的轻量级 JavaBean 容器框架。使用 JavaBean 代替 EJB ,并提供了丰富的企业应用功能,降低应用开发的复杂性。

  • 轻量:非入侵性的、所依赖的东西少、资源占用少、部署简单,不同功能选择不同的 jar 组合
  • 容器:工厂模式实现对 JavaBean 进行管理,通过控制反转(IOC)将应用程序的配置和依赖性与应用代码分开
  • 松耦合:通过 xml 配置或注解即可完成 bean 的依赖注入
  • AOP:通过 xml 配置 或注解即可加入面向切面编程的能力,完成切面功能,如:日志,事务...的统一处理
  • 方便集成:通过配置和简单的对象注入即可集成其他框架,如 Mybatis、Hibernate、Shiro...
丰富的功能:JDBC 层抽象、事务管理、MVC、Java Mail、任务调度、JMX、JMS、JNDI、EJB、动态语言、远程访问、Web Service...

解释一下什么是 aop(切面)?

在业务系统中,总有一些不得不处理的事情,我们将这些重复性的代码抽取出来,放在专门的类中,在通过spring的AOP的核心对代码段进行增强处理。在不改变原代码的基础上进行功能增强。有五种增强方式,前置增强,后置增强,环绕增强,引介增强。异常增强

解释一下什么是 ioc?

什么是 IoC

IoC (Inversion of control )控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。

例如:现有类 A 依赖于类 B

    传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来

    使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面取即可。

从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)

为什么叫控制反转

    控制 :指的是对象创建(实例化、管理)的权力

    反转 :控制权交给外部环境(Spring 框架、IoC 容器)

spring 有哪些主要模块?

Spring框架的七大模块

1. Spring Core

框架的最基础部分,提供 IoC 容器,对 bean 进行管理。

2.Spring Context

基于 bean,提供上下文信息,扩展出JNDI、EJB、电子邮件、国际化、校验和调度等功能。

3.Spring DAO

提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码,还提供了声明性事务管理方法。

4.Spring ORM

提供了常用的“对象/关系”映射APIs的集成层。 其中包括JPA、JDO、Hibernate、MyBatis 等。

5.Spring AOP

提供了符合AOP Alliance规范的面向方面的编程实现。

6.Spring Web

提供了基础的 Web 开发的上下文信息,可与其他 web 进行集成。

7.Spring Web MVC

提供了 Web 应用的 Model-View-Controller 全功能实现。

spring 常用的注入方式有哪些?

        *构造器注入

        *Setter方法注入

        *接口注入

spring 中的 bean 是线程安全的吗?

Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。

Spring 的 bean 作用域(scope)类型

1、singleton:单例,默认作用域。

2、prototype:原型,每次创建一个新对象。

3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。

4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。

5、global-session:全局会话,所有会话共享一个实例。

线程安全这个问题,要从单例与原型Bean分别进行说明。

原型Bean

对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。

单例Bean

对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。

如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。

对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。

注: Spring容器本身并没有提供线程安全的策略,因此是否线程安全完全取决于Bean本身的特性。

使用ThreadLocal的好处

使得多线程场景下,多个线程对这个单例Bean的成员变量并不存在资源的竞争,因为ThreadLocal为每个线程保存线程私有的数据。

spring 自动装配 bean 有哪些方式?

组件扫描(component scanning):Spring会自动扫描发现上下文中所创建的bean;对应java配置@ComponentScan注解,其会默认会扫描与配置类相同的包和其子包;如果是xml就是<context:component-scan>

自动装配(autowiring):Spring自动化管理bean之间的依赖关系;java配置@Autowiring注解,相当于java规范中的@Inject注解;其可以使用在任何类的任何方法上;不建议将required属性设置为false,否则如果在bean未装配的情况下就会报空指针异常;

@Component注解:赋予spring上下文组件类一个ID;如果不指定value默认就是将类名的第一个字母变为小写形式;其相当于java规范中的@Name注解;

spring 事务实现方式有哪些?

实现方式共有两种:编码方式声明式事务管理方式

基于AOP技术实现的声明式事务管理,实质就是:在方法执行前后进行拦截,然后在目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务。

声明式事务管理又有两种方式:基于XML配置文件的方式;另一个是在业务方法上进行@Transactional注解,将事务规则应用到业务逻辑中。

事务的4个特性

    原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做,要么全部做。

    一致性:数据不会因为事务的执行而遭到破坏。

    隔离性:一个事务的执行,不受其他事务(进程)的干扰。既并发执行的个事务之间互不干扰。

    持久性:一个事务一旦提交,它对数据库的改变将是永久的。

说一下 spring 的事务隔离?

Spring的事务隔离级别

首先说明一下事务并发引起的三种情况。

  • Dirty Read 脏读

一个事务正在对数据进行更新但更新未提交,另一个事务读取到了未提交数据,而前一个事务操作失败回滚,后一个事务就脏读了。

  • Non-Repeatable Reads不可重复读

一个事务多次读取同一数据,该事务还未结束时,另一个事务也对该数据进行了操作。而且在第一个事务两次读取之间,第二个事务对数据进行了更新。那么第一个事务前后两次读到的数据是不同的。

  • Phatom Reads 幻读

第一个事务正在查询符合条件的数据,这时,另一个事务又插入了一条符合数据的数据,第一个事务 在第二次查询符合同一条数据时,发现多了一条前一次查询没有的数据,这就是幻读。

隔离级别:

名称

结果

脏读

不可重复读

幻读

读未提交 

ReadUnCommitted

什么都不解决

读提交

Read Committed

解决了脏读的问题

——

重复读 

Repeatable Read

mysql的默认级别 解决了不可重复读

——

——

序列化 

Serializable

解决所有问题

——

——

——

说一下 spring mvc 运行流程?

执行流程:

  1. 用户向服务器发送请求,请求被 Spring 前端控制 Servelt DispatcherServlet 捕获(捕获)
  2. DispatcherServlet对请求  URL进行解析,得到请求资源标识符(URI)。然后根据该  URI,调用 HandlerMapping获得该Handler配置的所有相关的对象(包括  Handler对象以及   Handler对象对应的拦截器),最后以 HandlerExecutionChain对象的形式返回;(查找   handler)
  3. DispatcherServlet  根据获得的 Handler,选择一个合适的  HandlerAdapter。  提取Request 中的模型数据,填充 Handler 入参,开始执行 Handler(Controller), Handler执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象(执行  handler)
  4. DispatcherServlet  根据返回的 ModelAndView,选择一个适合的 ViewResolver(必须是已经注册到 Spring 容器中的 ViewResolver) (选择  ViewResolver)
  5. 通过 ViewResolver 结合 Model 和 View,来渲染视图,DispatcherServlet 将渲染结果返回给客户端。(渲染返回)

快速记忆技巧:

核心控制器捕获请求、查找Handler、执行Handler、选择ViewResolver,通过ViewResolver渲染视图并返回

spring mvc 有哪些组件?

Spring MVC的组件主要包括:
1. 前端控制器组件(DispatcherServlet)

DispatcherServlet 是 Spring MVC 的入口函数。接收请求,响应结果,相当于转发器,中央处理器。有了 DispatcherServlet ,可以大大减少其它组件之间的耦合度。

用户请求到达前端控制器,就相当于 mvc 模式中的 c,DispatcherServlet 是整个流程控制的中心,由它调用其它组件来处理用户的请求。
2. 处理器组件(Controller)

Handler 是继 DispatcherServlet 前端控制器的后端控制器,在 DispatcherServlet 的控制下,Handler 对具体的用户请求进行处理。由于 Handler 涉及到具体的用户业务请求,所以一般情况下需要工程师根据业务需求来开发 Handler。
3. 处理器映射器组件(HandlerMapping)

HandlerMapping 负责根据用户请求(URL),找到相应的 Handler 即处理器(Controller),SpringMVC 提供了不同映射器实现的不同映射方式,例如:配置文件方式,实现接口方式,注解方式等。
4. 处理器适配器组件(HandlerAdapter)

按照特定规则(HandlerAdapter 要求的规则)去执行 Handler,通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行处理。
5. 拦截器组件(HandlerInterceptor)
6. 视图解析器组件(ViewResolver)

作用:进行视图解析,根据逻辑视图名解析成真正的视图(View),View Resolver 负责将处理结果生成 View 视图。首先,根据逻辑视图名解析成物理视图名(即具体的页面地址),再生成 View 视图对象,最后对 View 进行渲染,将处理结果通过页面展示给用户。

Spring MVC 框架提供了很多的 View 视图类型,包括:jstlView、freemarkerView、pdfView 等。 一般情况下,需要通过页面标签或页面模版技术,将模型数据通过页面展示给用户,这需要由工程师根据业务需求开发具体的页面。
7. 视图组件(View)

View 是一个接口,实现类才可以支持不同的View类型(jsp、freemarker、pdf...)
8. 数据转换组件(DataBinder)
9. 消息转换器组件(HttpMessageConverter)

总结:处理器 Handler(也就是平常说的 Controller 控制器)以及视图层 View ,都是需要自行开发的。其他的一些组件,如:前端控制器 DispatcherServlet、处理器映射器 HandlerMapping、处理器适配器 HandlerAdapter 等都是由框架提供。

@RequestMapping 的作用是什么?

用来标识 http 请求地址与 Controller 类的方法之间的映射。

@Autowired 的作用是什么?

@Autowired 是一个注释,它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。

@Autowired 默认是按照类去匹配,配合 @Qualifier 指定按照名称去装配 bean。

十一、Spring Boot/Spring Cloud

什么是 spring boot?

spring boot就是一个大框架里面包含了许许多多的东西,其中spring就是最核心的内容之一,当然就包含spring mvc。spring mvc 是只是spring 处理web层请求的一个模块。

因此他们的关系大概就是这样:

spring mvc < spring <springboot。

为什么要用 spring boot?

使用springBoot的最大好处就是简化配置,它实现自动化配置

springBoot常用注解及解释

1、@SpringBootApplication

这个注解是Spring Boot最核心的注解,用在 Spring Boot的主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。实际上这个注解是@Configuration,@EnableAutoConfiguration,@ComponentScan三个注解的组合。

2、@EnableAutoConfiguration

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

3、@Configuration

用于定义配置类

4、@ComponentScan

组件扫描。让spring Boot扫描到Configuration类并把它加入到程序上下文。

@ComponentScan注解默认就会装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中。

5、@Repository

用于标注数据访问组件,即DAO组件。

6、@Service

一般用于修饰service层的组件

7、@RestController

用于标注控制层组件(如struts中的action),表示这是个控制器bean,并且是将函数的返回值直 接填入HTTP响应体中,是REST风格的控制器;它是@Controller和@ResponseBody的合集。

8、@ResponseBody

表示该方法的返回结果直接写入HTTP response body中

一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@responsebody后,会直接返回json数据。

9、@Component

泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

10、@Bean

相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理。

11、@AutoWired

完成自动装配的工作。

当加上(required=false)时,就算找不到bean也不报错。

12、@Qualifier

当有多个同一类型的Bean时,可以用@Qualifier("name")来指定。与@Autowired配合使用

13、@Resource(name="name",type="type")

没有括号内内容的话,默认byName。与@Autowired干类似的事。

14、@RequestMapping

RequestMapping是一个用来处理请求地址映射的注解;提供路由信息,负责URL到Controller中的具体函数的映射,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

15、@RequestParam

用在方法的参数前面。将请求参数和控制器方法参数关联。

16、@PathVariable

接收请求路径中占位符的值。参数与大括号里的名字一样要相同。例:

17、@Profiles

Spring Profiles提供了一种隔离应用程序配置的方式,并让这些配置只能在特定的环境下生效。

18、@ConfigurationProperties

Spring Boot可使用注解的方式将自定义的properties文件映射到实体bean中,比如config.properties文件。

spring boot 核心配置文件是什么?

Spring Boot 有两种类型的配置文件,application bootstrap 文件

Spring Boot会自动加载classpath目前下的这两个文件,文件格式为 properties 或 yml 格式

*.properties 文件是 key=value 的形式

*.yml 是 key: value 的形式

*.yml 加载的属性是有顺序的,但不支持 @PropertySource 注解来导入配置,一般推荐用yml文件,看下来更加形象

bootstrap 配置文件是系统级别的,用来加载外部配置,如配置中心的配置信息,也可以用来定义系统不会变化的属性.bootstatp 文件的加载先于application文件

application 配置文件是应用级别的,是当前应用的配置文件

spring boot 有哪些方式可以实现热部署?

1、模板热部署

        在SpringBoot中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不到修改后的页面的,因此我们可以在application.properties中关闭模版引擎的缓存,如下:

        Thymeleaf的配置:

spring.thymeleaf.cache=false

        FreeMarker的配置:

spring.freemarker.cache=false  

        Groovy的配置:

spring.groovy.template.cache=false

        Velocity的配置:

spring.velocity.cache=false

2、使用调试模式Debug实现热部署

此种方式为最简单最快速的一种热部署方式,运行系统时使用Debug模式,无需装任何插件即可,但是无发对配置文件,方法名称改变,增加类及方法进行热部署,使用范围有限。

3、spring-boot-devtools

        在Spring Boot 项目中添加 spring-boot-devtools依赖即可实现页面和代码的热部署。如下:

<dependency>

         <groupId>org.springframework.boot</groupId>

         <artifactId>spring-boot-devtools</artifactId></dependency>

此种方式的特点是作用范围广,系统的任何变动包括配置文件修改、方法名称变化都能覆盖,但是后遗症也非常明显,它是采用文件变化后重启的策略来实现了,主要是节省了我们手动点击重启的时间,提高了实效性,在体验上回稍差。

spring-boot-devtools 默认关闭了模版缓存,如果使用这种方式不用单独配置关闭模版缓存。

4、Spring Loaded

此种方式与Debug模式类似,适用范围有限,但是不依赖于Debug模式启动,通过Spring Loaded库文件启动,即可在正常模式下进行实时热部署。此种需要在 run confrgration 中进行配置。

5、JRebel

Jrebel是Java开发最好的热部署工具,对Spring Boot 提供了极佳的支持,JRebel为收费软件,试用期14天。,可直接通过插件安装。

jpa 和 hibernate 有什么区别?

JPA - Java Persistence API,是Java EE 5的标准ORM接口,也是ejb3规范的一部分。

Hibernate,当今很流行的ORM框架,是JPA的一个实现,但是其功能是JPA的超集。

区别与联系

JPA和Hibernate之间的关系:可以简单的理解为JPA是标准接口,Hibernate是实现。

什么是 spring cloud?

spring cloud是一个基于spring boot实现的微服务架构开发工具。

它为微服务架构中涉及的配置管理、服务治理、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、

分布式会话和集群状态管理等操作提供了一种简单的开发方式。

spring cloud 断路器的作用是什么?

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

spring cloud 的核心组件有哪些?

    Eureka:服务注册于发现。

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

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

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

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

十二、Mybatis

mybatis 中 #{}和 ${}的区别是什么?

Mybatis 的Mapper.xml语句中parameterType向SQL语句传参有两种方式:#{}和${}

我们经常使用的是#{},是因为这种方式可以防止SQL注入,#{}这种方式SQL语句是经过预编译的,它把#{}中间的参数转义成字符串。

比如:

select * from table where name = #{zhangSan}

预编译后,会动态解析成一个参数标记符?:

select * from table where name = ?

而使用${}在动态解析时候,会传入参数字符串

select * from table where name = ${zhangSan}

动态解析时候,会传入参数字符串

select * from table where name = 'zhangSan'

总结:

#{} 这种取值是编译好SQL语句再取值     即    #{}:动态解析 -> 预编译 -> 执行

${} 这种是取值以后再去编译SQL语句     即    ${}:动态解析 -> 编译 -> 执行

#传入的参数在SQL中显示为字符串(当成一个字符串),会对自动传入的数据加一个双引号。

$传入的参数在SqL中直接显示为传入的值

${ } 变量的替换阶段是在动态 SQL 解析阶段,而 #{ }变量的替换是在 DBMS 中。

2、#可以防止SQL注入的风险(语句的拼接);但$无法防止Sql注入。

3、$方式一般用于传入数据库对象,例如传入表名。

4、大多数情况下还是经常使用#,一般能用#的就别用$;但有些情况下必须使用$,例:MyBatis排序时使用order by 动态参数时需要注意,用$而不是#。

mybatis 有几种分页方式?

一种是使用pagehelper

一种是自己写分页逻辑使用limited

还有一种是使用mybatis-plus的分页

RowBounds 是一次性查询全部结果吗?为什么?

RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。

mybatis 逻辑分页和物理分页的区别是什么?

逻辑分页: 从数据库将所有记录查询出来,存储到内存中,展示当前页,然后数据再直接从内存中获取。优点:效率高;缺点:占用内存比较高。

mybatis 是否支持延迟加载?延迟加载的原理是什么?

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

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

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

说一下 mybatis 的一级缓存和二级缓存?

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

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

对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

mybatis 和 hibernate 的区别有哪些?

Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。

Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。

Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。

mybatis 有哪些执行器(Executor)?

Mybatis有三种基本的执行器(Executor):

    SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

    ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。

    BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

mybatis 分页插件的实现原理是什么?

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

mybatis 如何编写一个自定义插件?

mybatis 如何编写一个自定义插件?

Mybatis自定义插件针对Mybatis四大对象(Executor、StatementHandler 、ParameterHandler 、ResultSetHandler )进行拦截,具体拦截方式为:

    Executor:拦截执行器的方法(log记录)

    StatementHandler :拦截Sql语法构建的处理

    ParameterHandler :拦截参数的处理

    ResultSetHandler :拦截结果集的处理

Mybatis自定义插件必须实现Interceptor接口:

public interface Interceptor {

    Object intercept(Invocation invocation) throws Throwable;  //拦截器具体处理逻辑方法

    Object plugin(Object target);  //根据签名signatureMap生成动态代理对象

    void setProperties(Properties properties);  //设置Properties属性

}

自定义插件demo

// ExamplePlugin.java

@Intercepts({@Signature(

  type= Executor.class,

  method = "update",

  args = {MappedStatement.class,Object.class})})

public class ExamplePlugin implements Interceptor {

  public Object intercept(Invocation invocation) throws Throwable {

  Object target = invocation.getTarget(); //被代理对象

  Method method = invocation.getMethod(); //代理方法

  Object[] args = invocation.getArgs(); //方法参数

  // do something ...... 方法拦截前执行代码块

  Object result = invocation.proceed();

  // do something .......方法拦截后执行代码块

  return result;

  }

  public Object plugin(Object target) {

    return Plugin.wrap(target, this);

  }

  public void setProperties(Properties properties) {

  }

}

一个@Intercepts可以配置多个@Signature,@Signature中的参数定义如下:

    type:表示拦截的类,这里是Executor的实现类;

    method:表示拦截的方法,这里是拦截Executor的update方法;

    args:表示方法参数。

十三、MySql

数据库的三范式是什么?

第一范式(1NF):原子性 字段不可再分,否则就不是关系数据库;

第二范式(2NF):唯一性 一个表只说明一个事物;

第三范式(3NF):每列都与主键有直接关系,不存在传递依赖;

PS:第二范式要遵循第一范式,第三范式要遵循第二范式。

一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几(8)?

一般情况下,我们创建的表的类型是InnoDB,如果新增一条记录(不重启mysql的情况下),这条记录的id是8;但是如果重启(文中提到的)MySQL的话,这条记录的ID是6。因为InnoDB表只把自增主键的最大ID记录到内存中,所以重启数据库或者对表OPTIMIZE操作,都会使最大ID丢失。

但是,如果我们使用表的类型是MylSAM,那么这条记录的ID就是8。因为MylSAM表会把自增主键的最大ID记录到数据文件里面,重启MYSQL后,自增主键的最大ID也不会丢失。

注:如果在这7条记录里面删除的是中间的几个记录(比如删除的是3,4两条记录),重启MySQL数据库后,insert一条记录后,ID都是8。因为内存或者数据库文件存储都是自增主键最大ID

如何获取当前数据库版本?

使用 select version() 获取当前 MySQL 数据库版本。

说一下 ACID 是什么?

Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,

不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,

就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。

Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。

这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。

Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,

隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,

包括读未提交(Read uncommitted)、读提交(read committed)、

可重复读(repeatable read)和串行化(Serializable)。

Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

char 和 varchar 的区别是什么?

        char(n) :固定长度类型,比如订阅 char(10),当你输入"abc"三个字符的时候,

它们占的空间还是 10 个字节,其他 7 个是空字节。

        char 优点:效率高;缺点:占用空间;

适用场景:存储密码的 md5 值,固定长度的,使用 char 非常合适。

        varchar(n) :可变长度,存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。

所以,从空间上考虑 varcahr 比较合适;

从效率上考虑 char 比较合适,二者使用需要权衡。

float 和 double 的区别是什么?

float 最多可以存储 8 位的十进制数,并在内存中占 4 字节。

double 最可可以存储 16 位的十进制数,并在内存中占 8 字节。

mysql 的内连接、左连接、右连接有什么区别?

内连接,显示两个表中有联系的所有数据;

左链接,以左表为参照,显示所有数据,右表中没有则以null显示

右链接,以右表为参照显示数据,,左表中没有则以null显示

mysql 索引是怎么实现的?

索引是满足某种特定查找算法的数据结构,而这些数据结构会以某种方式指向数据,从而实现高效查找数据。

具体来说 MySQL 中的索引,不同的数据引擎实现有所不同,但目前主流的数据库引擎的索引都是 B+ 树实现的,B+ 树的搜索效率,可以到达二分法的性能,找到数据区域之后就找到了完整的数据结构了,所有索引的性能也是更好的。

怎么验证 mysql 的索引是否满足需求?

使用 explain 查看 SQL 是如何执行查询语句的,从而分析你的索引是否满足需求。

explain 语法:explain select * from table where type=1。

说一下数据库的事务隔离?

Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,

不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,

就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。

Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。

这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。

Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,

隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,

包括读未提交(Read uncommitted)、读提交(read committed)、

可重复读(repeatable read)和串行化(Serializable)。

Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

说一下 mysql 常用的引擎?

MyISAM存储引擎:不支持事务、也不支持外键,优势是访问速度快,对事务完整性没有 要求或者以select,insert为主的应用基本上可以用这个引擎来创建表
支持3种不同的存储格式,分别是:静态表;动态表;压缩表
  InnoDB存储引擎:该存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MyISAM引擎,写的处理效率会差一些,并且会占用更多的磁盘空间以保留数据和索引。 
InnoDB存储引擎的特点:支持自动增长列,支持外键约束

MEMORY存储引擎:Memory存储引擎使用存在于内存中的内容来创建表。每个memory表只实际对应一个磁盘文件,格式是.frm。memory类型的表访问非常的快,因为它的数据是放在内存中的,并且默认使用HASH索引,但是一旦服务关闭,表中的数据就会丢失掉。

MEMORY存储引擎的表可以选择使用BTREE索引或者HASH索引,两种不同类型的索引有其不同的使用范围

MERGE存储引擎:Merge存储引擎是一组MyISAM表的组合,这些MyISAM表必须结构完全相同,merge表本身并没有数据,对merge类型的表可以进行查询,更新,删除操作,这些操作实际上是对内部的MyISAM表进行的。

 说一下 mysql 的行锁和表锁?

MyISAM 只支持表锁;InnoDB 支持表锁和行锁,默认为行锁。

    表锁:开销小,加锁快,不会出现死锁。锁粒度大,发生锁冲突的概率最高,并发量最低。

    行锁:开销大,加锁慢,会出现死锁。锁粒度小,发生锁冲突的概率小,并发度最高。

说一下乐观锁和悲观锁?

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

mysql 问题排查都有哪些手段?如何做 mysql 的性能优化??

MySQL 问题排查都有哪些手段?

  • 使用 show processlist 命令查看当前所有连接信息。
  • 使用 explain 命令查询 SQL 语句执行计划。
  • 开启慢查询日志,查看慢查询的 SQL。

如何做 MySQL 的性能优化?

  • 为搜索字段创建索引。
  • 避免使用 select *,列出需要查询的字段。
  • 垂直分割分表。
选择正确的存储引擎。

十四、Redis

redis 是什么?都有哪些使用场景?

Redis:REmote DIctionary Server(远程字典服务器)

是完全开源免费的,用C语言编写的,遵守BSD协议,

是一个高性能的(key/value)分布式内存数据库,基于内存运行

并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,

也被人们称为数据结构服务器。

优点:

    Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

    Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储

    Redis支持数据的备份,即master-slave模式的数据备份

应用场景:

  • 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
  • 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
  • 模拟类似于HttpSession这种需要设定过期时间的功能
  • 发布、订阅消息系统
定时器、计数器

redis 有哪些功能?

基于本机内存的缓存

为了解决调用API依然需要2秒的问题,经过排查,其主要原因在于使用SQL获取热点新闻的过程中消耗了将近2秒的时间,于是乎,我们又想到了一个简单粗暴的解决方案,即把SQL查询的结果直接缓存在当前api服务器的内存中(设置缓存有效时间为1分钟)。后续1分钟内的请求直接读缓存,不再花费2秒去执行SQL了。假如这个api每秒接收到的请求时100个,那么一分钟就是6000个,也就是只有前2秒拥挤过来的请求会耗时2秒,后续的58秒中的所有请求都可以做到即使响应,而无需再等2秒的时间。

服务端的Redis

在API服务器的内存都被缓存塞满的时候,我们发现不得不另想解决方案了。最直接的想法就是我们把这些缓存都丢到一个专门的服务器上吧,把它的内存配置的大大的。然后我们就盯上了redis。。。至于如何配置部署redis这里不解释了,redis官方有详细的介绍。随后我们就用上了一台单独的服务器作为Redis的服务器,API服务器的内存压力得以解决。

持久化(Persistence

单台的Redis服务器一个月总有那么几天心情不好,心情不好就罢工了,导致所有的缓存都丢失了(redis的数据是存储在内存的嘛)。虽然可以把Redis服务器重新上线,但是由于内存的数据丢失,造成了缓存雪崩,API服务器和数据库的压力还是一下子就上来了。所以这个时候Redis的持久化功能就派上用场了,可以缓解一下缓存雪崩带来的影响。redis的持久化指的是redis会把内存的中的数据写入到硬盘中,在redis重新启动的时候加载这些数据,从而最大限度的降低缓存丢失带来的影响。

哨兵(Sentinel)和复制(Replication

Redis服务器毫无征兆的罢工是个麻烦事。那么怎办办?答曰:备份一台,你挂了它上。那么如何得知某一台redis服务器挂了,如何切换,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要Sentinel和Replication出场了。Sentinel可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能;Replication则是负责让一个Redis服务器可以配备多个备份的服务器。Redis也是利用这两个功能来保证Redis的高可用的。此外,Sentinel功能则是对Redis的发布和订阅功能的一个利用。

集群(Cluster

单台服务器资源的总是有上限的,CPU资源和IO资源我们可以通过主从复制,进行读写分离,把一部分CPU和IO的压力转移到从服务器上。但是内存资源怎么办,主从模式做到的只是相同数据的备份,并不能横向扩充内存;单台机器的内存也只能进行加大处理,但是总有上限的。所以我们就需要一种解决方案,可以让我们横向扩展。最终的目的既是把每台服务器只负责其中的一部分,让这些所有的服务器构成一个整体,对外界的消费者而言,这一组分布式的服务器就像是一个集中式的服务器一样

redis 和 memecache 有什么区别?

1、存储方式:

memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小

redis有部份存在硬盘上,这样能保证数据的持久性。

2、数据支持类型:

redis在数据支持上要比memecache多的多。

3、使用底层模型不同:

新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

4、运行环境不同:

redis目前官方只支持LINUX 上去行,从而省去了对于其它系统的支持,这样的话可以更好的把精力用于本系统 环境上的优化,虽然后来微软有一个小组为其写了补丁。但是没有放到主干上

redis 为什么是单线程的?

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

什么是缓存穿透和雪崩?怎么解决?

缓存穿透:缓存穿透的概念很简单,用户想要查询一个数据,发现 redis 内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就出现了缓存穿透。

解决方案:

1.布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以 hash 形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力

2.缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源

但是这种方法会存在两个问题:

1.如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;

2.即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

缓存击穿:这里需要注意和缓存穿透的区别,缓存击穿,是指一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

当某个 key 在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

解决方案

1. 设置热点数据永不过期

从缓存层面来看,不设置过期时间,就不会出现热点 key 过期后产生的问题。

2.加互斥锁

分布式锁:使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩:缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

其实缓存集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

解决方案

1.Redis 高可用

这个思想的含义是,既然 redis 有可能挂掉,那我多增设几台 redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。

2.限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。

3.数据预热

数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

redis 支持的数据类型有哪些?

Redis支持五种数据类型:string(字符串)hash(哈希)list(列表)set(集合)zset(sorted set:有序集合)

1、String(字符串)

string是redis最基本的类型,一个key对应一个value。

    string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

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

2、Hash(哈希)

hash 是一个键值对集合。

Redis hash 是一个 string类型的 field和 value的映射表,hash 特别适合用于存储对象。3、List(列表)

列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的左边或者右边。列表可以从两端压入或者弹出元素

特点:

有序

可以重复

可以在左右两边插入弹出

4、Set(集合)

Redis 的 Set是 string 类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

特点

无序

无重复

可以集合间操作

5、zset(有序集合)

Redis zset 和 set 一样也是 string 类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复

特点:

无重复元素

有序

redis 支持的 java 客户端都有哪些?

Redis Desktop Manager 、Redis Client、Redis Studio、jedis

jedis 和 redisson 有哪些区别?

Jedis 和 Redisson 都是Java中对Redis操作的封装。Jedis 只是简单的封装了 Redis 的API库,可以看作是Redis客户端,它的方法和Redis 的命令很类似。Redisson 不仅封装了 redis ,还封装了对更多数据结构的支持,以及锁等功能,相比于Jedis 更加大。但Jedis相比于Redisson 更原生一些,更灵活。

怎么保证缓存和数据库数据的一致性?

淘汰缓存还是更新缓存?

选择淘汰缓存

原因:数据可能为简单数据,也可能为较复杂的数据,复杂数据进行缓存的更新操作,成本较高,因此一般推荐淘汰缓存

先淘汰缓存还是先更新数据库?

选择先淘汰缓存,再更新数据库

原因:假如先更新数据库,再淘汰缓存,假如缓存淘汰失败,那么后面的请求都会得到脏数据,直至缓存过期。假如先淘汰缓存再更新数据库,如果数据库更新失败,只会产生一次缓存miss,相比较而言,后者对业务影响更小一点。

延时双删策略

如下场景:同时有一个请求A进行更新操作,另一个请求B进行查询操作。

(1)请求A进行写操作,删除缓存

(2)请求B查询发现缓存不存在

(3)请求B去数据库查询得到旧值

(4)请求B将旧值写入缓存

(5)请求A将新值写入数据库

次数便出现了数据不一致问题。采用延时双删策略得以解决

public void write(String key,Object data){

    redisUtils.del(key);

    db.update(data);

    Thread.Sleep(100);

    redisUtils.del(key);

}

这么做,可以将1秒内所造成的缓存脏数据,再次删除。这个时间设定可根据俄业务场景进行一个调节。

数据库读写分离的场景

两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。

(1)请求A进行写操作,删除缓存

(2)请求A将数据写入数据库了,

(3)请求B查询缓存发现,缓存没有值

(4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值

(5)请求B将旧值写入缓存

(6)数据库完成主从同步,从库变为新值

依旧采用延时双删策略解决此问题

redis 持久化有几种方式?

redis 持久化的两种方式

    RDBRDB 持久化机制,是对 redis 中的数据执行周期性的持久化。

    AOFAOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。

通过 RDB 或 AOF,都可以将 redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。

如果 redis 挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动 redis,redis 就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务。

如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。

RDB 优缺点

  • RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 redis 中的数据。
  • RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。

  • 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
  • 如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
  • RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

AOF 优缺点

  • AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据。
  • AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
  • AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
  • AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。
  • 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
  • AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
  • 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

RDB 和 AOF 到底该如何选择

  • 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
  • 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。

redis 怎么实现分布式锁?

要点

Redis要实现分布式锁,以下条件应该得到满足

  • 互斥性:在任意时刻,只有一个客户端能持有锁。
  • 不能死锁:客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

实现

可以直接通过 set key value px milliseconds nx 命令实现加锁, 通过Lua脚本实现解锁。

//获取锁(unique_value可以是UUID等)

SET resource_name unique_value NX PX  30000

//释放锁(lua脚本中,一定要比较value,防止误解锁)

if redis.call("get",KEYS[1]) == ARGV[1] then

    return redis.call("del",KEYS[1])

else

    return 0

end

代码解释

    set 命令要用 set key value px milliseconds nx,替代 setnx + expire 需要分两次执行命令的方式,保证了原子性,

    value 要具有唯一性,可以使用UUID.randomUUID().toString()方法生成,用来标识这把锁是属于哪个请求加的,在解锁的时候就可以有依据;

    释放锁时要验证 value 值,防止误解锁;

通过 Lua 脚本来避免 Check And Set 模型的并发问题,因为在释放锁的时候因为涉及到多个Redis操作 (利用了eval命令执行Lua脚本的原子性);

加锁代码分析

首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,用来标识这把锁是属于哪个请求加的,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。

解锁代码分析

将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。在执行的时候,首先会获取锁对应的value值,检查是否与requestId相等,如果相等则解锁(删除key)。

存在的风险

如果存储锁对应key的那个节点挂了的话,就可能存在丢失锁的风险,导致出现多个客户端持有锁的情况,这样就不能实现资源的独享了。

  • 客户端A从master获取到锁
  • 在master将锁同步到slave之前,master宕掉了(Redis的主从同步通常是异步的)。

主从切换,slave节点被晋级为master节点

客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。导致存在同一时刻存不止一个线程获取到锁的情况。

redis 分布式锁有什么缺陷?

Redis 分布式锁不能解决超时的问题,分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题。

redis 如何做内存优化?

1、缩减键值对象

  缩减键(key)和值(value)的长度,

    key长度:如在设计键时,在完整描述业务情况下,键值越短越好。

    value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。以JAVA为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如: protostuff,kryo等,下图是JAVA常见序列化工具空间压缩对比。

2、共享对象池

  对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。 除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。

3、字符串优化

4、编码优化

5、控制key的数量

redis 淘汰策略有哪些?

  • noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。 大多数写命令都会导致占用更多的内存(有极少数会例外, 如 DEL )。
  • allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
  • volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
  • allkeys-random: 所有key通用; 随机删除一部分 key。
  • volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。

redis 常见的性能问题有哪些?该如何解决?

  • Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
  • Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
  • Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内

十五、JVM

说一下 jvm 的主要组成部分?及其作用?

JVM包括类加载子系统、堆、方法区、栈、本地方法栈、程序计数器、直接内存、垃圾回收器、执行引擎。

1、类加载子系统

类加载子系统负责加载class信息,加载的类信息存放于方法区中。

2、直接内存

直接内存是在Java堆外的、直接向系统申请的内存空间。访问直接内存的速度会由于Java堆。出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。

3、垃圾回收器

垃圾回收器可以对堆、方法区、直接内存进行回收。

4、执行引擎

执行引擎负责执行虚拟机的字节码,虚拟机会使用即时编译技术将方法编译成机器码后再执行。

说一下 jvm 运行时数据区?

运行时数据区包括堆、方法区、栈、本地方法栈、程序计数器。

1、堆

堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。

2、方法区

方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。

3、栈

栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动态链接、方法出口等信息。

    栈帧

每个方法从调用到执行的过程就是一个栈帧在虚拟机栈中入栈到出栈的过程。

    局部变量表

用于保存函数的参数和局部变量。

    操作数栈

操作数栈又称操作栈,大多数指令都是从这里弹出数据,执行运算,然后把结果压回操作数栈。

4、本地方法栈

与栈功能相同,本地方法栈执行的是本地方法,一个Java调用非Java代码的接口。

5、程序计数器(PC寄存器)

程序计数器中存放的是当前线程所执行的字节码的行数。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。

说一下堆栈的区别?

(1)Java的堆是一个运行时数据区,类的对象从堆中分配空间。这些对象通过new等指令建立,通过垃圾回收器来销毁。

(2)堆的优势是可以动态地分配内存空间,需要多少内存空间不必事先告诉编译器,因为它是在运行时动态分配的。但缺点是,由于需要在运行时动态分配内存,所以存取速度较慢。

(1)栈中主要存放一些基本数据类型的变量(*byte,**short,***int,long,float,double,boolean,char)和对象的引用。

(2)栈的优势是,存取速度比堆快,栈数据可以共享。但缺点是,存放在栈中的数据占用多少内存空间需要在编译时确定下来,缺乏灵活性。

队列和栈是什么?有什么区别?

队列是一种顺序表,先进先出。

栈作为一种数据结构,只能在一段进行删除或插入操作,所以是先进后出。

说一下类加载的执行过程?

类文件加载的顺序

1、

执行先后顺序按排列的先后顺序)

2、再加载执行本类的静态变量及静态初始化块

只要类没有被销毁,静态变量及静态初始化块只会执行1次,后续再对该类进行其他操作也不会再执行这两个步骤。

类实例创建过程

只有在调用new方法时才会创建类的实例

1、按照上面类文件加载的顺序(类已被加载则跳过此步)

2、父类的非静态变量及非静态初始化块

3、父类的构造方法

4、本类的非静态变量及非静态初始化块

5、本类的构造方法

4、类实例销毁时候,首先销毁子类部分,再销毁父类部分

静态方法和非静态方法都是被动调用

即系统不会自动调用执行。所以用户没有调用时都不执行,主要区别在于静态方法可以直接用类名直接调用(实例化对象也可以),而非静态方法只能先实例化对象后才能调用。

怎么判断对象是否可以被回收?

引用计数法

给对象添加一个引用计数器,每当一个地方引用它object时计数加1,引用失去以后就减1,计数为0说明不再引用。

优点

实现简单,判定效率高;缺点:无法解决对象相互循环引用的问题,对象A中引用了对象B,对象B中引用对象A。

可达性分析算法

当一个对象到GC Roots没有引用链相连,即就是GC Roots到这个对象不可达时,证明对象不可用。

java 中都有哪些引用类型?

强引用:

User user=new User()

我们开发中使用最多的对象引用方式。

特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。

通过关键字new创建的对象所关联的引用就是强引用。

当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。

软引用:

SoftReference object=new SoftReference(new Object());

特点:软引用通过SoftReference类实现。软引用的生命周期比强引用短一些。

只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。

应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

弱引用:

WeakReference object=new WeakReference (new Object();

ThreadLocal中有使用。

弱引用通过WeakReference类实现。弱引用的生命周期比软引用短。

在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。应用场景:弱应用同样可用于内存敏感的缓存。

虚引用:

几乎没见过使用, ReferenceQueue 、PhantomReference。

说一下 jvm 有哪些垃圾回收器?

新生代收集器:

  • Serial
  • ParNew
  • Parallel Scavenge

老年代收集器:

  • Serial Old
  • CMS
  • Parallel Old

堆内存垃圾收集器:

G1

详细介绍一下 CMS 垃圾回收器?

CMS全称为:Concurrent Mark Sweep意为并发标记清除,他使用的是标记清除法。主要关注系统停顿时间。

使用-XX:+UseConcMarkSweepGC进行设置老年代使用该回收器。

使用-XX:ConcGCThreads设置并发线程数量。

特点

CMS并不是独占的回收器,也就说CMS回收的过程中,应用程序仍然在不停的工作,又会有新的垃圾不断的产生,所以在使用CMS的过程中应该确保应用程序的内存足够可用。

CMS不会等到应用程序饱和的时候才去回收垃圾,而是在某一阀值的时候开始回收,回收阀值可用指定的参数进行配置:-XX:CMSInitiatingoccupancyFraction来指定,默认为68,也就是说当老年代的空间使用率达到68%的时候,会执行CMS回收。

如果内存使用率增长的很快,在CMS执行的过程中,已经出现了内存不足的情况,此时CMS回收就会失败,虚拟机将启动老年代串行回收器;SerialOldGC进行垃圾回收,这会导致应用程序中断,直到垃圾回收完成后才会正常工作。

这个过程GC的停顿时间可能较长,所以-XX:CMSInitiatingoccupancyFraction的设置要根据实际的情况。

标记清除法有个缺点就是存在内存碎片的问题,那么CMS有个参数设置-XX:+UseCMSCompactAtFullCollecion可以使CMS回收完成之后进行一次碎片整理。

-XX:CMSFullGCsBeforeCompaction参数可以设置进行多少次CMS回收之后,对内存进行一次压缩。

新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

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

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

整堆回收器:G1

新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

简述分代垃圾回收器是怎么工作的?

        分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

        新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

        把 Eden + From Survivor 存活的对象放入 To Survivor 区;

清空 Eden 和 From Survivor 分区;

From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

        每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。

        老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程

说一下 jvm 调优的工具?

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。

jconsole用于对 JVM 中的内存、线程和类等进行监控;

jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。 208.

常用的 jvm 调优的参数都有哪些?

  • -Xms256m初始化堆大小为 256m;
  • -Xmx2g堆最大内存为 2g;
  • -Xmn50m新生代的大小50m;
  • -XX:+PrintGCDetails打印 gc 详细信息
  • -XX:+HeapDumpOnOutOfMemoryError在发生OutOfMemoryError错误时,来dump堆快照
  • -XX:NewRatio=4设置年轻的和老年代的内存比例为 1:4;
  • -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
  • //参数上写的,都是新生代的垃圾回收器
  • -XX:+UseSerialGC新生代和老年代都用串行收集器 Serial + Serial Old
  • -XX:+UseParNewGC指定使用 ParNew + Serial Old 垃圾回收器组合;-
  • XX:+UseParallelGC:新生代使用Parallel Scavenge,老年代使用Serial Old//参数上写的,都是老年代的垃圾回收器
  • -XX:+UseParallelOldGC:新生代ParallelScavenge + 老年代ParallelOld组合;-
  • X:+UseConcMarkSweepGC:新生代使用ParNew,老年代的用CMS;
  • -XX:NewSize:新生代最小值;
  • -XX:MaxNewSize:新生代最大值
  • -XX:MetaspaceSize:元空间初始化大小
  • -XX:MaxMetaspaceSize:元空间最大值

十六、Linux(常用命令)

Linux命令大全(手册)
pandas中的groupby函数用于按照指定的列对数据集进行分组,并对每个分组进行特定的操作。groupby函数的基本语法为: ```python df.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, **kwargs) ``` 其中,by参数用于指定分组依据的列名或列名列表;axis参数用于指定分组的轴向,默认为0,表示按照行进行分组;level参数用于多层索引时指定分组的级别;as_index参数用于指定分组后的结果是否以分组列作为索引;sort参数用于指定分组后的结果是否按照分组列排序;group_keys参数用于指定是否在分组后的结果中保留分组键;squeeze参数用于指定是否在分组后的结果中压缩单一分组的维度。 groupby函数返回的是一个GroupBy对象,可以对该对象进行多种操作,例如聚合、过滤、变换等。常用的聚合函数包括sum、mean、count、max、min等。groupby函数的原理是先将数据按照行或列进行分组,然后对每个分组进行特定的操作,并将结果合并成一个新的数据集。在实现上,groupby函数使用了split-apply-combine的策略,即先将数据集拆分成多个小组,然后对每个小组进行操作,最后将结果合并成一个新的数据集。 需要注意的是,groupby函数并不会改变原始的数据集,而是返回一个新的数据集,因此在使用groupby函数时需要注意将结果保存到变量中。另外,groupby函数对于大型数据集的性能较低,因此在处理大型数据集时需要谨慎使用。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值