面试宝典

JavaSE基础

Java面向对象

>>>面向对象有哪些特性,以及对这些特性的理解

封装、继承、多态、(抽象)

封装:封装就是将数据和对数据操作的方法绑定起来,对数据的访问只可以通过对外暴露的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写的一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可以隐藏的东西,只向外界提供最简单的编程接口

继承:是从已有类得到继承信息并创建新类的过程。提供继承信息的类被称为父类、超类或基类,得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段

多态:多态是指允许不同子类型的对象对同一消息做出不同的相应,简单地说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时多态性和运行时多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以理解为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说对是透明的。方法重载实现的是编译时的多态性,而方法重写实现的是运行时的多态性。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1、方法重写;2对象造型

抽象:抽象是将一类对象的共同特性总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节

>>>访问权限修饰符public、private、protected以及缺省时的区别

pulic的可见性为:当前类、同包、子类、其他包

protected的可见性为:当前类、同包、子类

default的可见性为:当前类、同包

private的可见性为:当前类

>>>为什么使用clone

在实际编程过程中,我们常常遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A对象完全相同的新对象B,并且此后对B任何改动不会影响到A中的值,也是说,A与B是两个独立的对象,但是B的初始值是由A对象确定的,Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone方法是最简单最高效的

>>>new一个对象过程和clone一个对象过程区别

new操作符的本意是分配内存,程序执行到new操作符时,首先去看new操作符后面的类型,因为知道了类型,才能知道分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用发布到外部,在外部就可以使用这个引用操作这个对象

clone在第一步和new相似,都是分配内存,调用clone方法时,分配的内存和原对象相同,然后在使用原对象中对应的各个域,填充新对象的域,填充成功之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部

JavaSE语法

>>>java中有没有goto语句

goto是java的保留字,在目前版本的java中没有使用。根据James Gosling编写书中的java关键字列表,其中有goto和const,但是目前这两个是无法使用的关键字,因此有些地方称其为保留字

>>>&和&&的区别

&运算符有两种用法:按位与、逻辑与

&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true

&&之索引成为短路运算符是因为,如果&&左边的表达式值为false,右边的表达式会直接短路掉,不会进行运算。很多时候我们可能使用的是&&而不是&,例如在验证用户登录时判断用户名不可以是null而且不是空字符串,应该写为username != null && !username.equals(""),二者的顺序不能交换,更不能使用&运算符,因为第一个条件如果不成立,就不可以进行字符串的equals比较,否则会造成空指针异常

>>>java中,如何跳出当前的多重嵌套循环

在最外层循环前加一个标记如A,然后使用break A;可以跳出多重循环

>>>两个对象值相同

不对,如果两个对象x、y的equals比较返回true,那么他们的hashCode应该相同。

java对于equals方法和hashCode方法是这样规定的:1、如果两个对象相同即equals方法返回true,那么他们的hashCode值一定要相同2、如果两个对象的hashCode值相同,那么它们并不一定相同。当然,你未必要按照要去做,但是如果你违背上述原则,就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率大大下降

>>>是否可以继承String

由于String类是final类,所以不可以被继承

继承String本身就是一个错误的行为,对String类型最好的重用方式就是关联关系和依赖关系,而不是继承关系

>>>当一个对象被当作参数传递到一个方法后,此方法可以改变这个对象的属性,并可以返回变化后的结果,那么这里到底是值传递还是引用传递

是值传递,java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用。对象的属性可以在被调用过程中被改变,但是在方法内部对引用的改变,并不会影响到调用者传入方法的对象

>>>重载overload和重写override的区别,重载的方法能否根据返回类型进行区分

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,后者实现的是运行时的多态性。重载发生在同一个类中,同名的方法如果有不同的参数列表,则视为重载;重写发生在子父类之间,重写要求子类重写的方法和父类被重写的方法有相同的返回类型,比父类被重写方法更好访问(访问权限大于父类方法),不能比父类被重写的方法声明更多的异常(里氏代换原则)。重载对返回值类型没有特殊的要求,只针对方法名和参数列表

>>>为什么函数不能根据返回类型来区分重载

因为在调用的时候,编译器只能通过方法名和形参列表来确定返回类型

方法的返回值,只可以作为方法运行之后的一个状态,而并不可以作为方法的一个标识

>>>char 型变量中能不能存储一个中文汉字,为什么?

char类型可以存储一个中文汉字,因为java使用的编码是Unicode,Unicode编码是不使用任何特定的编码格式,而是所有的字符都使用字符集中的编码,一个char类型占用两个字节,所以可以放一个中文

>>>抽象类(abstract class)和接口(interface)有什么异同

不同点:

1、抽象类中可以定义构造函数,接口中不可以

2、可以有抽象方法和具体的方法,接口中的方法只可以是抽象方法

3、接口中所有的成员都是public的,抽象类的成员可以是四种权限任意的

4、抽象类中可以定义成员变量,接口中定义的成员变量都是常量psf的

5、凡是拥有抽象方法的类一定是抽象类,但是抽象类中未必有抽象方法

6、抽象类中有静态的方法,接口中不可以有静态方法

7、类的单继承多实现

相同点:

1、都不可以实例化

2、可以将抽象类和接口类型作为引用类型,也就是多态的体现

3、一个类继承抽象类或实现接口,都需要将抽象方法全部实现,否则该类还是抽象的

>>>抽象的(abstract)方法是否可同时是静态的(static), 是否可同时是本地方法(native),是否可同时被 synchronized

都不可以,因为抽象方法需要被重写,静态方法不可以被重写;至于native,因为native是用本地代码例如C语言实现的,所以不可以通过重写实现;synchronized是和方法具体实现有关的,但是抽象方法没有具体实现细节,所以也不可以

>>>静态变量和实例变量的区别

静态变量:也就是使用static关键字修饰的变量,它属于类,在类加载的时候就会进行初始化,分配内存,所以无论这个对象有多少实例,静态变量都只有一份在内存

实例变量:属于某一个具体的实例,需要通过创建对象,才会在内存中分配空间,才可以进行操作

>>>==和 equals 的区别

==:如果比较的是基本数据类型,那么比较的就是值是否相等;如果比较的是引用数据类型,那么比较的就是地址值是否相等

equals:是Object类的一个方法,用来比较两个对象的内容是否相等

>>>break和continue的区别

break:用于跳出循环体,一旦程序执行到break,立即跳出当前循环

continue:用于跳出本次循环,程序执行到continue,会跳出本次循环,开始执行下一次循环

>>>

没有改变,因为String是不可变类,所以它所有对象都是不可变对象,代码中,变量s指向一个String对象,内容是“hello”,然后对s+“world!”此时s已经指向了一个新的String对象,内容是“Hello world!”,原来的对象还存在于内存中,只是s没有再指向它了

所以,得出一个结论,如果我们需要对于一个字符串类型的对象经常修改,那么使用String会给内存带来很大的开销,因为每次修改,String都需要创建新的对象,这个时候,就应该考虑使用StringBuffer类了

Java中的多态

>>>Java 中实现多态的机制是什么

靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法

Java的异常处理

>>>Java中异常分为哪些种类

