导图社区 java思维导图上
java思维导图上,如异常处理是程序在执行过程中,出现非正常情况,如果不处理最终会导致JVM的非正常停止。
编辑于2023-06-14 17:47:12 湖北省java
一阶段
运算符
移位运算符
左移
a<<b 将二进制形式的a逐位左移b位,最低位空出的b位补0
带符号右移
a>>b将二进制形式的a逐位右移b位,最高位空出b位补原来的符号位
无符号右移
a>>>将二进制形式的a逐位右移b位,最高位空出的b位补0
数组Arrays常用工具类
数组元素拼接
static String toString(int[] a)
返回的字符串由数组元素组成
static String toString(Object[] a)
返回的字符串是元素调用自身重写的toString方法进行拼接,如果没有重写会返回hash值
数组排序
static void sort(int[] a)
将数组a从大到小排序
static void sort(int[] a,int fromIndex,int toIndex)
将数组的fromIndex到toIndex按照升序进行排序
static void sort(Object[] a)
按照元素的自然顺序对指定对象的数组按照升序进行排序
static void sort(T[] a Comparator<? super T>c)
根据指定比较器生产的顺序对指定对象数组进行排序
数组元素的二分查找
static int binarySearch(int[] a,int key) static int binarySearch(Object[] a,Object key)
要求数组有序,在数组中查找key是否存在,如果存在返回第一次找到的下标,不存在返回负数
数组的复制
static int[] copyOf(int[] original, int newLength)
根据 original 原数组复制 一个长度为 newLength 的新数组,并返回新数组
static T[] copyOf(T[] original,int newLength)
根据 original 原数组复制一 个长度为 newLength 的新数组,并返回新数组
static int[] copyOfRange(int[] original, int from, int to)
复制 original 原数 组的 [from,to) 构成新数组,并返回新数组
static T[] copyOfRange(T[] original,int from,int to)
复制 original 原数组的 [from,to) 构成新数组,并返回新数组
数组的填充
static void fill(int[] a, int val)
用 val 值填充整个 a 数组
static void fill(Object[] a,Object val)
用 val 对象填充整个 a 数组
static void fill(int[] a, int fromIndex, int toIndex, int val)
将 a 数组 [fromIndex,toIndex) 部分填充为 val 值
static void fill(Object[] a, int fromIndex, int toIndex, Object val)
将 a 数组 [fromIndex,toIndex) 部分填充为 val 对象
面向对象
匿名对象
new 对象名().方法名()
可以不定义对象名,直接调用方法
jvm内存结构划分
堆heap
存放对象实例
栈stack
存放局部变量,方法执行完会自动释放
方法区Method Area
存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据
方法
方法的重载
定义
在同一个类中,允许存在一个以上的同名方法,只要参数列表不同即可,重载与修饰符和返回值类型无关,只看参数列表,且列表必须不同,JVM是通过方法的参数列表调用匹配的方法,先找个数类型匹配的,然后再找个数和类型可以兼容的
可变参数
格式
方法名(参数的类型名 ...参数名)
特点
可变参数部分指定类型的参数的个数可以是0个1个或者多个
可变参数必须放在最后
一个方法最多只能声明一个可变
构造器
说明
构造器必须与它所在的类名必须相同
没有返回值,不需要返回值类型,也不需要void
修饰符只能是权限修饰符,不能被其他修饰符修饰
注意
系统会默认提供一个无参构造器并且该构造器的修饰符默认与类的修饰符相同
当显式定义类的构造器后,系统就不再提供默认的无参构造器了
构造器可以重载
类中属性的赋值(由上至下顺序)
默认初始化
显式初始化
构造器中初始化
通过对象.属性或者对象.方法的方式给属性复制
javaBean
可重用组件
标准
公共的
有无参的公共构造器
有属性,且有对应的get、set方法
封装
作用
高内聚:类的内部数据操作细节自己完成,不允许外部干涉
低耦合:仅暴露少量的方法给外部使用,尽量方便外部调用
实现
privat修饰符
作用域:本类内部
缺省
作用域:本类内部、本包内
protected
作用域:本类内部、本包内、其他包的子类
public
作用域:本类内部、本包内、其他包的子类、其他包非子类
this关键字
方法内部使用,表示调用这个方法的对象
构造器内使用,表示该构造器正在初始化的对象
使用this方法访问属性和方法时,如果本类未找到,那么会从父类中查找
继承
方法的重写
重写的方法必须和父类被重写的方法具有相同方法名称、参数列表
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
子类重写的方法使用的权限不能小于父类被重写的方法的访问权限
父类私有方法不能重写
跨包的父类缺省方法也不能重写
子类方法抛出的异常不能大于父类被重写方法的异常
super关键字
作用
可以用来访问父类定义的属性
调用父类中定义的方法
在子类构造器中调用父类的构造器
注意
父子类出现同名成员,可以用super调用父类的成员
super追溯不仅限于直接父类
super代表父类内存空间的标识
使用场景
子类中调用父类已经被重写的方法
子类中调用父类中同名的成员变量
调用父类构造器协助当前对象的实例化,只能在构造器首行
如果子类构造器中未显示调用父类或本类的构造器,且父类中又没有空参的构造器,会编译出错的
多态
java中的体现
父类引用指向子类对象
父类类型 变量名=子类对象;
理解
java运用变两有两个类型:编译时类型和运行时类型。编译时类型由生命该变量时使用的类型决定,运行时类型由实际赋值给该变量的对象决定
编译时看父类,只能调用父类生命的方法,不能调用子类扩展的方法
运行时看子类,如果子类重写了方法,一定是执行子类重写的方法体
优劣
优:变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活,功能更加抢答,可维护性和扩展性更好
劣:一个引用类型变量如果声明为父类的类型,单实际引用的是子类对象,那么该变量就不能访问子类中添加的属性和方法了
使用
开发中使用父类方法做为形参,这样即便增加了新的子类,方法也无需改变,提高了父类扩展性,符合开闭原则
注意
若子类重写了父类方法,意味着子类里定义的方法彻底覆盖了父类的同名方法,系统将不可以将父类的方法转移到子类中
实例变量不存在这种现象,即时子类定义了父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
类型转换
向上转型
当左边变量的类型(父类)>右边对象/变量的类型(子类)
自动完成,只能调用父类中有的变量和方法,执行的方法是子类重写的方法
向下转型
左边变量的类型(子类)<右边对象/变量编译时的类型(父类)
编译时按照左边变量的类型处理,可以调用子类特有的变量和方法,运行时,仍是对象本身的类型(不安全,需要强制转换)
islnstanceof
判断是否是父子关系
Object
equals()
作用
基本类型比较值
引用类型比较是否指向同一个对象(可以重写)
重写
所有类都继承了Object,同时继承了equals()方法
重写原则
对称性
如果x.equals(y)返回结果是true,那么y.equals(x)也应该返回true
自反性
x.equals(x)必须返回true
传递性
如果x.equals(y)返回true,而且y.equals(z)返回是true,那么z.equals(x)也应该返回true
一致性
如果 x.equals(y)返回是“true”,只要 x 和 y 内容一直不变,不管重复 x.equals(y)多少次,返回都是“true”
任何情况下:任何情况下,x.equals(null),永远返回是“false”; x.equals(和 x 不同类型的对象)永远返回是“false”。
与==的区别
==可以比较基本类型也可以比较引用类型,对于基本类型式比较值,对于引用数据类型式比较内存地址
equals,如果没有被重写默认功能也是==,通常情况下重写的equals方法会比较累中相应属性是否都相等
toString()
默认情况下返回对象hashCode值的十六进制形式
finalize()
当对象被回收时,系统自动调用该对象finalize方法,不是垃圾回收器调用的,是本类对象调用的
不要主动调用某个对象的finalize方法
子类可以重写该方法,目的是在对象被清理之前执行必要的清理操作,例如在方法内断开相关链接资源
getClass()
获取对象运行时的类型
hashCode()
返回对象的hash值
关键字
static
静态变量的特点
值对所有对象共享
在本类中,可以在任意方法、代码块、构造器中直接使用
权限修饰符允许,在其他类中可以通过”类名.静态变量“直接访问,也可以通过”对象.静态变量“的方式访问(后者不建议)
静态变量的get/set方法也是静态的,当 局部变量与静态变量重名是,使用”类名.静态变量“进行区分
静态方法的特点
静态方法在本类的任意方法、代码块、构造器中都可以直接被调用
权限修饰符允许的情况下,静态方法在其他类中可以通过”类名.静态方法“的方式调用,也可以通过”对象.静态变量“的方式访问(后者不建议)
static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构
静态方法可以被子类继承,但是不能被子类重写
静态方法的调用只看编译时类型
因为static方法不需要创建对象就可以使用,因此static方法内部不能有this,也不能有super,如果有重名问题,使用”类名.“进行区别
代码块
静态代码块
可以有输出语句
可以对类的属性、类的声明进行初始化操作
不可以对非静态的属性初始化,即不可以调用非静态的属性和方法
若多个静态的代码块,那么按照冲上到下的顺序执行
静态代码块的执行先于飞静态代码块
静态代码块随着类的加载而加载,且只能执行一次
非静态代码块
类似构造器,用于实例变量的初始化等操作
当多个构造器有公共代码时,可以将这部分代码抽取到非静态代码块中
可以有输出语句,可以对类的属性,类的声明进行初始化操作
除了调用非静态的结构外,还可以调用静态的变量或者方法
多个非静态的代码块,按照从上到下的顺序依次执行
每次创建对象都会执行一次,先于构造器执行
实例变量赋值顺序
1、声明成员变量的默认初始化
2、显示初始化、多个初始化块依次被执行(同级别下按先后顺序执行)
3、构造器再对成员进行初始化操作
4、通过 对象.属性 或者 对象.方法 的方式,可多次给属性赋值
final
修饰类
不能被继承,没有子类
修饰方法
不能被子类重写
修饰变量
赋值后不能被修改,常量名建议使用大写字母
类
抽象类
不能创建对象,只能创建其非抽象子类的对象(只要包含抽象方法的类就是抽象类,就不能创建对象)
抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体,若没有重写全部的抽象方法,仍是抽象类
抽象类中有构造方法,供子类创建对象时,初始化父类成员变量使用
抽象类中,不一定包含抽象方法,但是抽象方法的类必定是抽象类
接口
说明
公共的静态常量,其中public static final可以省略
公共的抽象方法,其中public abstract可以省略
公共默认的方法,public可以省略,建议保留,但是default不能省略
公共静态的方法,public可以省略,建议保留,但是static不能省略
注意
如果接口的实现类是非抽象类,必须重写接口中所有抽象方法
默认方法可以选择保留,也可以重写
重写时,default单词不要再次了,只用于在接口中表示默认方法,当没中就没有默认方法的概念
接口中的静态方法不能被重写也不能被继承
一个类可以实现多个接口,接口中有多个抽象方法时,实现类必须重写所有抽象方法,如果抽象方法有重名,只需要重写一次
接口可以继承另一个接口,实现类实现的时候所有父接口的抽象方法都需要重写,但是方法签名相同的抽象方法只需要实现一次
接口不可以直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量
对于接口的静态方法,直接使用接口名.调用即可,也只能使用接口名.进行调用,不能通过实现类的对象进行调用
对于接口的抽象方法、默认方法、只能通过实现类对象才可以调用,接口不能直接创建对象,只能创建实现类的对象
总结
类实现接口,关键字式implement,而且支持多实现,如果实现类不是抽象类,就必须实现接口中的所有抽象方法,如果实现类既要继承弗雷又要实现弗雷借口,那么继承在前,实现在后
接口可以继承接口,支持多继承
接口的默认方法可以选择充血或者不重写,子类重写父接口的默认方法要去掉default,子接口重写父接口的默认方法不要去掉default
接口的静态方法不能被继承,也不能被重写,接口的静态方法只能通过接口名.静态方法名进行调用
内部类
成员内部类
特点
角色
作为类的成员的角色
可以声明为private和protected
可以调用外部类的结构(静态内部类不能使用外部类的非静态成员)
可以声明为static,但声明后不能使用外层累的非static的成员变量
作为类的角色
可以在内部定义属性、方法、构造器等结构
可以继承自己需要继承的父类,实现想要实现的父类接口,与外部类的父类和父类接口无关
可以声明为abstract类,因此可以被其他内部类继承
可以声明为final,表示不能够被继承
编译后生成OuterClass$InnerClass.class字节码文件
注意
外部类访问成员内部类的成员需要 内部类.成员 或 内部类对象.成员 的方式进行访问
成员内部类可以直接使用外部类的所有成员,包括私有的数据
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
语法格式
静态成员内部类
实例化
外部类名.静态内部类名 变量=外部类名.静态内部类名;
变量.非静态方法;
非静态成员内部类
实例化
外部类名 变量1=new 外部类();
外部类名.非静态内部类名 变量2=变量1.new 非静态内部类类名();
局部内部类
特点
编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号
前面不能有权限修饰符
有作用域
局部内部类是否可以访问外部类的非静态成员,取决于所在的方法
语法格式
非匿名局部内部类
匿名局部内部类
包装类
包装类与基本数据类型的转换
包装类对象的拆箱和装箱都是自动的
基本数据类型、包装类与字符串间的转换
基本数据类型转为字符串
调用字符串重载的valueOf()方法 String str = String.valueOf(a);
String str = a + "";
字符串转为基本数据类型
所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型
字符串转为包装类(同基本字符串转为基本数据类型)然后自动拆箱为基本数据类型
通过包装类的构造器实现
API
包装类型的最大值和最小值
Integer.MAX_VALUE 和 Integer.MIN_VALUE
Long.MAX_VALUE 和 Long.MIN_VALUE
Double.MAX_VALUE 和 Double.MIN_VALUE
字符串转大小写
Character.toUpperCase('x');
Character.toLowerCase('X');
整数转进制
Integer.toBinaryString(int i)
Integer.toHexString(int i)
Integer.toOctalString(int i)
比较方法
Double.compare(double d1, double d2)
Integer.compare(int x, int y)
异常处理
定义
程序在执行过程中,出现非正常情况,如果不处理最终会导致JVM的非正常停止
异常体系Throwable
常用方法
printStackTrace()
打印异常的信息,包含异常类型、异常原因、异常出现的位置、在开发和调试阶段都需要使用printStackTrace
getMessage()
获取发生异常的原因
Error
虚拟机无法解决的严重问题,例如系统内部错误、资源好近等严重情况,一般不编写针对性的代码进行处理
Exception
定义
因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,是程序继续运行,否则发生异常,程序也会挂掉
编译时异常
定义
在代码编译阶段,编译器可以明确警示当前代码可能发生异常,如果没有处理异常,则无法运行Exception类的非RuntimeException子类都是编译时异常
运行时异常
定义
在代码编译阶段,编译器不做任何检查,只有运行起来并确实发生了一场才会被发现,RuntimeException类及其子类都是运行时异常
异常处理
捕获异常try-catch-finally
语法
声明抛出异常类型throws
语法
重写要求
如果父类被重写的方法的方法签名后面没有throws编译时异常类型,那么重写方法时,方法签名后面也不能出现throws编译时异常类型
如果父类被重写方法的方法签名后面有throws编译时异常类型,那么重写方法时,throws的编译时异常类型必须<=被重写方法throws的变异时异常类型,或者不throws编译时异常
方法重写,对于throws运行时异常类型没有要求
处理方式选择
如果代码中涉及到资源调用,必须考虑使用第一种,保证不出现内存泄漏
如果父类被重写的方法没有throws异常类型,则子类重写的方法中如果出现异常,只能考虑使用第一种处理,不能throws
开发中,如果方法a中依次调用了方法bcd等方法,方法bcd是递进关系,且如果方法bcd中有异常,我们通常选择使用throws,二方法a中通常选择第一种
手动抛出异常throw
格式
throw new 异常类型(参数)
规则
如果是编译时异常类型的对象,同样需要使用throws或try-catch处理,否则编译不通过
如果是运行时异常类型的对象,编译器不提示
可以报出的异常必须是Throwable或其子类的实力,下面的语句在编译时将会产生语法错误
注意
无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有被try-catch合理的处理,都会导致程序崩溃
throw语句会导致程序执行流程被改变,throw语句是明确抛出一个异常对象,因此它后面的代码不会被执行
如果方法没有try-catch处理这个异常对象,throw语句会代替return语句提前终止当前方法的执行,并返回一个异常对象给调用者
自定义异常
方法
继承一个异常类型
编译时异常,继承java.lang.Exception
运行时异常,继承java.lang.RuntimeException
建议至少提供两个构造器,一个无参构造,一个(String message)构造器
注意
自定义异常只能通过throw
自定义异常最重要的是一场的名字和message属性,需要可以通过名字判断异常类型
自定义异常对象只能手动抛出,抛出后由try-catch处理,也可throws由调用者处理
多线程
线程调用
分时调度
所有线程使用CPU的使用权,并且平均分配每个线程占用CPU的时间
抢占式调度
让优先级高的线程以较大的概率有限使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用为抢占式调用
并行与并发
并行
指两个或多个事件在同一时刻发生(同时发生),指在同一时刻,有多条指令在多个CPU上同时执行
并发
指两个或多个事件在同一个时间段内发生,即在一段时间内,有多条执行在单个CPU上快速轮换、交替执行,使得在宏观上具有多个进行同时执行的效果
启动和创建线程
继承Thread类
步骤
定义Thread类的子类,并重写run()方法,该run()方法的方法体代表了线程需要完成的任务
创建Thread子类的实例,即创建了线程对象
调用线程对象的start()方法地洞线程
注意
手动调用run()方法,只是普通方法,无法启动多线程模式
run()方法由JVM调用该,调用时间、过程控制由操作系统的CPU调用决定
启动多线程必须调用start方法
一个对象只能调用一次start()方法启动,如果重复调用,会抛出异常IllegalThreadStateException
构造器
public Thread()
分配一个新的线程对象
public Thread(String name)
分配一个指定名字的心得线程对象
public Thread(Runnable target)
指定创建线程的目标对象,它实现了Runnable接口中的run方法
public Thread(Runnable target,String name)
分配一个带有指定目标心得线程对象并指定名字
常用方法
pubic viod run()
次线程要执行的任务在此处定义代码
public void start()
到此次线程开始执行,java虚拟机调用次线程的run方法
public String getName()
获取当前线程名称
public void setName(String name)
设置线程名称
public static Thread currentThread()
返回当前正在执行的线程对象的引用。在Thread子类中就是this,通常用于主线程和Runnable实现类
public static void yield()
让当前的线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程获的执行机会,但很有可能是某个县城暂停之后,线程调度再次调度起来重新执行
public final boolean isAlive()
测试县城是否处于活动状态,如果线程已经启动且尚未终止,则为活动状态
void join()
等待该线程终止
void join(long millis)
等待该线程终止的时间最长时间为millis毫秒,如果millis时间到,将不再等待
void join(long millis,int nanos)
等待该线程终止的时间最长为millis毫秒+nanos纳秒
public final void stop()
不建议使用,强行结束一个线程的执行,直接进入死亡状态,run()立即停止,可能会导致一些请理性工作得不到完成,stop会立即释放该线程所有的锁,导致数据得不到同步处理,出现数据不一致的情况
void suspend()/void resume()
暂停或恢复线程,必须成对使用,暂停时不会释放任何锁,其他线程无法访问它占用的锁,已经过时,不建议使用
优先级
优先级常量
MAX_PRIORITY(10)
最高优先级
MIN_PRIORITY(1)
最低优先级
NORM_PRIORITY(5)
普通优先级,默认情况main线程具有普通优先级
public final int getPriority()
返回线程优先级
public final void setPriority(int newPriority)
改变线程优先级
实现Runnable接口
步骤
定义Runable接口的实体类,并重写该接口的run()方法,该run()方法的方法体是该线程的线程执行体
创建Runable实现类的实例,并以此作为Thread的target参数来创建Thread对象,该Thread对象才是真正的线程对象
调用线程对象start()方法,启动线程,调用Runable接口实现类的run方法
说明
Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法进作为线程执行体,而实际的线程对象仍然是Thread实例,只是该Thread线程负责执行器target的run()方法
变形写法
使用匿名内部类对象来实现线程的创建和启动
两种方法的比较
联系
Thread类实际上也是实现了Runable接口的类
区别
继承Thread:线程代码存放在Thread子类run方法中
实现Runable:线程代码存在接口的子类的run方法
Runnable的优势
避免了单继承的局限性
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源
增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
实现Callable接口
线程池
作用
容纳多个线程的容器,其中线程可以反复使用,免去频繁创建/销毁线程对象的操作,无需反复创建线程而损耗不必要的资源
组成
线程容器
线程池当中复杂存储、维护一组工作线程容器,这些被存储管理的任务执行结束后,并不会被销毁,而是再次回到线程池中称为空闲状态,等待下次使用
任务队列
线程池使用一个阻塞队列来存储当前线程池正在等待执行的任务。当所有线程都在执行任务时,新提交的任务会进入任务队列等待。一旦有线程执行完当前任务,它会从任务队列中获取下一个任务并开始执行
流程
创建线程池(对象)
向线程池提交任务,先提交的任务先执行,后提交的任务后执行
任务的调度和执行,不同的线程池会采取不同任务调度方案和执行策略
如果任务比较少,当前线程池中的线程够用,则按照提交任务的顺序执行,先提交先执行
任务比较多时,线程池有时会选择创建更多的线程来执行任务
当线程池的线程已达到最大值,还有多余任务需要执行,这些任务会去任务队列中排队
线程池关闭
创建和使用
java.util.concurrent.Executors
线程池的工厂类,通过此类的静态工厂方法可以常见多种类型的线程池对象
常用线程池
Executors.newCachedThreadPool()
可自动扩容的线程容器,每当需要执行一个新任务时,有活动的线程就使用该线程,否则就新建一条线程
线程池的最大线程数为Integer.MAX_VALUE,基本上不受限制
使用完毕后的线程会回归线程池,如果这个这个线程在60秒后依旧空闲,那么就会被移除
线程池初始线程个数为0
用于线程生命周期短且频繁创建的场景
Executors.newFixedThreadPool(int nThreads)
创建一个固定线程数量的线程池,在整个线程池的生命周期中,线程池的数量不会变化
维护一个无界队列(暂存已提交的来不及执行的任务)按照任务的提交顺序,将任务执行完毕
适合执行长时任务或需要限制并发数量的场景
Executors.newSingleThreadExecutor()
线程数量固定为1个,在整个线程池的生命周期中,线程池中的数量不会变化
维护了一个无界队列(暂存已提交的来不及执行的任务)按照任务的提交顺序,将任务执行完毕
这种类型的线程池适用于需要保证任务顺序执行,不需要同时执行的场景
Executors.newScheduledThreadPool(int corePoolSize)
创建 一个线程池,它可安排在给定延迟后运行命令或者定期地执行
java.util.concurrent.ExecutorService
一个接口,是全体线程池类型的父类,它用于指代线程池对象
void execute(Runnable command)
ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new NumberThread())
执行任务/命令,没有返回值,一般用来执行Runnable
<T> Future<T> submit(Callable<T> task)
ExecutorService pool = Executors.newFixedThreadPool; pool.submit(new MyCallableTask()); es.submit(new ThreadPoolRunnable());
两种线程都可以提交
void shutdown()
执行完任务列表中所有已提交的任务,关闭线程池,不再接受新任务。
void shutdown()
立刻停止所有正在执行的活动任务,也不再处理任务列表中等待的任务,并直接返回等待执行的任务列表。
List<Runnable> shutdownNow()
守护线程
概念
为其他线程提供服务,这种线程被称为守护线程如果所有非守护线程都死亡,那么守护线程自动死亡
setDeamon(true)
将指定线程设置为守护线程
isDeamon()
判断线程是否是守护线程
声明周期
新建
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪
当线程对象调用了start()方法之后,线程将从新建状态转为就绪状态,JVM会为其创建方法调用栈和程序计数器
运行
如果处于就绪状态的线程获得了CPU资源时,开始执行run()方法的线程体代码,则该线程处于运行状态
阻塞
进入阻塞
线程调用sleep()方法,主动放弃所占用的CPU资源
线程视图获取一个同步监视器,但该同步监视器正在被其他线程锁持有
线程执行过程中,同步监视器调用了wait(),让它等待某个通知(notify)
线程执行过程中,同步监视器调用了wait(time)
线程执行过程中,遇到了其他线程对象的加塞(join)
线程被调用suspend方法被挂起(以过时,容易发生死锁)
解除阻塞
线程的sleep()时间到
线程成功获得了同步监视器
线程等到了通知(notify)
线程wait的时间到了
加塞的线程结束了
被挂起的线程又被调用了resume回复方法(已经过时,容易发生死锁)
死亡
结束后的线程处于死亡状态
三种结束方式
run()方法执行完成,线程正常结束
线程执行过程中抛出了未捕获的异常或错误
直接调用该线程的stop()来结束该线程(已过时)
代码层面的线程状态
NEW
至今尚未启动(未start)的线程处于这种状态
对应理论状态的“新建”
RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态
对应理论状态的
“就绪”
和
“运行”
。也就是说,在Java代码当中规定:
一个线程只要start启动,无论是否抢到CPU执行权,它的状态都是RUNNABLE
BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态
WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态
TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态
对应理论状态阻塞
TERMINATED
已退出的线程处于这种状态
对应理论状态的“死亡”
线程安全
synchronized锁
同步代码块
同步代码块必须定义在局部范围,但普遍都会定义在方法内部的局部位置
锁对象
静态方法
当前类的Class对象(类名.class)
非静态方法
this
注意
锁对象可以填入任意对象的引用,甚至可以写匿名对象,但为了实现线程同步,必须保证多个线程共用一个锁对象,也就是用同一把锁才有效果
当某个线程开始执行同步代码块中的代码时,除非该线程执行完其中的代码,否则线程切换由于锁的存在其余线程也无法执行原子操作代码
同步方法
synchronized关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等待
锁对象是this指向的当前对象
静态同步方法
用synchronized关键字修饰的静态成员方法
锁对象是该类型的字节码Class对象
异常处理
线程在执行同步代码块时,如果抛出异常,则会同时释放锁对象,如果没有抛出异常而是处理异常,则不会释放锁对象
多线程的线程间相对独立,某个线程抛出异常,不会影响其他线程执行
优劣
优点
使用简单,只要使用同一把锁,在同一时间就只能有一个线程执行原子操作
缺点
线程在执行原子操作代码时需要频繁判断锁的状态,频繁获取/释放锁,这是很耗费资源的,效率偏低
synchronized关键字实现线程同步的格式非常固定,锁的获取和释放都是JVM底层决定的,限制较多
Lock同步锁
基本使用
需要使用Lock锁,必须创建Lock接口的子类对象,需要使用其实现类java.util.concurrent.locks.ReentrantLock的对象
常用方法
lock()
加锁,获取锁
unlock()
释放锁
注意
为了保证锁对象正常释放,建议将unlock()方法调用放入finally代码块
lock()和unlock()方法往往成对出现,不要单独使用
两种锁的对比
lock是显式锁,需要手动开启和关闭
synchronized是隐式锁,除了作用域遇到异常自动解锁
lock只有代码块锁
synchronized有代码块锁和方法所
lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类),更体现面相对象
lock锁可以对读不加锁,对写加锁,synchronized不可以
lock锁可以有多种获取锁的方式,可以从sleep的线程中抢到锁,synchronized不可以
Lock解决的事synchronized格式比较固定的缺点,并没有解决synchronized效率低下的问题,它们的效率是差不多的
又发死锁的原因
互斥条件
占用且等待
不可抢夺(不可抢占)
循环等待
线程的通信
等待唤醒机制
wait()/wait(time)
线程不再活动,不参与调度,进入wait set中,这个时候线程状态是WAITING或TIMED_WAITING,需要等待通知或者等待时间到,那么这个对象上等待的线程就会从wait set中释放出来,重新进入到调度队列中
notify()
选取所通知对象的wait set中的一个线程释放
notifyAll
释放所通知对象的wait set上的全部线程
注意
即时被唤醒后也不一定能立即恢复执行,需要再次尝试获取锁,成功后才能呢个在当初调用wait方法之后的地方恢复执行
wait 方法与 notify 方法必须要由同一个锁对象调用。因为:对应的锁对象可以通 过 notify 唤醒使用同一个锁对象调用的 wait 方法后的线程
wait 方法与 notify 方法是属于 Object 类的方法的。因为:锁对象可以是任意对 象,而任意对象的所属类都是继承了 Object 类的
wait 方法与 notify 方法必须要在同步代码块或者是同步函数中使用。因为:必须 要通过锁对象调用这 2 个方法。否则会报 java.lang.IllegalMonitorStateException 异 常
会释放锁的操作
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到
break return 终止了该代码块、该方 法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的
Error 或 Exception ,导致 当前线程异常结束。
当前线程在同步代码块、同步方法中执行了锁对象的
wait() 方法,当前线程被挂 起,并释放锁。
不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread.sleep() Thread.yield() 方 法暂停当前线程的执行。
线程执行同步代码块时,其他线程调用了该线程的suspend() 方法将该该线程挂 起,该线程不会释放锁(同步监视器)。
常用类和API
java.lang.String
特点
字符串对象不可变
字符串常量池共享字符串对象
构造器
public String()
初始化新创建的 String 对象,以使其表示空字符序列
String(String original)
初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本
public String(char[] value)
通过当前参数中的字符数组来构造新的 String
public String(char[] value,int offset, int count)
通过字符数组的 一部分来构造新的 String
public String(byte[] bytes)
通过使用平台的默认字符集解码当前参数中的 字节数组来构造新的 String
public String(byte[] bytes,String charsetName)
通过使用指定的字符 集解码当前参数中的字节数组来构造新的 String
常用api
判断功能
boolean equals(Object obj)
用来比较字符串的内容,注意区分大小写
boolean isEmpty()
判断一个字符串是不是一个长度为0的空字符串(注意不是判null)
boolean endsWith(String str)
判断当前字符串,是否以目标字符串作为结尾,常用于确定文件后缀名格式
boolean startsWith(String str)
判断当前字符串对象是否以目标字符串作为开头
boolean contains(String str)
判断当前字符串对象是否包含目标字符串
boolean equalsIgnoreCase(String str)
忽略字符串大小写来比较字符串内容,常见用于比较网址URL
获取功能
int length()
返回当前字符串的长度
char charAt(int index)
获取字符串当中对应下标位置的字符(下标从0开始,将String看成char数组即可)
int indexOf(int ch)
在当前字符串中查找指定的字符,如果找到就返回字符,首次出现的位置,如果没找到返回-1
当然也可以直接填字符
int indexOf(int ch,int fromIndex)
指定从当前字符串中查找目标字符的指定开始下标,返回目标字符首次出现的下标位置(如果没找到返回-1)
当然也可以直接填字符
int indexOf(String str)
在当前字符串中查找目标字符串
如果找到,就返回目标字符串首次出现的下标
这里返回的下标是指目标字符串的第一个字符,在当前字符串中的下标
当然,如果没找到,仍然会返回-1
int indexOf(String str,int fromIndex)
在当前字符串中查找目标字符串,但可以指定开始查找的起始下标位置
如果找到,就返回目标字符串首次出现的下标
这里返回的下标是指目标字符串的第一个字符,在当前字符串中的下标
当然,如果没找到,仍然会返回-1
String substring(int start)
返回字符串,将调用方法的字符串从start下标开始(包含start下标字符)到结束的一部分字符串返回
String substring(int start,int end)
返回字符串,将调用方法的字符串从start下标开始(包含start下标字符)到end下标(不包括)结束的一部分字符串返回串
转换功能
byte[] getBytes()
提供了将String转换成byte数组的方式,这个字节数组实际上就是该字符串在默认编码集下的字节编码形式
byte[] getBytes(String charsetName)
同上,但指定编码集,关于编码和编码集的概念,在后续课程中会详细讲解
char[] toCharArray()
提供了将String转换成char数组的方式,但注意返回的char数组并不是String对象的value数组
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
提供了将当前字符串内容复制到目标char数组的目标位置的一种方式,这里涉及区间都是左闭右开的
static String valueOf(char[] chs)
静态方法,把字符数组转换成String
static String valueOf(int i/double/Object...)
静态方法,提供了把各种基本数据类型和对象转换成字符串的方式
String toLowerCase()
把当前字符串全部转化为小写
String toUpperCase()
把当前字符串全部转换为大写
String concat(String str)
字符串拼接,作用等价于"+"运算符实现的字符串拼接
public String[] split(String regex)
Java 8中的String类提供了一个名为split的方法,该方法可以将字符串分割成多个子字符串,并返回一个字符串数组。
其中,regex是一个正则表达式,用于指定分隔符。一般都是直接指定一个字符作为分隔符,比如逗号","下划线"_"等 public String[] split(String regex)
替换功能
String replace(String old,String new)
在新的字符串中,用新的字符串(new), 替换旧(old)字符串
String trim()
去除空格字符
在新的字符串中,去掉开头和结尾的空格字符
String replace(char old,char new)
在新的字符串中,用新(new)字符,替换旧(old)字符
可变字符串
StringBuffer
StringBuilder
File类
注意
判断file对象表示的文件或者目录是否存在
public boolean exists()
相对路径中用..来表示跳跃到上级目录
构造方法
File (String pathname)
创建一个File对象,输入的字符串是路径名
File(String parent,String child)
File (File parent,String chile)
常用Api
public boolean createNewFile()
负责创建文件,目录路径如果不存在,会报错
public boolean mkdir()
负责创建目录,但只能创建单层目录,如果是多级目录的上层目录不存在,则创建失败
public boolean mkdirs()
负责创建目录,但可以创建多级目录,如果多级目录不存在,则全部帮你创建
public boolean delete()
删除此抽象路径名表示的文件或目录,如果此路径名表示一个目录,则目录必须为空才可以删除
delete()方法删除成功返回true,不会因为路径名不正确,文件不存在报错,但是会返回false表示删除失败
这个方法是通过本地方法调用系统API删除文件,直接删除而不是进入回收站,开发慎用
public boolean renameTo(File dest)
移动并重命名文件功能,File对象本身表示源文件,参数表示目标文件,操作成功返回true,失败返回false、
源文件和目标文件在同一目录且不同命的时候效果是重命名
源文件和目标文件,不在同一目录的时候,移动并重新命名
当源文件和修改后的目标文件,且同名同目录,那么方法返回true,但没有效果
这个方法是移动文件,可附带从命名功能,但不是复制文件
public boolean isFile()
判断File对象是否表示的是一个文件
public boolean isDrectory
判断File对象是否表示的是一个目录
public boolean exists
判断File对象所表示的文件或者目录是否存在
public String getAbsolutePath()
获取File对象表示的抽象文件的绝对路径
public String getPath()
获取File对象表示的抽象路径名的字符串,创建这个对象时用什么路径获取的就是什么路径
public String getName()
获取File对象表示的文件或目录的名字
public long length()
获取文件所占硬盘空间的大小,只能获取文件的大小,不能获取目录的大小
public long lastModified()
返回此File对象表示的文件最后一次修改的时间,表示的是毫秒值
public String[] list ()
返回一个字符串数组,数组包括此抽象路径名表示的目录中所有文件和文件夹的名字,如果File对象表示的是一个文件,返回null,如果是一个空目录返回一个长度为0的数组
public File[] listFiles()
规则同上,但是返回的是绝对路径名
File[] listFiles(FileFilter filter)
按照一定规则过滤一个文件目录下的文件 其中filter表示过滤的规则
子主题
异常
常见异常
ArrayIndexOutOfBoundsException
数组下标异常
NullPointerException
空指针异常
jvm虚拟机
内存划分
虚拟机栈
用于存储正在执行每个java方法的局部变量表,局部变量表存放了克制长度的各种基本数据类型,对象引用,方法执行完自动释放
堆内存
存储对象(包括数组对象),new来创建的都存储在堆内存
方法区
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
本地方法栈
当程度中调用了native本地方法时,本地方法执行期间的内存区域
程序计数器
程序计数器是cpu中的寄存器,包含每一段线程吓一跳要执行的指令的地址
二阶段
集合类
泛型
好处
提高程序的安全性
将运行期的问题转移到了编译期
省去了类型强转的麻烦
泛型使用
泛型类
将泛型定义在类上
注意
写法
如果某个地方需要指明泛型类,但是没有指明,那么默认为Object
多泛型
语法上允许在一个泛型类假设多个泛型类型,但不建议使用
在泛型类上定义了某个泛型,类中不使用也是对的
泛型接口
将泛型定义在接口上
泛型方法
将泛型定义在方法上
使用了范型的方法不是范型方法,定义了范型的方法才是范型方法
注意
泛型的假设写法(建议使用这几个)
T : Type
E : Element
K : Key
V : Value
泛型必须是引用类型
泛型不允许new
泛型类定义的泛型作用域局限于自己的类名和类体内
泛型按照正常的继承逻辑继承
泛型通配
① 泛型通配符<?>
用来代指任意类型
② <? extends E>
向下限定,代指E类型 以及 E的子类型
③ <? super E>
向上限定,代指E类型 以及 E的父类型
泛型擦除
泛型擦除会将所有类型参数转换为它们的上界,因此Java中的泛型容器List<T>在编译时也会被转换成List<Object>。在添加元素时,由于Java的类型检查是在编译时进行的,因此会在编译时检查元素的类型是否与容器中指定的类型参数T相匹配。如果元素的类型与T不匹配,编译器就会报错。
Collection
特点
Collection集合体系的顶级接口
定义了一个数据容器,存储数据
Collection子实现有有序的也有无序的
Collection子实现有的允许存储重复数据,有的不允许
Collection子实现有的允许存储null,有的不允许
API
增删改查
boolean add(E e): 添加方法
boolean addAll(Collection<? extends E> c): 添加所有
boolean remove(Object o): 根据内容删除
boolean removeAll(Collection<?> c): 删除所有匹配数据
boolean contains(Object o): 查找
boolean containsAll(Collection<?> c): 查找是否都存在
boolean retainAll(Collection<?> c): 保留匹配数据
辅助方法
int size()
boolean isEmpty()
boolean equals(Object o): 重写了这个方法, 按照内容进行比较
int hashCode()
void clear()
特殊方法
Object[] toArray()返回包含此 collection 中所有元素的数组。
<T> T[] toArray(T[] a)返回包含此 collection 中所有元素的数组;返回与指定数组的运行时类型相同。
Iterator<E> iterator()返回在此 collection 的元素上进行迭代的迭代器。(遍历)
本质
通过Collection的iterator方法产生了一个iterator对象, 这个对象的在内存上的本质就是iterator对象内维护了一些指向源collection对象数据的标记;
相关方法
boolean hasNext(): 向后是否可以遍历
E next() : 向后遍历
void remove() : 删除刚刚遍历过的元素: (不要在未遍历之前删除, 也不要做连续的删除)()
并发修改异常
一些Collection的子实现是线程不安全的,使用Iterator会产生线程安全问题,所以一些集合类维护了一个用来记录修改次数的标记,每次遍历前Iterator都会检测自己保存的修改次数和标记是否一致,不一致就会抛出并发异常
foreach
foreach依赖于iterator迭代,如果需要使用这个循环遍历,必须有iterator方法
如果要在自己设计容器使用foreach,应该让自己设计的类在父子关系中满足Iterable的子类,因为Iterable中定义了Iterable方法
List
特点
Collection的接口的子接口
在Collection作为数据容器定义的基础上,定义了数据结构为线性表
所有的子实现存储数据都有序
允许存储重复数据
允许存储null
API
Collection继承的API
添加删除相关
void add(int index, E element): 根据下标的添加
boolean addAll(int index, Collection<? extends E> c): 根据下标添加所有
E remove(int index): 根据下标的删除
E get(int index): 根据下标获取下标位置存储的内容
int indexOf(Object o): 根据内容查找这个数据出现的第一次下标位置
int lastIndexOf(Object o): 根据内容查找这个数据出现的最后一次下标位置
E set(int index, E element): 根据修改下标位置存储的内容
特殊API
ListIterator<E> listIterator()
ListIterator<E> listIterator(int index): 提供了一个从某个位置开始, 不仅可以向后遍历, 也可以向前遍历的方式previous()
List<E> subList(int fromIndex, int toIndex)返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。
subList
视图方法,没有真正的持有,所以如果对视图方法获得的数据进行增删改查,实际上就是对原本数据进行正删改查
List<E> subList(int fromIndex, int toIndex)返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。
视图方法也会产生并发修改异常,所以使用视图方法的时候不要调用源集合类修改结构的方法修改源集合数据
Vector
特点
List的子实现
数据结构为线性表
的底层结构是数组
底层数组的默认长度10, 扩容机制(如果Vector有大于0的增量, 那么,每次扩容扩大增量个, 如果增量是小于等于0, 每次扩容扩为原来的2倍)
存储数据有序
允许存储重复数据
允许存储null
线程安全 (有锁)
Vector是jdk1.0的时候出现 (ArrayList在jdk1.2时候出现, ArrayList出现就是为了取代Vector)
Stack
在使用Stack的时候, 虽然我们可以使用来自于其父类继承add,remove...等方法, 但是建议不要使用.
Stack是一个栈, 但是是线程安全(效率略低), 所以Java提供了另个一个集合类, 也可以表示为栈, Queue接口下的Deque. (Deque接口主要是作为双端队列, 但是也定义了栈这种数据结构)
ArrayList
特点
数据结构是线性表
底层是数组
底层持有数组默认长度10,扩容机制1.5呗
存储数据有序
允许数据重复
允许存储null
线程不安全
构造方法
ArrayList()构造一个初始容量为 10 的空列表。
ArrayList(int initialCapacity)构造一个具有指定初始容量的空列表。
ArrayList(Collection<? extends E> c)构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
API
List继承API
Collection继承API
特殊API
Object clone()返回此 ArrayList 实例的浅表副本。
void trimToSize()将此 ArrayList 实例的容量调整为列表的当前大小。
void ensureCapacity(int minCapacity)如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
LinkedList
特点
List的子实现, 同时还是Deque接口的子实现.(主要把LinkedList看做List的子实现)
数据结构表现为: 线性表, 队列, 双端队列, 栈
底层是一个双向链表
存储元素有序
允许存储重复数据
允许存储null
线程不安全
构造方法
LinkedList()构造一个空列表。
LinkedList(Collection<? extends E> c)构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列。
API
具有Collection定义的方法
boolean addAll(Collection<? extends E> c): 添加所有
boolean remove(Object o): 根据内容删除
boolean removeAll(Collection<?> c): 删除所有匹配数据
boolean contains(Object o): 查找
boolean containsAll(Collection<?> c): 查找是否都存在
boolean retainAll(Collection<?> c): 保留匹配数据
int size()
boolean isEmpty()
boolean equals(Object o): 重写了这个方法, 按照内容进行比较
int hashCode()
void clear()
Object[] toArray()返回包含此 collection 中所有元素的数组。
<T> T[] toArray(T[] a)返回包含此 collection 中所有元素的数组;返回与指定数组的运行时类型相同。
Iterator<E> iterator()返回在此 collection 的元素上进行迭代的迭代器。
具有List定义的方法
void add(int index, E element): 根据下标的添加
boolean addAll(int index, Collection<? extends E> c): 根据下标添加所有
E remove(int index): 根据下标的删除
E get(int index): 根据下标获取下标位置存储的内容
int indexOf(Object o): 根据内容查找这个数据出现的第一次下标位置
int lastIndexOf(Object o): 根据内容查找这个数据出现的最后一次下标位置
E set(int index, E element): 根据修改下标位置存储的内容
ListIterator<E> listIterator()
ListIterator<E> listIterator(int index):
List<E> subList(int fromIndex, int toIndex)
具有Queue定义的方法(队列的api)
boolean offer(E e)将指定元素添加到此列表的末尾(最后一个元素)
E peek()获取但不移除此列表的头(第一个元素)
E poll()获取并移除此列表的头(第一个元素)
具有Deque定义的方法(双端队列, 栈)
boolean offerFirst(E e)在此列表的开头插入指定的元素。
boolean offerLast(E e)在此列表末尾插入指定的元素。
E peekFirst()获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
E peekLast()获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
E pollFirst()获取并移除此列表的第一个元素;如果此列表为空,则返回 null。
E pollLast()获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。
E pop()从此列表所表示的堆栈处弹出一个元素。
void push(E e)将元素推入此列表所表示的堆栈。
Queue
特点
Collection的子接口
数据结构定义队列
存储数据有序
允许存储重复数据
不允许存储null (LinkedList除外)
如果队列中没有存储数据,且调用了队列出对方法,会返回一个null用来标记没有数据可以出队,所以设计时不允许存储null
API
boolean offer(E e)将指定元素添加到此列表的末尾(最后一个元素)。
E peek()获取但不移除此列表的头(第一个元素)。
返回null作为标识队列为空的依据
E poll()获取并移除此列表的头(第一个元素)
返回null作为标识队列为空的依据
Deque
特点
是Queue接口的子接口
在Queue定义为队列的基础上, 又表现为双端队列 和 栈
存储数据有序
允许存储重复数据
不允许存储null (LinkedList除外)
API
Collection定义的API
Queue定义的API
自身API
boolean offerFirst(E e)在此列表的开头插入指定的元素。
boolean offerLast(E e)在此列表末尾插入指定的元素。
E peekFirst()获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
E peekLast()获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
E pollFirst()获取并移除此列表的第一个元素;如果此列表为空,则返回 null。
E pollLast()获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。
E pop()从此列表所表示的堆栈处弹出一个元素。
void push(E e)将元素推入此列表所表示的堆栈。
ArrayDeque
特点
Deque接口的子实现
数据结构表现: 队列, 双端队列, 栈
底层结构是数组: 循环数组
默认初始长度:16; 默认扩容机制:2倍 --> 数组长度保持2的幂值
假如创建ArrayDeque指明长度, 它的底层数组会变成大于我们给定的值的最小的2的幂值的长度(给定值要大于等于8, 不然长度8) --> 和第四条特点( ArrayDeque底层数组永远是2的幂值 )
存储元素有序
允许存储重复数据
不允许存储null
线程不安全
BlockingQueue
阻塞队列
一个大小容量有限的队列
添加操作的时候, 队列添加满了, 添加线程等待
删除操作的时候, 队列删除空了, 删除线程等待
应用场景
缓存,例如线程池中容纳任务的容器(BlockingQueue)
API
抛出异常:
在添加的时候, 容器满抛出异常: add
在删除的时候, 容器空了抛出异常: remove
返回特殊值:
在添加的时候, 容器满 返回假: offer
在删除的时候, 容器空了 返回null
一直阻塞:
在添加的时候, 容器满, 阻塞, 直到有容空出空间可供存储: put
在删除的时候, 容器空了, 阻塞, 直到容器被别的线程加了数据: take
超时阻塞: 可以设置一定的阻塞时间, 超过这个时间,也不情况变成返回特殊值
在添加的时候, 容器满, 阻塞一定时间, 时间内有容空出空间可供存储,就存储: 超时offer
在删除的时候, 容器空了, 阻塞一定时间, 时间内容器被别的线程加了数据, 就删除: 超时poll
Set
特点
Collection接口的子接口
数据结构表示为集合
子实现全部都是持有对应Map接口下的实现对象
LinkadHashSet、TreeSet大小有序,HashSet大小无序
子实现都不允许重复存储
HashSet、LinkedHashSet不允许存储null、TreeSet允许存储null
HashSet
底层持有了一个HashMap
LinkedHashSet
底层持有了一个LinkedHashMap
TreeSet
底层持有了一个TreeMap
特点
TreeSet是Set的子实现
TreeSet底层次有一个TreeMap对象
TreeSet的特点和TreeMap的key保持一致
TreeSet存储数据:大小有序
TreeSet不允许存储重复数据
TreeSet不允许存储null
TreeSet线程不安全
构造方法
TreeSet()
构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
TreeSet(Comparator<? super E> comparator)
构造一个新的空 TreeSet,它根据指定比较器进行排序。
TreeSet(Collection<? extends E> c)
构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序。
TreeSet(SortedSet<E> s)
构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet。
API
boolean add(E e)
将指定的元素添加到此 set(如果该元素尚未存在于 set 中)。
boolean addAll(Collection<? extends E> c)
将指定 collection 中的所有元素添加到此 set 中。
E ceiling(E e)
返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null。
void clear()
移除此 set 中的所有元素。
Object clone()
返回 TreeSet 实例的浅表副本。
Comparator<? super E> comparator()
返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null。
boolean contains(Object o)
如果此 set 包含指定的元素,则返回 true。
Iterator<E> descendingIterator()
返回在此 set 元素上按降序进行迭代的迭代器。
NavigableSet<E> descendingSet()
返回此 set 中所包含元素的逆序视图。
E first()
返回此 set 中当前第一个(最低)元素。
E floor(E e)
返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null。
SortedSet<E> headSet(E toElement)
返回此 set 的部分视图,其元素严格小于 toElement。
NavigableSet<E> headSet(E toElement, boolean inclusive)
返回此 set 的部分视图,其元素小于(或等于,如果 inclusive 为 true)toElement。
E higher(E e)
返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null。
boolean isEmpty()
如果此 set 不包含任何元素,则返回 true。
Iterator<E> iterator()
返回在此 set 中的元素上按升序进行迭代的迭代器。
E last()
返回此 set 中当前最后一个(最高)元素。
E lower(E e)
返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null。
E pollFirst()
获取并移除第一个(最低)元素;如果此 set 为空,则返回 null。
E pollLast()
获取并移除最后一个(最高)元素;如果此 set 为空,则返回 null。
boolean remove(Object o)
将指定的元素从 set 中移除(如果该元素存在于此 set 中)。
int size()
返回 set 中的元素数(set 的容量)。
NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
返回此 set 的部分视图,其元素范围从 fromElement 到 toElement。
SortedSet<E> subSet(E fromElement, E toElement)
返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。
SortedSet<E> tailSet(E fromElement)
返回此 set 的部分视图,其元素大于等于 fromElement。
NavigableSet<E> tailSet(E fromElement, boolean inclusive)
返回此 set 的部分视图,其元素大于(或等于,如果 inclusive 为 true)fromElement。
Map
HashMap
特点
HashMap是Map接口的子实现
HashMap的底层结构是数组+链表+红黑树
底层数组的默认初始长度 16, 默认的加载因子0.75, 默认的扩容机制: 2倍
HashMap存储数据无序
HashMap不允许存储重复的key
HashMap允许存储null作为key
HashMap线程不安全
注意
HashMap底层的数组
加载因子
hash值的问题
HashMap对key的重复的判断
存储重复数据
当在HashMap存储了一份重复的key-value数据 (key重复:两个key的hashCode一样; 并且两个key要么直接想等, 要么相equals ), 会用新的key-value的value来覆盖旧的key-value的value, 并且返回旧的value
不要通过引用直接操作key
如果在HashMap中已经存储了key-value数据, 尽量不要通过key的应用直接修改key, `有可能`再也操作不了这个数据了
链表什么时候转化为红黑树
在HashMap不断的添加数据, 有可能在某一个数组的下标位置构建了链表(不同的key-value数据都经过计算散列到这个下标位置), 我们不希望链表过长(链表太长会导致效率降低), 所以当链表长度超过8达到9的时候(算上新添加这个key-value), 链表会变成红黑树(红黑树适合存储多的数据).
在HashMap中, 某个数组下标位置链表长度超过8达到9的时候, 链表是否一定会转化为红黑树
不一定当HashMap的底层数组长度是小于64的时候, 在这种情况下, 即使某一个下标位置散列的key-value数据个数超过8达到9个, 也不会转化为红黑树, 而是选择对hashmap的底层数组扩容
扩容问题
红黑树转化为链表
当红黑树的根结点/根结点的左右结点/根结点的左结点的左结点, 这四个结点只要有一个不存在, 就意味着红黑树结点过少, 就要在删除的时候由红黑树转化回链表
当扩容的时候, 会导致红黑树拆成两部分, 这两部分的任何一部分, 只要数据量少于等于6个, 对应的位置就不能继续维护红黑树, 就要由红黑树转化回链表
注意
为什么删除不以6这个数据量作为转化回链表的参考?
因为以6作为标识, 就意味着需要遍历链表, 遍历链表效率降低
为什么链表转化为红黑树 阈值是8 红黑树转换链表阈值是 6?
为了避免链表和红黑树之间反复转化, 所以留了两个参数个数的缓冲余地
红黑树比较大小
红黑树是一个特殊的二叉搜索树, 也就意味着在HashMap的红黑树中存储数据, 是需要比较大小的; 但是我们没有要求存储到HashMap中的key可以比较大小, 那么在HashMap的红黑树中比较大小的依据是key的hash值(因为key的hash值是int类型, 天然可以比较大小)
注意: 在HashMap的某个下标位置存储数据的时候, 如果这个位置存储的红黑树, 那么key的hash值只是作为选取左右方向查找的依据, 真正在HashMap的红黑树, 判断两个key的是否重复的依据: 还是hash值要先一样, hash值一样之后, key是否直接相等或者相equals (还是这个判断条件)
给定长度
在HashMap的构造方法中指明数组长度, 那么HashMap的底层数组, 长度会变成`大于等于`我们给定值的最小的2的幂值,作为底层数组长度
构造方法
HashMap()构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity)构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor)构造一个带指定初始容量和加载因子的空 HashMap。
HashMap(Map<? extends K,? extends V> m)构造一个映射关系与指定 Map 相同的新 HashMap。
API
HashMap是Map接口的子实现, 它并没有在Map接口的基础上额外定义什么api, 完全遵照于Map接口对api的定义, HashMap只是对这些定义进行实现
LinkedHashMap
特点
HashMap的子类
底层结构完全复用了HashMap的结构(数组+链表+红黑数)
特点和HashMap特点基本一致(初始长度.扩容机制.红黑数列表转化)
在HashMap的基础上(数组+链表+红黑数)维护了一个双向链表,以保证迭代顺序
存储数据有序(外在表现)
不允许存储重复key
允许存储null
线程不安全
构造方法
LinkedHashMap()
构造一个带默认初始容量 (16) 和加载因子 (0.75) 的空插入顺序 LinkedHashMap 实例。
LinkedHashMap(int initialCapacity)
构造一个带指定初始容量和默认加载因子 (0.75) 的空插入顺序 LinkedHashMap 实例。
LinkedHashMap(int initialCapacity, float loadFactor)
构造一个带指定初始容量和加载因子的空插入顺序 LinkedHashMap 实例。
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
构造一个带指定初始容量、加载因子和排序模式的空 LinkedHashMap 实例。
accessOrder设置元素排序方式(输出)false是添加顺序(即元素的取出顺序与添加的顺序是一致的) true是访问顺序(被访问过的顺序排在最后)
LinkedHashMap(Map<? extends K,? extends V> m)
构造一个映射关系与指定映射相同的插入顺序 LinkedHashMap 实例。
API
LinkedHashMap,没有在HashMap的基础上定义API,复用了HashMap的API也就是说,LinkedHashMap复用了Map的api
TreeMap
特点
Map接口的子实现
底层是个红黑数(链表模拟成的红黑数)
存储数据大小有序
不允许存储重复的key(对key重复的定义:所谓重复是大小重复)
不允许存储null作为key
线程不安全
构造方法
TreeMap() 使用键的自然顺序构造一个新的、空的树映射。 如果使用空的/默认的构造方法,要存储到这个TreeMap上的数据,key是具有自然顺序的
TreeMap(Comparator<? super K> comparator) 构造一个新的、空的树映射,该映射根据给定比较器进行排序。 这个构造方法,是给当我们不想让存储的key-value数据的key实现自然顺序的时候使用,需要在TreeMap中提供一个比较器
TreeMap(Map<? extends K,? extends V> m) 构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序 进行排序。
TreeMap(SortedMap<K,? extends V> m) 构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射。
API
Map接口定义的api
自己的新的关于大小操作的api
Map.Entry<K,V> ceilingEntry(K key): 返回大于等于给定值的键值对
K ceilingKey(K key): 返回大于等于给定值的key Map.Entry<K,V> floorEntry(K key): 返回小于等于给定值的键值对 K floorKey(K key): 返回小于等于给定值的key Map.Entry<K,V> higherEntry(K key): 返回大于给定值的键值对 K higherKey(K key): 返回大于给定值的key Map.Entry<K,V> lowerEntry(K key): 返回小于给定值的键值对 K lowerKey(K key): 返回小于给定值的key
Map.Entry<K,V> firstEntry(): 获得第一份键值对()最小
K firstKey(): 获得第一份key()最小 Map.Entry<K,V> lastEntry(): 获得最后一份键值对(最大) K lastKey(): 获得最后一份key(最大) Map.Entry<K,V> pollFirstEntry(): 删除第一份数据(最小) Map.Entry<K,V> pollLastEntry(): 删除最后一份数据(最大)NavigableSet
<K> navigableKeySet()返回此映射中所包含键的 NavigableSet 视图,如果此映射为指定值映射一个或多个键,则返回 true。 NavigableSet <K> descendingKeySet()返回此映射中所包含键的逆序 NavigableSet 视图。 NavigableMap<K,V> descendingMap()返回此映射中所包含映射关系的逆序视图。
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)返回此映射的部分视图,其键的范围从 fromKey 到 toKey。
SortedMap<K,V> subMap(K fromKey, K toKey)返回此映射的部分视图,其键值的范围从 fromKey(包括)到 toKey(不包括)。 SortedMap<K,V> tailMap(K fromKey)返回此映射的部分视图,其键大于等于 fromKey。 NavigableMap<K,V> tailMap(K fromKey, boolean inclusive)返回此映射的部分视图,其键大于(或等于,如果 inclusive 为 true)fromKey。 SortedMap<K,V> headMap(K toKey)返回此映射的部分视图,其键值严格小于 toKey。 NavigableMap<K,V> headMap(K toKey, boolean inclusive)返回此映射的部分视图,其键小于(或等于,如果 inclusive 为 true)toKey。
Hashtable
Properties
特点:是一个用来做持久化的key-value的数据容器,必须存储字符串
用法
读文件
写文件
只能处理String类型
特点
是Map接口的子实现
底层结构是数组+链表(*没有红黑数)在jdk1.8之前一模一样
数组的默认初始长度11,默认扩容机制2倍+1
存储数据无序
不允许存储重复数据(重复数据的判断依据和HashMap一样)
不允许存储null作为key,也不允许存储null作为value
线程安全
jadk1.0的时候产生 HashMap是jdk1.2的时候产生 后者就是为了来取代前者
逆序
Stream
特点
jdk1.8增加,简化快速操作java集合类数据处理的一种方式,不需要编写实现操作处理数据,用类似数据库通过查询语句来表达
思想
Pipelining
中间操作都会返回流本身,这样多个操作可以串联成一个管道
内部迭代
以前对集合遍历都通过增强for等方式显式迭代,可以成为外部迭代,Stream调用遍历方法在底层实现,不可以直接看见
优点
Stream流是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素.
Stream流是在对函数模型进行操作,(在终结触发之前)集合元素并没有真正被处理.只有当终结方法执行的时候,整个模型才会按照指定策略执行操作.
对集合数据操作的性能优化,解决代码冗长问题.
使用
根据数据源创建一个流
多个中间操作行程一条流水线
一个终止、中断操作,执行流水线,并生成结果
创建流的方式
直接创建,通过Collection的stream()方法创建
值创建
数组创建
后两个基本不会使用
中间操作
每次中间操作原有的Stream对象不改变,返回一个新的Stream,可以有多次中间操作
filter
用于通过设置的条件过滤出元素
distinct
筛选元素,相当于去除重复元素
limit
用于获取指定数量的流
skip
跳过前个元素
map
映射每个元素对应的结果
sorted
对流进行排序
这里面的comparing这个方法只能在stream.sorted使用
reversed()方法是逆序的意思,不写就是正序
终止操作
anyMatch
检查流到最后的数据,是否有一个或者多个数据匹配某种的情况
allMatch
检查所有的元素是否都匹配
nonematch
检查是否没有匹配的元素
findAny
返回流中任意元素:默认第一个
findFirst
返回第一个元素
forEach
遍历元素
count
返回元素的数量
reduce
计算元素数量
collect
用于收集数据经过流计算的结果
Collectors.toList()
Collectors.toCollection()
Collectors.toSet()
Collectors.toMap()
Git
相关命令
clone
下载仓库内容,并在本地创建一个和远程仓库同名的文件夹
git clone
status
查看工作区和缓冲区的变化
git status
add
将工作区的变化提交到缓冲区
git add 文件名
git add *.文件后缀名
git add .
commit
将缓冲区变化内容提交到本地仓库
git commit -m "备注/注释"
会产生文件版本号
需要设置用户名和邮件地址
文件配置:设置.gitconfig文件
name=xxx
email=xxx@xx.com
命令设置
git config --global user.email xxx@xx.com
git config --global user.name xxx
push
将本地仓库中的变化推送到远程仓库
注意
第一次操作需要填写对应的用户名和密码
不能指定文件push
只有本地仓库的版本领先于远程仓库才可以push
pull
将远程仓库的变化取到本地,并显示版本号
log
查看仓库中的所有的版本信息
本地项目提交
方式一
手动创建远程仓库
创建本地仓库(git clone)
将文件移动到本地仓库管理目录
管理文件(git add.)
产生版本(git commit -m "注释")到本地仓库
提交到远程仓库 git push
正常使用
方式二
在代码文件所属目录,创建本地仓库(git init)
管理文件 (git add .)
产生版本(git commit -m "注释")到本地仓库
创建远程仓库:不选择任何模板和初始化
管理远程仓库:git remote add origin 仓库地址
提交到远程仓库:git push -u origin "master"
正常使用
解决冲突的规则
先push的人不处理冲突,后push的人要处理冲突(谁遇到冲突, 谁解决)
和组员一起开发的时候,尽量不要开发同一个文件,很容易产生冲突
准备提交代码之前最好先pull一下(最好再git add之前就pull),不然可能会push失败
早上上班之后,第一件事情,拉取最新的代码(pull)
晚上下班之前,最后的事情,把最新的本地代码推送上去(push)
如果今天下班了, 代码没写完, 要不要push, 不要push
分支管理
git checkout -b 分支名
创建并切换分支
git checkout 分支名
切换分支
git push --set-upstream origin 分支名
创建分支后第一次push需要建立本地分支和远程分支之间的联系
合并分支
先切换到承载合并结果的分支
git checkout 分支名
合并分支
git merge 其他分支名
提交
git branch -a(git branch -all)
查看所有分支
回退
HTTP
网络模型

