Java 面试题大全
这个面试题,是通过网络上收集的,但是下面的参考答案也是我通过网上收集与结合我自己的理解填上的,如若有错,欢迎指正,水平有限请见谅。
第一阶段
一、基础篇
1.1、Java基础
1、封装
封装就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员。隐藏了类的实现,类的使用者只需知道公共的接口,就可以使用该类;封装帮助防止意外的改变和误用;对程序调试有很大的帮助,因为改变类的成员变量只用通过公共接口。
好处:
- 将变化隔离。
- 便于使用。
- 提高重用性。
- 提高安全性。
** 2、继承**
继承是指可以使用现有类的所有功能,可以使一个对象直接使用另一个对象的属性和方法。通过继承创建的新类称为“子类”或者“派生类”,被继承的类称为“基类”或者“父类”。
好处
- 提高了代码的复用性。
- 让类与类之间产生了关系,提供了多态的前提。
特点
- Java只支持单继承,不支持多继承。
因为如果可以多继承,就会出现调用不明确的问题。- Java支持多重继承(继承体系)
3、多态
在同一个方法中,由于参数不同而导致执行效果各异的现象就是多态。
前提条件:
- A:要有继承关系。
- B:要有方法重写。
- C:要有父类引用指向子类对象。
优点:
- 提高代码的扩展性和可维护性。
弊端:
- 父类引用不能使用子类特有功能。
** 1、final**
final关键字可以用来修饰类,方法以及成员变量,当用在不同的场景下时具有不同的意义。
修饰类
如果修饰类,则代表这个类不可继承
修饰方法
如果修饰方法,则代表这个方法不可覆写;同时,允许编译器将所有对这个方法的调用转化为inline调用,也就是说,把所有的调用处的方法名全部换为方法主体,这也会使得代码主体变得异常庞大,非常影响性能。
修饰变量
如果修饰基本类型,则代表该变量的值不可改变。
如果修饰引用类型,则代表该对象的引用不可改变。
2、finally
finally是用于异常处理时使用的语句,由finally关键词修饰的代码主体,无论异常是否发生,该代码块总会执行。
注意:哪怕try/catch中存在return,finally修饰的代码块依然会执行。
具体情况可以如下:
- finally中包含return语句,则无论之前try语句中是否包含retrun,都不再执行,只执行finally中的return。
- finally中不包含return语句,也没有改变try中的返回值,则finally中的语句执行完后会继续执行try中的return。
- finally中不包含return语句,但是改变了try中的返回值,则finally中的语句执行完后会继续执行try中的return,并通过return继续返回值(此处类似函数调用,如果finally改变的返回值是基本类型,则改变不起作用;如果是引用类型,则finally中对该引用类型的属性值的改变起作用)。
3、finalize
finalize是一个方法名,当需要从堆中永久删除某个对象之前,垃圾回收器会主动调用该对象的finalize()方法。
需要注意的是:
程序猿无法确定垃圾回收器何时调用该方法(哪怕你明着写出来,依然无法确定,所以一般程序猿不需要调用该方法)
无法保证调用不同对象的方法的顺序。换句话说如果对象A里面引用了对象B,则有可能先调用A的finalize()方法,也有可能先调用B的finalize()方法。
Throwable是Java错误处理的父类,有两个子类:Error和Exception。
Error:无法预期的严重错误,导致JVM虚拟机无法继续执行,几乎无法恢复捕捉的
Exception:可恢复捕捉的。java健壮程序的手段。
Java提供了两类主要的异常:runtime exception和checked exception (编译时被检查的异常)。
- checked exception (编译时被检查的异常):JAVA编译器强制要求我们必需对出现的这些异常进行catch或throws。所以,面对这种异常不管我们是否愿意,只能写一大堆catch块或throws去处理可能的异常。如IO异常,以及SQL异常等。
- runtime exception:编译通过,但运行通不过,出现RuntimeException,通常是程序员出错。虚拟机接管会终止线程或主程序。如错误的类型转换、数组越界访问和访问空指针等
最常见到的runtime exception
- NullPointerException
int a1[]=null; |
ArrayIndexOutOfBoundsException
int a[]={2,3,5,32,6};
for (int i = 0; i <6; i++){
System.out.print(a[i]);
}ClassCastException
Object i=new Integer(1);
System.out.println((String)i);ArithmeticException
int a=5/0;
NegativeArraySizeException
String[] s=new String[-10];
int与Integer的基本使用对比
- Integer是int的包装类;int是基本数据类型;
- Integer变量必须实例化后才能使用;int变量不需要;
- Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
- Integer的默认值是null;int的默认值是0。
int与Integer的深入对比
- 由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。
- Integer变量和int变量比较时,只要两个变量的值是相等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)
- 非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)
- 对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
原因: java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100)。而java API中对Integer类型的valueOf的定义如下,对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了。
归结于java对于Integer与int的自动装箱与拆箱的设计,是一种模式:叫享元模式(flyweight)。
加大对简单数字的重利用,Java定义在自动装箱时对于值从–128到127之间的值,它们被装箱为Integer对象后,会存在内存中被重用,始终只存在一个对象。
而如果超过了从–128到127之间的值,被装箱后的Integer对象并不会被重用,即相当于每次装箱时都新建一个 Integer对象。
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
- 三者在执行速度方面的比较:StringBuilder > StringBuffer > String
Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。- 如果操作少量的数据用String
- 单线程下操作大量的数据用StringBuilder
- 多线程下操作大量的数据用StringBuffer
override(重写)
- 方法名、参数、返回值相同。
- 子类方法不能缩小父类方法的访问权限。
- 子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
- 存在于父类和子类之间。
- 方法被定义为final不能被重写。
overload(重载)
- 参数类型、个数、顺序至少有一个不相同。
- 不能重载只有返回值不同的方法名。
- 存在于父类和子类、同类中。
接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象
抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么
- 接口是抽象类的变体,接口中所有的方法都是抽象的。而抽象类是声明方法的存在而不去实现它的类。
- 接口可以多继承,抽象类不行
- 接口定义方法,不能实现,而抽象类可以实现部分方法。
- 接口中基本数据类型为static 而抽类象不是的
反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改其本身状态或行为的一种能力
反射机制允许程序在执行时获取某个类自身的定义信息,例如属性和方法等也可以实现动态创建类的对象、变更属性的内容或执行特定的方法的功能。从而使Java具有动态语言的特性,增强了程序的灵活性和可移植性。
反射的主要用途
- 反射最重要的用途就是开发各种通用框架
比如Spring 框架,为了保证框架的通用性,他们可能根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。- 当我们在使用 IDE(如 Eclipse\IDEA)时,当我们输入一个队长或者类并向调用它的属性和方法时,一按 (“.”)点号,编译器就会自动列出她的属性或方法,这里就会用到反射。
反射机制的作用
Java反射机制主要用于实现以下功能。
- 在运行时判断任意一个对象所属的类型。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法,甚至可以调用private方法。
反射的核心:是 JVM 在运行时 才动态加载的类或调用方法或属性,他不需要事先(写代码的时候或编译期)知道运行对象是谁
Java反射机制API
实现Java反射机制的API在Java.lang.reflect包下,具有以下几点。
- Class类:代表一个类。
- Filed类:代表类的成员变量。
- Method类:代表类的方法。
- Constructor类:代表类的构造方法。
- Array类:提供了动态创建数组及访问数组元素的静态方法。该类中的所有方法都是静态的。
反射的实现
1、获得Class对象
有3种方法:
- 使用Class类的forName静态方法:
如JDBC开发中常用此方法加载数据库驱动:Class.forName(driver);- 直接获取某一个对象的class,比如:
Class> klass = int.class; Class> classInt = Integer.TYPE;- 调用某个对象的getClass()方法,比如:
StringBuilder str = new StringBuilder(“abc”);
Class<?> klass = str.getClass();
2、判断是否为某个类的实例
一般地,我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断是否为某个类的实例,它是一个Native方法
(被native关键字修饰的方法叫做本地方法,另外native方法在JVM中运行时数据区也和其它方法不一样,它有专门的本地方法栈。native方法主要用于加载文件和动态链接库,由于Java语言无法访问操作系统底层信息。比如:底层硬件设备等,这时候就需要借助C语言来完成了。被native修饰的方法可以被C语言重写):
public native boolean isInstance(Object obj); |
3、创建实例
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class; |
- 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
4、获取方法
获取某个Class对象的方法集合,主要有以下几个方法:
- getDeclaredMethods()方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
public Method[] getDeclaredMethods() throws SecurityException |
getMethods()方法返回某个类的所有公用(public)方法,包括其继承类的公用方法
public Method[] getMethods() throws SecurityException
getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象
public Method getMethod(String name, Class<?>... parameterTypes)
5、获取构造器信息
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
6、获取类的成员变量(字段)信息
主要是这几个方法,在此不再赘述:
getFiled: 访问公有的成员变量
getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量
getFileds和getDeclaredFields用法同上(参照Method)
7、调用方法
当我们从类中获取了一个方法后,我们就可以用invoke()方法来调用这个方法。栗子
public class test1 { |
详细可参考:这里
在一个应用系统中,无论使用何种语言开发,必然存在模块之间的调用,调用的方式分为几种:
- 同步调用
同步调用是最基本并且最简单的一种调用方式,类A的方法a()调用类B的方法b(),一直等待b()方法执行完毕,a()方法继续往下走。这种调用方式适用于方法b()执行时间不长的情况,因为b()方法执行时间一长或者直接阻塞的话,a()方法的余下代码是无法执行下去的,这样会造成整个流程的阻塞。- 异步调用
异步调用是为了解决同步调用可能出现阻塞,导致整个流程卡住而产生的一种调用方式。类A的方法方法a()通过新起线程的方式调用类B的方法b(),代码接着直接往下执行,这样无论方法b()执行时间多久,都不会阻塞住方法a()的执行。但是这种方式,由于方法a()不等待方法b()的执行完成,在方法a()需要方法b()执行结果的情况下(视具体业务而定,有些业务比如启异步线程发个微信通知、刷新一个缓存这种就没必要),必须通过一定的方式对方法b()的执行结果进行监听。在Java中,可以使用Future+Callable的方式做到这一点
模板方法模式:
模板方法模式是一种基于继承的设计模式
概念:定义一个算法中的操作框架,而将一些步骤延迟到子类中。使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。说的通俗一点,就是为子类设计一个模板以便于子类复用里面的方法。为了避免子类恶意修改方法的实现细节,一般模板方法模式都会在方法上加final。
模板方法模式的优点
- 封装不变部分,扩展可变部分。把认为不变部分的算法封装到父类中实现,而可变部分的则可以通过继承来继续扩展。
- 提取公共部分代码,便于维护。
- 行为由父类控制,子类实现。
模板方法模式的缺点
按照设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类负责完成具体的事务属性和方法,但是模板方式正好相反,子类执行的结果影响了父类的结果,会增加代码阅读的难度。
模板方法模式的使用场景
- 多个子类有共有的方法,并且逻辑基本相同
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现
- 重构时,模板方法是一个经常使用的方法,把相同的代码抽取到父类中,然后通过构造函数约束其行为。
这个开放性题目,随便说
发布订阅模式从广义上讲是一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式。
观察者模式在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们
观察者模式简单的可以理解为:一个狗仔队为了获取到某超火人气明星的一首资料,得时时刻刻盯着,一旦有点风吹草动都知道,恨不得所有事情都围绕这个事去做。
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
在发布-订阅模式,消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者(订阅者),意思就是发布者和订阅者不知道对方的存在。需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息。
换句话说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在
观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
KMP算法是一种改进后的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特-莫里斯-普拉特操作(简称KMP算法)
具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息
看了一篇文章,写得不错:https://www.cnblogs.com/xiaoyulong/p/8783029.html
JMM:Java Memory Model(Java内存模型),围绕着在并发过程中如何处理可见性、原子性、有序性这三个特性而建立的模型。
- 可见性:JMM提供了volatile变量定义、final、synchronized块来保证可见性。
例如:线程a在将共享变量x=1写入主内存的时候,如何保证线程b读取共享变量x的值为1,这就是JMM做的事情。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。- 原子性:JMM提供保证了访问基本数据类型的原子性(其实在写一个工作内存变量到主内存是分主要两步:store、write),但是实际业务处理场景往往是需要更大的范围的原子性保证,所以模型也提供了synchronized块来保证
- 有序性:这个概念是相对而言的,如果在本线程内,所有的操作都是有序的,如果在一个线程观察另一个线程,所有的操作都是无序的,前句是“线程内表现为串行行为”,后句是“指令的重排序”和“工作内存和主内存同步延迟”现象,模型提供了volatile和synchronized来保证线程之间操作的有序性。
内存屏障(Memory barrier) 简介
程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。内存乱序访问主要发生在两个阶段:
编译时,编译器优化导致内存乱序访问(指令重排)
运行时,多 CPU 间交互引起内存乱序访问
重排序:在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序(编译器、处理器),就是因为这些重排序,所以可能会导致多线程程序出现内存可见性问题(数据安全问题)和有序性问题。
JMM是如何处理的呢?
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序
对于处理器重排序,JMM的处理器重排序规则会要求java编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers,intel称之为memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序
总之一句话,JMM是通过禁止特定类型的编译器重排序和处理器重排序来为程序员提供一致的内存可见性保证。
Java自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志注解,就进行日志记录。
有篇文章不错:http://www.cnblogs.com/digdeep/p/4525567.html
这属于一个开放性的题目,可以扯一些和其相关的东西即可。
Session是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。
具体详情可参考:http://www.cnblogs.com/linguoguo/p/5106618.html
比如:
- java.lang –语言包
这是Java语言的核心包,系统自动将这个包引入到用户程序,该包中主要类有:Object、数据类型包装类、数学类Math、字符串类String和StringBuffer类、系统和运行时类、线程类…- java.util –实用工具包
提供了各种实用功能的类,主要包括日期类、数据结构类和随机数类等。 比如:Date、Calendar、LinkedList、List、Random …- java.io –输入输出包
提供了系统输入输出类和接口,只要包括输入流类InputStream和输出流OutputStream就可以实现文件的输入输出、管道的数据传输以及网络数据传输的功能 .- java.net –网络函数包
提供了实现网络应用程序的类,主要包括用于实现Socket通信的Socket类,此外还提供了便于处理URL的类- java.sql –数据库API包
提供了与数据库连接的很多接口类:DriverManager 类(建立与驱动程序的连接)、Driver 接口(提供用来注册和连接基于 JDBC 技术的驱动程序的 API)、SQLException(SQL 异常类)….
(开放性答案,按自己的理解来回答)MVC英文即Model-View-Controller,即把一个应用的输入、处理、输出流程按照Model、View、Controller的方式进行分离,这样一个应用被分成三个层——模型层、视图层、控制层。
MVC应用程序总是由三个部分组成.Event(事件)导致Controller改变Model或View,或者同时改变两者.只要Controller改变了Models的数据或者属性,所有依赖的View都会自动更新.类似的,只要Controller改变了View,View会从潜在的Model中获取数据来刷新自己
==:比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
equals:用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。
hashCode()方法和equal()方法的作用都是用来对比两个对象是否相等一致,相同的功能为什么需要两个,请听我一一道来
因为重写的equal()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?
因为hashCode()并不是完全可靠
1、equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
2、hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
所有对象都出现hash冲突,而hashCode()本身的性能也会降级。
为啥呢?你问我,我也不知道啊,你去看看哈希算法呗,但是呢,可以打个比方:一个10000页的书,目录中的页码全是第一页。你找东西肯定很麻烦
其实这个方法的目的,主要就是将对象按字符串的方式输出出来,反过来的话,假如没有toString(0方法,那我们所得到的一个对象,是完全不知道里面拥有什么样的值
1、当我们只需要对象的内容相等时,就表示它相同,那只需要重写equals 方法即可。
2、重写equals方法时需要重写hashCode方法,主要是针对Map、Set等集合类型的使用;
集合类判断两个对象是否相等,是先判断equals是否相等,如果equals返回TRUE,还要再判断HashCode返回值是否ture,只有两者都返回ture,才认为该两个对象是相等的。
把JAVA对象转换为字节序列的过程就称为对象的序列化,将字节序列恢复成Java对象的过程称为对象的反序列化
只有实现了 serializable和Externalizable接口的类的对象才能被序列化 后者是前者的子类 实现这个接口的类完全由自身来控制序列化的行为,而仅仅实现前者的类可以采用默认的序列化方式。实现这两个接口 标志着对象可以被序列化了。。。
简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。
专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。
扩展:在jdk1.5以后,将同步synchronized替换成了Lock,将同步锁对象换成了Condition对象,并且Condition对象可以有多个
Java从四个方面支持了平台无关性
最主要的是Java平台本身。
- Java平台扮演Java程序和所在的硬件与操作系统之间的缓冲角色。这样Java程序只需要与Java平台打交道,而不用管具体的操作系统。
- Java语言保证了基本数据类型的值域和行为都是由语言自己定义的。而C/C++中,基本数据类是由它的占位宽度决定的,占位宽度由所在平台决定的。不同平台编译同一个C++程序会出现不同的行为。通过保证基本数据类型在所有平台的一致性,Java语言为平台无关性提供强有力的支持。
- Java class文件。Java程序最终会被编译成二进制class文件。class文件可以在任何平台创建,也可以被任何平台的Java虚拟机装载运行。它的格式有着严格的定义,是平台无关的。
- 可伸缩性。Sun通过改变API的方式得到三个基础API集合,表现为Java平台不同的伸缩性:J2EE,J2SE,J2ME。
简单的说,jre 是运行环境,jdk 是开发环境
- 1、Lambda表达式和函数式接口
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) ); |
如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> {
System.out.print( e );
System.out.print( e );
} );
- 2、接口的默认方法和静态方法
默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:
private interface Defaulable { |
Defaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。
Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:
private interface DefaulableFactory { |
下面的代码片段整合了默认方法和静态方法的使用场景:
public static void main( String[] args ) { |
- 3、方法引用
- 4、重复注解
- 5、更好的类型推断
- 6、拓宽注解
可参考:https://blog.csdn.net/u014470581/article/details/54944384
使代码变得更加紧凑,可读性增强,并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码;
用到什么说什么,Lambda表达式 什么的
快速排序
引用计数法(ReferenceCounting):每当有一个地方引用他时,计数器值就+1,;当引用失效时,计数器值就-1;任何时刻计数器为0的对象就是不可能在被使用,不会完全准确,因为如果出现两个对象相互引用的问题就不行了。
可达性分析算法(Reachability Analysis):通过一系列的GC Roots的对象作为起始点,从这些根节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
可参考:http://baijiahao.baidu.com/s?id=1583441733083989684&wfr=spider&for=pc
浅拷贝 只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,
深拷贝 不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
可参考:https://blog.csdn.net/wangxueming/article/details/52034841
String s=”abc”;这里不会在堆中创建对象,首先在常量池寻找这个常量“abc”,如果没有“abc”则把abc存放到运行时常量池,然后把引用赋值给是s,如果有就直接把存在的地址赋值给s。
String s=new String(“abc”);首先在堆中创建对象,然后再把对象引用赋值给s。
1.2、Java常见集合
ArrayList 初始化大小是 10 ,JDK 1.7之后,扩容机制:当前容量的1.5倍赋值给新的容量
linkedList 是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。
List:
- 1.可以允许重复的对象。
- 2.可以插入多个null元素。
- 3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
- 4.常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
Set:
- 1.不允许重复对象
- 2.无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
- 3.只允许一个 null 元素
- 4.Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。
因为Set 不允许添加重复的元素,而保证元素对象是否相同,必须重写这两个方法。
Set 接口为我们提供了一个 add() 方法,add()方法里实际执行的是map的put方法,因为map中的key是不允许重复的,所以set中的元素不能重复.
List 和 Map 区别
1.Map不是collection的子接口或者实现类。Map是一个接口。
2.Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
3.TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
4.Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
5.Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)Arraylist 与 LinkedList 区别
ArrayList、LinKedList都不是线程安全,Vector是线程安全
Arraylist 底层数据结构是数组,查询快,增删慢
LinkedList 底层数据结构是链表,查询慢,增删快
ArrayList 是线性表(数组)
get() 直接读取第几个下标,复杂度 O(1)
add(E) 添加元素,直接在后面添加,复杂度O(1)
add(index, E) 添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度O(n)
remove()删除元素,后面的元素需要逐个移动,复杂度O(n)
LinkedList 是链表的操作
get() 获取第几个元素,依次遍历,复杂度O(n)
add(E) 添加到末尾,复杂度O(1)
add(index, E) 添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n)
remove()删除元素,直接指针指向操作,复杂度O(1)
Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。
Hashtable和HashMap它们的性能方面的比较类似 Vector和ArrayList,比如Hashtable的方法是同步的,而HashMap的不是。
HashMap 实现了Map接口,存储键值对,调用put 向map中添加元素,HashMap使用键(Key)计算Hashcode
HashSet 实现Set接口,仅存储对象,调用add()方法向Set中添加元素,HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性
一个线程不安全的,一个线程安全的
工作原理及实现可参考:https://www.cnblogs.com/qlqwjy/p/8472325.html,
什么时候用到 红黑树:在hash值相同的情况下(且重复数量大于8),用红黑树来管理数据。 红黑树相当于排序数据。可以自动的使用二分法进行定位。性能较高。
不懂没研究源码
主要是多线程同时put时,如果同时触发了rehash操作,会导致HashMap中的链表中出现循环节点,进而使得后面get的时候,会死循环。
可以参考这篇文章:https://blog.csdn.net/xuefeng0707/article/details/40797085
ConcurrentHashMap采用了非常精妙的”分段锁”策略,ConcurrentHashMap的主干是个Segment数组。如果我们要统计整个ConcurrentHashMap里元素的大小,就必须统计所有Segment里元素的大小后求和
可以参考:http://ifeve.com/concurrenthashmap/
你行,你上
这就看个人学习能力了
CopyOnWriteArrayList这是一个ArrayList的线程安全的变体,其原理大概可以通俗的理解为:初始化的时候只有一个容器,很常一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程),都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。
可参考:https://blog.csdn.net/hua631150873/article/details/51306021
1.3、进程和线程
并发指一个CPU可以异步的处理多个进程
并行则是一个CPU同时处理多个进程
进程是一个程序的实例。每个进程都有自己的虚拟地址空间和控制线程,
线程是操作系统调度器(Schduler)分配处理器时间的基础单元。
//五个参数的构造函数 |
常见四种线程池:
- CachedThreadPool可缓存线程池
- FixedThreadPool 定长线程池
- SingleThreadPool 单个工作线程
- ScheduledThreadPool 有延迟执行和周期执行任务的线程池
可参考:https://www.jianshu.com/p/ae67972d1156
1 无名管道通信
2 高级管道通信
3 有名管道通信
4 消息队列通信
5 信号量通信
6 信号
7 共享内存通信
8 套接字通信
可参考:https://blog.csdn.net/violet_echo_0908/article/details/51201278
CountDownLatch | CyclicBarrier |
---|---|
减计数方式 | 加计数方式 |
计算为0时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
计数为0时,无法重置 | 计数达到指定值时,计数置为0重新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可重复利用 |
具体调用的还是AbstractQueuedSynchronizer这个类的逻辑,
可参考 :https://blog.csdn.net/buyaoxx/article/details/77935730
Exchanger,它允许在并发任务之间交换数据。具体来说,Exchanger类允许在两个线程之间定义同步点。当两个线程都到达同步点时,他们交换数据结构,因此第一个线程的数据结构进入到第二个线程中,第二个线程的数据结构进入到第一个线程中。
可参考:https://blog.csdn.net/chenssy/article/details/72550933
ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
可参考:https://blog.csdn.net/bntx2jsqfehy7/article/details/78315161
http://www.importnew.com/22039.html
可参考:https://blog.csdn.net/gol_phing/article/details/49032055
常见四种线程池:
- CachedThreadPool可缓存线程池
- FixedThreadPool 定长线程池
- SingleThreadPool 单个工作线程
- ScheduledThreadPool 有延迟执行和周期执行任务的线程池
(1)新建状态——(2)就绪状态—((4)阻塞状态)—(3)运行状态——(5)死亡状态
AtomicInteger的核心就是一个CAS算法(CompareAndSwap)的乐观锁实现,比较并交换算法,此算法是由unsafe的底层代码实现,它是一个原子的操作,原理就是:如果内存中的实际值与update值相同,则将实际值更新为expect值,反之则返回失败,由上层系统循环获取实际值后,再次调用此CAS算法:
可参考:https://www.cnblogs.com/mantu/p/5796450.html 、https://blog.csdn.net/qfycc92/article/details/46489553
按照申请锁的顺序来一次获得锁称为公平锁,synchronized的是非公平锁,ReentrantLock分为“公平锁”和“非公平锁”。它们的区别体现在获取锁的机制上是否公平。可以通过构造函数实现公平锁。new RenentrantLock(boolean fair)
Java语言CAS底层如何实现?利用unsafe提供了原子性操作方法。
会发生ABA问题?怎么解决?
当一个值从A更新成B,又更新会A,普通CAS机制会误判通过检测。
解决方法:利用版本号比较可以有效解决ABA问题。
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet
ArrayBlockingQueue
Atomic ===>AtomicInteger 、AtomicBoolean
线程状态以及API怎么操作会发生这种转换;
t.sleep():继续持锁,不会释放.线程状态有运行转换为阻塞,当时间到达后有阻塞转换为可运行状态,执行该操作不会考虑优先级的问题.可以让优先级低的线程执行,当然这完全取决于调度机制.
t.join():不涉及锁.仅仅是将其他的线程加入到当前线程.在其他线程没有完成之前当前线程处于阻塞状态.不建议使用.原因会阻塞当前线程.建议使用callback.
t.wait():释放锁.一般该方法配合notify或者notifyAll使用可以达到线程协助的目的.释放之后进入等待该锁的队列中
t.notify()/t.notifyAll():不持有锁.通过t.wait()释放锁后,其他等待该锁的一个或多个线程是否运行,完全取决于谁先获得锁.当然在采用notify的时候只能唤醒一个等待该锁的线程.只要获得相关资源并且被调度就可以执行
避免一个线程同时获取多个锁
避免一个线程同时占用多个资源,尽量保证每个锁只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
由于状态只有就绪、阻塞、执行,状态是无法由执行转化为执行的,所以会报不合法的状态!)
wait是final类型的,不可以被重写,不仅如此,notify和notifyall都是final类型的),wait能不能被中断;
可以用线程类的join()方法在一个线程中启动另一个线程,另一个线程完成
public static void main(String[] args) { |
进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。)
1.4、锁机制
线程安全:就是多线程访问同一代码,不会产生不确定结果。(比如死锁)
如何保证呢:
1、使用线程安全的类
2、使用synchronized同步代码块,或者用Lock锁
3、多线程并发情况下,线程共享的变量改为方法局部级变量
所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的 synchronized 和 ReentrantLock 都是可重入锁
通过为每个锁关联一个请求计数和一个占有它的线程。当计数为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,jvm讲记录锁的占有者,并且讲请求计数器置为1 。
如果同一个线程再次请求这个锁,计数将递增;每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。
可参考:https://blog.csdn.net/joker_apple/article/details/52790181
产生死锁的四个条件(互斥、请求与保持、不剥夺、循环等待)
(1)互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
(2)请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
(3)不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
(4)环路等待条件:是指进程发生死锁后,必然存在一个进程–资源之间的环形链如何检查死锁(通过jConsole检查死锁)
可以使用 jstack或者pstack 和 gdb 工具对死锁程序进行分析。
pstack: 功能是打印输出此进程的堆栈信息。可以输出所有线程的调用关系栈
jstack: jstack是java虚拟机自带的一种堆栈跟踪工具,所以仅适用于java程序,功能跟pstack一样,但是更强大,可以提示哪个地方可能死锁了。
pstack和jstack判断死锁,都需要多执行几次命令,观察每次的输出结果,才能推测是否死锁了。
gdb:
1 运行程序,设置能影响程序运行的参数和环境 ;
2 控制程序在指定的条件下停止运行;
3 当程序停止时,可以检查程序的状态;
4 当程序 crash 时,可以检查 core 文件;
5 可以修改程序的错误,并重新运行程序;
6 可以动态监视程序中变量的值;
7 可以单步执行代码,观察程序的运行状态。
禁止指令重排、刷新内存
通过对象监视器保存同步,可参考:https://www.cnblogs.com/dpains/p/7205093.html?utm_source=itdadao&utm_medium=referral
两者区别:
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
参考:https://www.cnblogs.com/iyyy/p/7993788.html
谈到并发,不得不谈ReentrantLock;而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)!类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch…
可参考:https://www.cnblogs.com/waterystone/p/4920797.html
无锁编程只保证单个读(本身就是原子的)和写操作的原子性,并不保证组合操作的原子性
独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
从java1.5开始,jdk提供了java.util.concurrent.atomic包,这个包中的原子操作类,提供了一种用法简单,性能高效,线程安全的更新一个变量的方式。
atomic包里面一共提供了13个类,分为4种类型,分别是:原子更新基本类型,原子更新数组,原子更新引用,原子更新属性,这13个类都是使用Unsafe实现的包装类。
- 原子更新基本类型
AtomicBoolean:原子更新布尔类型
AtomicInteger : 原子更新整型
AtomicLong:原子更新整型- 原子更新数组
AtomicIntegerArray:原子更新整型数组里的元素
AtomicLongArray:原子更新长整型数组里的元素
AtomicReferenceArray:原子更新引用类型数组里的元素- 原子更新引用类型
AtomicReference:原子更新引用类型
AtomicReferenceFieldUpdater:原子更新引用类型里的字段
AtomicMarkableReference:原子更新带有标记为的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef,boolean initialMark)- 原子更新属性类
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
AtomicLongFieldUpdater:原子更新长整型的字段的更新器
AtomicStampedReference:原子更新带有版本号的引用类型。该类型将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题
可参考:https://blog.csdn.net/fjse51/article/details/56842777
ABA 问题:当第一次读取V的A值, 此时, 内存V的值变为B值, 然后在未执行CAS前, 又变回了A值.
JDK的并发包中的AtomicStampedReference和 AtomicMarkableReference来解决。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet
ArrayBlockingQueue
Atomic ===>AtomicInteger 、AtomicBoolean
可参考:https://www.cnblogs.com/longshiyVip/p/5211298.html
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
重量级锁Synchronized,作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。
Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。
轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;
可参考:https://blog.csdn.net/zqz_zqz/article/details/70233767
写出一个必然会产生死锁的伪代码;
public class DeadLock {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args){
Thread a = new Thread(new Lock1());
Thread b = new Thread(new Lock2());
a.start();
b.start();
}
}
class Lock1 implements Runnable{
public void run(){
try{
System.out.println("Lock1 running");
while(true){
synchronized(DeadLock.obj1){
System.out.println("Lock1 lock obj1");
Thread.sleep(3000);//获取obj1后先等一会儿,让Lock2有足够的时间锁住obj2
synchronized(DeadLock.obj2){
System.out.println("Lock1 lock obj2");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
class Lock2 implements Runnable{
public void run(){
try{
System.out.println("Lock2 running");
while(true){
synchronized(DeadLock.obj2){
System.out.println("Lock2 lock obj2");
Thread.sleep(3000);
synchronized(DeadLock.obj1){
System.out.println("Lock2 lock obj1");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
1.5、JVM
设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
可参考:http://blog.51cto.com/zhouanya/1370017
首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。因此,在Java中我们常常说到的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)。
运行时数据区通常包括这几个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。
内存溢出是由于没被引用的对象(垃圾)过多造成JVM没有及时回收,造成的内存溢出。如果出现这种现象可行代码排查:
- 是否应用中的类中和引用变量过多使用了Static修饰
- 是否 应用 中使用了大量的递归或无限递归
- 是否使用了大量循环或死循环
- 检查 应用 中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”
- 检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中。
- 检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行”+”就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。
栈溢出的原因
一)、是否有递归调用
二)、是否有大量循环或死循环
三)、全局变量是否过多
四)、数组、List、map数据是否过大
五)、使用DDMS工具进行查找大概出现栈溢出的位置
- 引用计数法
引用计数法是一种古老的垃圾收集方法,引用计数器实现很简单,对于一个对象A,有任何一个对象引用了A,那么A的计数器+1,引用失效时,A的计数器-1。当A的引用计数器是0的时候。那么A对象就不能被使用了。 - 标记清除法
标记清除算法将垃圾的回收两阶段进行:标记阶段和清除阶段。
在标记阶段,顾名思义,就是从根节点开始标记可到达的对象。没有被标记的对象也就是没有被引用的对象那个就是垃圾对象。在第二个阶段也就是清除阶段,清除所有没有被标记的对象。标记清除算法。可能会产生空间的碎片,这也是这个算法的最大的问题。
- 复制算法
复制算法的原理:将分配的内存空间分为2块,每次只是用1块,在垃圾回收呢时,讲正在使用的内存中的存活对象复制到没有被使用的内存的一块,完成之后,清除正在使用的内存块中的所有对象,然后两个内存块交换,以此完成垃圾回收。
- 标记压缩法
压缩算法的使用前提是存活对象少、垃圾对象多的情况下使用。这种情况经常发生在新生代,在老年到就不合适了,因为老年代大部分都是存活对象,如果要使用复制算法,复制的成本很高,所以老年代使用的是其他的算法,老年人比较特殊嘛
- 分区算法
面说的都是出来堆空间的算法,对于栈是如何处理的呢。分区算法是将整个堆空间分成连续的不同的小区间,如下图所示。每一个小区间都是独立使用,独立回收,这样设计可以控制一次回收多少个区间。不回去全扫描。
可参考:https://blog.csdn.net/qq_30739519/article/details/51111328
https://blog.csdn.net/tyronerenekton/article/details/59114835
jps、jstat、jmap、jinfo、jconsole等
可参考:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
双亲委派机制:
通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
可参考:https://www.cnblogs.com/zwjcom/p/6067797.html
可参考https://blog.csdn.net/u011080472/article/details/51337422
Minor GC ,Full GC 触发条件
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
1.serial收集器
单线程,工作时必须暂停其他工作线程。多用于client机器上,使用复制算法
2.ParNew收集器
serial收集器的多线程版本,server模式下虚拟机首选的新生代收集器。复制算法
3.Parallel Scavenge收集器
复制算法,可控制吞吐量的收集器。吞吐量即有效运行时间。
4.Serial Old收集器
serial的老年代版本,使用整理算法。
5.Parallel Old收集器
第三种收集器的老年代版本,多线程,标记整理
6.CMS收集器
目标是最短回收停顿时间。标记清除算法实现,分四个阶段:
初始标记:GC Roots直连的对象做标记
并发标记:多线程方式GC Roots Tracing
重新标记:修正第二阶段标记的记录
并发清除。
缺点:标记清除算法的缺点,产生碎片。CPU资源敏感。
7.G1收集器
基本思想是化整为零,将堆分为多个Region,优先收集回收价值最大的Region。
并行并发
分代收集
空间整合(标记整理算法)
可预测的停顿
启动类加载器(Bootstrap ClassLoader)加载,启动的时候加载的。
可参考:https://www.jianshu.com/p/2133558b4735
设置两个Survivor区最大的好处就是解决了碎片化,新生代使用的是复制收集算法,两个Survivor区是为了配合复制收集算法,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生
可参考:https://stackoverflow.com/questions/21476348/java-gc-why-two-survivor-spaces
https://segmentfault.com/q/1010000006886669?_ea=1165966
会吗?我也不知道
堆区:(存放所有new出来的对象;)
栈区:(存放基本类型的变量数据和对象的应用,对象(new出来的对象)本身并不存在栈中,而是存放在堆中或者常量池中(字符串常量对象存放在常量池中))
堆大小 = 新生代 + 老年代。默认下,新生代 ( Young ) = 1/3 的堆空间大小,老年代 ( Old ) = 2/3 的堆空间大小;
新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。默认的,Edem : from : to = 8 : 1 : 1;
参考:https://blog.csdn.net/u012102536/article/details/58587090
设计模式六大原则
- 单一职责原则
定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
遵循单一职责原的优点有:
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
- 提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
可参考文章:https://blog.csdn.net/zhengzhb/article/details/7278174
- 里氏替换原则
定义:所有引用基类的地方必须能透明地使用其子类的对象。
通俗讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能
- 依赖倒置原则
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置
通俗的讲就是:降低类之间的耦合性,提高系统的稳定性
- 接口隔离原则
定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
简单的说就是:将臃肿的接口拆分为独立的几个接口,提高内聚,减少对外交互,接口尽量小,但是要有限度。但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
- 迪米特法则
定义:一个对象应该对其他对象保持最少的了解。
通俗讲就是:尽量降低类与类之间的耦合。
- 开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
简单理解:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
写得不错的博客文章:https://blog.csdn.net/zhengzhb/article/details/7296944
饿汉式:单例实例在类装载时就构建,急切初始化。
优点
1.线程安全
2.在类加载的同时已经创建好一个静态对象,调用时反应速度快
缺点
资源效率不高,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化
public class Test { |
懒汉式:单例实例在第一次被使用时构建,延迟初始化
优点:
避免了饿汉式的那种在没有用到的情况下创建事例,资源利用率高,不执行getInstance()就不会被实例,可以执行该类的其他静态方法。
缺点:
懒汉式在单个线程中没有问题,但多个线程同事访问的时候就可能同事创建多个实例,而且这多个实例不是同一个对象,虽然后面创建的实例会覆盖先创建的实例,但是还是会存在拿到不同对象的情况。解决这个问题的办法就是加锁synchonized,第一次加载时不够快,多线程使用不必要的同步开销大
class Test { |
工厂模式,这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了
代理模式,在Aop实现中用到了JDK的动态代理
单例模式,这个比如在创建bean的时候。
原型模式:使用原型模式创建对象比直接new一个对象在性能上好得多,因为Object类的clone()方法是一个native方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
模版模式,这个也很明显,在各种BeanFactory以及ApplicationContext实现中也都用到了
一、装饰模式
二、建造者模式
三、工厂方法
四、适配器模式
五、模板方法
六、动态代理
7、责任链模式
看你个人了
开放性
真的重要,非常重要
1.7、数据结构
深度优先搜索算法(Depth-First-Search),是搜索算法的一种。它沿着树的深度遍历树的节点,尽可能深的搜索树的分 支。当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发 现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。DFS属于盲目搜索。
深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。一般用堆数据结构来辅助实现DFS算法。
广度优先搜索算法(Breadth-First-Search),是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树(图)的宽度遍历树(图)的节点。如果所有节点均被访问,则算法中止。BFS同样属于盲目搜索。一般用队列数据结构来辅助实现BFS算法。
参考:https://blog.csdn.net/u013372487/article/details/72082059
算法这东西对笔者来说,好高级。有几篇文章,感觉还行
https://blog.csdn.net/ljj583905183/article/details/41723699、
https://blog.csdn.net/u014507083/article/details/71198946
其原理是通过使用与对象存储一样的Hash算法将机器也映射到环中(一般情况下对机器的hash计算是采用机器的IP或者机器唯一的别名作为输入值),然后以顺时针的方向计算,将所有对象存储到离自己最近的机器中
可参考:https://blog.csdn.net/cywosp/article/details/23397179/
快排、折半查找、堆排序等
/**冒泡排序*/ |
开放地址法、链地址法、再哈希法、建立公共溢出区等
TreeMap是一个有序的key-value集合,基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建时提供的Comparator进行排序、
可参考:https://blog.csdn.net/itmyhome1990/article/details/76213883、https://blog.csdn.net/u010853261/article/details/54312932
1.8、网络/IO基础
GET方法:
使用GET方法时,查询字符串(键值对)被附加在URL地址后面一起发送到服务器:
/test/demo_form.jsp?name1=value1&name2=value2
特点:
GET请求能够被缓存
GET请求会保存在浏览器的浏览记录中
以GET请求的URL能够保存为浏览器书签
GET请求有长度限制
GET请求主要用以获取数据
POST方法:
使用POST方法时,查询字符串在POST信息中单独存在,和HTTP请求一起发送到服务器:
POST/test/demo_form.jsp HTTP/1.1
Host:w3schools.com
name1=value1&name2=value2
特点:
POST请求不能被缓存下来
POST请求不会保存在浏览器浏览记录中
以POST请求的URL无法保存为浏览器书签
POST请求没有长度限制
JAVA BIO :同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持
TCP
TCP是传输层的一个协议,基于IP协议,用来传输类似HTTP的信息。如果把IP协议类比为一个“公路”的话,那TCP协议可以看成是在公路上行驶的“卡车”。TCP协议是面向连接的协议,通过三次握手机制,尽量保证连接的可靠性。
TCP三次握手机制
第一次:客户端发送一个SYN包到服务端,并进入SYN_SEND状态,等待服务端的响应。
第二次:服务端收到SYN包,并确认,同时自己也发送一个SYN包,即SYN+ACK包,此时服务端进入SYN_RECV状态。
第三次:客户端收到服务端的SYN+ACK包,向服务端发送一个确认ACK包,进入ESTABLISHED状态,完成连接。
UDP
UDP也是传输层的一个协议。但是与TCP不同的是,UDP不是面向连接的,并不保证传输的可靠性,没有TCP的建立连接的三次握手机制,对于传输效率上面有了提升。
HTTP
HTTP是在应用层的一个协议,本身就是一个协议,是从Web服务器传输超文本到本地浏览器的传输协议。
HTTP协议基于请求\响应模型的,并且是基于TCP协议的。
TCP的优点:
可靠,稳定
TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。
TCP的缺点:
慢,效率低,占用系统资源高,易被攻击
TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。
而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。
UDP的优点:
快,比TCP稍安全
UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击……
UDP的缺点:
不可靠,不稳定
因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。
参考:https://blog.csdn.net/yakerwei/article/details/19342977、https://blog.csdn.net/xiaobangkuaipao/article/details/76793702
TCP/IP五层模型的协议
应用层
传输层:四层交换机、也有工作在四层的路由器
网络层:路由器、三层交换机
数据链路层:网桥(现已很少使用)、以太网交换机(二层交换机)、网卡(其实网卡是一半工作在物理层、一半工作在数据链路层)
物理层:中继器、集线器、还有我们通常说的双绞线也工作在物理层
UDP 底层是传输层协议
可参考:http://blog.chinaunix.net/uid-22166872-id-3716751.html
每个udp包的最大大小是多少?
65507 约等于 64K
为什么最大是65507?
因为udp包头有2个byte用于记录包体长度. 2个byte可表示最大值为: 2^16-1=64K-1=65535
udp包头占8字节, ip包头占20字节, 65535-28 = 65507
如果要发送的udp报文大于65507怎么办?
需要在应用层由开发者自己分片发送. 分片的粒度最大65507字节. 系统的sendto函数是不支持大于65507字节的单包发送的.
以太网Ethernet最大的数据帧是1518字节,(以太网帧的帧头14字节和帧尾CRC校验4字节(共占18字节),剩下承载上层协议的地方也就是Data域最大就只剩1500字节. 这个值我们就把它称之为MTU),单个TCP包实际传输的最大量就缩减为1448字节。1448=1500-20(IP头)-32(20字节TCP头和12字节TCP选项时间戳)
参考:https://blog.csdn.net/qq_30667875/article/details/71216281
UDP报文:
源端口
目的端口
长度
校验和
数据部分
TCP报文:
端口号
源端口
目的端口
序号和确认号
数据偏移/首部长度
保留
控制位
窗口
校验和
紧急指针
选项和填充
数据部分
可参考:https://www.cnblogs.com/Allen-rg/p/7190042.html
SYN Flood是一种广为人知的DoS(拒绝服务攻击)是DDoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。
防御措施
- (1)使用TCP Wrapper,服务端只处理有限来源IP的TCP连接请求,其它未指定来源的连接请求一概拒绝。
- (2)缩短SYN Timeout时间,
- (3)设置SYN Cookie,
- (4)使用SYN Proxy防火墙
可参考:https://www.cnblogs.com/qiaoconglovelife/p/5713661.html、
- 转义特殊符号,比如:‘<’ ‘>’ ,转义后就会变成 :‘<’ ‘>’
- 过滤 html 标签
这个也是开放题,
我以前遇到过是这样解决的。每次发包,数据包= 包头+ 包数据体。这个比较简单,包头我直接放入这个数据包的数据大小,包头可以固定2个字节这样(看需求),服务端首先读取包头的数据,判断该包是否接受完,为接受完就继续等待…
在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。
而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:
Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
HTTP1.0的缺陷
每个请求都需单独建立连接(keep-alive能解决部分问题单不能交叉推送)
每个请求和响应都需要完整的头信息
数据未加密
HTTP2.0的优势
多路复用
压缩头信息
请求划分优先级
支持服务器端主动推送
HTTPS (基于安全套接字层的超文本传输协议 或者是 HTTP over SSL) 是一个 Netscape 开发的 Web 协议。
你也可以说:HTTPS = HTTP + SSL
HTTPS 在 HTTP 应用层的基础上使用安全套接字层作为子层。
可参考:https://blog.csdn.net/xhbxhbsq/article/details/79385179
因为HTTP是一个基于TCP的协议,而TCP是一种可靠的传输层协议.
建立TCP连接时会发生:三次握手(three-way handshake)
firefox > nginx [SYN] 在么
nginx > firefox [SYN, ACK] 在
firefox > nginx [ACK] 知道了
关闭TCP连接时会发生:四次挥手(four-way handshake)
firefox > nginx [FIN] 我要关闭连接了
nginx > firefox [ACK] 知道了,等我发完包先
nginx > firefox [FIN] 我也关闭连接了
firefox > nginx [ACK] 好的,知道了
参考:https://www.zhihu.com/question/67772889?answer_deleted_redirect=true
总体来说分为以下几个过程:
DNS解析
TCP连接
发送HTTP请求
服务器处理请求并返回HTTP报文
浏览器解析渲染页面
连接结束
参考:https://segmentfault.com/a/1190000006879700
ObjectOutputStream 、ObjectInputStream、 readObject writeObject
链路层是为网络层提供数据传送服务的,这种服务要依靠本层具备的功能来实现。链路层应具备如下功能:
① 链路连接的建立,拆除,分离.
② 帧定界和帧同步.链路层的数据传输单元是帧,协议不同,帧的长短和界面也有差别,但无论如何必须对帧进行定界.
③ 顺序控制,指对帧的收发顺序的控制.
④ 差错检测和恢复。还有链路标识,流量控制等等.差错检测多用方阵码校验和循环码校验来检测信道上数据的误码,而帧丢失等用序号检测.各种错误的恢复则常靠反馈重发技术来完成.
数据链路层功能:
链路管理:数据链路的建立、维持和释放
帧同步:接收方应当能从收到的比特流中准确区分一帧的开始和结束在什么地方
流量控制:控制发送方发送数据的速率
差错控制:接收端能够发现传输错误,并能纠正错误
帧的透明传输:能判断控制字符和数据
寻址:保证传送到正确的目的节点
数据链路层协议:为实现数据链路控制功能而制定的规程或协议
IP 是网络层,Mac 是数据链路层
TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据
参考:https://blog.csdn.net/hhcrazy12345/article/details/46682223
TCP滑动窗口分为接受窗口,发送窗口
滑动窗口协议是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。
[1] 确认和重传机制
建立连接时三次握手同步双方的“序列号 + 确认号 + 窗口大小信息”,是确认重传、流控的基础
传输过程中,如果Checksum校验失败、丢包或延时,发送端重传
[2] 数据排序
TCP有专门的序列号SN字段,可提供数据re-order
[3] 流量控制
窗口和计时器的使用。TCP窗口中会指明双方能够发送接收的最大数据量
[4] 拥塞控制
TCP的拥塞控制由4个核心算法组成。
“慢启动”(Slow Start)
“拥塞避免”(Congestion avoidance)
“快速重传 ”(Fast Retransmit)
“快速恢复”(Fast Recovery)
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。
简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
HTTP协议是无状态的,指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系。HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)
参考:【https://www.cnblogs.com/gotodsp/p/6366163.html】
地址解析协议ARP就是将ip地址解析为以太网中的MAC地址,其实我们都知道,在实际的数据传输的过程中,我们使用的是二层的数据帧,但是我们平时使用的是三层的ip地址,这样,我们就需要这个协议了。
参考 :https://blog.csdn.net/wswit/article/details/52578878
如果此时ACK在网络中丢失,那么Server端该TCP连接的状态为SYN_RECV,并且依次等待3秒、6秒、12秒后重新发送SYN+ACK包,以便Client重新发送ACK包。
Server重发SYN+ACK包的次数,可以通过设置/proc/sys/net/ipv4/tcp_synack_retries修改,默认值为5。
如果重发指定次数后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接。
但是Client认为这个连接已经建立,如果Client端向Server写数据,Server端将以RST包(用于强制关闭tcp连接)响应,方能感知到Server的错误。
二、数据存储和消息队列
2.1、数据库
数据库索引,什么是全文索引,全文索引中的倒排索引是什么原理;
简单的说索引就像书本的目录方便查看数据,它是存储的表中一个特定列的值数据结构(最常见的是B-Tree)
全文索引是用于检索字段中是否包含或不包含指定的关键字,有点像搜索引擎的功能,其内部的索引结构采用的是与搜索引擎相同的倒排索引结构
倒排索引,其原理是对字段中的文本进行分词,然后为每一个出现的单词记录一个索引项,这个索引项中保存了所有出现过该单词的记录的信息,也就是说在索引中找到这个单词后,就知道哪些记录的字段中包含这个单词了。因此适合用大文本字段的查找数据库最佳左前缀原则是什么?
在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边
可参考:https://blog.csdn.net/SkySuperWL/article/details/52583579悲观锁和乐观锁的原理和应用场景;
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁
悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
总结:两种所各有优缺点,读取频繁使用乐观锁,写入频繁使用悲观锁。
左连接、右连接、内连接、外连接、交叉连接、笛卡儿积等;
左外连接(LEFT OUTER JOIN或LEFT JOIN)
右外连接(RIGHT OUTER JOIN或RIGHT JOIN)
全外连接(FULL OUTER JOIN或FULL JOIN)
内连接 (INNER JOIN)
外连接 (OUTER JOIN)
交叉连接 (CROSS JOIN)一般情况下数据库宕机了如何进行恢复(什么是Write Ahead Log机制,什么是Double Write机制,什么是Check Point);
自由发挥
什么是redo日志、什么是undo日志;
Undo日志记录某数据被修改前的值,可以用来在事务失败时进行rollback;
Redo日志记录某数据块被修改后的值,可以用来恢复未写入data file的已成功事务更新的数据数据库中的隔离性是怎样实现的;原子性、一致性、持久性又是如何实现的;
基于日志的REDO/UNDO机制,并通过加锁的机制?
小弟懵懂,欢迎大神拍砖什么是组合索引,组合索引什么时候会失效;
简单的说组合索引就是两个字段组成的索引,像这样创建一个3个字段的索引:ALTER TABLE people ADD INDEX lname_fname_age (lame,fname,age);
对于组合索引,不是使用的第一部分,则不会使用索引既失效。或者使用 or 语句时。关系型数据库和非关系型数据库区别;
非关系型数据库的优势:
- 性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
- 可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
关系型数据库的优势:
- 复杂查询,可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
- 事务支持,使得对于安全性能很高的数据访问要求得以实现
MySQL并发情况下怎么解决;
通过事务、隔离级别、锁
MySQL中的MVCC机制是什么意思,根据具体场景,MVCC是否有问题;
MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能
理想MVCC难以实现的根本原因在于企图通过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并无区别,而二提交是目前这种场景保证一致性的唯一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,二者矛盾,故理想的MVCC难以真正在实际中被应用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已
参考:https://www.cnblogs.com/chenpingzhao/p/5065316.html
MySQL 索引使用的注意事项
创建索引大大加快数据的查询速度,但创建索引和维护索引需要消耗时间并占据磁盘空间,增删改索引也要动态的维护
更新频繁的列不应设置索引
数据量小的表不要使用索引(毕竟总共2页的文档,还要目录吗?)
重复数据多的字段不应设为索引(比如性别,只有男和女,一般来说:重复的数据超过百分之15就不该建索引)
首先应该考虑对where 和 order by 涉及的列上建立索引DDL、DML、DCL分别指什么
DML(data manipulation language):
它们是SELECT、UPDATE、INSERT、DELETE,就象它的名字一样,这4条命令是用来对数据库里的数据进行操作的语言
DDL(data definition language):
DDL比DML要多,主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定义或改变表(TABLE)的结构,数据类型,表之间的链接和约束等初始化工作上,他们大多在建立表时使用
DCL(Data Control Language):
是数据库控制功能。是用来设置或更改数据库用户或角色权限的语句,包括(grant,deny,revoke等)语句。在默认状态下,只有sysadmin,dbcreator,db_owner或db_securityadmin等人员才有权力执行DCL
事物的隔离级别
读未提交、读以提交、可重复读、可序列化读
脏读、幻读、不可重复读
脏读:
一个事务读取另外一个事务尚未提交的数据
不可重复读:
其他事务的操作导致某个事务两次读取数据不一致
不可重复读,针对已经提交的数据。2.两次或多次读取同一条数据
幻读:
其他事务的数据操作导致某个事务两次读取数据数量不一致。例如:
对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
幻读针对已经提交的数据。2.两次或多次读取不同行数据,数量上新增或减少
数据库的几大范式
数据库的三大范式以及五大约束
第一范式(1NF):数据表中的每一列(每个字段)必须是不可拆分的最小单元,也就是确保每一列的原子性;
第二范式(2NF):满足1NF后,要求表中的所有列,都必须依赖于主键,而不能有任何一列与主键没有关系,也就是说一个表只描述一件事情;
第三范式(3NF):必须先满足第二范式(2NF),要求:表中的每一列只与主键直接相关而不是间接相关,(表中的每一列只能依赖于主键);
数据库五大约束
1.primary KEY:设置主键约束;
2.UNIQUE:设置唯一性约束,不能有重复值;
3.DEFAULT 默认值约束,height DOUBLE(3,2)DEFAULT 1.2 height不输入是默认为1,2
4.NOT NULL:设置非空约束,该字段不能为空;
5.FOREIGN key :设置外键约束。
数据库常见的命令
select alert update delete drop …..
说说分库与分表设计
对于互联网企业来说,大部分数据都是与用户关联的,因此,用户id是最常用的分表字段。因为大部分查询都需要带上用户id,这样既不影响查询,又能够使数据较为均衡地
拆分后表的数量一般为2的n次方
一种分库分表的路由策略如下:- 中间变量 = user_id % (分库数量 * 每个库的表数量)
- 库 = 取整数 (中间变量 / 每个库的表数量)
- 表 = 中间变量 % 每个库的表数量
分库与分表带来的分布式困境与应对之策(如何解决分布式下的分库分表,全局表?)
分布式困境:
数据迁移与扩容问题
表关联问题
分页与排序问题
分布式事务问题
分布式全局唯一ID
参考:http://blog.720ui.com/2017/mysql_core_09_multi_db_table2/
说说 SQL 优化之道
1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
3.应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描
4.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,
5.in 和 not in 也要慎用,否则会导致全表扫描
6.应尽量避免在使用 like 查询
7.如果在 where 子句中使用参数,也会导致全表扫描
8.应尽量避免在 where 子句中对字段进行表达式操作
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.select count() from table;这样不带任何条件的count会引起全表扫描
12.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率
13.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销
14.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间
15.任何地方都不要使用 select * from t ,用具体的字段列表代替“”,不要返回用不到的任何字段
16.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
。。。。。
可参考:http://www.cnblogs.com/yunfeifei/p/3850440.html
MySQL遇到的死锁问题、如何排查与解决
通过SHOW ENGINE INNODB STATUS;来查看死锁日志
参考:http://blog.jobbole.com/110301/存储引擎的 InnoDB与MyISAM区别,优缺点,使用场景
主要区别:
1).MyISAM是非事务安全型的,而InnoDB是事务安全型的。
2).MyISAM锁的粒度是表级,而InnoDB支持行级锁定。
3).MyISAM支持全文类型索引,而InnoDB不支持全文索引。
4).MyISAM相对简单,所以在效率上要优于InnoDB,小型应用可以考虑使用MyISAM。
5).MyISAM表是保存成文件的形式,在跨平台的数据转移中使用MyISAM存储会省去不少的麻烦。
6).InnoDB表比MyISAM表更安全,可以在保证数据不会丢失的情况下,切换非事务表到事务表(alter table tablename type=innodb)。
应用场景:
1).MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。
2).InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。
什么是自适应哈希索引(AHI)
来个大佬写答案
为什么要用 B+tree作为MySQL索引的数据结构
来个大佬写答案,好像是磁盘存取原理有关
聚集索引与非聚集索引的区别
聚集(clustered)索引,也叫聚簇索引。
定义:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。
非聚集(unclustered)索引。
定义:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。
根本区别:聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致。
遇到过索引失效的情况没,什么时候可能会出现,如何解决
1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
2.对于多列索引,不是使用的第一部分,则不会使用索引
3.like查询是以%开头
4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
limit 20000 加载很慢怎么解决
1.子查询优化法
先找出第一条数据,然后大于等于这条数据的id就是要获取的数据
缺点:数据必须是连续的,可以说不能有where条件,where条件会筛选数据,导致数据失去连续性
2.倒排表优化法
倒排表法类似建立索引,用一张表来维护页数,然后通过高效的连接得到数据
缺点:只适合数据数固定的情况,数据不能删除,维护页表困难
3.反向查找优化法
当偏移超过一半记录数的时候,先用排序,这样偏移就反转了
缺点:order by优化比较麻烦,要增加索引,索引影响数据的修改效率,并且要知道总记录数
,偏移大于数据的一半
4.limit限制优化法
把limit偏移量限制低于某个数。。超过这个数等于没数据,
参考:https://www.cnblogs.com/shiwenhu/p/5757250.html
常见的数据库优化方案,在你的项目中数据库如何进行优化的
1、选取最适用的字段属性
2、使用索引
3、SQL 优化
哎呀,自由发挥一个Controller调用两个Service,这两Service又都分别调用两个Dao,问其中用到了几个数据库连接池的连接?
一个连接池的两个连接 ?这个还真没测过
InnoDB的插入缓冲和两次写的概率和意义;
插入缓冲:对于非聚集类索引的插入和更新操作,不是每一次都直接插入到索引页中,而是先插入到内存中。具体做法是:如果该索引页在缓冲池中,直接插入;否则,先将其放入插入缓冲区中,再以一定的频率和索引页合并,这时,就可以将同一个索引页中的多个插入合并到一个IO操作中,大大提高写性能
如果建了一个单列索引,查询的时候查出2列,会用到这个单列索引吗?
会用到
如果建了一个包含多个列的索引,查询的时候只用了第一列,能不能用上这个索引?查三列呢?
用到
接上题,如果where条件后面带有一个 i + 5 < 100 会使用到这个索引吗?
我觉得不会
like %aaa%会使用索引吗? like aaa%呢?
我觉得不会
drop、truncate、delete的区别?
相同点:
1.truncate和不带where子句的delete、以及drop都会删除表内的数据。
2.drop、truncate都是DDL语句(数据定义语言),执行后会自动提交。
不同点:
- truncate 和 delete 只删除数据不删除表的结构(定义)
drop 语句将删除表的结构被依赖的约束(constrain)、触发器(trigger)、索引(index);依赖于该表的存储过程/函数将保留,但是变为 invalid 状态。- 速度,一般来说: drop> truncate > delete
更多可参考:http://www.cnblogs.com/8765h/archive/2011/11/25/2374167.html
平时你们是怎么监控数据库的? 慢SQL是怎么排查的?(慢查询日志)
自由发挥
你们数据库是否支持emoji表情,如果不支持,如何操作?选择什么编码方式?如果支持一个表情占几个字节?
支持,选择utf8mb4,一个表情占4个字节
如果查询很慢,你会想到的第一个方式是什么?
(数据库索引)
2.2、Redis
2.Hash(哈希)
3.List(列表)
4.Set(集合)
5.zset(sorted set:有序集合)
Redis 内部结构
数据库主要由 dict 和 expires 两个字典构成,其中 dict 保存键值对,而 expires 则保存键的过期时间
数据库的键总是一个字符串对象,而值可以是任意一种 Redis 数据类型,包括字符串、哈希、集合、列表和有序集
….可参考:https://blog.csdn.net/tianshijianbing1989/article/details/50730572
Redis 持久化机制
两种方式:rdb(redis database)和aof(append of file)
Redis 集群方案与实现
主从复制,高可用集群
Redis 为什么是单线程的?
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了
缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级
使用缓存的合理性问题
热点数据,缓存才有价值
Redis常见的回收策略
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
Redis主从是怎么选取的
一种是主动切换,另一种是使用sentinel自动方式
Redis复制的过程;
1、slave向master发送sync命令。
2、master开启子进程来将dataset写入rdb文件,同时将子进程完成之前接收到的写命令缓存起来。
3、子进程写完,父进程得知,开始将RDB文件发送给slave。
4、master发送完RDB文件,将缓存的命令也发给slave。
5、master增量的把写命令发给slave。Redis队列应用场景;
大并发下对写的操作
Redis主节点宕机了怎么办,还有没有同步的数据怎么办;
还能怎么办,重启呗。看看是否配置持久化文件,如果配置了,数据会恢复。或者启动哨兵模式,从节点变成master.
Redis中zSet跳跃表问题;
高深莫测,可参考:https://blog.csdn.net/acceptedxukai/article/details/17333673
Redis的set的应用场合?
当需要存储一个列表数据,又不希望出现重复数据时,可选用set
Redis高级特性了解吗?
高级特性是什么? 主从复制、哨兵、集群、事务、消息发布与订阅、持久化 ?是考这些吗?
Redis的pipeline(管道)有什么用处?
原生批量命令是原子性,Pipeline是非原子性的.
原生批量命令是一个命令对应多个key,Pipeline支持多个命令.
原生批量命令是Redis服务端支持实现的,而Pipeline需要服务端与客户端的共同实现
Redis集群宕机如何处理,怎么样进行数据的迁移;
复制 rdb文件或者aof 文件
Redis的集群方案;
主从复制、哨兵、高可用集群
Redis原子操作怎么用比较好;
Redis的原子性有两点:
1、单个操作是原子性的
2、多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来
原子操作的意思就是要么成功执行要么失败完全不执行。用现实中的转账比喻最形象,你转账要么成功,要么失败钱不动,不存在你钱转出去了,但收款方没收到这种成功一半失败一半的情况Redis过期策略是怎么实现的呢?
懒汉式删除、定期删除、定时删除