按照异常需要处理的实际分为编译时异常(也称为受检时异常)CheckedException和运行时异常RuntimeException,其中编译时异常,Java认为都是可以被处理的异常,所以需要显示的进行处理,如果没有处理,那么程序会在编译期就会报错,无法编译,编译时异常的处理方法有两种,一个是使用try-catch捕获并处理异常,一个是抛出该异常;运行时异常,只有在程序运行的过程中才会出现的异常,例如除零异常,数组角标越界等,这类异常不要求强制处理

>>>以下代码,返回值是多少,为什么
public int getNum(){
    try {
        int a = 1/0;
        return 1;
    } catch (Exception e) {
        return 2;
    }finally{
        return 3;
    }

返回值应该是3,因为程序在运行中,遇到除零异常,那么会进入catch块中,但是,Java异常机制是,如果程序执行到return,但是存在finally块,那么必须先执行finally块中的代码,再进行返回,但是finally块中的语句是return,所以程序到此结束,返回的是3

>>>error和exception的区别

Error类和Exception类的父类都是Throwable类

但是Error类主要声明了一些与虚拟机相关的错误,例如系统崩溃、虚拟机错误、内存空间不足等,这些错误,无法靠程序本身来解决或预防,所以出现这种错误,建议程序终止运行

Exception类表示的是程序可以处理的异常,可以捕获,并且恢复,出现这种异常,应该及时的处理,而不是停止程序的运行

>>>说出5个常见的RuntimeException

NullPointerException:空指针异常,一般出现调用未初始化的对象的变量或方法

ClassNotFoundException:类加载异常,一般出现再通过字符串加载类时,如加载Mysql的Driver使用Class.forName("")

NumberFormatException:数值转换异常,一般出现再通过字符串转数值类型的时候

IllegalArgumentException:非法参数异常,一般出现在方法传入的参数不合法,例如使用SimpleDateFormat的构造方法里面,字符串的格式不正确

ClassCastException:类型转换异常,一般出现在强转类型的时候

SQLException:SQL异常,一般出现在操作数据的SQL语句错误

InstantiationException:实例化异常,一般出现在构造函数私有化,无法创建对象

>>>throw和throws的区别

throw:

1、throw语句用于方法体内,表示抛出异常

2、throw时具体向外抛出异常的动作,抛出的是一个异常实例

throws:

1、在方法后面进行声明,如果抛出异常,那么需要上层的调用者来处理

2、throws主要声明这个方法可能抛出的异常类型,不是一定会出现

>>>final、finally、finalize的区别

final:用于声明方法、属性、类,表示属性不可变、方法不可重写、类不可继承

finally:异常处理语句结构的一部分,表示总是执行

finalize:Object类的一个方法,GC在垃圾回收时会调用被回收对象的该方法

JavaSE的常见API

>>>

Math.round(11.5)等于12,Math.round(-11.5)等于-11,因为Math.round取整四舍五入,无论正负,都会在原参数上加0.5

>>>switch能否作用与byte上,能否作用在long上,能否作用在String上

以常用的Java8来说,可以作用在byte、String上,不可以作用在long上

Java5之前,switch可以作用在byte、short、char、int上,java5增加enum类型,Java7增加String类型

>>>数组有没有length方法、String有没有length方法

数组没有length方法,而是length属性;String有length方法,一般来说JavaScript中String的长度是length属性,所以容易和Java中的混淆

>>>String、StringBuffer、StringBuilder的区别

String:只读字符串,也就是String类型的对象只支持读,而不支持写,因为如果String的字符串内容改变,那么引用指向的就不是原来的对象,而是新的对象

StringBuffer:读写字符串,就是修改字符串的内容而不会产生新的对象,而且StringBuffer线程安全,所有方法使用synchronized修饰

StringBuilder:读写字符串,和StringBuffer的方法完全相同,只不过,所有的方法没有加synchronized关键字,所以,线程不安全,但是效率高

Java的数据类型

>>>Java的基本数据类型都有哪些,各占多少字节

byte:1字节

short:2字节

int:4字节

long:8字节

float:4字节

double:8字节

char:2字节

boolean:1字节

>>>String是基本数据类型吗

不是,String是引用数据类型,底层使用char数组实现

>>>

前者不对,后者对,因为对于前者表达式中的+1,1是int类型,所以计算结果自动类型提升为int,需要强转才可以赋值给short类型,而后者可以正确进行编译,结果就是short类型

>>>int和Integer的区别

int是基本数据类型,Integer是引用数据类型,在编码的时候,int可以自动装箱为Integer,Integer也可以自动拆箱为int

>>>String类的常用方法
//返回当前字符串长度
int length();
//查找字符ch第一次出现的位置
int indexOf(char ch);
//查找字符串str第一次出现的位置
int indexOf(String str);
//查找字符ch最后一次出现的位置
int lastIndexOf(char ch);
//查找字符串str最后一次出现的位置
int lastIndexOf(String str);
//获取从begin位置开始到结束的字符串
String subString(int begin);
//获取从begin带end直接的字符串
String subString(int begin,int end);
//返回去除前后空格的字符串
String trim();
//字符串转换为全小写
String toLowerCase();
//字符串转换为全大写
String toUpperCase();
//获取字符串中index位置的子父
char charAt(int index);

Java的IO

>>>Java中有几种类型的流

按照数据的流向:输入流(inputStream)和输出流(outputStream)

按照实现功能:节点流(如FileReader)和处理流(如BufferedReader)

按照处理数据的单位:字节流(继承于InputStream和OutputStream)和字符流(继承与Reader和Writer)

>>>字节流如何转为字符流

字节输入流转字符输入流:通过InputStreamReader实现,该类的构造器可以传入InputStream对象

字节输出流转子父输出流:通过OutputStreamWriter实现,该类的构造器可以传入OuttputStream对象

>>>如何将一个对象序列化到文件里面

需要被序列化对象的类必须实现Serializable接口

//建立对象输出流
ObjectOutputStream oop = new ObjectOutputStream(new FileOutputStream(new File("指定文件路径")));
//写入
oop.writeObject(object);
//关闭资源
oop.close();
>>>字节流和字符流的区别

字节流读取的时候,读到一个字节就返回一个字节;字符流在读取的时候,读到一个字符返回一个字符。字节流可以处理所有数据类型的数据,而字符流只能处理字符数据,就是说,如果处理纯文本数据,那么优先使用字符流,其他情况都用字节流,字节流主要操作的是byte数组

>>>如何实现对象的克隆

一共有两种方法实现:

1、实现Cloneable接口,并且重写clone方法,但是这种是浅克隆

2、实现Serializable接口,通过对象的序列化和反序列化实现,这种的方式,可以实现深克隆

>>>什么是Java序列化,如何实现Java序列化

序列化就是用来处理对象流的机制,所谓对象流雁去吃将对象的内容进行流化。可以对流化后的对象进行读写操作,也可以将流化后的对象传输与网络之间,序列化是为了解决对对象流进行读写操作时所引发的问题

序列化的实现:将需要被序列化的类实现Serializble接口,该接口没有需要实现的方法,然后使用一个输出流来构造一个ObjectOutputStream对象,接着使用ObjectOutputStream对象的writeObject方法可以将参数对象写出

Java的集合

>>>ArrayList、HashSet、HashMap是线程安全的吗?如果不是我想要的线程安全的集合怎么办?

三个集合都是线程不安全的,源码中也没有对核心方法进行加锁

在集合中的Vector、HashTable是线程安全的,他们的核心方法上都添加了synchronized关键字

而且,Collections工具类提供了API可以将线程不安全的集合转为线程安全,如Collections.synchronized(c),原理也就是在核心方法上添加synchronized关键字

>>>ArrayList的内部实现

JDK1.8中,ArrayList底层是一个Object类型的数组,该数组使用transient关键字修饰,transient关键字就是如果序列化ArrayList对象,这个数组不会进行序列化,当调用无参构造创建对象的时候,构造方法会对该数组进行赋值,一个空数组,长度为0,所以,ArrayList的初始长度为0,在第一次add的时候,底层会使用Math.max方法进行判断,如果当前的数组的长度为0,会创建一个长度为10的数组,如果新添加元素后的长度大于当前数组的长度,那么会进行1.5倍的扩容

>>>并发集合和普通集合的区别

Java中的常用的并发集合,有对应List的CopyOnWriteArrayList;对应Set的CopyOnWriteArraySet;对应Map的ConcurrentHashMap,都是在JUC包下。在Java中有普通集合、同步集合、并发集合,普通集合的性能最高,但是不能保证多线程的安全和并发可靠性,同步集合,如Vector、HashTable都是使用synchronized关键字强同步,严重的牺牲了性能,而且如果在并发的条件下,效率更低,并发集合通过复杂的策略例如写时复制的做法,实现了读写分离,也就是在往集合中添加元素的时候,并不是直接进行添加,而是,复制原集合,并添加,同时,用于保存数据的Obejct数组,使用volatile修饰,添加完成之后,再将原集合的引用指向新集合

>>>List的三个子类的特点

ArrayList:底层结构是一个数组,因为数组是连续空间,所以读操作效率高、增删效率低

LinkedList:底层结构式链表,因为链表是不连续的空间结构,所以,增删效率高、读效率低

Vector:底层结合是一个数组,由于所有的核心方法都添加了synchronized关键字,虽然保证了线程安全,但是操作效率低

>>>List、Map、Set的区别

List和Set都是用来存储单列数据的集合,Map存取由key-value组成的双列数据,其中List存储的数据有序,并且运行重复元素;Set存储的数据无序、要求元素不可以重复,Set底层使用的也是对应的Map;Map存储的数据没有顺序,key不可以重复、value可以重复,因为Map集合中元素的位置是根据HashCode进行计算的,所以这个位置是固定的,但是位置不是用户可以控制的,所以说无序

>>>List、Map、Set的实现类

List接口有三个实现类:ArrayList:基于数组实现,线程不安全、但是效率高,查询的效率高,增删效率低;LinkedList:基于双向链表实现,由于链表的在内存中是散乱的,所以,每个元素除了存储自身的地址,还需要存储指向上一个、下一个元素的地址,增删快、查找慢;Vector:基于数组实现,和ArrayList不同的是,Vector初始长度10,扩容时为2倍,而ArrayList初始长度0,扩容为原来的1.5倍

Set接口有三个实现类:HashSet:基于HashMap实现,Map中所有的key组成了HashSet,由于HashMap中key不允许重复,所有,HashSet的值,不可以重复;LinkedHashSet:继承于HashSet,同时基于LinkedHashMap实现,底层除了维护一个hash表,还维护了一个链表,保证有序;TreeSet,底层是一个红黑树,可以对元素进行排序,一种是自然排序、一种是定制排序

Map接口有五个实现类:HashMap:底层是一个数组+链表/红黑树的结构,HashTable:底层数组+链表/红黑树,由于多有的关键方法都使用了synchronized关键字修饰,所以线程安全,但是效率不高;LinkedHashMap,继承与HashMap,底层在HashMap的基础上,使用了双向链表,来保证元素的顺序;TreeMap,底层红黑树,可以对元素根据key进行排序,一种自然排序,一种定制排序;Properties:继承于HashTable,所以线程安全,常用来保存配置

>>>HashMap和HashTable的区别

1、HashMap线程不安全、HashTable线程安全

2、HashTable不允许存放null的键值对,HashMap可以

3、HashMap的默认长度16,HashTable默认长度11

4、扩容时,HashMap扩容原来的2倍,HashTable扩容原来的2倍+1

>>>HashMap在jdk1.7和1.8的区别

1、7中,初始创建一个长度为16的数组,8中在第一次添加元素的时候才会创建16的数组

2、7中底层是一个Entry数组,8中底层是一个Node数组

3、7中底层只有数组+链表,8中加入红黑树

4、链表中的指向,七上八下,7中指向的是旧元素,8中指向的是新元素

>>>ArrayList和LinkedList的区别

ArrayList使用了数组的实现,可以认为ArrayList里面封装了对内部数组的操作,比如添加、删除、修改,以及对数组的动态扩容

LinkedList使用循环双向链表的数据结构,链表由一系列的表项连接而成,在JDK中,无论LinkedList中是否有值,链表的内部都有一个header表项,即表示了链表的开始,也表示了链表的结尾表项header后驱表是链表的第一个元素,header表项的前去表项,就是链表的最后一个元素

>>>

第一种,体现了Java面向对象的多态,也就是父类或接口引用指向子类或实现类对象,ArrayList有但是List没有的方法和属性,例如trimToSize方法,就不能用了;第二种,是ArrayList的对象指向ArrayList的引用,所有的方法和属性都可以使用

>>>要对集合更新操作时,ArrayList和LinkedList哪个更合适

首先,ArrayList基于动态数组的数据结构,LinkedList基于链表的数据结构

如果对集合访问,ArrayList绝对比LinkedList效率高,因为LinkedList需要移动指针

如果对集合增删,那么LinkedList更加具有优势,因为ArrayList需要移动数据

>>>Map中的key和value可以为null吗

HashMap对象的key、value都可以为null

HashTable对象的key、value都不可以为null

而且二者的key都不可以重复,如果添加了相同key的键值对,后面的value会自动覆盖前面的value

Java的多线程和并发库

>>>ThreadLocal

ThreadLocal的作用和目的是:实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据

>>>Java中,线程关闭就成了死线程,不可以再启动,启动就会报错,怎么解决这个问题

可以使用Executors.newSingleThreadExecutor()来再次启动一个线程

>>>多线程的创建方式

1、继承Thread类,但是Thread本质上实现了Runnable接口的一个实例,他代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start实例方法。start方法是一个native方法,它将启动一个新的线程,并执行run方法

2、实现Runnable接口,实现run方法,实例化Thread,并且构造传入Runnable实例

3、实现Callable接口,并且实现call方法,然后将实现Callable接口的实例传入FutureTask构造,实例化FutrueTask对象,然后将FutureTask对象传入Thread构造器,实例化Thread对象,调用Thread的start方法开启线程,调用FutrueTask对象的get方法可以获取call方法的返回值

4、使用线程池

>>>在Java中wait和sleep方法的区别

1、wait方法会释放锁,sleep方法不会释放锁

2、wait方法为Object中的native方法,sleep方法为Thread中的static native方法

3、sleep方法可以在程序的任何位置使用,wait方法必须使用在同步代码块或同步方法中

>>>synchronized和volatile关键字的作用

一旦一个变量被volatile修饰后,那么就有两个作用

1、保证了不同线程对这个变量操作的可见性,即一个线程修改了这个变量,新值对其他线程是可见的

2、禁止进行指令重排

一、本质

volatile本质上就是告诉jvm当前变量在寄存器(工作内存)中的值不确定的,需要到贮存中读取

synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞

二、作用级别

volatile只可以用在变量级别

synchronized可以使用在变量、方法、类级别

三、可见性、原子性

volatile只可以保证修改的可见性,不可以保证修改的原子性

synchronized既可以保证变量修改的可见性、也可以保证原子性

四、线程阻塞

volatile不会导致线程阻塞

synchronized会导致线程阻塞

五、编译器优化

volatile标记的变量不会被编译器优化

synchronized标记的变量可以被编译器优化

>>>什么是线程池,如何使用

线程池就是事先将多个线程放在一个容器中,当使用的时候就不用new线程,而是直接去池中拿线程就可以了,节省了开辟新的线程的时间,大大提高了代码执行效率

在JUC包中的Executors线程池工厂类,提供了四种线程池,分别是单线程线程池newSingleThreadExecutor、固定数目的线程池newFixedThreadPool、可缓存线程的线程池newCachedThreadPool、定时周期执行的线程池newScheduledThreadPool

但是,根据阿里开发手册,不建议使用以上方法进行创建线程池,建议使用new ThreadPoolExecutor();进行创建,可以自己控制相关的参数,明确线程运行规则

>>>对线程池的理解

1、降低资源消耗,通过反复利用已经创建的线程降低线程的创建和销毁造成的小号

2、提高响应速度,任务到达时,任务可以不需要等待线程的创建就可以立即执行

3、提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统的资源,还会降低系统的稳定性,使用线程池,可以统一进行分配、调优、监控

>>>线程池的启动策略(线程池的启动、运行流程)

1、线程池的创建,new ThreadPoolExecutor()中一共有七个参数:

参数1:线程池的核心线程数corePoolSize

参数2:线程池的最大线程数maxPoolSize

参数3:线程池除了核心线程外,其他线程的超时时间

参数4:超时时间的单位

参数5:用于存放任务的阻塞队列

参数6:用于创建线程的线程工厂

参数7:用于拒绝任务的拒绝策略

2、线程池中调用execute方法添加任务的执行流程

a、当线程池中核心线程有空闲,那么直接使用核心线程进行任务

b、如果线程池中核心线程没有空闲,如果阻塞队列没有满,那么将任务放入阻塞队列

c、如果阻塞队列已经满了,而且线程池中的线程数小于最大线程数,那么新建线程,进行任务

d、如果线程池中,核心线程、最大线程、阻塞队列都满了,那么抛出异常

>>>什么情况下导致死锁,遇到线程死锁怎么解决

死锁指的是多个线程因为竞争资源而造成的一个僵局,如果没有外力作用,那么进程无法向前推进

产生死锁的条件:

1、互斥条件:线程中所分配的资源必须进行排他控制,也就是这个资源在同一时间内,只可以被一个线程占用,如果其他线程请求这个资源,只可以等待

2、不剥夺条件:也就是线程获得的资源不可以被其他线程强行夺走,只可以是当前线程自己释放

3、请求和保持条件:线程已经保持了至少一个资源,但是又提出新的资源请求,这个资源已经被其他线程占用,那么此时进程阻塞,但是线程不释放自己持有的资源

4、循环等待条件:在一个由线程组成的循环等待链中,链中的每一个线程都以获得一个资源,而且这个资源被下一个线程所请求,同样这个线程也在上一个线程所持有的资源

如何避免线程:

1、清晰加锁顺序,避免出现循环等待

2、线程时限,线程尝试获取锁的时候一定加一定的时限,超时的话则放弃自己对锁的请求,同时释放自己的锁

>>>Java中多线程的通信怎么实现

1、共享变量,例如一个boolean类型的变量

2、wait/notify机制,通过一个线程wait并notify其他线程

>>>线程和进程的区别

进程:具有一个独立功能的程序,是操作系统进行资源分配和调度的一个独立单位

线程:是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的可以独立运行的基本单位

>>>同步线程及线程调度相关的方法

wait():使一个线程处于等待(阻塞)状态,并且释放当前线程持有的锁

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,此方法不会使线程释放锁

notify():唤醒一个处于等待状态的线程,当然在调用这个方法时,并不能明确唤醒的是哪一个线程

notifyAll():唤醒所有处于等待状态的线程,该方法并不是将锁给所有线程,而只是唤醒,让这些线程自己竞争

>>>启动一个线程是调用run()还是调用start()

启动一个线程调用的是start方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行,但是并不一定会立即执行

run()方法是启动线程后进行回调的方法,由虚拟机执行

Java的内部类

>>>静态内部类(静态嵌套类)和非静态内部类(内部类)的区别

静态内部类:是被声明再一个类中的静态的内部类,它可以不依赖外部类实例被实例化

非静态内部类:需要在外部类实例化之后才可以进行实例化

JavaSE高级

Java中的反射

>>>对Java中反射的理解

Java的反射首先是能够获取到Java中需要反射的类的字节码,获取字节码有三种方式,1、Class.forName(className)2、类名.class3、this.getClass()。然后将字节码中的方法、变量、构造等映射为相应的Method、Filed、Constructor对象,这些对象有丰富的方法可以使用

Java中的代理

>>>写一个ArrayList的动态代理类
List<String> list = new ArrayList<>();

List<String> proxy = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(), list.getClass().getInterfaces(), new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(list, args);
    }
});
proxy.add("你好");
System.out.println(list);
>>>动静态代理的区别,使用场景