应用层
最靠近用户的一层,给用户直接提供网络服务。常见的应用层协议有HTTP、HTTPS、FTP、SFTP等
传输层
建立端到端的链接,传输层的作用是为上层协议提供端到端的可靠且透明的数据传输服务。TCP,UDP是该层协议
网络层
利用IP寻址来建立两个节点之间的链接,将发送端的传输层传递过来的数据通过选择合适的路由,准确的传递给接收端的传输层,也称之为IP协议层
链路层
将有差错的物理线路转换成无差错的数据链路,向网络层提供高质量的数据传输服务
物理层
最终信号的传递都需要借助于物理层实现
HTTP协议
概念
HTTP的全称Hyper Text Transfer Protocol(超文本传输协议).超文本指的是超越了普通的文本,不仅仅是文本,还有音视频、图片等;传输指的是通讯双方互相之间进行传递数据;协议指的是互相传递数据时数据的格式。
存在目的
把一个http请求,从一个用户发起的简单请求翻译成报文格式
协议
网络通信过程中协议主要指的是约定着通讯双方在传递信息时应当具有的格式
工作流程
域名解析
浏览器缓存
浏览器首先查找自身DNS缓存,如果找到则结束,找不到进入下一个环节
操作系统缓存
查找操作系统缓存,找到则结束,找不到进入下一个环节
hosts文件
查找hosts文件中的配置信息(localhost、127.0.0.1等永远代表的是本机)
DNS服务器
向本地配置的DNS服务器发起域名解析请求,找不到则最终解析失败
注意
域名一串具有特殊含义的字符,在网络通讯过程中,计算机之间相互识别需要通过ip地址来进行识别,但是ip地址比较难记忆,不容易被人记忆,
TCP三次握手
大多数情况下,客户端和服务器之间建立的链接都是TCP链接。因此需要进行TCP三次握手,简历一个TCP链接
HTTP请求
TCP链接建立后,客户端会根据用户输入的地址,给用户生成相应的HTTP请求信息并发送到服务器(HTTP请求报文是由传输层、网络层、链路层从客户机主机发出,在网络中进行路由中转传输,到达服务器主机)
HTTP响应
服务器接收客户端发送过来的信息之后,解析出请求,并且做出相应(HTTP响应报文由传输层、网络层、链路层从服务器主机发出,在网络中中转传输,到达客户端)
解析HTML
服务器发出的HTTP响应信息被浏览器接收到之后,浏览器对于HTTP响应信息加以解析
渲染页面
浏览器将静态资源文件以及HTML标签等进行渲染,呈现给用户
HTTP请求
概念
客户端发送给服务器的信息,称之为请求信息,服务器返回给客户端的信息称之为响应信息,一般情况下,我们也将发送的信息称之为报文,即HTTP请求报文和HTTP响应报文
请求行
构成
请求方法/请求方式
GET
主要用来进行数据查询(主要区别)
发送GET请求,会将请求参数复在地址栏,也就是请求报文的请求资源后(浏览器行为)
发送方式
浏览器地址栏输入地址默认是get方法
form表单设置为method=get
POST
主要用来提交数据(主要区别)
请求参数位于请求体中(浏览器行为)
发送方式
form表单设置为method=post
请求资源/URL
访问两个不同页面时,主要的区别在于请求资源不同,它是一个目标服务器的重要标识
请求协议/协议版本
目前HTTP协议的版本是HTTP/1.1,之前的一个版本是HTTP/1.0,区别在于是否支持畅链接,长连接就是在一个TCP链接内发送多个HTTP请求
请求头
Accept
Accept-Charset
Accept-Encoding
Accept-Language
Host
Referer
Content-Type
Content-Length
If-Modified-Since
User-Agent
Connection
Cookie
Date
空行
仅做标识使用
请求正文/请求体
一般用来传输客户端需要传递给服务器的数据信息,这部分可以存放大量数据,若客户端需要提交一个文件到服务器,那么就可以将文件数据放置在请求体中
HTTP响应(HTTP响应报文)
响应行
版本/协议
HTTP/1.1
状态码
100: 100段 (当前相当于废弃状态)
200: 正常(请求响应成功)
301、302、307 重定向 (http://bing.com/)
404 未找到(访问的资源不存在, 但是目标服务器存在)
500 服务器内部错误
原因短语
成功或者失败. (不重要,从语法上可以随意写)
响应头/消息头
Location:指示新的资源的位置。要搭配着重定向状态码一起使用。指引浏览器往改地址再次发起请求。
Server: apache tomcat 指示服务器的类型
Content-Encoding: gzip 服务器发送的数据采用的编码类型
Content-Length: 80 告诉浏览器正文的长度。响应体的长度。
Content-Language: zh-cn服务发送的文本的语言
Content-Type: text/html; 服务器发送的内容的MIME类型
Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT文件的最后修改时间
Content-Disposition: attachment; filename=aaa.zip指示客户端保存文件
Refresh: 1;url=指示客户端刷新频率。单位是秒
Set-Cookie: SS=Q0=5Lb_nQ; path=/search服务器端发送的Cookie: 非常重要
空行
标识使用
响应体/响应正文
响应正文。可以是文本数据也可以是二进制类型数据。其中响应头中Content-Type指示了该资源的类型,比如文本类型资源时,Content-Type:text/html;图片资源时Content-Type:image/png;Content-Length指示了响应体的长度。
HTTPS
http协议的缺点
通讯使用明文,内容可能会被窃听
不验证通讯方的身份,可能遭遇伪装
无法验证报文的完整性,内容可能被穿该
解决
HTTPS分别引入了加密算法、证书、完整性保障来解决对应的问题
HTTP响应
子主题
Tomcat
目录结构
bin目录
二进制文件存放目录,启动停止Tomcat的文件都在这里
conf目录
配置文件存放目录
logs目录
日志存放目录,tomcat正常启动及错误启动的日志信息均回记录在这个目录,可以作为tomcat启动失败时一个调试方式
wabapps目录
在Tomcat中部署的资源目录
lib目录
Tomcat运行时依赖的jar包存放在此
temp目录
临时文件的存放目录
work目录
tomcat工作时的工作目录
启动/停止
启动
bin目录下命令行工具输入./startup.sh
停止
bin目录下命令行工具输入./shutdown.sh
资源部署
直接部署(主要切重要的方式)
直接在webapps下新建目录,当tomcat运行时便会将webapps目录下这个新建目录解析成一个应用
访问:http://主机:端口号/应用名(目录的名称)/相对应用的一个相对路径
War包部署
可以通过部署java项目的war包的形式,war包会自动压缩成目录,原理上和上面等价
虚拟映射
方式一
配置文件: conf/Catalina/localhost目录下新建一个xml文件,并写入<Context docBase="需要映射的文件的地址" />
表示配置了一个叫做XXX(XXX为xml文件的名称),应用指向的路径为docBase的值
访问:http://主机:端口号/应用名/指向路径内需要访问的文件路径
方式二
配置文件conf/server.xml文件中
访问方式同上
Tomcat组件
server配置文件
整个Tomcat是一个Server,Server文件可以包含多个Service(多个服务空间)
一个Service可以有多个Connector(功能和端口不能重复),Connector监听的请求需要全部触发到Engine中
一个Service只能有一个Engine
一个Engine可以包含多个Host(虚拟主机)
一个虚拟主机可以包含多个Context(一个Context代表一个应用/项目)
Connector
负责接收客户端的请求以及对客户端做出相应,主要职责是负责次序不断监听某一端口号,将客户端传递过来的请求报文解析成一个request对象,同时还会提供一个response对象,将这两个对象一起传递给Engine
Engine
负责接收connector传递过来的request对象以及response对象,并进一步传递给Host
Host
负责接收Engine传递过来的request对象以及response对象,并挑选一个合适context对象进行进一步传递
Context
负责处理具体业务逻辑,比如利用路径拼接请求资源,接下来在硬盘上查找文件是否存在,并且写入数据到reponse中,最终Connector会读取reponse中的数据
设置
默认端口设置
http默认端口号80,https默认端口号443
缺省应用
访问ROOT目录下文件夹的时候不可以通过/ROOT/文件名访问,需要去掉应用名ROOT,在默认情况下ROOT位Tomcat的缺省
欢迎页面
数据库
SQL的基本操作
登录数据库
mysql -uroot -p[回车] 输入密码
库操作
[ ] 可选
查看数据库

show databases;
查看所有数据库
show databases like '%数据库名%';
查看和期望命名相匹配的数据库
show create database 数据库名;
查看数据库创建信息
show create database 数据库名
查看之前是如何创建的数据库 SQL语句
创建数据库
CREATE DATABASE [IF NOT EXISTS] <数据库名> [[DEFAULT] CHARACTER SET <字符集名>] [[DEFAULT] COLLATE <校对规则名>];
IF NOT EXISTS
在创建数据库之前进行判断,只有该数据库目前尚不存在时才能执行操作
[DEFAULT] CHARACTER SET
指定数据库的字符集
[DEFAULT] COLLATE
指定字符集的默认校对规则
删除数据库
DROP DATABASE [IF EXISTS] <数据库名>
修改数据库
ALTER DATABASE [数据库名]{ [ DEFAULT ] CHARACTER SET <字符集名> | [ DEFAULT ] COLLATE <校对规则名> }
选择数据库
USE <数据库名>
表操作
查看表
SHOW TABLES
查看数据库中的所有表
SHOW CREATE TABLE<表名>
查看表的创建语句
DESCRIBE<表名>
查看表结构
DESC<表名>
查看表结构
创建表
create table employee( id int , name varchar(20), gender char, birthday date, job varchar(20), salary double(10,2) )character set utf8 collate utf8_bin;
CREATE TABLE <表名> ( <列名1> <类型1> , […] , <列名n> <类型n> ) [表选项] [分区选项];
修改表(不建议使用)
alter table employee add column height float(5,2); alter table employee add column height float(5,2) first; alter table employee add column height float(5,2) after name; alter table employee modify column age float(5, 0); alter table employee change column age age1 float(5, 0); alter table employee alter column age set default 20; alter table employee alter column age drop default; alter table employee drop column height; alter table employee rename to aaa; alter table aaa rename as employee; alter table employee rename aaa; rename table aaa to employee; alter table employee character set utf8mb4; alter table employee collate utf8mb4_unicode_ci; alter table employee character set gbk collate gbk_bin;
添加列
ALTER TABLE <表名> ADD COLUMN <列名> <类型>;
头位置添加列
ALTER TABLE <表名> ADD <新字段名> <数据类型> FIRST;
指定位置添加列
ALTER TABLE <表名> ADD <新字段名> <数据类型> AFTER <已经存在的字段名>;
修改某列类型
ALTER TABLE <表名> MODIFY COLUMN <列名> <类型>;
修改列及类型
ALTER TABLE <表名> CHANGE COLUMN <旧列名> <新列名> <新列类型>;
修改某列默认值
ALTER TABLE <表名> ALTER COLUMN <列名> SET DEFAULT <默认值>;
删除某列默认值
ALTER TABLE <表名> ALTER COLUMN <列名> DROP DEFAULT;
删除某列
ALTER TABLE <表名> DROP COLUMN <列名>;
修改表名
ALTER TABLE <表名> RENAME TO <新表名>;
修改表名
ALTER TABLE <表名> RENAME AS <新表名>;
修改表名
ALTER TABLE <表名> RENAME <新表名>;
修改表名
RENAME TABLE <表名> TO <新表名>;
修改表字符集
ALTER TABLE <表名> CHARACTER SET <字符集名>;
修改表排序规则
ALTER TABLE <表名> COLLATE <校对规则名>;
ALTER TABLE <表名> [DEFAULT] CHARACTER SET <字符集名> [DEFAULT] COLLATE <校对规则名>;
删除表(不推荐)
DROP TABLE [IF EXISTS] 表名1 [ ,表名2, 表名3 ...]
drop table if exists table_a, table_b, table_c
添加数据
insert into employee1 (id, name, gender, graduate_year, birthday, job, salary, create_time) values(1, 'zs', '男', 2022, '1999-01-01', '程序员', 100.2, '2022-09-09 16:51:49'),(2, 'ls', '男', 2020, '1997-01-01', '程序员', 10000.2, '2022-09-09 16:51:50'); insert into employee1 set id=4, name='ls', gender='男', graduate_year=2022, birthday='1999-01-01', job='程序员', salary=220.05, create_time='2022-09-09 16:55:49';
INSERT INTO <表名> [ (<列名1>, … <列名n> )] VALUES (值1, … 值n), … (值1, … 值n);
INSERT INTO <表名> SET <列名1>=<值1>, … <列名n>=<值n>;
数据操作
查询数据
SELECT * FROM <表名字> [ WHERE <条件> ];
SELECT <列名1>, …<列名n> FROM <表名字> [ WHERE <条件> ];
修改数据
UPDATE <表名> SET 列1=值1 [, 列2=值2 … ] [WHERE <条件> ]
删除数据
DELETE FROM <表名> [WHERE <条件>]
特殊关键字
where
Distinct
对数据表中一个或多个字段重复的数据进行过滤,重复的数据只返回其一条数据给用户
SELECT DISTINCT <字段名> FROM <表名>;
DISTINCT 只能在SELECT语句中使用 (对select的查询结果做去重处理)
当对一个或多个字段去重时,DISTINCT 要写在所有字段的最前面
如果 DISTINCT 对多个字段去重时,只有多个字段组合起来完全是一样的情况下才会被去重
Limit
SELECT <查询内容|列等> FROM <表名字> LIMIT 记录数目
LIMIT 记录数目: 从第一条开始, 限定记录数目
SELECT <查询内容|列等> FROM <表名字> LIMIT 初始位置,记录数目
LIMIT 初始位置,记录数目: 从起始位置开始, 限定记录数目
SELECT <查询内容|列等> FROM <表名字> LIMIT 记录数目 OFFSET 初始位置
LIMIT 记录数目 OFFSET 初始位置: 从起始位置开始, 限定记录数目
As
为表和字段指定别名
<内容> AS <别名>
Order By
对查询数据进行排序
SELECT <查询内容|列等> FROM <表名字> ORDER BY <字段名> [ASC|DESC];
ASE
升序
DESC
降序
Group By
对数据进行分组
SELECT <查询内容|列等> FROM <表名字> GROUP BY <字段名...>
聚合函数
COUNT:计数
SELECT <查询内容|列等> , COUNT <列|*> FROM <表名字> GROUP BY HAVING COUNT <表达式|条件>
COUNT(*): 表示表中总行数
COUNT(列): 计算除了列值为NULL以外的总行数
AVG:平均值
SELECT <查询内容|列等> , AVG<列> FROM <表名字> GROUP BY HAVING AVG<表达式|条件>
MIN:最小值
SELECT <查询内容|列等> , MIN<列> FROM <表名字> GROUP BY HAVING MIN<表达式|条件>
SUM:和
SELECT <查询内容|列等> , SUM<列> FROM <表名字> GROUP BY HAVING SUM<表达式|条件>
MAX:最大值
SELECT <查询内容|列等> , MAX<列> FROM <表名字> GROUP BY HAVING MAX<表达式|条件>
主键
主键约束
每个表只能定义一个主键,主键值必须唯一标识表中的每一行,且不能为null
定义方式为在字段后写PRIMARY KEY
自增
制度庵后加上PRIMARY KEY AUTO_INCREMENT
初始值是 1,数据增加一条,该字段值自动加 1
字段应该要设置 NOT NULL 属性
约束的字段只能是整数类型
上限为所约束的类型的数值上限
数据类型
整形
浮点型
日期
字符串
SQL数据库执行顺序
(5) SELECT column_name, ... (1) FROM table_name, ... (2) [WHERE ...] (3) [GROUP BY ...] (4) [HAVING ...] (6) [ORDER BY ...];(7)[Limit ...]
小括号中的数字代表执行顺序
SQL的基本数据库

information_schema
主要存储了系统中的一些数据库对象信息,比如用户表信息、列信息、权限信息、字符集信息和分区信息等。
mysql
MySQL 的核心数据库,主要负责存储数据库用户、用户访问权限等 MySQL 自己需要使用的控制和管理信息。常用的比如在 mysql 数据库的 user 表中修改 root 用户密码。
update mysql.user set authentication_string=password('123456') where user='root'; flush privileges;
performance_schema
主要用于收集数据库服务器性能参数。
sys
sys 数据库主要提供了一些视图,数据都来自于 performation_schema,主要是让开发者和使用者更方便地查看性能问题。
三阶段
Servlet
sql命令、JDBC、MyBatis之间的关系
sql命令:SQL标准委员会指定的一个语言标准,主要是用来和关系型数据库进行通讯
JDBC:使用java语言来和数据库进行通信,最终底层还是要依赖于sql命令
MyBatis:对于JDBC过程的进一步封装,抽象
概念
Servlet
Servlet是一个合成词。Servlet=Server+applet.Servlet其实就是运行在服务器里面的一个java小程序
动态web资源
程序,如果是希望开发动态web资源,那么便需要编写程序,如果该程序需要运行在tomcat则需要编写servlet
静态web资源
访问的是页面、图片等
开发
继承GenerivServlet
不使用idea
示例代码 import javax.servlet.*; public class Servlet1 extends GenericServlet{ public void service(ServletRequest req, ServletResponse resp) throws ServletException,java.io.IOException{ System.out.println("servlet1"); } }
编译方式
因为javax.servlet包下面的类在JDK是不存在的,所以编译时,无法识别出编写的类是什么,所以无法解析
JDK内的类库是位于硬盘中的,但编译时,是在内存中进行编译的,需要将硬盘中的类库加载到内存中,需要由类加载器完成
命令行输入javac -classpath xxx.jar xxx.java进行编译
部署在tomcat(或其他服务器中)
因为Servlet没有main方法,所以无法使用java指令来进行运行操作,所以必须要运行在服务器中才可以运行,需要将Servlet部署到服务器中
部署方式
直接部署
虚拟映射
虚拟映射需要指向一个目录,并将文件置于一个目录中,采用虚拟映射的方式来设置一个应用,Class文件存放在应用中。
方式
Tomcat的conf/Catalina/localhost目录下新建ee51.xml文件
<?xml version="1.0" encoding="UTF-8"?> //docBase中需要写指向目录的地址 <Context docBase="C:\Users\song\Desktop\servlet" />
在应用指向的目录中部署资源文件,里面可以部署静态资源文件或者动态资源文件,calss文件等等
访问
问题
直接输入地址文件是下载而不是运行
服务地上面的源代码文件被哭护短下载到本地存在安全问题
解决
服务器源代码等相关文件,凡是不希望客户端直接访问,需要放在WEB-INF中,这个文件在应用根目录中,字母全部大写
为了让服务器上面的Servlet能够运行,ee规范了一个映射关系 url-pattern和全类名的映射关系,只要你访问对应的url-pattern那么便意味着需要运行对应的Service
例如 /ss1-------Servlet1映射关系 如果用户在浏览器地址栏输入了地址+ss1,那么此时服务器便可以识别出希望访问的事Serlvet1,随后服务器便会调用Servlet1
文件结构
/ee51 ---WEB-INF(主要是用来保护浏览器上面的资源文件) ------classex(存放源代码文件,里面存放全类名的类) ------lib(如果class文件运行还需要一些其他的类库,即第三方jar包,需要将jar包存放在这里) ------web.xml文件(配置映射关系)
web.xml文件(配置映射关系) servlet-name 可以随意设置 servlet-class 全类名类 url-pattern 设置访问的映射
 需要保留的节点  修改方式(上面Servlet-calss需要写全限定类名)
执行过程

域名解析
TCP连接建立socket(clientAdd clientPort serverAdd serverPort)
发送HTTP请求报文(GET /app/ss1 HTTP/1.1.....)
请求报文到达服务器之后,被监听着8080端口号的程序Connector接收到,将请求报文解析成为reqeust对象,同时提供一个response对象
Connector随即将这两个对象传给engine,engine进一步传给host
host根据请求的资源地址,去挑选一个合适的Context,尝试将/app当做一个应用去解析,如果找到,则将这两个对象进行进一步传递
Context内部根据输入的地址/ss1,查看有没有servlet相关的映射,如果找到,则利用反射调用servlet的service方法,service方法运行时需要传递两个参数,那么刚好这两个对象作为参数传递进入service方法
service方法运行,可以从request中读取请求报文信息,也可以从往response中写入相关的数据
Connector会读取response里面的数据,然后生成HTTP响应报文,传输给客户端,客户端将响应报文进行解析渲染,最终呈现出页面
使用IDEA
创建项目

新建Servlet,在web/WEB-INF/web.xml文件中配置映射关系
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>s1</servlet-name> <servlet-class>com.cskaoyan.servlet.Servlet1</servlet-class> </servlet> <servlet-mapping> <servlet-name>s1</servlet-name> <url-pattern>/ss1</url-pattern> </servlet-mapping> </web-app>
关联本地tomcat,来部署对应的servlet

部署应用

设置应用的应用名

启动服务器

配置页的详细配置

继承HttpServlet
必须重写如下方法
doGet
用于HTTP GET请求
doPost
用于HTTP POST请求
form表单来进行发送
点击form表单的提交按钮,会往对应的action地址也就是servlet,发送HTTP请求,此时请求方法为POST方法
POST http://localhost/app/ss2 HTTP/1.1 Host: localhost Connection: keep-alive Content-Length: 19 Cache-Control: max-age=0 sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 Origin: http://localhost Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost/app/index.jsp Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: JSESSIONID=1657C1B1D6226094CE19E3F203E86F8D username=&password=
doPut
用于HTTP PUT请求
doDelete
用于HTTP DELETE请求
init和destroy
用于管理servlet生命周期中保留的资源
getServletInfo
servlet使用它来提供有关自身的信息
执行流程
Servlet的执行入口是Servlet的Service方法,但为什么HttpServlet的Servlet中,入口函数编程了doxxx方法,实际上程序入口仍然是service方法没有变
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { //向下转型操作 request = (HttpServletRequest)req; response = (HttpServletResponse)res; } catch (ClassCastException var6) { throw new ServletException("non-HTTP request or response"); } //调用了自身的service方法 this.service(request, response); }
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取当前请求方法的名称,比如GET或者POST等 String method = req.getMethod(); long lastModified; if (method.equals("GET")) { lastModified = this.getLastModified(req); if (lastModified == -1L) { //调用自身的doGet方法 this.doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since"); } catch (IllegalArgumentException var9) { ifModifiedSince = -1L; } if (ifModifiedSince < lastModified / 1000L * 1000L) { this.maybeSetLastModified(resp, lastModified); this.doGet(req, resp); } else { resp.setStatus(304); } } } else if (method.equals("HEAD")) { lastModified = this.getLastModified(req); this.maybeSetLastModified(resp, lastModified); this.doHead(req, resp); } else if (method.equals("POST")) { //如果请求方法是POST,那么调用了自身的doPost方法 this.doPost(req, resp); } else if (method.equals("PUT")) { this.doPut(req, resp); } else if (method.equals("DELETE")) { this.doDelete(req, resp); } else if (method.equals("OPTIONS")) { this.doOptions(req, resp); } else if (method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } }
流程图

建议使用
package com.cskaoyan.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.ws.WebServiceRef; import java.io.IOException; //@WebServlet(name = "s3", urlPatterns = "/ss3") //可以将新⼀步省略,将name属性去掉 //@WebServlet(urlPatterns = "/ss3") //还可以进⼀步省略,如果注解中只写了⼀个单⼀的value值,那么表示的便是url-pattern //注解内的name属性和urlPatterns属性等价的,设置任何⼀个即可 @WebServlet("/ss3") public class Servlet3 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("ss3"); } }
HttpServlet继承了GenericServlet
继承HttpServlet⼜分发出不同的doGet或者doPost⽅法,由更具有针对性
注解方式
原理
注解的原理和web.xml文件的方式是完全相同的,都是应用被加载到tomcat服务器时,tomcat服务器会扫描web.xml文件以及扫描所有的类
扫描web.xml文件时,会解析serlvet节点以及对应的servley-mapping节点里面的信息
扫描注解,扫描那些类的头上标注了@WebServlet注解,取出里面的value值或者urlPattern值,可以行程全类名和url-pattern的映射关系
@WebServlet
@WebServlet 属于类级别的注解,标注在继承了 HttpServlet 的类之上。常用的写法是将 Servlet 的相对请求路径(即 value)直接写在注解内
@WebServlet(urlPatterns = “/MyServlet”)
@WebServlet(“/MyServlet”) 省略了 urlPatterns 属性名
@WebServlet 中需要设置多个属性,则属性之间必须使用逗号隔开.
通过实现 Serlvet 接口或继承 GenericServlet 创建的 Servlet 类无法使用 @WebServlet 注解
使用 @WebServlet 注解配置的 Servlet 类,不要在 web.xml 文件中再次配置该 Servlet 相关属性。若同时使用 web.xml 与 @WebServlet 配置同一 Servlet 类,则 web.xml 中 的值与注解中 name 取值不能相同,否则容器会忽略注解中的配置
类加载器
BootStrap类加载器
负责加载JDK中的核心类库,例如rt.jar里面的类库
Extension类加载器
负责加载JDK中ext目录下的核心类库,负责加载jdk/ext目录下的类库
System类加载器
负责将用户类路径 (java -classpath所指的目录,即当前类所在路径及其引用的第三方类库的路径)下的类库 加载到内存中
IDEA和Tomcat整合关联的方式

idea会复制本地tomcat的配置文件,利用这些配置文件重新开启一个新的tomcat,利用新的tomcat进行部署
声明周期(Servlet从出⽣到销毁所经历的整个阶段)
package com.cskaoyan.servlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName LifeCycleServlet4 * @Description: TODO * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Date 2023/6/9 17:02 * @Version V1.0 **/ @WebServlet(value = "/life",loadOnStartup = 1) public class LifeCycleServlet4 extends HttpServlet { // private String username; private String domain; /** * 当servlet被创建时,会调⽤init⽅法来完成初始化⼯作 * 它只被调⽤了⼀次,说明了只被实例化⼀次,也进⼀步说明只有⼀个实例对象-----单例 * 如果多个⽤户访问,会不会有问题?正常情况下来说,没有 * 前提是慎⽤成员变量,⽐如存储登录之后⽤户的⽤户名,可不可以设置⼀个成员变量来存储? * 绝对不可以使⽤成员变量来存储⽤户所特有的数据 * 但是不可以说绝对,⽐如存储当前的主机、端⼝号,可不可以存储呢?因为这些数据和⽤户⽆关 * 正常情况下来说,init⽅法是在第⼀次访问之前调⽤;通过设置load-on-startup=⾮负数可以让init⽅法 随着应⽤的启动⽽调⽤,执⾏的时间提前,主要⽤在init中进⾏初始化,并且初始化完毕之后的数据和其他servlet进 ⾏共享时可以⽤到 * ⽐如某个数据,当前的主机、端⼝号,假设我们配置在数据库中,某个servlet中进⾏了⼀次查询得到了结 果,希望可以和其他servlet进⾏共享,此时,我们便可以将查询之后的数据放⼊某个对象中,其他servlet也可以在 运⾏时访问到该数据 * 但是如果不设置load-on-startup=⾮负数,要求当前servlet必须⾸先访问才可以 * @throws ServletException */ @Override public void init() throws ServletException { System.out.println("servlet init"); } /** * service⽅法给客户端提供服务,⽤户每发送⼀次请求,那么便调⽤⼀次service⽅法 * 为什么servlet是单例,service⽅法给多线程的⽤户提供服务时,不会有问题? * 因为service⽅法⾥⾯的变量全部都是局部变量 * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("service get"); } /** * 应⽤卸载、服务器关闭时 */ @Override public void destroy() { System.out.println("servlet destroy"); } }
init

Servlet被创建时,会调用init方法来完成初始化工作
如果希望岁应用的启动而调用,可以设置load-on-startup=⾮负数,如果不设置,则当前servlet必须首先访问才可以
只会创建一次,多个用户访问也是一样,但不可以用成员变量来存储用户所特有的数据
service
任何客户端的请求都会交给Service方法来处理
用户每发出一次请求,便调用一次service方法,service方法中所有变量都是局部变量
destroy
不再发挥作用,会调用destroy来完成销毁工作
应用卸载或者服务器关闭时发生
Url-pattern
合法写法
/xxxx
xxxx代表任意字符
*.xxxx
*代表的事通配符,表示的是匹配任意字符
不可以多个servlet映射到同一个url-pattern
优先级
/xxx优先级⾼于 *.xxxx
针对 /xxxx 部分,满⾜匹配程度越⾼,优先级越⾼
缺省Servlet
概念
Tomcat会提供一个缺省Servlet,该Servlet处理请求的逻辑就是会把当前地址当做一个静态资源文件去看待,但是如果在项目中重新配置了一个新的缺省Servlet,那么Tomcat就不会再提供缺省Servlet了

配置
在项目中配置一个Servlet,url-pattern为/那么就是配置了一个新的缺省Servlet,Tomcat就不会再提供缺省Servlet了,而是会使用开发者自己实现的
缺省Servlet的conf/web.xml配置 <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
处理流程
域名解析
TCP连接建立
发送HTTP请求报文 (GET /app/1.html HTTP/1.1)(GET /app/ss1 HTTP/1.1)
请求报文到达服务器之后,会监听着80端口号的Connector接收到,Connector将其解析成为request对象,同时提供一个response对象
Connector将这两个对象交给engine来处理,engine进一步传递给host
host根据请求的资源地址,尝试去找一个叫做/app的应用,如果找到,则将这两个对象进行进一步传递
这两个对象到达Context之后,Context的执行逻辑首先判断/1.html或者/ss1有没有servlet可以处理该请求,如果找到,则交给一个优先级最高的servlet来处理,如果没有找到,则交给缺省Servlet来处理;缺省Servlet如果用户自己在项目中重新实现了一个,那么就会交给用户自己实现的,如果用户没有重写,则最终会交给tomcat提供的默认缺省Servlet。该servlet会将请求的资源地址当做一个静态资源文件来看待,拼接处docBase查找该文件是否存在;存在,则往response中写入文件数据;不存在,则写入404
最终Connector会读取response里面的数据,生成HTTP响应报文
ServletConfig(了解)
作用
程序运行过程中,获取到init-param里面的初始化参数
使用方法
ServletContext(掌握)
概念
ServletContext对象在每个应用中有且只有一个,他的生命周期基本上和应用的生命周期保持一致,当应用被加载ServletContext对象被创建,当应用被销毁之前ServletContext对象被销毁
context域

ServletContext对象内部有一个类似于map的数据结构。可以作为共享数据的场所。凡是在同一个应用下的Servlet,那么均可以拿到同一个ServletContext对象的应用。那么这些Servlet在运行时就可以利用ServletContext来进行数据共享。该功能被称之为context域
应用 package com.cskaoyan.servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName ${NAME} * @Description: TODO * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Date 2022/6/20 17:30 * @Version V1.0 **/ @WebServlet("/cc1") public class ContextServlet_17 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //拿到servletContext对象 ServletContext servletContext = getServletContext(); servletContext.setAttribute("name", "zhangsan"); } } ------------------------ package com.cskaoyan.servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName ${NAME} * @Description: TODO * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Date 2022/6/20 17:30 * @Version V1.0 **/ @WebServlet("/cc2") public class ContextServlet_18 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = getServletContext(); Object name = servletContext.getAttribute("name"); System.out.println(name); } }
获取EE项目文件的绝对路径
注意
不能使用SE阶段new File相对路径的方式获取文件的绝对路径,因为此时dile的相对路径是tomcat的bin目录,file的相对路径,可以理解为是用户工作目录,即在哪个目录下调用了JVM,就返回哪个目录,EE项目中没有main方法,tomcat服务器会将编写的代码合在一起,看做一个java程序,所以相对路径获取到的是tomcat的bin目录
获取方式
利用ServletCountext对象暴露应用的docBase
如果希望获取某个文件的路径,那么只需要自行拼接出该文件相对docBase的相对路径即可
ServletContext servletContext = getServletContext(); String docBase = servletContext.getRealPath("");
输入一个相对应用跟目录的一个相对路径,它会帮助我们返回绝对路径,WEB-INF目录下同理
ServletContext servletContext = getServletContext(); String realPath = servletContext.getRealPath("static/1.html");
ServletRequest
简介
ServletRequest可以理解为对请求报文的封装服务器收到客户端的http请求,会针对每一次请求分别创建request对象(请求)和response对象(响应),如果要获取浏览器提交的数据,需要找request对象,要向客户机输出数据,需要找response对象
HttpServletRequest可以理解为是对HTTP请求报文的封装,它进一步扩展了ServletRequest接口
因为浏览器发送的请求基本都是HTTP请求,所以这两者基本可以看作是相同的
常用方法
获的请求报文(对请求报文的封装)
代码示例: package coom.cskaoyan.req; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; @WebServlet("/req1") public class ReqServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //常用方法 //该对象就是对于请求报文的封装体,可以获取到所有的请求报文信息 //利用request提供的相关方法,可以将HTTP请求报文全部复原 String method = request.getMethod(); String requestURI = request.getRequestURI(); String requestURL = request.getRequestURL().toString(); String protocol = request.getProtocol(); System.out.println(method + " " + requestURI + " " + protocol); System.out.println(method + " " + requestURL + " " + protocol); //获取请求头 键值对 Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); String headerValue = request.getHeader(headerName); System.out.println(headerName + ":" + headerValue); } //获取请求体 //在今天的课程中,我们没有必要自己主动去获取请求体,即便用户提交的请求参数位于请求体中,那么也不需要开发者去获取 // request.getInputStream(); } }
getMethod方法返回请求行的请求方式
getRequestURL方法,返回客户端发出请求时的完整URL
getRequestURI方法,返回请求行中的资源名部分
URI + 访问协议、主机、端口号 = URL
getProtocol方法返回请求行的版本协议
获取请求报文信息
getHeader(name)方法请求报文的指定请求头信息
getHeaders(String name)方法返回请求报文的制定
获取请求头
getInputStream()获取请求体
获的请求体: 如果发送的是get请求,正常情况下是不会有请求体的 一般不用主动获取解析,即便参数位于请求头中,也无需开发者主动获取
获的客户机及服务器主机信息
用处:设置ip地址黑名单,商城统计每天独立ip地址访问kpi 代码示例 package coom.cskaoyan.req; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; /** * @ClassName ${NAME} * @Description: TODO * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Version V1.0 **/ @WebServlet("/req2") public class ReqServlet2 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取客户机和服务器主机的信息 //客户端信息 封俄罗斯ip 小黑屋 String remoteAddr = request.getRemoteAddr(); int remotePort = request.getRemotePort(); String localAddr = request.getLocalAddr(); int localPort = request.getLocalPort(); System.out.println("位于" + remoteAddr + "的客户机使用了端口号" + remotePort + "访问了位于" + localAddr + "服务器主机上面的" + localPort + "端口号上面的程序"); } }
getRemoteAddr方法,返回发出请求的客户机IP地址
getRemoteHost方法,返回发出请求的客户机的完整主机名
getRemotePort方法,返回客户机所使用的网络端口号
getLocalAddr方法,返回WEB服务器的IP地址
getLocalName方法,返回web服务器的主机名
获取请求参数(核心功能)
代码示例 html代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="http://localhost/app/form1" method="post"> <input type="text" name="username"><br> <input type="password" name="password"><br> <input type="radio" name="gender" value="male">男 <input type="radio" name="gender" value="female">女<br> <input type="checkbox" name="course" value="java">java <input type="checkbox" name="course" value="python">python <input type="checkbox" name="course" value="c++">c++<br> <input type="submit"> </form> </body> </html> package coom.cskaoyan.req; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Arrays; @WebServlet("/form1") public class FormServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //注意事项: //1.只可以获取key=value&key=value类型的数据 //2.参数中传入key,(其实该key就是form表单input的name属性)可以获取到value值 username=admin&password=admin123 String username = request.getParameter("username"); String password = request.getParameter("password"); String gender = request.getParameter("gender"); //note:需要注意的是该方法只可以获取单个值,如果提交的是类似于chekcbox这种多个值 //String course = request.getParameter("course"); String[] courses = request.getParameterValues("course"); System.out.println(username); System.out.println(password); System.out.println(gender); System.out.println(Arrays.toString(courses)); Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()){ String key = parameterNames.nextElement(); String[] value = request.getParameterValues(key); System.out.println(key + "=" + Arrays.toString(value)); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
getParameter(name)通过参数名获取单个参数的值
getParameterValues(name)通过参数名获取参数的集合
getParameterNames()获取所有参数的名
getParameterMap()获取所有参数的键值对集合
获取请求参数时中文乱码问题
为何会出现乱码数据到达服务器时,中文编码格式是utf-8,但是通过API方法获取出来后,由服务器解析成字符串使用的不是utf-8,所以需要通过重新设置编码格式
setCharacterEncoding(编码方式)
将数据请求到对象中
目的
将数据请求到一个对象中更方便进行后续操作
方法
BeanUtils.populate(对象名, Map键值对)
导包 <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.4</version> </dependency> 代码示例 @WebServlet("/param6") public class ParamServlet6 extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { User user = new User(); //需要接受两个参数,一个是bean对象,一个是map //作用过程便是将map中的参数封装到第一个参数object bean对象中 try { BeanUtils.populate(user, req.getParameterMap()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } System.out.println(user); } }
网络路径写法总结
全路径
以访问协议、主机、端口号、资源路径为一个整体的路径称之为全路径
全路径的可读性是最好的,但是在编写的时候,不是很推荐,因为全路径中设计了主机\端口号信息,这个信息在不同的开发阶段不同,所以,写全路径的时候要讲可变部分以配置文件形式进行配置,使这部分数据从代码中获取
相对路径
相对于当前资源的一个相对路径
相对路径写法
param
这种路径写法过分依赖当前页面所在的位置,如果当前页面发生了变更,那么相对路径也需要同步发生改变,不推荐
/开头路径
相对于全路径来说,刚好少了访问协议,主机,端口号部分
公式:对于servlet来说:/{应用名}/{url-pattern}主:url-pattern不包含*
对于静态资源来说:/{应用名}/{文件相对于应用根目录的相对路径}
转发包含(了解即可)
代码示例 @WebServlet("/fd1") public class ForwardServlet9 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //如果在一个servlet中需要调用另外一个servlet,不要使用下面的方式来实现 //因为servlet在tomcat服务器中都是单例的,破坏了单例特性 //正确的方式应该给tomcat服务器发送一条指令,让tomcat服务器去调用-----转发 //给tomcat服务器发送一条指令,让tomcat去调用/app/fd2 System.out.println("fd1"); req.getRequestDispatcher("/fd2").forward(req, resp); // new ForwardServlet10().doGet(req, resp); } } @WebServlet("/fd2") public class ForwardServlet10 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("fd2"); } }
给tomcat服务器发出一条指令,让tomcat服务器进行调用(转发)
req.getRequestDispatcher("/fd2").forward(req, resp)
Request域(面试知识点)

域
内存空间,可以用来进行数据的共享
context域
每个应用中有且只有一个ServletContext对象,内部有一个类似map的数据结构,可以用来进行数据的共享,又要可以持有同一个ServletContext对象的引用,那么便可以进行数据共享
只要是同一个应用下的任何一个资源,均可以共享context域,使用场景:存储当前系统的配置信息等
request域
request对象内部也有一个类似map的数据结构,可以用来进行数据共享。只要可以持有同一个request对象的引用,那么便可以进行数据共享
只有转发是,两个servlet组件之间可以共享,使用场景:一个servlet进行数据库查询,查询到的结果和jsp(本质也是servlet,只不过处理页面非常方便)进行共享
目前前后端分离,jsp基本不会再用了,所以关于request域也就不会再使用了。基本都是作为EE规范的知识点,面试的时候需要知道。
拆解HTTP请求报文的思路
利用换行符\r\n可以分割出请求行部分;再利用空格分割,可以依次分割出请求方法、请求资源、版本协议
请求头开始的标志\r\n,结束的标志\r\n\r\n;李用换行符\r\n分割---若干部分;再每个部分中利用:来进行分割----请求头的key和value值,将key和value值保存到map中
请求体----不处理
路径总结(ee项目中的三种路径)
应用跟目录
一般存放静态资源文件,因为春芳在此处的文件,可以直接被浏览器访问到,无需开发者去做一步处理,本质是借助于缺省Servlet将其显示给客户端
WEB-INF目录
应用根目录 /WEN-INF:一般存放一些不希望被客户端直接访问的资源,,比如配置文件可以存放在癌该处 也可以存放在resoures里面
resources目录
classpath目录 应用根目录/WEB-INF/classes:一般配置文件等一切服务器相关的配置等,军肯呢个放在该目录下,获取的方式比较件
注意:如果静态资源文件,存放在2和3处,是可以的,但是处理起来会比较麻烦,需要主动写逻辑,将其响应出去
ServletResponse
概念
协助Servlet给客户端返回响应信息的一个对象,后续服务器会读取ServletResponse里面的内容,根据HTTP响应报文格式生成HTTP响应报文
指定了一个统一的标准、规范接口。兑现内部有一系列的属性,并且提供了一系列的set方法用来进行赋值操作
API
setStatus(code)
该方法可以给response里面的成员变量进行赋值,后续服务器会读取该数值作为当前相应报文的响应状态码
setHeader(key,value)
设置响应头的键值对
getWriter().println(obj)
往response的成员变量数组中写入数据,后续服务器会读取该数据,放置与响应报文的响应体处
getOutputStream().write(obj)
输出二进制数据类型,与上面方法互斥,不能同时调用
resp.setHeader("Content-Type","text/html;charset=utf-8");
setContentType(text/html;charset=utf-8)
设置一个响应头,包含的信息是返回资源的文本类型,并且编码格式为utf-8
输出字符数据
使用字符流
需要设置编码格式不然会出现乱码
设置编码格式,并告知浏览器
输出字节数据
使用字节流
ServletOutputStream
往response内部的一个数据中写数据
FileOutputStream
往硬盘上的文件写数据
页面跳转(refresh定时跳转)
利用refresh响应头可以设置刷新时间
设置一个单一的值,表示的事每隔指定秒数刷新当前页面
@WebServlet("/rf1") public class RefreshServlet5 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //希望可以时刻显示最新的时间 resp.getWriter().println(new Date()); //设置refresh相应头,表示浏览器每隔一秒将重新加载页面 resp.setHeader("refresh", "1"); } }
设置一个单一的值,后续跟一个url,表示的是经过指定秒数之后跳转至一个新的页面
@WebServlet("/rf2") public class RefreshServlet6 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); //希望可以时刻显示最新的时间 //经过3秒钟之后,跳转至2.jpg resp.getWriter().println("登录成功,3秒钟之后即将跳转至个人中心页面,如果没有自动跳转,可以点击下方链接进行手动跳转"); resp.getWriter().println("<a href='" + req.getContextPath() + "/2.jpg" + "'>点击跳转</a>"); //自动跳转的逻辑 //浏览器会对该响应头进行解析,经过3秒钟之后,会往url发起一个新的请求 resp.setHeader("refresh", "3;url=" + req.getContextPath() + "/2.jpg"); } }
重定向
@WebServlet("/stu/*") public class SendRedirectServlet7 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //凡是访问/stu/xxx开头的地址,那么全部重定向到一个新的地址 resp.setStatus(307); resp.setHeader("Location", "http://stu.cskaoyan.com"); //但是上述过程太过于繁琐,ee规范提供了一个简化的形式 resp.sendRedirect("http://stu.cskaoyan.com"); } }
概念
返回301、302、307状态码,同时搭配着Location响应头
一般模式
resp.setStatus(307); resp.setHeader("Location", "http://stu.cskaoyan.com");
简化模式
resp.sendRedirect("http://stu.cskaoyan.com");
下载(了解即可)
@WebServlet("/download") public class DownloadServlet8 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //下面这个响应头就是告诉浏览器将图片资源执行下载到本地操作,而不是打开 resp.setHeader("Content-Disposition", "attachment;filename=2.jpg"); //下载的代码和显示的代码基本上是一模一样的,和显示的区别在于要多一个响应头 //如果我们希望使用字节流输出数据,那么需要使用如下方法 ServletOutputStream outputStream = resp.getOutputStream(); //应用根目录下WEB-INF/1.jpg,希望你可以将该图片响应给客户端,如何做? //读取位于应用根目录/WEB-INF/1.jpg,需要文件输入流 String realPath = getServletContext().getRealPath("2.jpg"); // // String realPath = getServletContext().getRealPath("WEB-INF/1.jpg"); FileInputStream fileInputStream = new FileInputStream(new File(realPath)); int length = 0; byte[] bytes = new byte[1024]; while ((length = fileInputStream.read(bytes)) != -1){ outputStream.write(bytes, 0, length); } //关闭释放资源即可 fileInputStream.close(); //response提供的输出流可以关闭,也可以不关闭,如果不关闭,则tomcat在响应的时候会帮我们关闭 outputStream.close(); } }
正常情况下,浏览器会执行打开操作,如果需要下载,可以说设置一个响应头
resp.setHeader("Content-Disposition", "attachment;filename=2.jpg");
合并请求
代码示例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="/app/2.jpg">直接访问2.jpg</a> <a href="/app/stream">通过servlet去访问2.jpg</a> <a href="/app/download">下载2.jpg</a> ------------将stream和download的代码逻辑合并到一个servlet中------------- <a href="/app/pic/info">显示2.jpg优化版</a> <a href="/app/pic/download">下载2.jpg优化版</a> ------------还有一种方式也可以实现合并、分发的效果--------------------- <a href="/app/picture?op=info">显示2.jpg优化版2</a> <a href="/app/picture?op=download">下载2.jpg优化版2</a> </body> </html> 正常方法: @WebServlet("/pic/*") public class PicServlet9 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //无论是/pic/info /pic/download都会进入到doGet方法中,加以区分 //他们的请求路径是不同的,利用请求路径不同来进行分发 // /app/pic/ info String requestURI = req.getRequestURI(); //replace就是把一个旧的字符串替换成新的字符串 String op = requestURI.replace(req.getContextPath() + "/pic/", ""); if("info".equals(op)){ info(req, resp); }else if("download".equals(op)){ download(req,resp); } } private void download(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setHeader("Content-Disposition", "attachment;filename=2.jpg"); info(req, resp); } private void info(HttpServletRequest req, HttpServletResponse resp) throws IOException { //如果我们希望使用字节流输出数据,那么需要使用如下方法 ServletOutputStream outputStream = resp.getOutputStream(); //应用根目录下WEB-INF/1.jpg,希望你可以将该图片响应给客户端,如何做? //读取位于应用根目录/WEB-INF/1.jpg,需要文件输入流 String realPath = getServletContext().getRealPath("2.jpg"); // // String realPath = getServletContext().getRealPath("WEB-INF/1.jpg"); FileInputStream fileInputStream = new FileInputStream(new File(realPath)); int length = 0; byte[] bytes = new byte[1024]; while ((length = fileInputStream.read(bytes)) != -1){ outputStream.write(bytes, 0, length); } //关闭释放资源即可 fileInputStream.close(); //response提供的输出流可以关闭,也可以不关闭,如果不关闭,则tomcat在响应的时候会帮我们关闭 outputStream.close(); } } 优化方法: @WebServlet("/picture") public class PictureServlet10 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //当显示和下载的请求到来时,附着一个请求参数过来 // /picture?op=info /picture?op=download 获取请求参数,识别出究竟应该运行哪个方法 String op = req.getParameter("op"); if("info".equals(op)){ info(req, resp); }else if("download".equals(op)){ download(req,resp); } } private void download(HttpServletRequest req, HttpServletResponse resp) { } protected void info(HttpServletRequest request, HttpServletResponse response){ } }
按照同一功能模块的请求放到一个servlet中
FileUpload
概念
文件借助于HTTP请求报文(请求体/正文)传输到服务器,会被tomcat服务器所解析,封装到ServletRequest对象中,李彤request.getInputStream()来获取文件的数据,然后再构建一个FileOutputStream用来将文件存储到硬盘上
步骤
准备一个from表单,mothod方法需要是post请求方法
需要input type=file,需要有input框,才可以进行文件上传
form表单还需要设置enctype=multipart/form-data属性
具有该属性,那么form表单才会真正上传二进制数据,否则只会提交文本数据
导出文件分隔符问题
借助于第三方的工具类jar包,commons-fileupload组件,借助于该组件可以非常轻松地实现文件上传功能。---后续在SpringMVC阶段使用
在servlet3.1版本之后,服务器原生地支持文件上传操作,无需引入第三方的工具类jar包,即可进行处理。----JavaEE阶段
基于服务器的文件上传处理
@WebServlet("/upload2") @MultipartConfig public class UploadServlet2 extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //part对象便是对于文件数据的一个封装 Part part = req.getPart("image"); //便可以把文件的数据写入到对应的位置 //希望可以将文件上传到位于image目录下 //利用part来获取文件的名称 String fileName = part.getSubmittedFileName(); //确保应用根目录下image目录存在 String realPath = getServletContext().getRealPath("image/" + fileName); File file = new File(realPath); if(!file.getParentFile().exists()){ //判断父级目录是否存在,如果不存在,则创建父级目录 file.getParentFile().mkdirs(); } part.write(realPath); } }
处理文件上传的Servlet需要添加@MultipartConfig注解
编写Servlet去处理,主要利用request.getPart(fielename)即可
在进行文件上传时,如果同时提交了文本也是可以用原本的api获取
封装参数到对象中
@WebServlet("/upload4") @MultipartConfig public class UploadServlet4 extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //增加一部分代码来获取表单数据 key=value&key=value 8 15 // String username = req.getParameter("username"); // String password = req.getParameter("password"); // System.out.println(username); // System.out.println(password); //常规表单数据 Map<String, String[]> result = new HashMap<>(); Map<String, String[]> parameterMap = req.getParameterMap(); Set<String> keySet = parameterMap.keySet(); for (String key : keySet) { String[] value = parameterMap.get(key); result.put(key, value); } //part对象便是对于文件数据的一个封装 Part part = req.getPart("image"); //便可以把文件的数据写入到对应的位置 //希望可以将文件上传到位于image目录下 //利用part来获取文件的名称 String fileName = part.getSubmittedFileName(); //确保应用根目录下image目录存在 String relativePath ="image/" + fileName; String realPath = getServletContext().getRealPath(relativePath); File file = new File(realPath); if(!file.getParentFile().exists()){ //判断父级目录是否存在,如果不存在,则创建父级目录 file.getParentFile().mkdirs(); } part.write(realPath); //文件存储到硬盘之后,随即获取文件的网络路径部分 result.put("image", new String[]{relativePath}); //封装的操作 User user = new User(); try { BeanUtils.populate(user, result); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } }
上传的文件需要将文件的路径存储到对象中,存储的时候需要存储网络路径
目录内文件过多解决(参考方案)
按照年月日生成目录
使用哈希值,转换成十六进制,依次去每一位当做子目录
int hashCode = fileName.hashCode(); String hexString = Integer.toHexString(hashCode); char[] charArray = hexString.toCharArray(); String basePath = "image"; for (char c : charArray) { // image/1/2/3/4/5/6/a/f/xxx.jpg basePath = basePath + "/" + c; } String relativePath = basePath + "/" + fileName;
根据文件名切割
会话技术(Cookie)
HTTP协议无状态
指的是对于 服务器看来,任何客户端发送过来的请求报文基本上都是一模一样的,服务器根本不可能通过请求报文区分彼此
会话技术
客户端技术
Cookie
服务器技术
Session
会话技术分类,数据存储在哪里便是何种技术
原理
数据产生于服务器,产生数据后,借助HTTP响应报文(Set-Cookie:key=value)传输给客户端;客户端会将这个数据进行储存,下次访问服务器时,会把这个数据再次借助HTTP请求报文携带过去,服务器获取Cookie的值,从而判断是哪个客户端访问的
开发流程
@WebServlet("/add") public class AddCartServlet2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //开发cookie的流程,就以刚刚的购物车为例 //利用构造函数,创建一个cookie对象 Cookie cookie = new Cookie("goodsId", "1"); //cookie数据产生了之后,需要传输给客户端,让客户端去存储保存 //根据Cookie的定义,我们可以写出如下的代码 // resp.setHeader("Set-Cookie", "goodsId=1"); //实际上,我们无需使用上述方式给客户端去返回cookie信息,ee规范同样提供了一个封装的方法 resp.addCookie(cookie); } } @WebServlet("/view") public class ViewCartServlet3 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取cookie里面的goodsId的值,渲染页面 //如何从请求头获取Cookie呢? //根据定义,我们应该调用request.getHeader(Cookie)方法,但是同样无需这么去做 Cookie[] cookies = req.getCookies(); if(cookies != null){ for (Cookie cookie : cookies) { if("goodsId".equals(cookie.getName())){ resp.getWriter().println(cookie.getValue()); } } } } }
创建Cookie对象
利用response提供的方法将Cookie信息返回给客户端
利用request提供的获取Cookie的方法接收Cookie信息
Cookie设置
设置存活时间
如果不设置,那么Cookie存在浏览器的内存中,关闭浏览器,则Cookie失效,设置一个存活时间可以让Cookie持久化
@WebServlet("/age") public class CookieMaxAgeServlet6 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie cookie = new Cookie("age", "max"); //时间单位是秒 cookie.setMaxAge(180); resp.addCookie(cookie); } }
如果设置的是正数,表示的是硬盘中存活多久(单位是秒)
如果设置的是负数,则表示默认情况下存在浏览器内存中(和没有设置的情况下完全等效)
如果设置的是0,表示的是删除cookie.
特别注意
删除cookie需要浏览器去删除,cookie设置maxAge=0其实只是服务器上面的一个对象设置了一个属性。最终我们需要将所做的修改同步给客户端,让客户端去删除该cookie
设置路径
在没有设置的情况下,仅当访问当前路径以及当前路径的子路径时才会携带Cookie
比如在访问http://localhost/app/a/b/c/servlet1时,产生了一个cookie,那么当我访问下面的地址时,会不会携带cookie呢? http://localhost/app/a/b/c/d/servlet2 ---携带 http://localhost/app/a/b/c/servlet3 ---- 携带 http://localhost/app/a/b/servlet4 --不携带 http://localhost/app/a/servlet5 ---- 不携带
如果希望Cookie在访问其他地址时,也会携带Cookie,则可以设置一个path路径
cookie.setPath()
@WebServlet("/a/b/c/servlet1") public class PathServlet8 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie cookie = new Cookie("path", "abc"); //设置网络路径,因为该路径是浏览器使用 cookie.setPath(req.getContextPath() + "/a"); resp.addCookie(cookie); } }
意义
可以实现跨应用之间的数据共享。意味着访问当前主机下任何一个资源时均会携带Cookie
如果cookie没有设置path路径,则删除cookie时,仅设置setMaxAge=0即可,但是如果设置了path,那么在删除cookie时,需要将path再写一遍
设置域名
四阶段