面试笔记
001-面向对象的理解
面向对象是一个组织者的思维模式,而面向过程是执行者的思维模式。举个例子,比如我要产生一个0-10之间的随机数,如果以“面向过程”的思维,那我更多是关注如何去设计一个算法,然后保证比较均衡产生0-10的随机数,而面向对象的思维会更多关注,我找谁来帮我们做这件事,比如Random类,调用其中提供的方法即可。所以,面向对象的思维更多的是考虑如何去选择合适的工具,然后组织到一起干一件事。
面向对象有三大特性:封装、继承、多态。
002-JDK,JRE,JVM有什么区别
JDK:是java开发工具包,包含了java的开发环境和运行环境(JRE)
JRE:是java运行环境,包含了Java虚拟机(JVM)和一些基础类库。
JVM:Java虚拟机,提供执行字节码文件的能力。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oqjxHEXf-1619325824609)(F:\Desktop\07-JDK&JRE&JVM关系示意图.png)]
003-equals和==的区别
1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
如果作用于引用类型的变量,则比较的是所指向的对象的地址。
2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量;
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
004-final的作用
1)final修饰类的时候,表示这个类不可变,则不可继承。
2)final修饰方法时,表示这个方法不可以被重写。
3)final修饰变量是,这个变量就是常量,不可变。
注意:
修饰的是基本数据类型,这个值本身不能修改
修饰的是引用类型,引用的指向不能修改
final Student student = new Student(1,"Andy");
student.setAge(18);//注意,这个是可以的!
说明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uqa3nUD8-1619325824612)(F:\Desktop\final.png)]
而student对象的属性是可以更改的。
005-String,StringBuffer,StringBuilder区别
1)String是final类型的,声明的对象都是不可变的。每次操作都是产生新的对象。
2)StringBuffer和StringBuilder都是在原有的对象上进行操作,如果经常改变字符串的内容,建议使用这两种。
3)StringBuffer与StringBuilder,前者是线程安全的,因为它的源码中每个方法都有加synchronized关键字修饰;
后者是线程不安全的。线程不安全的性能更高,所以我们在开发中一般才用StringBuilder。因为我们在开发中通常使用StringBuilder拼接字符串,根本不需要考虑线程安全问题,类似的还有hashmap、ArrayList等。
006-接口和抽象类的区别
相同点
1)都不能被实例化
2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点
1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
3)接口强调特定功能的实现,而抽象类强调所属关系。
4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
007-Int和Integer的区别(重点)
为了能够将基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Integer i1 = new Integer(12);
Integer i2 = new Integer(12);
System.out.println(i1 == i2);//false
Integer i3 = 126;
Integer i4 = 126;
int i5 = 126;
System.out.println(i3 == i4);//true
System.out.println(i3 == i5);//true
Integer i6 = 128;
Integer i7 = 128;
int i8 = 128;
System.out.println(i6 == i7);//false
System.out.println(i6 == i8);//true
-
都是Integer类型的比较:
new:由于new会在堆内存中开辟一段存储空间,所以地址肯定是不一样的,如上i1与i2,所以答案是false。
没有new:由于Integer做了缓存,-128~127,当你取值在这个范围的时候,会采用缓存的对象,所以会相等,例如上面的i3与i4,i3与i5;当不在这个范围,内部创建新的对象,此时不相等,例如上面的i6 与 i7。
-
nteger和int的比较:
实际比较的是数值,Integer会做拆箱成int类型的动作,来跟基本数据类型做比较,此时跟是否在缓存范围内或是否new都没关系,例如上面的i6与i8。
源码分析:
当我们写Integer i = 126,实际上做了自动装箱:Integer i = Integer.valueOf(126);
008-重写与重载
重写(两同两小一大):
1)、重写方法只能存在于具有继承关系的类中。
2)、方法名,参数列表必须与被重写的方法相同。(两同)
3)、子类返回类型小于等于父类方法返回类型(返回值为基本数据类型时必须相等);
子类抛出异常小于等于父类方法抛出异常。(两小)
4)、子类方法的访问权限修饰符(public>protected>default>private)应大于等于父类方法。(一大)
5)、子类方法将覆盖父类方法,如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
重载(两同三不同):
1)、方法重载是让类以统一的方式处理不同类型数据的一种手段。Java的方法重载,就是在类中可以创建多个方法。与参数名和返回值类型无关。
2)、方法的重载必须在同一个类中,方法名必须相同。(两同)
3)、重载的方法 参数的类型、参数的个数、参数的顺序 至少有一个不同。(三不同)
4)、重载只有返回值不同的方法名时会编译报错。
009-List和Set的区别
- List:有序,可重复。
- Set:无序,不可重复。
这里的有序无序是指输出顺序与输入顺序是否相同,并不是指数据能否排序。可以根据子类联想,List子类例如:ArrayList。Set子类例如:HashSet、TreeSet。
010-ArrayList和LinkedList的区别
1)、底层数据结构的区别:
ArrayList,数组,连续一块内存空间,查找快,方便寻址,但删除,插入慢,因为需要发生数据迁移。
LinkedList,双向链表,不是连续的内存空间,查找慢,因为需要通过指针一个个寻找,但删除,插入块,因为只要改变前后节点的指针指向即可。
2)、其他围绕着数组和双链表的性质来思考就可以了。
011-HashSet的存储原理
HashSet的存储原理或者工作原理,主要是从如何保证唯一性来说起。HashSet底层采用的是HashMap来实现存储,其值作为HashMap的key。
这里面主要有2个问题,需要回答?
第一,为什么要采用Hash算法?有什么优势,解决了什么问题?HashSet如何保证保存对象的唯一性?会经历一个什么样的运算过程?
存储数据,底层采用的是数组,当判断数据是否唯一时采用遍历的方式,逐个比较,但是这种效率低,尤其是数据很多的情况下所以,为了解决这个效率低的问题,我们采用hash算法,通过计算存储对象的hashcode,然后再跟数组长度-1做位运算,得到我们要存储在数组的哪个下标下,如果此时计算的位置没有其他元素,直接存储,不用比较。
此处,我们只会用到hashCode但是随着元素的不断添加,就可能出现“哈希冲突”,这个时候,我们就需要用到equals方法如果equals相同,则不插入,不相等,则形成链表(1.7版本用的是头插法,1.8用的是尾插法)。
第二,所谓哈希表是一张什么表?
本质是一个数组,而且数组的元素是链表。
012-HashMap和HashTable的区别
hashmap | 线程不安全 | 允许有null的键和值 | 效率高一点 | 方法不是Synchronize的要提供外同步 | HashMap是Hashtable的轻量级实现 | HashMap 是Java1.2 引进的Map interface 的一个实现 |
---|---|---|---|---|---|---|
hashtable | 线程安全 | 不允许有null的键和值 | 效率稍低 | 方法是是Synchronize的 | Hashtable 比HashMap 要旧 | Hashtable 继承于Dictionary 类 |
013-ArrayList与Vector的区别
ArrayList:线程不安全,效率高,常用。
Vector:线程安全的,效率低,因为底层源码中设置了synchronized。
014-IO流的分类以及处理
1、分类:
- 按照方向分类:分为输入流和输出流。(注意,是站在程序的角度来看方向,输入流用于读文件,输出流用于写文件)
- 按照读取的单位分类:字节流(InputStream、OutputStream)、字符流(Reader、Writer)。
- 按照处理方式分类:节点流,处理流。
2、选择:
1)、字节流通常处理二进制文件(视频,音频,doc,ppt)。
2)、字符流通常处理文本文件(假如有解析文件的内容的需求,比如逐行处理,则采用字符流,比如txt文件)。
015-请描述下Java的异常体系
如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wcye0T2B-1619325824614)(F:\Desktop\Ex.png)]
异常的根是 Throwable,可被抛出的异常。再往下时Error和Exception。
Error:是虚拟机内部错误,如:
栈内存溢出错误:StackOverflowError(递归,递归层次太多或递归没有结束)
堆内存溢出错误:OutOfMemoryError(堆创建了很多对象)
Exception:是我们编写的程序错误,如:
RuntimeException:也称为LogicException,逻辑异常——代码写的不严谨;
为什么编译器不会要求你去try catch处理?
本质是逻辑错误,比如空指针异常,这种问题是编程逻辑不严谨造成的
应该通过完善我们的代码编程逻辑,来解决问题
非RuntimeException:
编译器会要求我们try catch或者throws处理
本质是客观因素造成的问题,比如FileNotFoundException
写了一个程序,自动阅卷,需要读取答案的路径(用户录入),用户可能录入是一个错误的路径,所以我们要提前预案,写好发生异常之后的处理方式,这也是java程序健壮性的一种体现
016-请列举五个RuntimeException异常
- 算数异常
- 空指针异常
- 类型转换异常
- 数组越界
- 数字格式异常
017-请列举五个非运行时异常
- IOException
- SQLException
- FileNotFoundException
- NoSuchFileException
- NoSuchMethodException
018-Throw和Throws的区别
throw,作用于方法内,用于主动抛出异常
throws, 作用于方法声明上,声明该方法有可能会抛些某些异常针对项目中,异常的处理方式,我们一般采用层层往上抛,最终通过异常处理机制统一处理(展示异常页面,或返回统一的json信息),自定义 异常一般继承RunntimeException。
019-进程与线程
什么是进程?什么是线程?进程与线程的联系是什么?进程与线程的区别是什么?
-
什么是进程?
进程是程序执行的实例,即运行中的程序,程序放在磁盘中,进程放在内存中,进程的启动及调度均是由内核发起的。
每个进程都有自己的地址空间,资源等如:内存、I/O、CPU。进程是资源分配、运行调度的基本单位,系统中并发执行的单位。
-
什么是线程?
每个进程中至少包含一个线程,而这些线程都在共享进程的资源空间等,当线程发生变化的时候只会引起CPU执行的过程发生变化,不会改变进程所拥有的资源。线程是进程执行运算的最小单位,也是执行处理机调度的基本单位。
-
进程与线程的联系是什么?
1)、创建线程使用的底层函数和进程是一样的,都是clone;
2)、从内核里看进程和线程是一样的,都有各自不同的PCB;
3)、进程可以蜕变成线程;
4)、线程可以看做寄存器和栈的集合;
5)、在linux下,线程是最小的执行单位,进程是最小的分配资源单位。 -
进程和线程的区别是什么?
1)、使用区别:当我们对资源的保护管理要求比较高的时候,建议使用多进程,当然存在的开销也会比较大,反过来,如果频繁切换下,对资源的保护管理要求不那么高,或者开销有限,使用多线程会比较方便;
2)、调度:
同一个进程中,线程的切换不会引起进程的切换;
由一个进程的线程切换到另一个线程的进程时,引起进程的切换;
3)、并发性:
进程可以并发执行,同一进程内的多个线程可以并发执行,不同进程内的多个线程也可以并发执行。4)、拥有的资源:
一般来说,线程并不会拥有自己的资源,但是它可以访问自己本进程中的资源,比如,一个进程打开的文件等,进程的其他线程是可以共享的。
020-创建线程的方式
常见的有三种创建线程的方式:
1、继承Thread
2、实现Runnable接口
3、实现Callable接口(可以获取线程执行之后的返回值)
明确一点:本质上来说创建线程的方式就是继承Thread,就是线程池,内部也是创建好线程对象来执行任务。
注意:
但实际后两种,更准确的理解是创建了一个可执行的任务,要采用多线程的方式执行,
还需要通过创建Thread对象来执行,比如 new Thread(new Runnable(){}).start();这样的方式来执行。
在实际开发中,我们通常采用线程池的方式来完成Thread的创建,更好管理线程资源。
Case1:继承Thread方式来开启一个新的线程:
public static void main(String[] args){
MyThread thread = new MyThread();
//正确启动线程的方式
//thread.run();//调用方法并非开启新线程
thread.start();
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":running.....");
}
}
Case2:实现Runnable接口来开启一个新线程:
public static void main(String[] args){
MyTask task = new MyTask();
//task.start(); //并不能直接以线程的方式来启动
//它表达的是一个任务,需要启动一个线程来执行
new Thread(task).start();
}
class MyTask implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":running....");
}
}
Case3:实现Callable接口来开启一个新线程与实现Runnable接口的不同:
class MyTask2 implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
return null;
}
}
不同之处:Callable可以有具体的返回值类型
021-描述线程的生命周期
在线程生命周期中,要经过**“新建(New)”、“就绪(Runnable)”、“运行(Running’)”、“阻塞(Blocked)”和“死亡(Dead)”**五种状态。
-
新建态:当用new关键字创建一个线程的时候,该线程就处于新建状态;
-
就绪态:调用start()方法之后,线程就进入了就绪状态。当不能立刻进入运行状态,要等待JVM里线程调度器的调度;(只能对处于新建状态的线程调用start()方法,并且只能使用一次,否则会引发异常。)
-
运行态:就绪状态的线程获得了CPU时间片,就开始执行run方法,就处于运行状态。当分配的时间用完后,又进入了就绪状态,等待下次分配到CPU在进入运行状态;
-
阻塞态:
以下5种情况会让线程进入阻塞态
1)、线程调用sleep()方法主动放弃所占用的处理器资源;
2)、线程调用了一个阻塞式IO方法,在该方法返回之时,该线程被阻塞;
3)、线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;
4)、线程在等待某个通知(notify);
5)、线程调用了线程的suspend()方法将该线程挂起。(该方法容易造成死锁,应尽量避免使用该方法)
以下5种情况会让线程恢复运行态
1)、调用sleep()方法的线程经过了指定时间;
2)、线程调用的阻塞式IO方法已经返回;
3)、线程成功的获得了试图取得的同步监视器;
4)、线程正在等待的某个通知时,其他线程发出了一个通知(signal);
5)、处于挂起状态的线程被调用了resume()恢复方法。
-
死亡态:
(1)、run()或call()方法执行完成,线程正常结束;
(2)、线程抛出一个未捕获的Exception或Error;
(3)、直接调用该线程的stop()方法来结束该线程。(该方法容易导致死锁,不推荐)
注意:当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,就拥有和主线程相同的地位,不会受主线程的影响。
022-对线程安全的理解
-
什么是线程安全?
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的。
-
什么时候需要考虑线程安全?
1)、多个线程访问同一个资源;
2)、资源是有状态的,比如字符串拼接,这个时候数据是会有变化的。
-
如何实现线程安全?
实现线程安全的方式有多种,其中在源码中常见的方式是,采用synchronized关键字给代码块或方法加锁,比如StringBuffer。
023-什么是死锁?如何防止死锁?
- 什么是死锁?
死锁最初由一个悲惨的故事说起,话说一群哲学家一起聚餐,然后在每个人的左边和右边分别放着一根筷子,而只有同时抓到两根筷子,才能正常吃饭,于是,不幸的故事发生了,每位哲学家都只抓到一根筷子,且都不愿意释放手中的筷子,于是,最终一桌的饭菜就这么浪费了。
这个故事确实形象说明了死锁的情况。转换到线程的场景,就是
线程A持有独占锁资源a,并尝试去获取独占锁资源b;
同时,线程B持有独占锁资源b,并尝试去获取独占锁资源a;
这样线程A和线程B相互持有对方需要的锁,从而发生阻塞,最终变为死锁。
public class Deadlock {
private static final Object a = new Object();
private static final Object b = new Object();
public static void main(String[] args){
new Thread(new Task(true)).start();
new Thread(new Task(false)).start();
}
static class Task implements Runnable{
private boolean flag;
public Task(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (a){
System.out.println(Thread.currentThread().getName()+"->获取到a资源");
synchronized (b){
System.out.println(Thread.currentThread().getName()+"->获取到b资源");
}
}
}else{
synchronized (b){
System.out.println(Thread.currentThread().getName()+"->获取到b资源");
synchronized (a){
System.out.println(Thread.currentThread().getName()+"->获取到a资源");
}
}
}
}
}
}
//有可能会出现死锁,如果第一个线程已经走完,第二个线程才获取到执行权限,那么就不会出现死锁
- 如何防止死锁?(重点)
减少同步代码块嵌套操作
降低锁的使用粒度,不要几个功能共用一把锁
尽量采用tryLock(timeout)的方法,可以设置超时时间,这样超时之后,就可以主动退出,防止死锁(关键)
024-synchronized和lock的区别
1)、作用的位置不同
synchronized可以给方法,代码块加锁
lock只能给代码块加锁
2)、锁的获取锁和释放机制不同
synchronized无需手动获取锁和释放锁,发生异常会自动解锁,不会出现死锁。
lock需要自己加锁和释放锁,如lock()和unlock(),如果忘记使用unlock(),则会出现死锁,
所以,一般我们会在finally里面使用unlock().
补充:
//明确采用人工的方式来上锁
lock.lock();
//明确采用手工的方式来释放锁
lock.unlock();
synchronized修饰成员方法时,默认的锁对象,就是当前对象
synchronized修饰静态方法时,默认的锁对象,当前类的class对象,比如User.class
synchronized修饰代码块时,可以自己来设置锁对象,比如
synchronized(this){
//线程进入,就自动获取到锁
//线程执行结束,自动释放锁
}
025-synchronized和volatile的区别
1)、作用的位置不同
synchronized是修饰方法,代码块
volatile是修饰变量
2)、作用不同
synchronized,可以保证变量修改的可见性及原子性,可能会造成线程的阻塞
volatile仅能实现变量修改的可见性,但无法保证原子性,不会造成线程的阻塞
026-Sleep()与Wait()的区别
主要有四个方面不同:生命周期不同、所属的类不同、对锁资源的处理方式不同、使用范围不同
-
生命周期:
1)、当线程调用sleep(time),或者wait(time)时,进入timed waiting(计时等待)状态;
2)、当线程调用wait()或者join时,线程都会进入到waiting(等待)状态,当调用notify或notifyAll时,或者join的线程执行结束后,会进入runnable(运行)状态。
-
所属的类不同:
1)、Sleep()定义在Thread类上,Sleep是线程休眠;
2)、Wait()定义在Object类上,原因是在同步代码块中,我们说需要一个对象锁来实现多线程的互斥效果,也就是说,Java的锁是对象级别的,而不是线程级别的。
-
对锁资源的处理方式不同:
1)、Sleep()不会释放锁;
2)、Wait()会释放锁资源。
-
使用范围不同:
1)、Sleep()可以使用在任何代码块中;
2)、Wait()必须在同步方法或同步代码块执行,原因是避免CPU切换到其他线程,而其他线程又提前执行了notify方法,那这样就达不到我们的预期(先wait再由其他线程来唤醒),所以需要一个同步锁来保护。
027-类加载机制和双亲委托机制
java编译器会将 .java 文件编译成扩展名为 .class 的文件。.class 文件中保存着java转换后,虚拟机将要执行的指令。当需要某个类的时候,类加载器ClassLoader会读取这个.class文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程被称为类的加载。
Class文件来源为三部分:
1,Java内部自带的核心类,位于
J
A
V
A
H
O
M
E
/
j
r
e
/
l
i
b
,
其
中
最
著
名
的
莫
过
于
r
t
.
j
a
r
2
,
J
a
v
a
的
扩
展
类
,
位
于
JAVA_HOME/jre/lib,其中最著名的莫过于rt.jar 2,Java的扩展类,位于
JAVAHOME/jre/lib,其中最著名的莫过于rt.jar2,Java的扩展类,位于JAVA_HOME/jre/lib/ext目录下
3,我们自己开发的类或项目开发用到的第三方jar包,位于我们项目的目录下,比如WEB-INF/lib目录
针对不同的来源,Java分了不同的ClassLoader来加载:
1,Java核心类,这些Java运行的基础类,由一个名为BootstrapClassLoader加载器负责加载。这个类加载器被称为“根加载器或引导加载器”(BootstrapClassLoader不继承ClassLoader,是由JVM内部实现。法力无边,所以你通过java程序访问不到,得到的是null。)
2,Java扩展类,是由ExtClassLoader负责加载,被称为“扩展类加载器”。
3,项目中编写的类,是由AppClassLoader来负责加载,被称为“系统类加载器”。
那凭什么,我就知道这个类应该由老大BootStrapClassLoader来加载?
这里面就要基于双亲委托机制?
所谓双亲委托机制,就是加载一个类,会先获取到一个系统类加载器AppClassLoader的实例,然后往上层层请求,先由BootstarpClassLoader去加载,如果BootStrapClassLoader发现没有,再下发给ExtClassLoader去加载,还是没有,才由AppClassLoader去加载。如果还是没有,则报错。
028-JSP的4大域对象和9大内置对象
- 4大域对象及其作用范围
ServletContext context域 context域: 只能在同一个web应用中使用 (全局的)
HttpSession session域 session域: 只能在同一个会话(session对象)中使用 (私有的,多个请求和响应之间)
HttpServletRequet request域 request域: 只能在同一个请求中使用 (转发才有效,重定向无效)
PageContext page page域: 只能在当前jsp页面使用 (当前页面)
- 9大内置对象:jsp的内置对象,则是不需要在jsp页面中创建,直接可以使用
内置对象名 类型
request (HttpServletRequest)response (HttpServletResponse)
session (HttpSession)
application (ServletContext)
config (ServletConfig)
exception (Throwable)
page (Object(this))
out (JspWriter)
pageContext (PageContext)
029-Servlet和JSP的区别
技术的角度:
JSP本质就是一个Servlet
JSP的工作原理:JSP->翻译->Servlet(java)->编译->Class(最终跑的文件)
应用的角度:
JSP=HTML+Java
Servlet=Java+HTML
各取所长,JSP的特点在于实现视图,Servlet的特点在于实现控制逻辑
030-Servlet的生命周期
生命周期的流程:
创建对象–>初始化–>service()–>doXXX()–>销毁
创建对象的时机:
1,默认是第一次访问该Servlet的时候创建
2,也可以通过配置web.xml,来改变创建时机,比如在容器启动的时候去创建,DispatcherServlet(SpringMVC前端控制器)就是一个例子
1
执行的次数
对象的创建只有一次,单例
初始化一次
销毁一次
关于线程安全
构成线程不安全三个因素:
1,多线程的环境(有多个客户端,同时访问Servlet)
2,多个线程共享资源,比如一个单例对象(Servlet是单例的)
3,这个单例对象是有状态的(比如在Servlet方法中采用全局变量,并且以该变量的运算结果作为下一步操作的判断依据)
031-Cookie和Session的区别(重点)
存储的位置不同
Cookie:客户端
Session:服务端
存储的数据格式不同
Cookie:value为字符串,如果我们存储一个对象,这个时候,就需要将对象转换为JSON
Session:value为对象,Object类型
存储的数据大小
Cookie:一般来说,最大为4k
Session:受服务器内存控制
生命周期不同
Cookie:客户端控制,其实是客户端的一个文件,分两种情况
1,默认的是会话级的cookie,这种随着浏览器的关闭而消失,比如保存sessionId的cookie
2,非会话级cookie,通过设置有效期来控制,比如这种“7天免登录”这种功能,
就需要设置有效期,setMaxAgeSession:服务器端控制,默认是30分钟,注意,当用户关闭了浏览器,session并不会消失
cookie的其他配置
httpOnly=true:防止客户端的XSS攻击
path="/" :访问路径
domain="":设置cookie的域名
cookie跟session之间的联系
http协议是一种无状态协议,服务器为了记住用户的状态,我们采用的是Session的机制
而Session机制背后的原理是,服务器会自动生成会话级的cookie来保存session的标识。
032-转发和重定向的区别
1)、重定向:
1.工作原理: 用户第一次通过【手动方式】通知浏览器访问OneServlet。OneServlet工作完毕后,将TwoServlet地址写入到响应头location属性中,导致Tomcat将302状态码写入到状态行。 在浏览器接收到响应包之后,会读取到302状态。此时浏览器自动根据响应头中location属性地址发起第二次请求,访问TwoServlet去完成请求中剩余任务 2.实现命令:response.sendRedirect("请求地址")//将地址写入到响应包中响应头中location属性 3.特征: 1)、请求地址: 既可以把当前网站内部的资源文件地址发送给浏览器 (/网站名/资源文件名) 也可以把其他网站资源文件地址发送给浏览器(http://ip地址:端口号/网站名/资源文件名) 2)、请求次数:>=2 浏览器至少发送两次请求,但是只有第一次请求是用户手动发送后续请求都是浏览器自动发送的。 3)、 请求方式:GET 重定向解决方案中,通过地址栏通知浏览器发起下一次请求,因此通过重定向解决方案调用的资源文件接收的请求方式一定是【GET】 4.缺点:浪费时间
发生在客户端的跳转,所以,是多次请求,这个时候,如果需要在多次请求之间传递数据,就需要用session对象。
2)、转发:
1.原理:用户第一次通过手动方式要求浏览器访问OneServlet。OneServlet工作完毕后,通过当前的请求对象代替浏览器向Tomcat发送请求,申请调用TwoServlet。Tomcat在接收到这个请求之后,自动调用TwoServlet来完成剩余任务 2.实现命令: 请求对象代替浏览器向Tomcat发送请求(2行) RequestDispatcher report = request.getRequestDispatcher("/资源文件名");//1.通过当前请求对象生成资源文件申请报告对象一定要以"/"为开头 report.forward(当前请求对象,当前响应对象)//2.将报告对象发送给Tomcat 3.优点: 1)、用户只需要手动通过浏览器发送一次请求 2) 、Servlet之间调用发生在服务端计算机上,节省服务端与浏览器之间往返次数增加处理服务速度 4.特征: 1)、请求次数:1次 2)、请求地址:只能向Tomcat服务器申请调用当前网站下资源文件地址 request.getRequestDispathcer("/资源文件名") ****不要写网站名**** 3)、请求方式: 在请求转发过程中,浏览器只发送一个了个Http请求协议包。参与本次请求的所有Servlet共享同一个请求协议包,因此这些Servlet接收的请求方式与浏览器发送的请求方式保持一致
发生在服务器内部的跳转,所以,对于客户端来说,至始至终就是一次请求,所以这期间,保存在request对象中的数据可以传递。
3)、面试官的问题:
在后台程序,想跳转到百度,应该用转发还是重定向?
答案:重定向,因为转发的范围限制在服务器内部。
033-TCP和UDP的区别
首先需要明确一点,两者都是传输层的协议。
其次:
TCP提供可靠的传输协议,传输前需要建立连接,面向字节流,传输慢
UDP无法保证传输的可靠性,无需创建连接,以报文的方式传输,效率高
034-三次握手和四次挥手
三次握手,建立可靠连接:
保证彼此都可以确认对方收到自己的消息
1、客户端向服务器端发送一个随机数x,表示想要建立连接;
2、服务器回馈一个x+1表示确认收到客户端的消息,并且发送一个随机数y,确认客户端可以收到消息;
3、客户端回复x+1,表示正确收到服务器端回馈的确认消息,并且回馈y+1,表示确认收到服务器端的消息。
简单理解——A:听得到我吗?B:听得到,你听得到我吗?A:我也可以听得到
四次挥手:
1、客户端发送FIN关闭连接指令和seq=u一个随机数给服务器端;
2、服务器端发送ACK确认指令、ack=u+1(表示确认我收到你发来的随机数)、seq=v随机数(确认客户端可以收到)给客户端,同时关闭输入流;
3、服务器端发送FIN关闭连接指令和seq=w一个随机数、ack=ack+1给客户端,同时关闭输出流;
4、客户端发送ACK确认指令、ack=w+1(表示确认我收到你发来的随机数)、seq=x随机数给服务器端。
简单理解——A:我说完了!B:我知道了,我把耳机关了!B:可以断开了吗?我把麦关了!A:可以的!滚吧!
035-三层架构的理解
JavaEE将企业级架构分为一下三层
- Web层:负责与用户交互并对外提供服务接口,其主要框架有SpringMVC。
- 业务逻辑层:实现业务逻辑模块,其主要框架有Spring。
- 数据存取层:将业务逻辑层处理的结果持久化,方便后续查询,其主要框架有Mybatis,Hibernate。
036-数据库设计的三范式
1)、第一范式:确保每列保持原子性
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。例如,比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式。
2)、第二范式:确保表中的每列都和主键相关(所有非主键字段完全依赖主键,不能产生部分依赖)
第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,这样就产生一个问题:这个表中是以订单编号和商品编号作为联合主键。这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品编号相关。所以在这里违反了第二范式的设计原则。而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了。
3)、第三范式:确保每列都和主键列直接相关,而不是间接相关(不可存在传递依赖)
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
037-JDBC如何实现对事务的控制及事务边界
JDBC事物机制:
1、JDBC中的事物是自动提交的:
只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事物行为。
但在实际的业务中,通常都是N条DML语句共同联合才能完成的
必须保证他们这些DML语句在同一个事物中同时成功或者同时失败
2、解决自动提交事物重点三行代码:
conn.setAutoCommit(false);//开启事务,业务代码前
conn.commit();//提交事务,try的最后一句
conn.rollback();//回滚事务,通常在catch里
注意:事务的边界我们是放在业务层进行控制,因为业务层通常包含多个dao层的操作。
038-事务的4大特征
原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持久性( Durability ),简称ACID。
原子性是基础,隔离性是手段,一致性 是约束条件,而持久性是我们的目的。
原子性:
事务是数据库的逻辑工作单位,事务中包含的各操作要么都完成,要么都不完成
(要么一起成功,要么一起失败)
一致性:
事务一致性是指数据库中的数据在事务操作前后都必须满足业务规则约束。
比如A转账给B,那么转账前后,AB的账户总金额应该是一致的。
隔离性:
一个事务的执行不能被其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。(设置不同的隔离级别,互相干扰的程度会不同)
持久性:
事务一旦提交,结果便是永久性的。即使发生宕机,仍然可以依靠事务日志完成数据的持久化。
日志包括回滚日志(undo)和重做日志(redo),当我们通过事务修改数据时,首先会将数据库变化的信息记录到重做日志中,然后再对数据库中的数据进行修改。这样即使数据库系统发生奔溃,我们还可以通过重做日志进行数据恢复。
039-事务的隔离级别
读未提交(READ UMCOMMITTED):允许一个事务可以看到其他事务未提交的修改。
读已提交(READ COMMITTED):允许一个事务只能看到其他事务已经提交的修改,未提交的修改是不可见的。
可重复读(REPEATABLE READ):确保如果在一个事务中执行两次相同的SELECT语句,都能得到相同的结果,不管其他事务是否提交这些修改。 (银行总账)该隔离级别为InnoDB的缺省设置。
串行化(SERIALIZABLE):将一个事务与其他事务完全地隔离。
特别说明:
幻读,是指在本地事务查询数据时只能看到3条,但是当执行更新时,却会更新4条,所以称为幻读
事务隔离级别 | 脏数据 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | √ | √ | √ |
读已提交 | × | √ | √ |
可重复读 | × | × | √ |
串行化 | × | × | × |