1、静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类

2、静态代理再程序运行之前就知道代理的是什么,而动态代理只有到运行才知道

3、两种代理都需要实现接口,实际上都是代理接口

4、除了Proxy实现动态代理,还有一种是CGLIB代理,这种代理不需要业务类实现接口,而是通过派生的子类实现代理,通过运行时,动态修改字节码达到修改类的目的。

Java中的设计模式&回收机制

>>>你所知道的设计模式有哪些

Java中一般认为有23中设计模式

创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式

结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式

行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

>>>JVM垃圾回收机制和常见算法

GC整体分为发现无用对象和回收无用对象两个部分组成

搜索算法

1、引用计数器算法(弃用)

引用计数器算法就是给每个对象设置一个计数器,当对象被引用一次,计数器加一,引用失效一个,计数器建议,当该对象的计数器为零时,JVM就认为该对象无用了,可以进行回收

2、根搜索算法(使用中)

根搜索算法,是通过一些“GC Roots”对象作为起点,从这些起点开始向下搜索,搜索通过的路径成为引用链,当一个对象没有被GC Roots的引用链链接的时候,声明这个对象是不可用的

GC Roots对象包括

a、虚拟机栈(栈帧中的本地变量表)中引用的对象

b、方法区中类的静态属性引用的对象

