面试题大全
以下是个人对于Java的理解,有大佬们认为我对Java理解有误,请下方评论区留言,谢谢~
C语言和Java的区别?
- c语言是面向过程,主要单位是函数,变量和函数的耦合性比较差,大部分是局部变量。Java是面向对象的,属性变量值和方法耦合成一个一个独立的单位–对象。
- 基本数据类型不同,c语言是int short long char float double 还有一些特殊类型、构造体、指针、联合体等、数组、字符串。Java是byte int short long float double char boolean,而且c语言的基本类型的位数和操作系统与机器相关,而Java是固定的。
- 文件组织方式不一样,c语言会把全局变量和方法的声明放在一个文件里,叫做头文件,后缀名是.h,而Java是以类来组织的。
- 对于方法的定义不同,要单独放在前面,int get (int apple,int banana);并且没有修饰符(private public protect等)。
JVM,JRE,JDK是什么?
JVM:
Java虚拟机–加载并运行.class文件(实现Java跨平台的核心部分 | 包含Java应用程序的类的解释器和类加载器等)。
JRE:
Java运行环境(JRE=JVM+Java系统类库)。
JDK:
Java开发工具包(JDK=JRE+编译,运行等命令工具)。
1.8有哪些新特征?
Lambda表达式:lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码;
函数式接口:函数式接口的提出是为了给Lambda表达式的使用提供更好的支持;
什么是函数式接口?
只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解:@Functionallnterface
常见的四大函数式接口
Consumer《T》:消费型接口,有参无返回值
Supplier《T》:供给型接口,无参有返回值
Function《T,R》:函数式接口,有参有返回值
Predicate《T》:断言型接口,有参有返回值,返回值是boolean类型
总结:函数式接口的提出是为了让我们更加方便的使用lambda表达式,不需要自己在动手创建一个函数式接口,直接拿来用就好了。
说说你对面向对象的理解?
对Java语言来说,一切皆是对象,真实世界中的所有事物都可以视为对象。
对象的特点:对象具有属性、行为和唯一性。
Java面向对象四个主要特性
抽象:就是找出事务的一些相似和共性之处,将这些事务归为一类。(抽象可以说是多态的基础)
封装:隐藏对象的属性和实现细节,仅对外提供公共的访问方式,提高代码的安全性和重用性,封装的目的就是要实现软件的“低耦合”,防止程序间相互依赖带来的变动影响。
继承:继承是从已有的类中派生出新的类,新类能吸收已有类的数据、属性和行为,并扩展新的能力。
继承的特征:
- 使用extends关键字来表示继承关系
- 相当于子类把父类的功能复制了一份
- Java只支持单继承
- 继承可以传递(爷爷/儿子/孙子)这样的关系
- 父类的私有成员也会被继承,但由于私有限制访问,所以子类不能使用父类的私有资源
- 继承多用于功能的修饰,子类可以在拥有父类功能的同时,进行功能扩展
多态:指一个实体同时具有多种形态,就是说同一个对象,在不同时刻,代表对象不一样,指的是对象的多种形态,多态有两个前提,一个是继承,一个是要有重写的方法,并且父类引用指向子类对象(向上转型)
什么是Java的多态?
同一个接口,使用不同的实例而执行不同的操作,同一个行为具有多个不同表现形式或形态的能力。
实现多态的三个条件:
继承的存在:继承是多态的基础,没有继承就没有多态
子类重写父类的方法,JVM会调用子类重写后的方法
父类引用变量指向子类对象
好处:消除类型之间的耦合关系
可替换性(substitutability)
可扩充性(extensibility)
接口性(interface-ability)
灵活性(flexibility)
简化性(simplicity)
向上转型(小转大):将一个父类的引用指向一个子类对象,自动进行类型转换。通过父类引用变量调用的方法是子类覆盖或继承父类的方法,而不是父类的方法。通过父类引用变量无法调用子类特有的方法。
向下转型(大转小):将一个指向子类对象的引用赋给一个子类的引用,必须进行强制类型转换。向下转型必须转换为父类引用指向的真实子类类型,不是任意的强制转换,否则会出现ClassCastException向下转型时可以结合使用instanceof运算符进行判断。
==和equals的区别
- ==是关系运算符,equals()是方法,返回结果都是boolean值
- ==是判断两个变量或指向的内存空间的值是不是相同
- ==是值内存地址进行比较,equals()是对字符串的内容进行比较
- ==指引用是否相同,equals()值的是指是否相同
原理如下:
Object的==和equals()比较的都是地址,作用相同
==作用:
基本类型,比较值是否相同
引用类型,比较内存地址值是否相同
不能比较没有父子关系的两个对象
equals()方法的作用:
JDK中的类一般已经重写了equals(),比较的是内容
自定义类如果没有重写equals(),将调用父类的equals()方法,this==obj
重写对象的equals()方法(一般需重写hashCode方法)
final,finally,finalize()区别?
final表示最终的,不可改变的。用于修饰类,方法和变量。
finally存在于捕捉异常try/catch语句中,位置在最后,finally语句块中的代码一定会被执行。
finalize()对象被回收时finalize()方法会被调用(一般由JVM调用)。
finally语句块一定执行吗?
不一定,存在很多特殊情况导致finally不执行。
- 直接返回未执行到try-finally语句块
- 抛出异常未执行到try-finally语句块
- 系统退出未执行到finally语句块
final与static的区别?
static可以修饰类的代码块,final不可以
static不可以修饰方法内局部变量,final可以
都可以修饰类,方法,变量
都不能用于修饰构造方法
static(静态的)
static修饰符全局唯一,全局共享
static可以修饰变量、方法、代码块和内部类(static修饰的变量可以重新赋值)
static变量是这个类所有,由该类创建的所有对象共享同一个static属性
static随着类加载而加载(优先于对象加载),可以用类名.访问
static修饰的代码块表示静态代码块,当JVM加载类时执行改代码块,只执行一次(提升程序性能)
static修饰的属性(类变量)在类加载时创建并初始化,只创建一次,不可以用来修饰局部变量
static不能和this或super公用,(因为有static时可能还没有对象)
静态只能调用静态,非静态可以随意调用
static方法必须被实现,而不能是抽象的abstract
static方法不能被重写
静态内部类和非静态内部类有什么区别?
- 静态内部类不需要有指向外部类的引用;非静态内部类需要持有对外部类的应用
- 静态内部类可以有静态方法、属性;非静态内部类则不能有静态方法、属性
- 静态内部类只能访问外部类的静态成员,不能访问外部类的非静态成员;非静态内部类能够访问外部类的静态和非静态成员
- 静态内部类不依赖于外部类的实现,直接实例化内部类对象;非静态内部类通过外部类的对象实例生成内部类对象
abstract 关键字的作用?
可以修饰类和方法
abstract修饰的类是抽象类,需要被继承
abstract修饰的方法是抽象方法,需要子类被重写
不能修饰属性和构造方法
重载和重写的区别?
- 重写发生在父子类(继承关系)中,重载是在统一各类中
- 重载方法名相同,参数列表不同,方法体不同,发生在“编译期绑定”看参数/引用类型来绑定方法
重写方法名相同,参数列表相同,方法体不同,遵循“运行期绑定”看对象类型绑定方法
重写遵循两同量小一大原则:
- 两同:方法名相同,参数列表相同
- 两小:子类返回值类型<=父类方法的
(引用类型<=基本类型和void时返回值类型必须相同)
子类方法抛出异常<=父类方法 - 一大:子类方法访问权限>=父类的访问权限
String、StringBuffer和StringBuilder的区别?(字符串动态拼接)
String 适用于少量字符串操作
StringBuffer 是线程安全的,因为它相关方法都加了synchronized关键字(低效)适用于多线程下,并发量不能很高的场景
StringBuilder线程不安全,Stringbuilder没有加任和锁,其效率高,使用单线程场景,也适用于高并发场景中,提高并发场景下程序的响应性能
锁的四种状态
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态(级别从低到高)
什么是死锁?
线程死锁是指由于两个或者多个线程互相持有所需要的资源,导致这些线程一直处于等待其他线程释放资源的状态,无法继续执行,如果线程都不主动释放所占有的资源,将产生死锁。当线程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
产生原因:
持有系统不可剥夺资源,去竞争其他已被占用的系统不可剥夺资源,形成程序僵死的竞争关系。持有资源的锁,去竞争锁已被占用的其他资源,形成程序僵死的争关系。
如何避免死锁?
并发程序一旦死锁,往往我们只能重启应用。解决死锁问题最好的办法就是避免死锁。
- 尽量以相同的顺序来访问索引记录和表
- 业务上能够接受幻读和不可重复读,考虑降低锁的级别到 Read committed,降低死锁发生的概率
- 添加合理的索引,走索引避免为每一行加锁,降低死锁的概率
- 在事务中一次锁定所需要的所有资源,如 MyISAM 引擎的表锁
- 避免大事务,尽量将大事务拆成多个小事务来处理
- 尽量避免同时并发对同一表进行读写操作,特别是执行加锁且操作数据量较大的语句
- 设置锁等待超时参数
死锁发生的条件
- 互斥,共享资源只能被一个线程占用
- 占有且等待,线程 t1 已经取得共享资源 s1,尝试获取共享资源 s2 的时候,不释放共享资源 s1
- 不可抢占,其他线程不能强行抢占线程 t1 占有的资源 s1
- 循环等待,线程 t1 等待线程 t2 占有的资源,线程 t2 等待线程 t1 占有的资源
避免死锁的方法
对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。
对于第一个条件 “互斥” 是不能破坏的,因为加锁就是为了保证互斥。
其他三个条件,我们可以尝试
- 一次性申请所有的资源,破坏 “占有且等待” 条件
- 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 “不可抢占” 条件
- 按序申请资源,破坏 “循环等待” 条件
迭代器 Iterator 是什么?
- 迭代器是Java中常用的设计模式之一。用于顺序访问集合对象的元素,无需知道集合对象的底层实现
- 它是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的便利操作和底层实现,从而解耦
- 缺点是每增加新的集合类也要对应增加新的迭代器,迭代器和集合类成对出现next()方法获得集合中下一个元素,hashNext()检查集合中是否还有元素,remove()将新返回的元素删除
为什么要用泛型?
使用反省编写的程序代码,要比使用 Object 变量在进行强制类型转换的代码,具有更好的安全性和可读性
多种数据类型执行相同的代码使用泛型可以复用代码
比如集合类使用泛型,取出和操作元素时无需进行类型转换,避免出现java.long.ClassCastException异常
集合和数组的区别?
数组的优点:数组效率高于集合类,数组能存放基本数据类型和对象,集合只能放对象
数组的缺点:一旦创建长度不能改变,只能存放相同类型的数据,遍历方式单一
集合:长度是可变的,可以存放多种类型数据,遍历方式丰富
Java有哪些常用的容器(集合)
Java分为collection和map两大集合,她们都有自己的很多子接口
collection常见的子接口分别是List(有序,可重复)和Set(值不可重复)集合
List主要实现类有ArrayList(底层数组,查询快,线程不安全,扩容增加1/2),默认初始容量都是10
Vector(底层数组,线程安全,扩容默认翻倍),LinkedList(底层是链表,增删快,线程不安全,更占内存)
Set主要实现类Hashset(无序,底层是哈希表,维护了HashMap),TreeSet(有序,无重复)基于TreeMap实现
Map是键值对(key唯一,value允许重复)集合
主要实现类有HashMap(底层数组加链表或红黑树,线程不安全,可以存null值)
HsahMap(默认初始容量16,扩容变为原来的2倍)计算hash值,没有contains方法,继承自AbstractMap
Hashtable(默认初始容量11,扩容变为原来的2倍+1)使用key的hashCode方法,包含contains方法,继承自Dictionary
TreeMap(底层是红黑数),Hashtable(线程安全,不存null值)中的方法是synchronized的
HashSet实现原理是什么?
HashSet 是基于 HashMap 实现的,查询速度特别快
HashMap 是支持 key 为 null 值的,所以 HashSet 支持添加 null 值
HashSet 存放自定义类时,自定义类需要重写 hashCode() 和 equals() 方法,确保集合对自定义类的对象的唯一性判断
(具体判断逻辑,见 HashMap put() 方法,简单概括就是 key 进行 哈希。判断元素 hash 值是否相等、key 是否为同个
对象、key 是否 equals。第 1 个条件为 true,2、3 有一个为 true,HashMap 即认为 key 相同)
无序、不可重复
HashSet和HashMap的区别?
HashMap:
实现 Map 接口
键值对的方式存储
新增元素使用 put(K key, V value) 方法
底层通过对 key 进行 hash,使用数组 + 链表或红黑树对 key、value 存储
HashSet:
实现 Set 接口
存储元素对象
新增元素使用 add(E e) 方法
底层是采用 HashMap 实现,大部分方法都是通过调用 HashMap 的方法来实现
conllection和collections的区别?
collection是集合框架的父接口,包含的方法所有集合对象都可以调用
collections是一个包装类,包含有关集合操作的静态方法(对集合的搜索,排序线程安全化等)不能实例化,collection集合框架的工具类
collection常用方法
add() 添加元素
addAll() 添加所有元素
remove()移除指定元素
isEmpty()判断是否为空
size()返回集合有多少元素
contains() 判断是否包含指定元素
toArray()将集合转换为对象数组
iterator()遍历集合中元素
String类的常用方法
isEmpty:(字符串长度)length()是否为0
contains:是否包含目标char值
equals:比较内容是否相同
indexOf:目标字符串在原字符串中的位置下标
lastIndexOf:目标字符或字符串在源字符串中最后一次出现的位置下标
valueOf:其他类型转字符串
getBytes:获取字符串的字节数组
charAt:获取指定下标位置的字符
join:以某字符串,连接某字符串数组
trim:去除字符串首尾空格
substring:截取字符串
split:以正则表达式分割字符串
length:字符串长度
replace:字符串替换
replaceAll:带正则字符串替换
toLowerCase:字符串转小写
toUpperCase:字符串转大写
接口和抽象类的区别?
- 抽象类可以有构造方法;接口不能有
- 抽象类中可以有普通成员变量;接口中没有
- 抽象类可以包含非抽象普通方法;JDK1.8以前接口中默认方法都是抽象的,
JDK1.8以后方法可以有默认和静态方法 - 抽象类中抽象方法的访问权限可以是public,protected和default;
接口中的抽象方法只能是public类型(并且默认带一个abstract类型) - 一个类可以实现多个接口,用逗号隔开,但只能继承一个抽象类
- 接口和类之间可以多实现,接口和接口之间可以多继承
(因为接口中的方法是没有方法体的,所以不能实现任何接口)
接口的特点
- 通过interface关键字来定义接口
- 通过implements让子类来实现接口
- 接口中的方法全部都是抽象方法
- 接口可以理解成一个特殊的抽象方法(接口不是类)
- 类描述的是一类事物的属性和方法,接口则是包含实现类要实现的方法
- 接口突破了java单继承的局限性
- 接口提高了程序的功能拓展,降低了耦合性
内部类的特点
- 内部类可以直接访问外部类中的成员,包括私有成员
- 外部类要访问内部类的成员,必须要建立内部类的对象
- 在成员位置的内部类是局部内部类
总结:成员内部类被private修饰以后,无法被外界直接创建对象使用
所以可以创建外部类对象,通过外部类对象间接访问内部类的资源
匿名内部类
匿名内部类相当于没有名字的局部内部类,一般和匿名对象一起使用
匿名内部类适合创建那种只需要使用一次的类,也就是说创建一个匿名内部类,只需要用一次即可。
Java访问修饰符权限的区别?
public(公共) 同类,同包,子类,其他包
protected(保护型) 同类,同包,子类
default (默认) 同类,同包
private (私有) 同类
什么是javap?
javap是java class文件分解器,可以反编译,也可以查看java编译器生成的字节码等
throw和throws的区别?
throw是写在方法体里面。throws则是用在方法头上面(使用位置不同)
throw就是直接抛出一个异常,而throws则是说我这个方法可能会抛出一个异常(作用不同)
throw只能用于抛出一种异常,而throws可以抛出多种异常
throw和whrows定义?
throw:
表示方法内抛出某种异常对象(只能是一个)
- 用于程序员自行产生并抛出异常
- 位于方法体内部,可以作为单独语句使用
- 如果异常对象是非 RuntimeException 则需要在方法申明时加上该异常的抛出,
即需要加上 throws 语句 或者 在方法体内 try catch 处理该异常,否则编译报错 - 执行到throw语句则后面的语句不再执行
throws:
- 方法的定义上使用throws表示这个方法可能抛出某些异常(可以有多个)
- 用于声明在该方法内抛出异常
- 必须跟在方法参数列表后面,不能单独使用
- 需要由方法的调用者进行异常处理
常见的异常类有哪些?
异常非常多,Throwable 是异常的根类。
Throwable 包含子类 错误-Error 和 异常-Exception 。
Exception 又分为 一般异常和运行时异常 RuntimeException。
运行时异常不需要代码显式捕获处理。
常见的异常
RuntimeException:
- NullPointerException(空指针异常)
- IndexOutBoundsException(下标越界异常)
- ClassCastException(类转换异常)
- ArrayStoreException(数据存储异常,操作数组时类型不一致)
- ClassNotFoundException(类找不到异常)
IOException: - FileNotFoundException(文件找不到异常)
进程
进程就是正在运行的程序,它会占用对应的内存区域,由CPU进行执行与计算。
程序执行时的一个实例
每个进程都有独立的内存地址空间
系统进行资源分配和调度的基本单位
进程里的堆,是一个进程中最大的一块内存,被进程中的所有线程共享的,进程创建时分配,主要存放 new 创建的对象实例
进程里的方法区,是用来存放进程中的代码片段的,是线程共享的
在多线程 OS 中,进程不是一个可执行的实体,即一个进程至少创建一个线程去执行代码
进程的特点
独立性:
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
动态性:
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程。
具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
并发性:
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.
为什么要用线程?
每个进程都有自己的地址空间,即进程空间。一个服务器通常需要接收大量并发请求,为每一个请求都创建一个进程系统开销大、请求响应效率低,因此操作系统引进线程。
线程
线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
- 一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。
- 我们看到的进程的切换,切换的也是不同进程的主线程
- 多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能。
并发和并行的区别?
- 并行指多个事件在同一个时刻发生;并发指在某时刻只有一个事件在发生,某个时间段内由于 CPU 交替执行,可以发生多个事件。
- 并行没有对 CPU 资源的抢占;并发执行的线程需要对 CPU 资源进行抢占。
- 并行执行的线程之间不存在切换;并发操作系统会根据任务调度系统给线程分配线程的 CPU 执行时间,线程的执行会进行切换。
串行、并行和并发的区别
串行:同一时刻一个CPU只能处理一件事,类似于单车道
并发:多个资源抢占CPU,让CPU来回切换去干活
并行:多个资源要干活,有多个CPU去的干活,没有发生抢占CPU的情况
(同一时刻多个CPU可以处理多件事,类似于多车道)
多线程的运行安全?
线程的安全性问题体现在:
原子性:一个或者多个操作在 CPU 执行的过程中不被中断(的特性)
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
有序性:程序执行的顺序按照代码的先后顺序执行
导致原因:(以下拓展)
缓存导致的可见性问题
线程切换带来的原子性问题
编译优化带来的有序性问题
解决办法:
JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
synchronized、volatile、LOCK,可以解决可见性问题
Happens-Before 规则可以解决有序性问题
线程终止有两种情况
线程的任务执行完成
线程在执行任务过程中发生异常
这两者属于线程自行终止,如何让线程 A 把线程 B 终止呢?
- Java 中 Thread 类有一个 stop() 方法,可以终止线程,不过这个方法会让线程直接终止,在执行的任务立即终止,
未执行的任务无法反馈,所以 stop() 方法已经不建议使用。 - 既然 stop() 方法如此粗暴,不建议使用,我们如何优雅地结束线程呢?
1)线程只有从 runnable 状态(可运行/运行状态) 才能进入终止状态(terminated状态),如果线程处于休眠状态(blocked、waiting、timed_waiting状态),
就需要通过 Thread 类的 interrupt() 方法,让线程从休眠状态进入 runnable 状态,从而结束线程。
2)当线程进入 runnable 状态之后,通过设置一个标识位,线程在合适的时机,检查该标识位,发现符合终止条件,自动退出 run () 方法,线程终止。
什么是线程池?
线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,
处理完滞后线程并不会被销毁,而是返回线程池中等带下一个任务。
为什么要使用线程池?
因为 Java 中创建一个线程,操作系统要为线程分配一系列的资源,成本很高,非常耗费资源。使用线程池就能很好地避免频繁创建和销毁。
线程是一个重量级的对象,应该避免频繁创建和销毁。
synchronized关键字的作用?
Java 中关键字 synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
作用:
确保线程互斥地访问同步代码
保证共享变量的修改能够及时可见
有效解决重排序问题
用法:
修饰普通方法
修饰静态方法
指定对象,修饰代码块
特点:
阻塞未获取到锁、竞争同一个对象锁的线程
获取锁无法设置超时
无法实现公平锁
控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll()
锁的功能是 JVM 层面实现的
在加锁代码块执行完或者出现异常,自动释放锁
原理:
同步代码块是通过 monitorenter 和 monitorexit 指令获取线程的执行权
同步方法通过加 ACC_SYNCHRONIZED 标识实现线程的执行权的控制
同步和异步有何异同,分别在什么情况下使用?
同步:发送一个请求,等待返回,然后再发送下一个请求
异步:发送一个请求,不等待返回,随时可以再发送下一个请求
使用场景:
- 如果数据存在线程间的共享,或竞态条件,需要同步。如多个线程同时对同一个变量进行读和写的操作
- 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就可以使用异步,提高效率、加快程序的响应
什么是守护线程?
Java线程分为用户线程和守护线程。
守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
注意:
- setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行
- 守护线程创建的线程也是守护线程
- 守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题
线程的生命周期(五个基本状态)
新建状态(new):线程创建对象后进入新建状态(Thread t=new MyThread)
就绪状态(Runnable):当执行start()方法,线程进入就绪状态。
运行状态(Running):当CPU开始调度处于就绪状态的线程时,线程才会真正执行进入到运行状态。
阻塞状态(Blocked):处于运行状态的线程由于某种原因,暂时放弃对CPU的使用权,程序停止执行,
此时进入阻塞状态,直到进入到就绪状态,才有机会被CPU调用进入到运行状态。根据阻塞产生的原因
终止状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
不同,阻塞状态又可以分为三种:
- 等待阻塞:处于运行状态中的线程执行wait()方法,使线程进入到等待阻塞状态;
- 同步阻塞:线程在获取synchronized同步锁失败(锁被其他线程占用),会进入同步阻塞状态;
- 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。
当sleep()状态超时,join()等待线程终止或者超时,或者I/O处理完毕时,线程重新转入就绪状态
Java中线程的状态分为6种
- 新建(new):实现Runnable接口和继承Thread可以得到一个线程类新创建了一个线程对象,但还没有调用start()方法。
- 运行(runnable):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调
度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 - 阻塞(blocktd):表示线程阻塞于锁(阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态)
- 等待(waiting):处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
- 超时等待(timed_waiting):该状态可以在指定的时间后自行返回。(处于这种状态的线程不会被分配CPU执行时间,不过
无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒) - 终止(terminated):表示该线程已经执行完毕。
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,
但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
Thread,Runnable
- 重写 Thread 类的 run() 方法。
1)new Thread 对象匿名重写 run() 方法
2)继承 Thread 对象,重写 run() 方法 - 实现 Runnable 接口,重写 run() 方法。
1)new Runnable 对象,匿名重写 run() 方法
2)实现 Runnable 接口,重写 run() 方法 - 实现 Callable 接口,使用 FutureTask 类创建线程
- 使用线程池创建、启动线程
Runnable和Callable有什么区别?
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
线程的run()方法和start()方法有什么区别?
启动一个线程需要调用 Thread 对象的 start() 方法,调用线程的 start() 方法后,线程处于可运行状态,
此时它可以由 JVM 调度并执行,这并不意味着线程就会立即运行
run() 方法是线程运行时由 JVM 回调的方法,无需手动写代码调用直接调用线程的 run() 方法,相当于
在调用线程里继续调用方法,并未启动一个新的线程
sleep和wait区别
- sleep 不释放锁, wait 释放锁(使得其他线程可以使用同步控制块或者方法)
- sleep 是 Thread 的静态方法( 让调用线程进入睡眠状态,让出执行机会(CPU)给其他线程,
等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间)
而wait 是 Object 的实例方法(当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对
象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。) - sleep 达到时间后, 自动恢复可运行状态, wait 需要其他线程 notify, notifyAll 来通知
- sleep可以在任何地方使用,而wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用(使用范围)
join是什么?
Join()是指把指定的线程加入到当前线程,比如join某个线程a,会让当前线程b进入等待,直到a的生命周期结束,此期间b线程是处于blocked状态。
notify()和notifyAll()有什么区别?
notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。
解释两个概念。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。
锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待
什么是反射?有什么作用?
- 获取任意类的名称,package信息,所有属性,方法,注解,类型等等(类加载器,modifiers,父类,实现接口)
- 获取任意private的属性及方法,并且可以改变
- 判断任意一个对选哪个所属的类
- 实例化任意一个类的对象
java的动态就体现在反射,通过反射我们可以实现动态装配,降低代码的耦合度;
反射的过度使用会严重消耗系统资源。JDK中java.lang.Class类,就是为了实现反射提供的核心类之一。
什么是Java序列化?什么情况下需要序列化?
序列化: 将java对象转换成字节的过程
反序列化:将字节转换成java对象的过程
序列化的实现:类实现Serializable接口,这个接口没有需要实现的方法。实现Serializable接口是为了告诉jvm这个类的对象可以被序列化。
注意:
某个类可以被序列化,则其子类也可以被序列化对象中的某个属性是对象类型,需要序列化也必须实现 Serializable 接口
声明为 static (和 transient )的成员变量,不能被序列化。static 成员变量是描述类级别的属性,(transient 表示临时数据反序列化读取序列化对象的顺序要保持一致)
break语句和continue语句的作用
break: 结束当前循环并退出当前循环体。
continue:结束本次循环,循环体后续的语句就不执行了,继续进行循环条件的判断,进行下一次循环语句的执行
Java中数组有什么特征?
在内存中申请一块连续的空间
数组下标从0开始
每个数组元素都有默认值,京本类型的默认值0,0.0,false,引用类型的默认值为null
数组的类型只能是一个,且固定,在申明时确定
数组的长度一经确定,长度就固定无法改变了
JDK8为什么用元空间取代永久代?
永久代是 HotSpot VM 对方法区的实现,JDK 8 将其移除的部分原因如下:
- 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出
- 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
- 将 HotSpot 与 JRockit 进行整合,JRockit 是没有永久代的
元空间特点?
每个加载器有专门的存储空间
不会单独回收某个类
元空间里的对象的位置是固定的
如果发现某个加载器不再存货了,会把相关的空间整个回收
两者区别:
元空间不在虚拟机中,使用的是本地内存;永久代使用的是JVM内存
元空间相比永久代的优势?
字符串常量池存在永久代中,容易出现性能问题和内存溢出
类和方法的信息大小难以确定,给永久代的代销指定带来困难
永久代会为GC带来不必要的复杂性
方便HotSpot与其他JVM如Jrockit的集成
什么是GC?
java中的垃圾回收机制,它的主要作用就是回收程序中不再使用的内存。JVM 需要跟踪程序中有用的对象,确定哪些是无用的,影响性能的。
特点:
只负责回收 JVM 堆内存里的对象空间,不负责回收栈内存数据
无法处理一些操作系统资源的释放,如数据库连接、输入流输出流、Socket 连接
可以将对象的引用变量设置为 null,垃圾回收机制可以在下次执行时回收该对象。
JVM 有多种垃圾回收 实现算法(),垃圾回收机制回收任何对象之前,会先调用对象的 finalize() 方法
可以通过 System.gc() 或 Runtime.getRuntime().gc() 通知系统进行垃圾回收,会有一些效果,但系统是否进行垃圾回收依然不确定不要主动调用对象的 finalize() 方法,应该交给垃圾回收机制调用
MinorGC、MajorGC、FullGC什么时候发生?
- MinorGC 在年轻代空间不足的时候发生
- MajorGC 指的是老年代的 GC,出现 MajorGC 一般经常伴有 MinorGC
- FullGC 老年代无法再分配内存;元空间不足;显示调用 System.gc;像 CMS 一类的垃圾回收器,在 MinorGC 出现 promotion failure 时也会发生 FullGC
新生代Eden和Survivor区域?
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历15次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
什么是类加载?
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。
类加载的步骤为,加载 -> 验证 -> 准备 -> 解析 -> 初始化。
- 加载:
获取类的二进制字节流
将字节流代表的静态存储结构转化为方法区运行时数据结构在堆中生成class字节码对象 - 验证:连接过程的第一步,确保 class 文件的字节流中的信息符合当前 JVM 的要求,不会危害 JVM 的安全
- 准备:为类的静态变量分配内存并将其初始化为默认值
- 解析:JVM 将常量池内符号引用替换成直接引用的过程
- 初始化:执行类构造器的初始化的过程
强引用、软引用、弱引用、虚引用是什么?
强引用,就是普通对象之间的引用关系,如果一个对象具有强引用,那么它就不会被垃圾回收器回收。 扩展:(Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题)
软引用(描述有些还有用但并非必须的对象),如果对象具有软引用,内存足够的时候,他不会被回收,但是当内存不足也就是将要发生内存溢出异常之前,将会把这些对象列进回收范围进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常
弱引用,相比软引用来说,要更加无用一些,它拥有更短的生命周期,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。WeakReference 实现
虚引用,是一种形同虚设的引用,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收。(PhantomReference 实现)
内存泄漏和内存溢出的区别?
- 内存溢出(out of memory):之程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。
- 内存泄露(memory leak):指程序在申请内存后,无法释放已申请的内存空间,内存泄露堆积会导致内存被占光。
- memory leak 最终会导致 out of memory。
总的来说,内存泄露问题,还是编码不认真导致的,不能责怪JVM没有更合理的清理。
不通过构造方法能创建对象吗?
java创建对象的方式:
- 用 new 语句创建对象
- 运用反射,调用 java.lang.Class 或 java.lang.reflect.Constructor 类的 newInstance() 方法
- 调用对象的 clone() 方法
- 运用反序列化手段,调用 java.io.ObjectInputStream 对象的 readObject() 方法
上述 1、2 会调用构造函数,上述 3、4 不会调用构造函数
匿名内部类可以继承类或实现接口吗?为什么?
匿名内部类本质上是对父类方法的重写或接口的方法的实现
从语法角度看,匿名内部类创建处是无法使用关键字继承类或实现接口
原因:
- 匿名内部类没有名字,所以它没有构造函数。因为没有构造函数,所以它必须通过父类的构造函数来实例化。即匿名内部类完全把创建对象的任务交给了父类去完成。
- 匿名内部类里创建新的方法没有太大意义,新方法无法被调用。
- 匿名内部类一般是用来覆盖父类的方法。
- 匿名内部类没有名字,所以无法进行向下的强制类型转换,只能持有匿名内部类对象引用的变量类型的直接或间接父类。
节点流和字符流区别于使用场景?
- Java 中的字节流处理的最基本单位为 1 个字节,通常用来处理二进制数据。字节流类 InputStream 和 OutputStream 类均为抽象类,代表了基本的输入字节流和输出字节流。
- Java 中的字符流处理的最基本的单元是 Unicode 代码单元(大小2字节),通常用来处理文本数据。
区别:
- 字节流操作的基本单元是字节;字符流操作的基本单元是字符
- 字节流默认不使用缓冲区;字符流使用缓冲区
- 字节流通常用于处理二进制数据,不支持直接读写字符;字符流通常用于处理文本数据
- 在读写文件需要对文本内容进行处理:按行处理、比较特定字符的时候一般会选择字符流;仅读写文件,不处理内容,一般选择字节流
特征:
- 以 stream 结尾都是字节流,reader 和 writer 结尾是字符流
- InputStream 是所有字节输入流的父类,OutputStream 是所有字节输出流的父类
- Reader 是字符输入流的父类,Writer 是字符输出流的父类
常见的字节流
文件流:FileOutputStream 和 FileInputStream
缓冲流:BufferedOutputStream 和 BufferedInputStream
对象流:ObjectOutputStream 和 ObjectInputStream
常见字符流
文件流:FileOutputStream 和 FileInputStream
缓冲流:BufferedOutputStream 和 BufferedInputStream
对象流:ObjectOutputStream 和 ObjectInputStream
缓冲流的优缺点?
不带缓冲的流读取到一个字节或字符,就直接写出数据带缓冲的流读取到一个字节或字符,先不输出,等达到了缓冲区的最大容量再一次性写出去
优点:减少了写出次数,提高了效率
缺点:接收端可能无法及时获取到数据
BIO、NIO、AIO有什么区别?
BIO:线程发起 IO 请求,不管内核是否准备好 IO 操作,从发起请求起,线程一直阻塞,直到操作完成。
NIO:客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。
AIO:线程发起 IO 请求,立即返回;内存做好 IO 操作的准备之后,做 IO 操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做 IO 操作完成或者失败。
BIO 是一个连接一个线程。
NIO 是一个请求一个线程。
AIO 是一个有效请求一个线程。
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的 IO 请求都是由 OS 先完成了再通知服务器应用去启动线程进行处理。
适用场景分析:
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。
NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。
AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
举个例子
同步阻塞:你到饭馆点餐,然后在那等着,啥都干不了,餐没做好,你就必须等着!
同步非阻塞:你在饭馆点完餐,就去玩儿了。不过玩一会儿,就回饭馆问一声:好了没?
异步非阻塞:饭馆打电话说,我们知道您的位置,一会给你送过来,安心玩儿就可以了,类似于外卖。
tcp和udp的区别?
TCP/IP 协议是一个协议簇,包括很多协议。命名为 TCP/IP 协议的原因是 TCP 和 IP 这两个协议非常重要,应用很广。
TCP 和 UDP 都是 TCP/IP 协议簇里的一员。
TCP,Transmission Control Protocol 的缩写,即传输控制协议。
面向连接,即必须在双方建立可靠连接之后,才会收发数据
信息包头 20 个字节
建立可靠连接需要经过3次握手
断开连接需要经过4次挥手
需要维护连接状态
报文头里面的确认序号、累计确认及超时重传机制能保证不丢包、不重复、按序到达
拥有流量控制及拥塞控制的机制
如何避免sql注入?
SQL 注入(SQL Injection),是 Web 开发中最常见的一种安全漏洞。
- 概念:
可以用它来从数据库获取敏感信息、利用数据库的特性执行添加用户、导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。 - 造成 SQL 注入的原因 :
程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的 SQL 脚本,程序在接收后错误的将攻击者的输入作为 SQL 语句的一部分执行,导致原始的查询逻辑被改变,执行了攻击者精心构造的恶意 SQL 语句。
什么是反射?
反射是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。反射非常强大,它甚至能直接操作程序的私有属性(暴力反射)。我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
JDBC介绍:
JDBC(Java Database Connectivity)是java和数据库之间的连接纽带
//1,注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2,获取和数据库的连接
//String url= "jdbc:mysql://localhost:3306/cgb2104?characterEncoding=utf8";//指定要连接哪个数据库
String url= "jdbc:mysql:///cgb2104?characterEncoding=utf8";//指定要连接哪个数据库
String user= "root" ; //使用的用户名
String pwd= "root" ; //使用的密码
Connection conn = DriverManager.getConnection(url, user, pwd);
//3,获取传输器,执行SQL
Statement st = conn.createStatement();
//4,执行SQL
ResultSet rs = st.executeQuery("select * from students");
//5,解析结果集
while( rs.next() ){//next()判断结果集中是否有数据
for (int i = 1; i <= 5 ; i++) {
//获取每列的值并打印
System.out.println( rs.getString(i) );
}
}
//6,释放资源
rs.close(); //关闭结果集
st.close();//关闭传输器
conn.close();//关闭连接
Tomcat介绍
Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。
Tomcat是一个免费的开放源代码的Servlet容器。
Servlet的介绍:
Java Servlet(Java服务器小程序)是一个基于Java技术的Web组件,运行在服务器端,它由Servlet容器所管理,用于生成动态的内容。 Servlet是平台独立的Java类,编写一个Servlet,实际上就是按照Servlet规范编写一个Java类。Servlet被编译为平台独立 的字节码,可以被动态地加载到支持Java技术的Web服务器中运行。
Servlet容器介绍:
Servlet容器也叫做Servlet引擎,是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务
Autowired的作用是什么?
@Autowired 是一个注释,它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。
@Autowired 默认是按照类去匹配,配合@Qualifier 指定按照名称去装配 bean。
Spring
Spring是一个轻量级的开源框架,以Ioc(控制反转)和AOP(面向切面编程)为内核,支持事务。
(组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现)
- 核心容器Spring Core: 核心容器提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。
BeanFactory 使用控制反转(IOC)模式,将应用程序的配置和依赖性规范与实际的应用程序代码分开。 - Spring上下文:Spring上下文是一个配置文件,向 Spring 框架提供上下文信息。
- Spring AOP: 通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。可以很容易地使 Spring框架管理的任何对象支持AOP。Spring AOP模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO: JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM: Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate和iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web: Web上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以Spring 框架支持与 Jakarta Struts的集成。Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC框架: MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
Spring框架两大核心?(IOC、DI)
- Ioc简单来说就是指将对象的创建和生命周期,对象的存储(map),对象的管理(依赖查找,依赖注入)交给了spring容器。所以在开发过程中不需要关注对象的创建和生命周期的管理,而是在需要的时候由Spring框架提供,这个由Spring框架管理对象创建和生命周期的机制称之为控制反转。
- 在创建对象的过程中Spring可以依据对象的关系,自动把其它对象注入(无需创建对象,直接拿着使用)进来,这个过程称之为DI注入。
所谓依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
IoC是设计思想,IoC有三个核心:BeanFactory、反射、DI。BeanFactory利用反射实现对象的创建,DI实现对象关系管理。
Spring框架的七大模块:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。
简单的说就是以后我们不用自己new对象了,对象的实例化都交给工厂来完成,我们需要对象的时候直接问工厂拿一个就行, spring IOC与工厂模式最大的不同在于普工厂模式内部是使用new来创建对象,但是spring IOC是用反射来创建对象。 - Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。
- Spring AOP:(通过配置管理特性)Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很轻松地让Spring 框架管理所有支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到我们的应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。
- Spring MVC 框架:Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View, Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错
SpringMVC介绍:
我们的POJO就是Model层,我们的JSP就是视图层,我们的Controller就是控制层。
现在主流基于SSM三大框架开发都是基于MVC上演化,可以分为持久层DAO,业务层Service,控制层Controller。持久层用来对数据库进行读写操作(读写ORM),业务层顾名思义就是用来处理业务逻辑的,控制层用来处理MVC的控制。SpringMVC就是基于MVC设计模式来实现的。
MVC模型介绍:
用来进行分层的结构,这样代码分离结构清晰,每一层代码都各司其职,方便开发项目。
MVC(Model模型、View视图、Control控制层),将软件进行分层达到松耦合的一个效果。
SpringMVC工作原理:
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用处理器映射器(HandlerMapping)。
- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象以及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用处理器适配器(HandlerAdapter)。
- 处理器适配器(HandlerAdapter)经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
MyBatis
Mybatis是一个优秀的持久层框架,基于ORM设计思想,实现了以对象的方式操作数据库.
MyBatis它支持自定义 SQL、存储过程以及高级映射。
MyBatis 封装了JDBC,简化了JDBC代码(以及设置参数和获取结果集的工作)
MyBatis的优缺点?
优点:
消除 JDBC 中的重复代码可以在 XML 或注解中直接编写 SQL 语句,比较灵活,方便对 SQL 的优化与调整,SQL 写在 XML 中,与代码解耦,按照对应关系方便管理
XML 中提供了动态 SQL 的标签,方便根据条件拼接 SQL,提供了 XML、注解与 Java 对象的映射机制与 Spring 集成比较方便
缺点:
字段较多、关联表多时,编写 SQL 工作量较大,SQL 语句依赖了数据库特性,会导致程序的移植性较差,切换数据库困难
为什么说MyBatis是半自动ORM?
说 MyBatis 是 半自动 ORM 最主要的一个原因是,它需要在 XML 或者注解里通过
手动或插件生成 SQL,才能完成 SQL 执行结果与对象映射绑定。
MyBatis的缓存
Mybatis对缓存提供支持,一级缓存是默认使用的,二级缓存需要手动开启。
一级缓存的作用域是一个sqlsession内;
二级缓存作用域是针对mapper进行缓存;
Mybatis有一级缓存和二级缓存,底层都是用HashMap实现的
key为CacheKey对象(后续说原因),value为从数据库中查出来的值。
Mybatis的二级缓存模块是装饰器的典型实现
一级缓存
Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。
也就是在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;
第二次以后是直接去缓存中取。
当执行SQL查询中间发生了增删改的操作,MyBatis会把SqlSession的缓存清空。
- 一级缓存是默认开启的;
- 底层其实是基于hashmap的本地内存缓存;
- 作用域是session(其实就相当于一个方法);
- 当session关闭或者刷新的时候缓存清空;
- 不通sqlsession之间缓存互不影响;
二级缓存
Mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。
- 首先mybatis默认是没有开启二级缓存的,
- 二级缓存需要我们手动开启,它是mapper级别的缓存;
- 同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
SpringBoot介绍
Spring Boot是基于Spring的一套快速配置脚手架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。
特点:
能快速的创建独立运行的Spring项目以及与主流框架集成
使用嵌入式的Tomcat,无需部署WAR文件
简化Maven配置
自动配置Spring
提供生产就绪型功能,如指标,健康检查和外部配置
设计模式
单例模式:懒汉式,饿汉式,双重校验锁 --/(单例模式:Bean 默认就是单例模式)
- 饿汉模式是在类加载时就创建了对象实例,不管你用不用这个类的对象,都会直接先创建一个
- 而懒汉模式是在使用时才创建(也就是调用获取实例对象方法时才创建)
先不给创建这个类的对象,等你需要的时候再创建–延迟加载的思想
延迟加载的思想:是指不会在第一时间就把对象创建好占用内存, 而是什么时候用到,什么时候再去创建对象
工厂模式:工厂,从字面意思理解,就是造东西的地方,通过 BeanFactory 来创建对象(所以,需要经常升级换代,就可以使用工厂模式)
装饰模式
装饰者模式在不改变原有对象的基础上,通过组合的方式给对象增加新功能
装饰者优点:
- 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能
- 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果
- 符合开闭原则
装饰者缺点: - 会出现更多的代码,更多的类,增加程序复杂性
- 动态装饰时,多层装饰时会更复杂
为什么要用数据库?
数据库可以高效且条理分明的存储mybatis数据
数据库可以分类保存数据,实现快速查询
数据库可以保证数据的一致性,完整性,减少数据的冗余
数据库满足的共享和安全方面的要求
数据库(Mysql)实现步骤
- 编辑mybatis-config.xml核心配置文件
执行数据源配置 - 编辑POJO实体对象.要求与数据库表中的字段一一对应
- 编辑Mapper接口. 添加接口方法
- 编辑接口的实现类(配置文件方式) 要求namespace id resultType
- mybatis加载指定的mapper映射文件
- 创建SqlSessionFactory工厂对象
- 获取SqlSession,开启数据库链接
- 获取接口对象(代理对象)
- 调用接口方法,获取返回值结果
- 关闭sqlSession链接.
事务的四大特性?
原子性:原子性是指事务是一个不可再分割的工作单位,事务中的操作要么都成功,要么都回滚。
一致性:一致性是指在事务开始之前和事务结束以后,数据库的完整性没有被破坏
隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;事务隔离分为不同级别,包括读未提交、读以提交、可重复读和串行化。
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,事务所对数据库的修改回永久的保存在数据库之中,并不会被回滚。即使数据库发生故障也不应该对其有任何影响。
数据库事务的隔离级别?
读未提交:顾名思义,就是一个事务可以读取另一个未提交事务的数据。
(最低级别,任何情况都无法保证)
读以提交:顾名思义,就是一个事务要等另一个事务提交后才能读取数据。
(可避免脏读的发生)
可重复读:就是在开始读取数据(事务开启)时,不再允许修改操作。
(可避免脏读、不可重复读的发生)
串行化:Serializable 是花费高代价但最可靠的事务隔离级别,事务会顺序执行。
(可以避免脏读、不可重复读与幻读)
缺点:但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
数据库事务存在问题?
脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
幻读(虚读):一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。
不可重复读:一个事务内,两次相同条件的查询返回了不同的结果。
不可重复读和脏读的区别?
脏某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
Mysql优化
避免数据类型不一致
尽量不用select *
尽量避免子查询
批量的insert插入
优化Group By语句
- 如果对group by语句的结果没有排序要求,要在语句后面加 order by null(group 默认会去排序);
- 尽量让group by过程用上表的索引,确认方法是explain结果里没有Using temporary 和 Using filesort;
- 如果group by需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大tmp_table_size参数,来避免用到磁盘临时表;
- 如果数据量实在太大,使用SQL_BIG_RESULT这个提示,来告诉优化器直接使用排序算法(直接用磁盘临时表)得到group by的结果。
MySQL的主要优势如下
运行速度快,MySQL体积小,命令执行的速度快。
使用成本低。MySQL是开源的,且提供免费版本,对大多数用户来说大大降低了使用成本。
使用容易。与其他大型数据库的设置和管理相比,其复杂程度较低,易于使用。
可移植性强。MySQL能够运行与多种系统平台上,如windouws,Linux,Unix等。
适用更多用户。MySQL支持最常用的数据管理功能,适用于中小型企业甚至大型网站应用。
你知道的数据库存储引擎?
-
InnoDB(底层存储结构B+树),B树的每个节点对应innodbd的一个page,page大小固定,一般为16k。
支持事务,支持行级锁,支持外键约束(因此支持写并发)
一个Innodb表存储在一个文件内,也可能为多个,受操作系统文件大小的限制
主键索引采用聚集索引,主键查询的性能高于其他类型的存储引擎 -
Myisam该存储引擎管理非事务性表,提供高速存储和检索,支持全文搜索。
不支持事务(但是整个操作是原子性的),不支持外键,支持表锁,每次锁住的是整张表。
一个MyISAM表有三个文件
一个MyISAM表有三个文件:索引文件,表结构文件,数据文件。
采用非聚集索引
采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。
MyISAM与InnoDB的区别?
InnoDB 支持事务;MyISAM 不支持事务
InnoDB 支持行级锁;MyISAM 支持表级锁
InnoDB 支持 MVCC(多版本并发控制);MyISAM 不支持
InnoDB 支持外键,MyISAM 不支持
InnoDB 是聚集索引,数据文件是和索引绑在一起的,必须有主键,通过主键索引效率很高。
辅助索引需要两次查询,先查询到主键,再通过主键查询到数据。主键太大,其他索引也会很大;MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针,主键索引和辅助索引是独立的
说一说MySQL中的锁机制?
数据库中数据是供多用户共享访问,锁是保证数据并发访问的一致性、有效性的一种机制。
锁的分类
按粒度分:
表级锁:粒度最大的一种锁,表示对当前操作的整张表加锁。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
行级锁:粒度最小的一种锁,表示只针对当前操作的行进行加锁。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高
页级锁:粒度介于行级锁和表级锁中间的一种锁。开销、加锁时间和并发度界于表锁和行锁之间;会出现死锁
按操作分:
读锁(共享锁):针对同一份数据,多个读取操作可以同时进行,不互相影响
写锁(排它锁):当前写操作没有完成前,会阻断其他写锁和读锁
什么是索引?什么场景使用?
索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息使用索引目的是加快检索表中数据
拓展 :(使用场景:
中到大数据量表适合使用索引
小数据量表,大部分情况全表扫描效率更高
特大数据量表,建立和使用索引的代价会随之增大,适合使用分区或分库)
索引的种类有哪些?
普通索引:最基本的索引,没有任何约束限制。
唯一索引:和普通索引类似,但是具有唯一性约束,可以有 null
主键索引:特殊的唯一索引,不允许有 null,一张表最多一个主键索引
组合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并
全文索引:对文本的内容进行分词、搜索
覆盖索引:查询列要被所建的索引覆盖,不必读取数据行
索引的优缺点?
索引的优点:
通过创建唯一索引,可以保证数据库每一行数据的唯一性
可以大大提高查询速度
可以加速表与表的连接
可以显著的减少查询中分组和排序的时间
索引的缺点:
- 创建索引和维护索引需要时间,而且数据量越大时间越长
- 存储过程的构造使得开发具有复杂业务逻辑的存储过程变得更加困难。
- mysql不允许调试存储过程。
- 开发和维护存储过程很难。
索引如何创建与删除?
创建单个字段索引的语法:CREATE INDEX 索引名 on 表名(字段名)
创建联合索引的语法:CREATE INDEX 索引名 on 表名(字段名1,字段名2)
索引命名格式一般可以这样:idx_表名_字段名。注意有长度限制
删除索引:DROP INDEX 索引名 ON 表名
索引对性能有哪些影响?
减少数据库服务器需要扫描的数据量,提高查询速度
唯一索引,能保证数据的唯一性
(帮助数据库服务器避免排序和临时表)
左连接、右连接、内连接和全外连接的区别?
左连接(left join):返回包括左表中的所有记录和右表中连接字段相等的记录。
右连接(right join):返回包括右表中的所有记录和左表中连接字段相等的记录。
内连接(inner join):只返回两个表中连接字段相等的记录。
全外连接(full join):返回左右表中连接字段相等的记录和剩余所有记录。
介绍一下Redis?
Redis 是一款高性能 key-value 数据库,他是使用 C 语言编写的,开源免费
优点:
- 性能极高,读写速度快
- 支持数据的持久化,可以异步地保存到磁盘上(对数据的更新采用Copy-on-write技术)
- 丰富的数据类型,String(字符串)、List(列表)、Hash(字典)、Set(集合)、ZSet(有序集合)
- 原子性:Redis 的所有操作都是原子性的,多个操作通过 MULTI 和 EXEC 指令支持事务
(先不说) - 丰富的特性:key 过期、publish/subscribe、notify
- 支持数据的备份,快速的主从复制
- 节点集群,很容易将数据分布到多个Redis实例中
缺点:
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写
- 适合的场景主要局限在较小数据量的高性能操作和运算上
Redis支持五种数据类型?
String:字符串
Hash:哈希
List:列表
Set:集合
Sorted Set:有序集合
为什么Redis主从复制?
主从复制就是现在有俩台redis服务器,把一台redis的数据同步到另一台redis数据库上。前面的redis服务器叫主节点(master),后面的为从节点(slave)。redis主从复制的数据是只能是master往slave单向复制的。
为什么需要Redis主从复制?
如果只有一台redis服务器(单机状态)
- 如果服务器宕机,会导致数据直接丢失。
- 然后就是内存问题,一台服务器内存是有限的。
所以基于这俩个问题,我们就需要准备几台服务器,配置主从复制。将数据保存在多个服务器上。并且保证每个服务器的数据是同步的。即使有一个服务器宕机了,也不会影响用户的使用。redis可以继续实现高可用、同时实现数据的冗余备份。 - Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构。(Redis主从复制可以根据是否是全量分为全量同步和增量同步)
一主一从和一主多从:
是最常见的主从架构,实施起来简单并且有效,不仅可以实现HA,而且还能读写分离,进而提升集群的并发能力。
级联复制
级联复制模式下,部分slave的数据同步不连接主节点,而是连接从节点。因为如果主节点有太多的从节点,就会损耗一部分性能用于replication,那么我们可以让3~5个从节点连接主节点,其它从节点作为二级或者三级与从节点连接,这样不仅可以缓解主节点的压力,并且对数据一致性没有负面影响。
Redis主从复制的作用?
- 针对单机故障问题。当主节点也就是master出现问题时,可以由从节点来提供服务也就是slave,实现了快速恢复故障,也就是服务冗余。
- 读写分离,master服务器主要是写,slave主要用来读数据,可以提高服务器的负载能力。同时可以根据需求的变化,添加从节点的数量。
- 负载均衡,配合读写分离,有主节点提供写服务,从节点提供读服务,分担服务器负载,尤其在写少读多的情况下,通过多个从节点分担读负载,可以大大提高redis服务器的并发量和负载。
- 高可用的基石,主从复制是哨兵和集群能够实施的基础,因此我们可以说主从复制是高可用的基石。
- 是数据冗余了,实现了数据的热备份,是持久化之外的另一种方式。
Redis如何做内存优化?
- 缩减键值对象:满足业务要求下 key 越短越好;value 值进行适当压缩
- 共享对象池:即 Redis 内部维护[0-9999]的整数对象池,开发中在满足需求的前提下,尽量使用整数对象以节省内存
- 尽可能使用散列表(hashes)
- 编码优化,控制编码类型
- 控制 key 的数量
Redis有哪些适用场景?
- 会话缓存(Session Cache),是 Redis 最常使用的一种情景
- 全页缓存(FPC)
- 用作网络版集合和队
- 排行榜和计数器,Redis 在内存中对数字递增、递减的操作实现的非常好。Set 和 Sorted Set 使得我们在执行这些操作的时候非常简单
- 发布和订阅
介绍一下Dubbo?
Dubbo 是一个分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案, 可以和 Spring 框架无缝集成。
Dubbo和Spring Cloud的区别?
- 定位:Dubbo 专注 RPC 和服务治理;Spirng Cloud 是一个微服务架构生态(微服务架构下的一站式解决方案)
(Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式) - 性能:Dubbo 强于 SpringCloud(主要是通信协议的影响)
- 功能范围:Dubbo 诞生于面向服务架构时代,是一个分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案;Spring Cloud 诞生于微服务架构时代,基于 Spring、SpringBoot,关注微服务的方方面面,提供整套的组件支持
- 协议:Dubbo 使用 Netty,基于 TCP 协议传输,用 Hessian 序列化完成 RPC 通信;SpringCloud 是基于 Http协议 + Rest 风格接口通信。Http 请求报文更大,占用带宽更多;Rest 比 RPC 灵活
- 更新维护:Dubbo 曾停止更新,2017年重启维护,中文社区文档较为全面;一直保持高速更新,社区活跃
- Dubbo 构建的微服务架构像组装电脑,组件选择自由度高、玩不好容易出问题;Spring Cloud 的像品牌机,提供一整套稳定的组件。
什么是主从复制?
也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
一个Master,两个Slave,Slave只能读不能写;当Slave与Master断开后需要重新slave of连接才可建立之前的主从关系;Master挂掉后,Master的关系依然存在,Master重启即可恢复。
上一个Slave可以是下一个Slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了 链条中下一个slave的Master,
如此可以有效减轻Master的写压力。如果slave中途变更转向,会清除之前的数据,重新建立最新的。
当Master挂掉后,Slave可键入命令 slaveof no one使当前redis停止与其他Master redis数据同步,转成 Master redis。(反客为主)
复制原理
- Slave启动成功连接到master后会发送一个sync命令;
- Master接到命令启动后的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步;
- 全量复制:而slave服务在数据库文件数据后,将其存盘并加载到内存中;
- 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步;
- 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。
哨兵模式(sentinel)
反客为主的自动版,能够后台监控Master库是否故障,如果故障了根据投票数自动将slave库转换为主库。一组sentinel能同时监控多个Master。
使用步骤:
- 在Master对应redis.conf同目录下新建sentinel.conf文件,名字绝对不能错;
- 配置哨兵,在sentinel.conf文件中填入内容:sentinel monitor 被监控数据库名字(自己起名字) ip port 1
说明:上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机。 - 启动哨兵模式:
命令键入:redis-sentinel /myredis/sentinel.conf
注:上述sentinel.conf路径按各自实际情况配置
复制的缺点
延时,由于所有的写操作都是在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使得这个问题更加严重。
缓存击穿、缓存穿透、缓存雪崩分别是什么?
缓存穿透—不断发起缓存(Redis)和数据库(DB)都没有的请求,导致数据库压力过大。
缓存击穿—缓存中没有,但数据库中有的数据,高并发下造成数据库压力过大
缓存雪崩—大批量的缓存击穿,大批量缓存过期,查询量过大导致数据库奔溃
缓存穿透
关键词:穿过 Redis 和数据库
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿
关键词:缓存时间到期—(定点打击)
缓存击穿是指缓存中没有,但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存,没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:
- 热点数据永远不过期
(比如我们可以将某个 key 的缓存时间设置为 25 小时,然后后台有个 JOB 每隔 24 小时就去批量刷新一下热点数据。就可以解决这个问题了) - 加互斥锁
(容易影响吞吐量,大部分项目设置热点 key 永不过期就妥妥的了)
缓存雪崩
关键词:缓存中数据大批量到过期时间,查询数据量巨大缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。
Linux系统中进程有五种状态
运行(正在运行或在运行队列中等待)
中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)
不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)
僵死(进程已终止, 但进程描述符存在, 直到父进程调用 wait4() 系统调用后释放)
停止(进程收到 SIGSTOP, SIGSTP, SIGTIN, SIGTOU 信号后停止运行)
命令:
Is:list 的缩写,通过 ls 命令可以查看 目录下文件,而且可以查看文件权限(包括目录、文件夹、文件权限)、查看目录信息等等。
cd:
cd / 进入要目录
cd ~ 进入 "home" 目录
cd - 进入上一次工作路径
pwd:命令用于查看当前工作目录路径
pwd -P :查看软链接的实际路径
mkdir:创建文件夹
rm:删除一个目录中的一个或多个文件或目录。如果没有使用 -r 选项,则 rm 不会删除目录。
rm -rf : 强制删除目录或文件,无需确认
cp:复制,将多文件或目录复制至目标目录
Session和Cookie的区别?
- Cookie可以存储在浏览器或者本地,Session只能存在服务器
- session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象
- Session比Cookie更具有安全性(Cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击)
- Session占用服务器性能,Session过多,增加服务器压力
- 单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie,Session是没有大小限制和服务器的内存大小有关。
三次握手四次挥手
三次握手的目的就是让这个客户端和服务端都确认他们的接收和发送能力是否是正常的
第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN。此时客户端处于 SYN_SENT 状态。
首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。
第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_RCVD 的状态。
在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。
第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。
如果两次握手
试想如果是用两次握手,则会出现下面这种情况:
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。
三次握手是为了建立连接,而四次挥手是为了终止一个连接(TCP的半关闭(half-close)造成的)
第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。
即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。
第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。
第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。
第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。
挥手为什么需要四次?
因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。