c、方法区中常量引用的对象

d、本地方法栈中Native方法引用的对象

搜索到无用对象后,就可以进行回收了,GC的回收算法有四种

1、标记清除算法,这种算法是直接将对象从内存中移除,但是会产生大片的不连续空间,也就是内存碎片

2、复制算法,这种算法,就是将内存划分为两个区域,大小相同,每次将一个区域的对象复制到另一个区域,过程中,将无用的对象移除,这种算法不会造成内存不连续

3、标记整理算法,这种算法是将无用对象回收后,然后将内存中的对象统一移向一段,不会造成内存不连续,一般用来回收老年代

4、分代收集,这种是将对象按照存活时间分为新生代和老年代,然后根据对象存活特点,每代采用不同的算法进行回收

>>>Java内存模型

Java虚拟机将其管辖的内存大致分为三个逻辑部分:方法区、Java栈、Java堆

1、方法区是静态分配的,编译器将变量绑定再某个存储位置上,而且这些绑定不会在运行时改变。常量池,源代码中的命名常量、String常量、static变量保存在方法区

2、Java栈是一个逻辑概念,特点先进后出,内存中的空间可能是连续的,也可能是不连续的

3、Java堆在运行时,进行存储空间分配和回收内存管理

>>>Java的内存分配

通常我们定义的基本数据类型变量、对象的引用、还有函数调用的现场保存都使用JVM中的栈空间,而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都是采用分代算法,所以对空间还可以细分为新生代和老年代,再具体新生代可以分为Eden、Survivor(分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量如直接书写的100、“hello”和常量都是放在常量池中,常量池是方法区的一部分。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池不足会引发OutOfMemoryError

Java的类加载器

>>>Java类加载器种类有哪些

1、根类加载器(BootStrapClassLoader):C++写的,加载Java的核心类

2、扩展类加载器(ExtClassLoader):加载位置jre\lib\ext,加载Java的扩展类

3、系统、程序类加载器(AppClassLoader):加载位置classpath中

4、自定义加载器:必须继承ClassLoader

>>>类什么时候被初始化

1、类实例化的时候

2、调用类或接口的静态变量

3、调用类的静态方法

4、类的反射

5、初始化一个类的子类,首先初始化子类的父类

6、JVM启动标明的启动类,即文件名和类名相同的那个类

>>>ClassLoader双亲委托机制

Java是一种类型安全的语言,Java程序的.java文件编译完成会生成.class文件,而.class文件被类加载器所加载,当加载的时候,首先AppClassLoader加载,它并不会自己尝试加载,而是把加载请求交给父类加载器ExtClassLoader,ExtClassLoader加载的时候也不会自己尝试加载,而是把加载请求委托给BootStrapClassLoader

如果BootStrapClassLoader加载失败,也就是没有在jre/lib中找到该class,那么会使用ExtClassLoader尝试加载,如果ExtClassLoader也加载失败,那么使用AppClassLoader加载,如果也加载失败,那么就会抛出ClassNotFoundException

>>>描述JVM加载class

JVM中类的装载由类加载器ClassLoader和它的子类实现,Java中类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类

由于Java的跨平台性,经编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接和初始化。类的加载是指类的.class文件中的数据读入到内存中,通常时创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后进入连接阶段,这个阶段包括验证、准备(伪静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用),最后JVM对类进行初始化

>>>获取Class对象的方式

1、类.class

2、对象.getClass()

3、Class.formName()

4、ClassLoader.getSystemClassLoader().loadClass("Person")

JVM基础知识

>>>既然有GC机制,为什么还会出现内存泄漏

Java中存在内存泄漏风险的因素:

1、各种静态容器类,由于这些容器是静态的,不会被GC回收,其中的元素引用的对象,也不会被回收

2、单例造成内存泄漏,单例对象一旦实例化,那么再JVM的声明周期中存在

3、各种的连接

例如数据库的连接,网络连接,IO连接,这些实例属于持久态,如果没有显示的关闭,对象无法被GC回收

>>>在开发中遇到过内存溢出吗?原因、解决方法

引起内存溢出的原因有很多种,常见的:

1、内存中加载数据量过大,如一次从数据库取出过多数据

2、集合类中有对对象的引用,使用完没有清空,导致JVM不能进行回收

3、代码中出现死循环或循环产生的过多重复的实体对象

4、使用第三方软件的BUG

5、启动参数内存值设定过小

解决方法:

1、修改JVM的启动参数,直接增加内存

2、检查错误日志,查看OutOfMemory错误前是否存在其他异常或错误

3、对代码进行走查和分析,找出可能发生内存溢出的位置

重点排查以下几点:

1、检查对数据库查询中,是否有一次获得大量数据的查询,一般来说,如果一次取十万条数据,那么可能导致内存溢出,这个问题比较隐蔽,因为在上线前,数据量不大,所以不容易出现问题,因此对于数据库的查询,尽量采用分页

2、检查代码中是否存在死循环或递归调用

3、检查代码中是否存在,集合类使用完后,保存了大量的对象,但是没有清空

Java8的新特性及使用

>>>什么是Stream流

拿常见的集合作为比较,集合更关注与数据的存储,和内存打交道,但是Stream更关注数据的运算,和CPU打交道,Stream流本身不具备存储能力,Stream流并不会改变原对象,而是创建一个新的对象,Stream流是懒加载,也就是需要结果的时候,才会进行运算

>>>创建一个Stream流有几种方式

对于数组,可以通过Arrays.stream创建,或者通过Stream.of,这个方法的形参是可变参数,可以传入一个数组或多个参数

对于集合,可以通过集合对象.stream()这个方法创建的顺序流,也可以创建并行流集合对象.parallelStream()

JavaWEB基础

JDBC技术

>>>说下原生jdbc操作数据库的流程

1、Class.formName()加载数据库连接驱动

2、DriverManager.getConnection()获取数据连接对象

3、根据SQL获取sql会话对象,一般有两种,statement、PreParedStatement

4、执行SQL处理结果集,执行SQL前如果有参数值,就设置参数值

5、关闭结果集,关闭会话,关闭连接

>>>为什么使用PreparedStatement

1、PreparedStatement接口继承Statement,PreparedStatement实例包换已编译的SQL语句,所以执速度比Statement快

2、作为Statement的子类,PreparedStatement继承了Statement的所有功能,三种方法execute、executeQuery和executeUpdate

3、在JDBC中,任何时候都不要使用Statement,因为:

a、代码的可读性和维护性,Statement需要拼接SQL,但是PreparedStatement不会

b、PreparedStatement尽最大的可能提高了性能,DB有缓存机制,相同的预编译语句再次被调用不会再次编译

c、最重要的一点就是极大的提高了安全性,Statement容易被SQL注入,而PreparedStatement传入的内容不会和sql语句发生匹配关系

>>>关系数据的连接池机制是什么

前提是为数据库连接建立一个缓冲池

1、从连接池获取或创建可用链接

2、使用完毕之后,把连接返回给连接池

3、在系统关闭前,断开所有的连接并释放连接占用的系统资源

4、能够处理无效的连接,限制连接池中连接总数不低于或不超过某一个值

HTTP协议

>>>http的长连接和短连接

HTTP协议有HTTP/1.0和HTTP/1.1版本,1.1默认保持长连接,数据传输完成保持TCP连接不会断开,等待在同域名下继续使用这个通道传输数据,反之就是短连接

1.0默认使用短连接,也就是浏览器和服务器每一次进行HTTP操作,就建立一次连接,任务结束就中断连接

>>>HTTP/1.1和HTTP/1.0的区别

可扩展性

1、1.1中,在消息中增加了版本号,用于兼容性判断

2、1.1增加了OPTIONS方法,同允许客户端获取一个服务器支持的方法列表

3、为了与未来的协议规范兼容,1.1在请求消息中包含了Upgrade头域,通过该头域,客户端可以让服务器知道同可以支持的其他备用通信协议,服务器可以据此进行协议转换,使用备用协议与客户端进行通信

缓存

在 HTTP/1.0 中,使用 Expire 头域来判断资源的 fresh 或 stale,并使用条件请求(conditional request)来判断资源是否仍有效。HTTP/1.1 在 1.0 的基础上加入了一些 cache 的新特性,当缓存对象的 Age 超过 Expire 时变为stale 对象,cache 不需要直接抛弃 stale 对象,而是与源服务器进行重新激活(revalidation)

带宽优化

HTTP/1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了。例如,客户端只需要显示一个文档的部分内容,又比如下载大文件时需要支持断点续传功能,而不是在发生断连后不得不重新下载完整的包

长连接

HTTP 1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟

>>>http常见的状态码

200:OK,客户端请求成功

301:永久移除,请求的URL已经被移走,Response中应该包含一个Location URL,说明资源现在所处的位置

302:重定向

400:客户端请求语法错误,服务器不能理解

401:请求未经授权

403:服务器收到请求,但是拒绝服务

404:请求资源不存在

500:服务器异常

503:服务器当前不能处理请求,一段时间后可能会恢复

>>>GET和POST的区别

1、get的请求参数会放在URL后面,但是post会放在请求体中

2、get的参数长度最大1024字节,get的参数限制实际上是URL的限制,取决于操作系统和浏览器,但是post理论上没有限制

3、post的安全性比get高,get一般用来对服务器索取数据,post用来提交数据

>>>http中重定向和转发的区别

本质上,转发是服务器的行为,重定向是客户端的行为

重定向的特点是,两次请求,浏览器的地址会发生变化,可以访问自己web外的资源,但是传输的数据丢失

转发的话,一次请求,浏览器地址不变,访问的是自己web的资源,数据不会丢失

Cookie和Session

>>>Cookie和Session的区别

Cookie保存在浏览器可就是客户端,web服务器发送给浏览器的一块信息,浏览器会在本地文件中给每一个web服务器存储cookie,以后再向服务器发送请求的时候,都会携带该服务器的cookie

Session存储在服务器,Session一般存储特定用户会话所需要的属性和配置信息,当用户在应用程序的Web页跳转的时候,存储在Session中的信息不会丢失

区别就是,无论浏览器怎么做,Session都可以正常工作,但是如果浏览器禁用Cookie,那么无法使用Cookie;存储数据方面,session可以存储任意Java对象,但是cookie只可以存储字符串

>>>单点登录中,cookie被禁用了怎么办

单点登录的原理,就是后端生成的令牌,这个令牌可以存储在cookie中,但是也可以存储在响应数据中,由前端保存

JSP

>>>什么是JSP,什么是Servlet,有什么区别

JSP本质就是一个servlet,他是Servlet的一个特殊形式,每个jsp页面都是一个servlet实例

Servlet是一个由Java提供用于开发web服务器应用程序的组件,运行在服务端,由servlet容器管理,用于生成动态内容,一个Servlet实例,是实现了特殊接口Servlet的Java类,所有自定义个servlet必须实现Servlet接口

区别:

1、jsp是html页面中内嵌java代码,侧重页面展示

2、Servlet是html代码和java代码分离,侧重逻辑控制mvc设计思想中jsp位于视图层,servlet位于控制层

jsp的执行流程:

JVM只可以识别Java类,并不能识别JSP代码,所以在web容器收到.jsp后缀的url请求时,会将访问请求交给tomcat中Jsp引擎处理,每个jsp页面第一次被访问的时候,jsp引擎将jsp代码解释为一个servlet源程序,编译为class文件,再有web容器servlet引擎去装载执行servlet程序,实现页面交互

>>>jsp有哪些域对象和内置对象及他们的作用

四大域对象:

pageContext:作用域为当前的jsp页面

request:一次请求范围内有效

session:一次会话中有效

application context:在一个web中使用

九个内置对象:

Request

Response

Session

Out

PageContext

Page

Exception

Application

Config

JavaWEB高级

Filter和Listener

>>>什么是Filter,在哪用过

Filter是Web三大组件(Filter、Listener、Servlet)之一,可以对请求和响应进行拦截,对路径、数据进行过滤,一般在使用tomcat8之前的版本,post请求的数据会出现乱码,所以使用Filter对数据进行处理,解决乱码,也可以使用Filter进行权限控制

>>>什么是Listener,在哪用过

Listener是Web三大组件之一,可以对Web程序中的事件进行监听,例如创建、删除、修改对象等,并且触发相应事件,JavaWeb提供了八种监听器,这八种监听器可以分为三类,监听Request、Context、Session对象的创建,监听对象属性的变化,监听Session内对象的变化,一般可以用来监听网站的登陆人数

Linux

>>>说出几个Linux的常用命令

列出文件列表:ls

创建目录和移除目录:mkdir、rmdir

用于显示文件后几行内容:tail

打包:tar -xvf

打包并压缩:tar -zcvf

查找字符串:grep

显示当前所在目录:pwd

创建空文件:touch

编辑:vim、vi

>>>Linux如何查看日志

动态打印日志信息:tail -f日志文件

>>>Linux怎么关闭进程

可以使用ps查看进程PID,用kill终止进程

ps用于查看当前正在运行的进程

grep搜索

例如:ps -ef|grep java

数据库

MySQL

>>>SQL的select语句完整执行顺序

1、from子句组装来自不同数据源的数据

2、where子句基于条件,对记录记录行进行筛选

3、group by子句将数据划分为多个分组

4、使用聚合函数进行计算

5、使用having子句筛选分组

6、计算所有的表达式

7、select的字段

8、使用order by对结果集进行排序

>>>SQL的聚合函数

聚合函数用来对一组值进行计算,并且返回单一值的函数,经常与select语句中的group by一起使用

avg:平均值

count:个数

max:最大值

min:最小值

sum:求和,只可以用在数字列

>>>SQL的sql注入问题

一般出现在表单输入恶意的sql语句,例如,登陆的时候,用户名为a or 1=1,那么这个时候sql条件部分恒成立

防止sql注入:

1、使用预编译语句,也就是在sql中使用占位符,然后传递参数,preparedStatement使用的就是这种

2、Mybatis框架中使用井号(#)也可以防止sql注入

>>>MySQL优化

1、一般明知道查询结果只会有一条,那么使用limit 1,这样MySQL引擎查询到一条就会立即终止,防止全表扫描

2、正确的选择数据库引擎,mysql有两种引擎,MyISAM和InnoDB

MyISAM适合大量用来查询的应用,对写不友好,因为你有的时候哪怕只是update一个字段,也会锁整个表,别的进程就算读,也需要等update操作完才可以,另外,MyISAM对于select count(*)这种操作效率非常高,MyISAM不支持事务或外键约束

InnoDB支持行锁和事务,所以在写的这方面比较优秀

>>>事务的四大特征

1、原子性:事务中的所有操作是一个最小的单位,要么全部成功,要么全部失败

2、一致性:事务开始和结束,数据库的约束没有破坏,多个结点的数据保持一致

3、隔离性:隔离状态下执行事务,多个事务之间相互不可以干扰

4、持久性:事务完成后的更改操作永久保存在数据库,而不会回滚消失

>>>MySQL的四种隔离级别

读未提交:未提交读隔离级别也叫读脏,就是事务可以读取其他事务还没有提交的数据

读已提交:在其他数据库系统比如Oracal默认的隔离级别就是提交读,读已提交就是事务没有提交之前所做的修改其他的事务是不可见的

可重复读:保证同一个事物在多次相同的查询结果是一致的,比如一个事务一开始查询了一条记录然后过了几秒钟后又执行相同的查询,这两次查询的结果是相同的,可重复读也是MySQL的默认隔离级别

可串行化:可串行化也就是保证了读取范围内没有新的数据插入,比如事务第一次查询得到某个范围的数据,第二次查询也同样得到相同范围的数据,中间没有新的数据插入到该范围中

>>>MySQL语句优化

1、where子句可以对字段进行null值判断吗

可以,比如select id from t where num is null这样的sql是可以的,但是最好不要给数据库留null,尽可能使用NOT NULL填充数据库,因为例如char(100)中,即使这个字段的值为null,也会占用100字符的空间,但是如果是varchar,那么不会占用空间,对于数值类型,如果可以的话,那么设置默认值为0

2、select * from admin left join log on admin.admin_id=log.admin_id where log.admin_id>10如何优化

这sql语句的优化,应该使用小表驱动大表的方法,也就是将对于admin的查询作为子查询,查询admin中id大于10的,然后再使用结果查询log表,也就是select * from (select * from admin where admin_id>10) T1 left join log on T1.admin_id=log.admin_id

3、limit基数比较大的时候,使用between

例如select * from admin order by admin_id limit 100000,10可以优化为select * from admin where admin_id between 100000 and 100010 order by admin_id

4、避免在索引列上进行任何操作,例如数据类型转换,由于MySQL优化器在优化的时候自动进行数据类型转换,比如一个字段的类型是数值类型,但是我们传入的是一个字符串类型的数值,那么会导致索引失效

>>>如何提高MySQL的安全性

1、如果MySQL客户端和服务端的链接需要跨越并通过不可信任的网络,那么需要使用ssh隧道来加密该链接的通信

2、删除MySQL默认的test数据库

3、修改root的用户名

4、设置MySQL中只有root用户可以访问MySQL主数据库的user表

5、修改MySQL的3306端口,避免端口扫描工具

Oracle

框架

SpringMVC

>>>SpringMVC的工作原理

1、用户向服务器发送请求,请求被SpringMVC的前端控制器DispatchServlet捕获

2、DispatcherServlet对请求URL进行解析,得到请求资源标识符URL,然后根据该URL调用HandlerMapping将请求映射到处理器HandlerExcutionChain

3、DispatchServlet根据获得Handler选择一个合适的HandlerAdapter适配器处理

4、Handler对数据处理完成后将返回一个ModelAndView对象给DispatchServlet

5、Handler返回的ModelAndView只是一个逻辑视图并不是一个真正的视图,DispatchServlet通过ViewResolver视图解析器将逻辑视图转化为真正的视图View

6、DispatcherServlet通过Model解析出ModelAndView中的参数进行解析最终展现出完整的View并返回给客户端

>>>SpringMVC的常用注解

@RequestMapping:用于请求url映射

@RequestBody:实现接受http请求的json数据,将json数据转化为java对象

@ResponseBody:实现将Controller方法返回对象转化为json响应给客户端

>>>如何开启注解处理器和适配器

项目中一般会在springmvc的xml文件中通过开启<mvc:annotation-driven>来实现注解处理器和适配器的开启

>>>怎么解决get和post乱码的问题

解决post请求乱码,可以在web.xml中配置一个CharaterEncodingFilter过滤器,设置为utf-8

get请求乱码,有两种方法可以解决:

1、修改tomcat配置文件添加编码与工程编码一致

2、使用字节数组,对参数进行重新编码

Spring

>>>谈谈对spring的理解

Spring是一个开源的框架,主要是为了简化企业级应用开发,可以使用简单的JavaBean实现以前只有EJB才能实现的功能,Spring是一个IOC和AOP容器框架

Spring容器的核心:

IOC控制反转,也就是在以前的传统java开发,我们需要通过new或者反射来创建对象,但是在Spring中,容器使用了工厂模式为我们创建所需要的对象,不需要我们自己创建,也就是把Bean的管理交给了容器管理;还有就是依赖注入DI,spring使用Bean对象的set或有参构造,在容器创建时将属性进行设值

AOP面向切面编程,与面向对象不同的是,面向对象是将事务纵向抽成了一个个的对象,而面向切面是将一个个对象相同的地方,横向抽成一个切面,并且在这个切面上进行一些如权限控制、日志等公共操作处理,AOP的底层是动态代理,如果是接口的话,使用的是JDK动态代理如果是类采用的是CGLIB进行动态代理,所以说AOP是OOP的扩展,而不是替代

>>>Spring中的设计模式

1、单例模式:spring中的代理模式,如果目标对象实现了接口,那么spring使用jdk的Proxy类代理,如果目标没有实现接口,那么使用CGLIB库生成目标类的子类,生成的对象默认为单例模式

2、前端控制器模式:spring提供了前端控制器DispatherServlet对请求进行分发

3、工厂模式:创建对象的逻辑不会对客户端进行暴露,而是通过使用接口来指向创建的对象,使用BeanFactory创建对象实例

>>>spring的常用注解

在spring2.5以后,可以使用注解来配置依赖注入,可以使用注解代替xml中的bean描述,但是注解模式默认是关闭的,所以需要在spring的核心配置文件中进行配置<context:annotation-config/>

常用的注解:

@Autowried

@Resource

@Aspect

@Component

>>>spring bean的生命周期

bean的定义:在spring核心配置文件中,使用<bean>定义

1、通过构造器,实例化,默认调用无参构造

2、通过set对对象的属性进行赋值

3、将bean实例传递给bean的后置处理器方法

4、调用bean的初始化方法

5、将bean实例传递给bean的后置处理器方法

6、bean创建完毕,可以使用

7、调用bean的销毁方法

>>>spring可以帮我们做什么

1、可以帮我们根据配置文件创建、组装对象之间的依赖关系

2、spring面向切面编程,可以帮助我们无耦合实现日志记录、性能统计等

3、spring可以非常简单的帮我们管理数据库事务,我们只需要获取连接,执行sql

4、spring可以和第三方数据库访问框架集成,而且自己也提供了一套jdbc访问模板

5、spring可以和第三方web框架集成,自己也提供了SpringMVC框架

>>>spring的事务

spring提供了声明式事务管理,和传统的编程式事务管理方式相比,无侵入,并不会和业务代码耦合,使用AOP的思想,所以大大的提高了系统的可维护性

经常使用的方式,实在spring核心配置文件中注入需要的transactionalManager,然后通过<context:annotation-config/>开启spring注解功能,然后在需要添加事物的方法或类上添加注解@Transactional开启事务

>>>BeanFactory的常用实现类

BeanFactory一般是spring内部使用的一个接口,我们经常使用的是继承了BeanFactory接口的ApplicationContext接口,常见的实现类有,ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext

>>>解释SpringJDBC、SpringDAO和SpringORM

springdao不是一个模块,而是只要写dao操作、写好dao的操作规范以后,使用@Repository进行注解

springjdbc提供了jdbc模板类,移除了连接代码,只需要写好sql以及相关参数

springorm是一个包含了多个持久层基数的总括模板

>>>spring的配置文件有什么用

spring配置文件是一个xml文件,文件里面包含了类信息,描述了如何配置,如何相互调用

>>>什么是springIOC容器

IOC即控制反转,springIOC负责创建对象,管理对象,通过依赖注入DI,装配对象,配置对象,也就是管理了对象的整个生命周期

>>>IOC的优点

IOC可以把应用的代码量降到最低,容易进行测试,而且默认单例,大大减少了代码量,可以以最小的代价和最小的侵入性松散代码的耦合,而且IOC支持服务的饿汉初始化以及懒加载

>>>什么是spring的依赖注入

在传统的java开发中,我们在一个类中需要另一个类的属性或方法,做法就是new一个实例,然后调用,但是new的实例不好管理,所以使用DI,依赖类的实例不再需要我们new,而是spring容器帮我们new然后注入

>>>什么是spring beans

spring beans是那些形成spring应用主干的java对象,它们被springIOC容器初始化、装配、管理,这些bean通过容器中配置的元数据创建

>>>怎么定义类的作用域

当定义一个bean标签在spring里,可以通过该标签的scope属性来定义作用域,如果需要spring在容器在每次调用该对象的时候都产生一个新的对象,那么可以将scope属性值指定为prototype,如果需要单例的,那么属性为singleton,这个也是默认的

>>>spring支持几种bean的作用域

五种:

singleton:单例,也是默认的

prototype:一个bean可以有多个实例

request:web环境下,作用域是一次请求

session:web环境下,作用于是一次会话

global-session:web环境下,作用域全局会话

>>>spring的单例bean线程安全吗

线程不安全

>>>spring中如何注入一个java集合

list标签,可以注入一列值,允许相同的值

set标签,注入一组值,但是不允许相同

map,注入一组键值对,键值随意

props,注入一组键值对,但是都只能是字符串

>>>不同方式的自动装配

四种自动装配方式,可以用来指导spring容器自动装配的方式进行依赖注入

1、no:默认方式不进行自动装配,需要显式的设置ref属性进行装配

2、byName:通过参数名进行装配,spring容器在配置文件中发现bean的autowired属性被设置为byname,之后容器尝试匹配、装配

3、byType:通过参数类型自动装配,spring容器在配置文件中发现bean的autowired属性设置为bytype,之后容器尝试匹配、装配相同类型的bean,如果有多个,抛出异常

4、default:通过对beans标签声明全局注入方式,确定bean标签的注入方式

>>>简单解释Spring的AOP

AOP,就是面向切面编程,对于常见的面向对象编程OOP来说,OOP通过引入封装、集成、多态来建立对象的层次结构,但是通常OOP是一种纵向的编程,并不支持定义横向关系,所以使用AOP,可以完成例如日志功能,日志功能的代码,可以横向的放入在多个对象的层次中,而不会影响原来的功能代码,可以降低代码的耦合和重复代码,这些添加增强的地方也就是切面,在SpringAOP里面,可以通过@Aspect注解来定义增强类

>>>spring两种代理JDK和CGLIB的区别

JDK的动态代理,是利用反射机制生成一个实现代理接口的匿名类,在具体调用方法前调用InvokeHandler处理,而CGLIB动态代理是利用开源包,对代理对象类的class文件加载,通过修改其字节码生成子类来处理

1、如果目标对象实现了接口,那么默认使用JDK来动态代理

2、如果对象没有实现接口,那么必须采用CGLIB

>>>Spring的通知是什么,有哪几种

通知就是在方法的前或后进行的动作,也就是程序执行时,通过SpringAOP触发的代码段

一共有五种通知

前置通知、后置通知、环绕通知、后置返回通知、异常通知

Shiro

>>>什么是Shiro

Apache Shiro是一个java的安全框架,使用shiro不但可以在javaSE环境也可以使用在javaEE环境,Shiro可以帮助我们完成认证、授权、加密、会话管理、与Web集成、缓存

Shiro三个核心的组件:

1、subject,主体对象,它不仅仅指人,也可以是任何与程序交互的东西

2、SecurityManager:他是Shiro框架的核心,Shiro也是通过安全管理器,完成对各种服务的管理

3、Realm:充当了Shiro与应用安全数据的桥梁和连接器,也就是用户在认证和授权的时候,需要在配置的Realm中查询

>>>Shiro的运行原理

1、应用程序如果需要进行权限控制,那么调用Subject的API

2、Subejct为主体,所有的Subject都绑定在SecurityManager上,与Subject的所有交互,都会委托给SecurityManager

3、Realm域,需要在Realm中,对于用户的身份以及权限进行验证

Mybatis

>>>Mybatis中#和$的区别

#相当于对数据加双引号,$相当于直接拼接数据

也就是在传入数据的时候,会进行数据转换,所以如果是数值列,而且不担心出现sql注入的问题的话,那么使用$,因为如果#传入数值99,那么传入的sql为'99',这样会导致该字段的索引失效

>>>JDBC的不足之处,Mybatis怎么解决

1、数据库连接的创建和销毁过于频繁,浪费系统性能,使用数据库连接池,可以解决这个问题

2、sql语句不容易维护,而使用mybatis的时候,在mapper的xml文件中进行维护

3、sql传参过于麻烦,需要将参数和占位符一一对应,而mybatis可以使用自动映射

4、对于结果集的处理也比较麻烦,mybatis可以将数据的记录解析为对象

>>>mybatis的mapper接口调用要求

1、mapper接口方法名和xml中的sql的id相同

2、mapper接口方法的参数类型和xml中parameterType类型相同

3、接口方法的返回类型需要和xml中的resultType类型相同

4、xml中的namespace是mapper接口的类路径

>>>mabatis的一级缓存和二级缓存

一级缓存:作用域是一次SqlSession,如果session被flush或close,那么这个缓存将会被清空

二级缓存和以及缓存的机制相同,只不过二级缓存是基于namespace的,这样多个线程的会话,可以使用同一个缓存,如果进行了cud操作,那么该作用于下的所有select的缓存,都会被clear

>>>Mybatis怎么在insert的时候返回主键id

数据库为mysql的时候,可以在insert标签设置属性,keyproperty为需要返回的属性,useGeneratedKeys改为true,表示id自增

如果数据库是Oracle,因为oracle没有自增,所以不可以使用useGeneratedKeys,使用selectKey标签获取Id,赋值到对象的属性

最新技术

Redis

>>>Redis的特点

Redis是使用C语言编写的,典型的NoSql数据库,基于key-value类型的内存存储系统,一共有五种数据类型:list、set、zset、hash、string

由于Redis是默认是纯内存操作,所以每秒可以处理10万次读写操作,是已知速度最快的key-value DB

但是Redis的缺点也是由于纯内存操作,不能进行海量数据的读写,所以Redis适合用在较小数据量的高性能操作上

>>>redis为什么把数据放在内存上

redis因为需要最快的读写速度,所以全部放在内存中,而且可以通过配置RDB或AOF的方式,将数据异步写入磁盘,如果不把数据放在内存,而是磁盘,往往磁盘IO是很慢的,严重影响redis的性能

>>>redis的持久化

RDB:这种持久化方案,不会记录数据产生、修改过程,只会根据某一时间点的快照保存最终数据,一种命令是使用save,这种是同步阻塞命令,还有一种使用bgsave命令,这种是异步非阻塞式命令,RDB也支持自动持久化,需要在redis.conf文件中进行配置

AOF:这种持久化方案,会记录数据的所有产生和修改的命令,服务器重启,会根据这些命令恢复数据,如果RDB和AOF同时存在,会优先恢复AOF记录的数据