JavaSE_第五周
异常
异常的概念
什么是异常
概念
概念:程序在运行过程中出现的特殊情况
异常-----通过Jvm将异常的信息打印在控制台---告诉开发者(当前程序在某个环节出现了哪些问题!)
异常处理的必要性
异常处理的必要性:任何程序都可能存在大量的未知问题、错误,如果不对这些问题进行正确处理,则可能导致程序的中断,造成不必要的损失。
异常的分类
Throwable
Throwable: 可抛出的,一切错误或异常的父类,位于java.lang包中
Error
Error: JVM,硬件,执行逻辑错误,不能手动处理
例:Error:StackOverflowError 堆栈溢出错误
Exception
Exception: 程序在运行和配置中产生的问题,可处理
RuntimeException
RuntimeException:程序在执行过程中产生的异常,可处理,可不处理
CheckedException
CheckedException:受查异常,也称编译时期异常,Java语法原因,导致出现的问题,必须处理
异常的产生
自动抛出异常
自动抛出异常:当程序在运行时遇到不规范的代码和结果时,会产生异常
手动抛出异常
手动抛出异常:语法:throw new 异常类型(实际参数);
产生异常结果
产生异常结果:相当于遇到return语句,导致程序因异常而终止
异常的传递
异常的传递
异常的传递:按照方法的调用链反向传递,如始终没有处理异常,最终会由JVM进行默认异常处理(打印堆栈跟踪信息)
受查异常
编译时期异常:
语法通过不了,或者使用jdk提供的一些本身就带有异常的方法,不处理不行
需要开发者要显示处理,否则报错!
受查异常:throws 声明异常,修饰在方法参数列表后端
运行时异常
运行时期异常:
开发者可以显示处理,也可以不显示处理,无需声明异常,可以通过逻辑代码判断...
public static void method2() {
//显示处理
/* try{
int a = 20 ;
int b = 0 ;
System.out.println(a/b);
}catch(Exception e){
System.out.println("除数不能为0");
}
*/
int a = 20 ;
int b = 0 ;
//代码逻辑判断
if(b!=0){
System.out.println(a/b);
}else{
System.out.println("除数为0!");
}
}
常见的的运行时期异常
public class TestRuntimeException {
public static void main(String[] args) {
m6();
}
//java.lang.NullPointerException
public static void m1() {
Object o = null;
o.hashCode();
}
//java.lang.ArrayIndexOutOfBoundsException
public static void m2() {
int[] nums = new int[4];
System.out.println(nums[4]);
}
//java.lang.StringIndexOutOfBoundsException
public static void m3() {
String str = "abc";
System.out.println(str.charAt(3));
}
//java.lang.ArithmeticException
public static void m4() {
System.out.println(3/0);
}
//java.lang.ClassCastException
public static void m5() {
Object o = new Integer(688);
String s = (String)o;
}
//java.lang.NumberFormatException
public static void m6() {
new Integer("10A");
}
}
JVM的默认处理方案
把异常的名称,错误原因及异常出现的位置等信息输出在了控制台 ,程序停止执行
异常的处理
捕获异常
try{
可能出现异常的代码
}catch(Exception e){
异常处理的相关代码,如:getMessage(); printStackTrace()
}finally{
无论是否出现异常,都需执行的代码结构,常用于释放资源
}
try{
int result = num1 / num2;//throw new ArithmeticException("/by zero")
System.out.println(result);
}catch(Exception e) {// new ArithmeticException();
// System.out.println("除数不能为零!");//处理方式1(自定义处理)
// e.printStackTrace();//处理方式2(打印堆栈跟踪信息)
System.out.println(e.getMessage());//处理方式3(打印throwable中详细消息字符串)
}
如果try不存在问题,最终try里面的代码执行完毕,执行finally代码;
finally中代码一定会执行,除非 jvm退出了!
finally语句中代码:
释放系统资源
数据库连接对象.close();
IO流中的流对象.close() ;
jdbc执行对象Statement.close()
获取数据结果集对象.close()...
public class ExceptionDemo4 {
public static void main(String[] args) {
try{
//给定一个日期文本格式
String source = "2021-2-23" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd "); //存在问题:模式不匹配
//解析
Date date = sdf.parse(source) ; //jvm执行:内存中:创建ParseException实例
System.out.println(date);
}catch (ParseException e){
System.out.println("文本解析出现问题了...");
//jvm退出
System.exit(0) ;//0:正常终止jvm
}finally{
System.out.println("这里面的代码一定会执行...");
}
System.out.println("程序over...");
}
}
抛出异常
throws :抛出,消极处理(告知了调用者,此方法可能会产生异常)
throws和throw的区别
throws和throw的区别
1)throws: 位置在方法声明上
throw:位置是在方法体中
2)throws的后面跟的是异常类名,后面可以跟多个异常类名,中间使用逗号隔开
throw的后面跟的是异常对象,一般情况 new XXXException() ; 后面跟的是某个具体的异常对象
3)throws表示的是抛出异常的一种可能性!(不一定)
throw:表示抛出异常的一种肯定性,执行这段代码,一会产生这个异常!
4)throws抛出异常,调用者必须对当前这个方法中的异常进行处理
throw抛出异常,方法体中通过逻辑代码控制!
public class ExceptionDemo2 {
public static void main(String[] args) {
System.out.println("程序开始了...");
//捕获处理
try {
method1() ;
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("程序结束了...");
// method2() ;
}
//throw
private static void method2() {
// 定义两个变量
int a = 10 ;
int b = 0 ;
if(b!=0){
System.out.println(a/b);
}else{
//代码执行此处,抛出异常
throw new ArithmeticException() ; //创建异常实例(匿名对象)
}
}
//解析日期文本格式
private static void method1() throws ParseException { //抛出异常
String s = "2022-6-30" ;
//String--->Date格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
//解析
Date date = sdf.parse(s) ; //日期格式
System.out.println("date格式为:"+date);
}
}
Throwable中的常用的功能
关于Throwable中的一些常用的功能
public String getMessage():获取异常的详细消息字符串
public String toString():获取异常的简单描述
异常类名: 详细的消息字符串getMessage()
public void printStackTrace():追踪错误的输出流(包含异常的toString()) (推荐)
public class ExceptionDemo3 {
public static void main(String[] args) {
try{
//给定一个日期文本格式
String source = "2021-2-23" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); //存在问题:模式不匹配
//解析
Date date = sdf.parse(source) ; //jvm执行:内存中:创建ParseException实例
System.out.println(date);
}catch (ParseException e){
//使用throwable 的功能
//执行catch语句
// String msgStr = e.getMessage() ;
// System.out.println(msgStr);
// public String toString()
//String msgStr = e.toString() ;
//System.out.println(msgStr);
//public void printStackTrace():
e.printStackTrace();
}
System.out.println("程序over...");
}
}
自定义异常
概念
自定义异常:需继承自Exception或Exception的子类,常用RuntimeException
构造方法
无参构造方法
有String message参数的构造方法
public class TestDefinedException {
public static void main(String[] args) {
Student s = new Student();
try {
s.setAge(333);
} catch (AgeException e) {
e.printStackTrace();//打印堆栈跟踪信息
}
try {
s.setSex("666");
}catch(SexMismatchException e){
System.err.println("性别输入有误," + e.getMessage());
}
}
}
//受查异常
class AgeException extends Exception{
public AgeException() {
super();
}
public AgeException(String message) {
super(message);
}
}
//运行时异常
class SexMismatchException extends RuntimeException{
public SexMismatchException() {
super();
}
public SexMismatchException(String message) {
super(message);
}
}
class Student{
private int age;
private String sex;
public int getAge() {
return age;
}
public void setAge(int age) throws AgeException {
if(age > 0 && age <= 253) {
this.age = age;
}else {
//抛出异常
throw new AgeException("年龄的正确取值区间:1~253");
}
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
if(sex.equals("男") || sex.equals("女")) {
this.sex = sex;
}else {
//抛出异常
throw new SexMismatchException("性别的正确取值:'男' 或 '女'");
}
}
}
方法重写
带有异常声明的方法覆盖:
方法名、参数列表、返回值类型必须与父类相同
子类的访问修饰符和父类相同,或比父类更宽泛
子类中的方法,不能抛出比父类或接口 更宽泛的异常
public class TestOverrideExceptionMethod {
public static void main(String[] args) {
Super s = new Sub();
try {
s.method();
} catch (ClassCastException e) {
e.printStackTrace();
}
Printable p = new MyClass();
try {
p.print();
} catch (IOException e) {
e.printStackTrace();
}
MyClass mc = new MyClass();
mc.print();
}
}
class Super{
public void method() throws ClassCastException {
System.out.println("Super---method()");
}
}
class Sub extends Super{
public void method() {
System.out.println("Sub---method()");
}
}
interface Printable{
public void print() throws IOException;
}
class MyClass implements Printable{
@Override
public void print(){
System.out.println("MyClass -- print()");
}
}
面试题
//1.打印输出结果
public class TestTryCatchFinally {
public static void main(String[] args) {
System.out.println(method(4));
}
public static int method(int n) {
try {
if(n % 2 == 0) {
throw new RuntimeException("不能为偶数");
}
return 10;
} catch (Exception e) {
System.out.println("捕获异常...");
return 20;
}finally {
System.out.println("方法结束");//必须执行
}
}
}
运行结果:
捕获异常...
方法结束
20
2.打印输出结果
public class FinallyTest {
public static void main(String[] args) {
System.out.println(getNum(20));//结果?
}
private static int getNum(int a) {
System.out.println(a);//20
try{
a = 10 ;
System.out.println(a/0);
}catch (ArithmeticException e){ //执行catch
a = 30 ; // 30
return a ; //形式返回路径了: return 30
}finally{
a = 40 ;
}
return a ;
}
}
运行结果:30
如果处理异常的时候,try...catch...finally格式,catch语句中有return语句, finally中代 码还会执行吗?如果会执行,是在catch之前还是之后?
finally会执行,具体在finally之前(中间),return 变量:已经形成返回路径!
finally代码一定会执行,除非是在执行finally语句之前,jvm退出了!
3. finally,final,finalize的区别
finally:捕获异常的finally语句, try...catch...finally
一般用途就是释放相关的资源(连接对象资源,流资源,执行(jdbc)资源)
finally中的代码一定会执行,在执行finally之前,如果jvm退出了,finally语句是不会执行的!
fiinal,状态修饰符
修饰类 :该类不能继承
修饰成员变量 :此时是一个常量
修饰成员方法 :该方法不能重写
finalize:Object类中的方法,开启垃圾回收器:System.gc()---->实际调用的finalize()
回收没有更多引用的对象!
4.处理异常的方式有几种
捕获异常
try...catch...finally
try...catch...catch...
抛出异常
throws:抛出在方法上
throw:在方法体中抛出
5.throw和throws的区别
throws:
1)在方法声明上使用
2)throws后面跟的异常类名,中间使用逗号隔开可以跟多个异常类名 (为了使用方便)
3)在当前方法上如果使用throws,调用者必须对这个方法处理!
4)throws表示出现异常的可能性!
throw:
1)方法语句体中使用
2)后面跟的异常对象,一般匿名对象(具体异常具体抛出)
throw new xxxException() ;
3)它对异常的处理,是通过逻辑代码进行判断
4)throw表示出现异常的肯定性!
多线程
什么是线程
进程
进程:正在运行的应用程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间 和系统资源。
程序是静止的,只有真正运行时的程序才能被称为进程
单核CPU在任何时间点上,只能运行一个进程,宏观并行,微观串行
多进程---为了提高CPU的使用率!同时玩游戏,听音乐,并不是同时进行,而是CPU的一点点时间片在
多个进程之前进行高效的切换!(和CPU有关系)
线程
线程(Thread) 一个轻量级的进程(是最小的基本单元) -----进程的某个任务
程序的一个顺序控制流程,是CPU的基本调度单位。
多线程
多个线程组成,彼此间完后不同的工作,交替执行,成为多线程
多个线程在互相抢占CPU的执行权,执行具有随机性
Java程序运行原理
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主 线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
JVM是多线程吗
Jvm,是一个假想计算机,是一个进程,是一个多线程环境,默认包含主线程(main)
通过代码创建多个独立的线程,与main并发执行
不断的创建对象------ 需要使用垃圾回收器回收,------垃圾回收线程
至少存在两条线程: main和垃圾回收线程(开启垃圾回收器)
进程与线程之间的区别
进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位
一个程序运行后至少有一个进程
一个进程可以包含多个线程,但是至少需要一个线程
进程之间不能共享数据段地址,但同进程的线程之间可以
线程的组成
任何一个线程都具有基本的组成部分:
CPU时间片:操作系统(os)会为每个线程分配执行时间
运行数据:
堆空间:存储线程需使用的对象,多个线程可以共享堆中的空间
栈空间;存储线程需使用的局部变量,每个线程都拥有独立的栈
线程的逻辑代码
多线程的实现
使用Java程序如何实现多线程?
线程----->依赖于进程---->需要有一个进程----->创建进程------需要创建系统资源
Java语言不能创建系统资源----创建系统资源---->使用C/C++
提供了一个类-----> java.lang.Thread:线程是程序中的执行线程!
jvm运行多个线程并发的执行!
多线程环境的实现方式
继承Thread类
继承关系
(1)自定义一个类,继承自Thread类
(2)重写Thread类中的run方法
(3)在main线程中,创建该类对象
(4)启动线程(start())
实现Runnable接口
实现Runnable接口的方式
(1)自定义类实现Runnable接口
(2)重写Runnable接口中的run 方法
(3)创建该类对象-----资源类(可以被多个线程共用)
创建Thread类对象将资源类作为参数进行传递
Thread(Runnable target,String name)
(4)启动线程
接口实现关系----(面向接口编程---- 多个线程对象在操作同一个资源:共享的概念) 里面用到代理模式之静态代理
代理模式
静态代理
动态代理
1)jdk动态代理---基于接口实现(InvocationHandler)
2)cglib动态代理----基于子类实现
线程池
线程池
1)实现Runnable接口
自定义类实现Runnable接口
重写Runnable接口中的run方法
使用工厂类(Executors)创建线程池对象(newFixedThreadPool)
提交异步任务(Future<?> submit(Runnable task))
关闭线程池(shutdown())
2)实现Callable接口
自定义类实现Callable接口
重写Calllable接口中的call方法
使用工厂类(Executors)创建线程池对象(newFixedThreadPool)
提交异步任务(<T> Future<T> submit(Callable<T> task))
获取结果(V get())
关闭线程池(shutdown())
Java能够开启多线程吗?
Java是不能够开启的,借助于底层语言开启多线程环境(根据系统资源)
start()
private native void start0(); 非Java语言实现(native:本地方法)
使用匿名内部类的方式实现
public class ThreadDemo {
public static void main(String[] args) {
//方式1
new Thread(){
@Override
public void run() {
for(int x = 0 ; x < 100 ; x ++){
System.out.println(getName()+":"+x);
}
}
}.start();
//方式2:
//自定义类一个---实现Runnable接口----> 资源类
new Thread(new Runnable(){
@Override
public void run() {
for(int x = 0 ; x < 100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}).start();
System.out.println("-----------------------------");
//内部类(有名字类)----->匿名内部类----> 前提:如果一个接口中只有一个抽象方法,这个接口@FunctionalInterface:函数式接口
//jdk8以后---拉姆达表达式
//企业中:jdk8新特性使用到./jdk7/jdk5
//要求:在开发中使用枚举类/拉姆达
}
}
Thread类(java.lang.Thread)
构造方法
public Thread():无参构造
public Thread(Runnable target):有参构造:里面传递Runnable接口 (间接使用就 静态代理)
成员方法
线程名称
public final void setName(String name):设置线程名称
public final String getName():获取线程名称
线程调度
线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级 高的线程获 取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
线程优先级
public final int getPriority():获取线程的优先级
默认的优先级:5
public final void setPriority(int newPriority):更改优先级
优先级的常量表
public static final int MAX_PRIORITY 10 最高优先级
public static final int MIN_PRIORITY 1 最低优先级
public static final int NORM_PRIORITY 5 默认优先级
优先级越大的----当前这个线程抢占到的CPU执行权越大
守护线程
public final void setDaemon(boolean on)
该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
这个方法调用必须在启动之前调用! (注意事项)
public static Thread currentThread()返回正在执行的线程对象的引用
public class ThreadDaemonDemo {
public static void main(String[] args) {
//创建线程类对象
ThreadDeamon td1 = new ThreadDeamon() ;
ThreadDeamon td2 = new ThreadDeamon() ;
//设置线程名称
td1.setName("关羽");
td2.setName("张飞");
//启动线程之前,将td1,td2标记为守护线程 ,jvm会自动退出
td1.setDaemon(true);
td2.setDaemon(true);
//执行两个子线程
td1.start();
td2.start();
Thread.currentThread().setName("刘备");
for(int x = 0 ; x <10 ; x ++){
//public static Thread currentThread()
//获取当前正在执行的线程main
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
public class ThreadDeamon extends Thread {
//td1,td2子线程
@Override
public void run() {
for(int x = 0 ; x < 100; x ++){
System.out.println(getName()+":"+x);
}
}
}
线程休眠
public static void sleep(long millis) throws InterruptedException:
线程休眠 ,参数为毫秒值
线程休眠----时间休眠期到了,又继续去执行线程! (属于阻塞式方法)
线程放弃
public static void yield() :暂停当前正在执行的线程对象,并执行其他线程
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
线程结合
public final void join()
throws InterruptedException等待该线程终止。
允许其他线程加入到当前线程中,并优先执行
底层依赖于 wait(long mills)非Java语言 (线程阻塞)
线程中断
public final void stop()让线程停止,过时了,但是还可以使用。
public void interrupt()中断线程。 把线程的状态终止,并抛出一个InterruptedException。
线程的状态
线程的状态有六种:
NEW---初始状态
RUNNABLE---运行状态
BLOCKED---阻塞状态
WAITING---无限期等待
TIMED_WAITING---限期等待
TERMINATED---终止状态
注:JDK5之前为7种状态,JDK5之后就绪(Ready)、运行(Running)统称为Runnable
线程安全
线程不安全
线程不安全:
当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省
校验多线程安全问题的标准
校验多线程安全问题的标准
1)当前是否是一个多线程环境
2)是否存在共享数据
3)是否有多条语句对共享数据操作
解决多线程安全问题
基本思想:让程序没有安全问题的环境。
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
使用synchronized(锁对象){ //多个线程必须使用的是同一把锁(钥匙)
多条语句对共享数据操作
}
线程同步
同步机制
synchronized基于jvm,------->多个线程持有"锁标志",通过同步代码块控制访问的字段,每一个持有的锁必须是同一个,
当某个线程如果执行了并且进入到同步代码块中,其他线程在当前线程执行期间,不能够持有该锁,
当前这个线程执行完毕,会释放"锁标志";其他线程如果进入到同步代码块中,持有该同一个"锁"
synchronized和Lock都属于可重入锁,Lock锁更灵活: lock()/unlock(),通用方式:synchronized
synchronized(锁对象){
语句体(多条语句多共享数据的操作)
}
同步方式1
同步方式:
同步代码块:
synchronized(临界资源对象){//对临界资源对象加锁
//代码(原子操作)
}
注:
每一个对象都有一个互斥锁标记,用来分配给线程的
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的的同步代码块
线程退出同步代码块时,会释放相应的互斥锁标记
同步方式2
同步方法:
synchronized 返回值类型 方法名称 (形参列表){ // 对当前对象(this)加锁
//代码(原子操作)
}
注:
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中
线程退出同步方法时,会释放相应的互斥锁标记
同步的规则
同步规则:
注意:
只有在调用包含同步代码块的方法或同步方法时,才需要对象的锁标记
如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用
同步方法的锁对象
同步方法 ---->这里面的锁对象是谁?
this:代表当前类对象的地址值引用 //锁对象可以是任意的java类对象
静态的同步方法?它的锁对象是谁?
锁对象:当前类的字节码文件对象---"反射思想"
类名.class
已知线程安全的类
已知JDK中线程安全的类
StringBuffer
Vector
Hashtable
以上类中的公开方法,均为synchronized修饰的同步方法
线程的同步和异步
线程的同步:
形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续
线程的异步:
形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回,二者竞争时间片,并发执行
LOCK接口
JDK5以后提供了一比Synchronized更广泛的锁定操作.也可以实现锁对象控制多个线程对共享资源的访问!
java.util.concurrent.locks.Lock:接口
与synchronized比较
lock与synchronized比较,显示定义,结构更灵活
提供更多实用性方法,功能更强大,性能更优越
常用方法
void lock() //获取锁,如锁被占用,则等待
boolean tryLock() //尝试获取锁(成功返回true,失败返回false,不阻塞)
void unlock() //释放锁
子实现类
ReentrantLock
重入锁:
ReentrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能
//银行取款案例
public class TestReentrantLock {
public static void main(String[] args) throws InterruptedException {
Account acc = new Account("1001","123456",2000D);
Husband h = new Husband(acc);
Wife w = new Wife(acc);
Thread t1 = new Thread(h);
Thread t2 = new Thread(w);
t1.start();
t2.start();
}
}
class Husband implements Runnable{
Account acc;
public Husband(Account acc) {
this.acc = acc;
}
@Override
public void run() {
this.acc.withdrawal("1001", "123456", 1200D);
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc = acc;
}
@Override
public void run() {
this.acc.withdrawal("1001", "123456", 1200D);
}
}
class Account{
Lock locker = new ReentrantLock();
String cardNo;
String password;
double balance;
public Account(String cardNo, String password, double balance) {
super();
this.cardNo = cardNo;
this.password = password;
this.balance = balance;
}
public void withdrawal(String no,String pwd,double money) {
//开启锁 synchronized(this){
locker.lock();
try {
System.out.println("请稍后。。。");
if(this.cardNo.equals(no) && this.password.equals(pwd)) {
System.out.println("验证成功,请稍后。。。");
if(money < balance) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= money;
System.out.println("取款成功,当前余额为:" + balance);
}else {
System.out.println("卡内余额不足!");
}
}else {
System.out.println("卡号或密码错误!");
}
}finally {
//释放锁 }
locker.unlock();
}
}
}
ReentrantReadWriteLock
读写锁:
ReentrantReadWriteLock:
一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁
支持多次分配读锁,使多个读操作可以并发执行
互斥规则:
写——写:互斥,阻塞
读——写:互斥,读阻写塞,写阻读塞
读——读:不互斥,不阻塞
在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率
public class TestReentrantReadWriteLock {
public static void main(String[] args) {
final Student s = new Student();
Callable<Object> writeTask = new Callable<Object>() {
@Override
public Object call() throws Exception {
s.setValue(111);
return null;
}
};
Callable<Object> readTask = new Callable<Object>() {
@Override
public Object call() throws Exception {
s.getValue();
return null;
}
};
ExecutorService es = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
for(int i = 0; i < 2; i++) {
es.submit(writeTask);
}
for(int i = 0; i < 18; i++) {
es.submit(readTask);
}
//停止线程池(不再接受新任务,将现有任务全部执行完完毕)
es.shutdown();
while(true) {
System.out.println("结束了吗?");
if(es.isTerminated()) {
System.out.println("终于结束了");
break;
}
}
//....
System.out.println(System.currentTimeMillis() - start);
}
}
class Student{
// ReentrantLock rLock = new ReentrantLock();
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
ReadLock rl = rwl.readLock();
WriteLock wl = rwl.writeLock();
int value;
//读
public int getValue() {
rl.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.value;
}finally {
rl.unlock();
}
}
//写
public void setValue(int value) {
wl.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
}finally {
wl.unlock();
}
}
}
线程间的通信
线程的通信:
等待:
public final void wait()
public final void wait(long timeout)
必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有的锁标记,
同时此线程因obj处在无限期等待的状态中,释放锁,进入等待队列。
通知:
public final void notify()
public final void notifyAll()
必须在对obj加锁的同步代码块中,从obj的waiting中释放一个或全部线程,对自身没有任何影响
等待唤醒机制
多个线程在使用资源的时候,如果当前资源并非同一个资源,可能会出现锁;
引入生产者消费者模式思想: (操作必须同一个资源对象!)
生产者线程----不断的产生数据----如果当前没有数据了,等待生产者线程产生数据
当前这个数据已经有了,需要通知(notify())消费者线程,使用这个数据
消费者线程----不断的使用数据----如果当前有数据了,等待使用完毕
如果没有数据了,通知(notify())生产者线程---产生数据....
sleep()和wait()的区别
1)是否会释放锁
sleep()的调用,不会释放锁---->通过Thread类的访问的
wait()的调用,会立即释放持有的"锁标志"---->通过锁对象访问的
2)来源不同
sleep()来源于Thread类
wait()----Object
3)都可能会有异常中断异常
sleep() throws InterruptedException{}
wait() throws InterruptedException{}
为什么wait(),notify(),notifyAll()等方法都定义在Object类中
1,这些方法存在与同步中。
2,使用这些方法时必须要标识所属的同步的锁。
3,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
静态代理
Thread类本身底层使用的就是静态代理
什么是静态代理
通过代理角色帮助真实角色完成业务,对真实角色业务进行增强!
开发中,打印日志/事务管理/权限校验....
真实角色(目标角色):专注于自己的事情
代理角色:帮助真实角色进行方法增强!
特点:
真实角色和代理角色都需要实现同一个接口!
动态代理
jdk动态代理:通过反射方式---基于接口
第三方jar包:cglib动态代理----基于子类实现
/**
* Thread类本身底层使用的就是静态代理
*
* 什么是静态代理
* 通过代理角色帮助真实角色完成业务,对真实角色业务进行增强!
* 开发中,打印日志/事务管理/权限校验....
*
* 真实角色(目标角色):专注于自己的事情
* 代理角色:帮助真实角色进行方法增强!
* 特点:
* 真实角色和代理角色都需要实现同一个接口!
*
*
* 举例:
* 结婚这件事情
*
* 1)自己完成结婚这件事情(真实角色)
* 2)加入一个代理角色:婚庆公司,帮助我们完成结婚这件事情! WeddingCompany
*
*
*
* //Thread:就是代理角色
* //MyRunnable:真实角色---->run()---> 业务操作....
*
*
*
*
* 反射---------->动态代理
* jdk动态代理:通过反射方式---基于接口
* 第三方jar包:cglib动态代理----基于子类实现
*
*/
//测试类
public class ThreadDemo {
public static void main(String[] args) {
//测试
//接口多态的方式
// Marry marry = new You() ;
//marray.happyMarry();
//具体类
You you = new You() ;
you.happyMarry();
System.out.println("--------------------------------------------------");
//创建婚庆公司类对象:代理角色
//创建真实角色对象
You you2 = new You() ;
WeddingCompany wc = new WeddingCompany(you2) ;
wc.happyMarry(); //对真实角色You类型中happyMarry()进行了增强
//对比Thread类
//创建线程的实现方式2: 自定义类
//class MyRunnable implement Runnable{
// public void run(){
//
// ...
// }
// }
/**
*
* 源码:
* public class Thread implements Runnable{
*
* private Runnable target; //Runnable接口类型
*
*
* public Thread(Runnable target,String name) {
*
* ....
* }
* @Override
* public void run() {
* if (target != null) { //如果当前Runnable接口对象(通过子实现类实例化)
* target.run();
* }
* }
* }
*/
}
}
//定义接口:Marry:结婚
interface Marry{
//结婚
void happyMarry() ;
}
//定义类:You----- 真实角色
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("很开心,要结婚了...");
}
}
//定义一个代理角色:目的就是happyMarry(),进行增强
class WeddingCompany implements Marry{//代理角色也实现该接口
//声明一个真实角色类型:You
private You you ;
//构造方法 // Thread t1 = new Thread(自定义的类实现Runnbale接口的对象) ;//代理
public WeddingCompany(You you){
this.you = you ;
}
@Override
public void happyMarry() {
//结婚之前
before() ;
//真实角色:专注于自己的事情:结婚
you.happyMarry(); //结婚
//结婚之后
after() ;
}
public void before() {
System.out.println("结婚之前,布置婚礼现场...");
}
public void after() {
System.out.println("结婚之后,男方付尾款...");
}
}
经典问题
死锁
当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁
死锁现象:当前多个线程操作的资源对象,不是同一个对象,而且加入同步代码块之后,出现了一种互相等待的情况
public class ThreadDemo {
public static void main(String[] args) {
//创建资源类对象DieLock
DieLock d1 = new DieLock(true) ;
DieLock d2 = new DieLock(false) ;
//创建线程类对象
Thread t1 = new Thread(d1) ;
Thread t2 = new Thread(d2) ;
//分别启动线程
t1.start();
t2.start();
/**
* 情况1:
* if objA t1
* else objB t2
*
* 情况2:
* else objB t2先抢占到
* if objA
*
*
* 理想状态:
* else objB t2抢占到
* else objA
* if objA t1执行
* if objB
*/
}
}
//自定义类中,创建两把锁对象
public class MyDieLock {
public static final Object objA = new Object() ;
public static final Object objB = new Object() ;
}
//资源类
public class DieLock implements Runnable{
//声明一个boolean类型的变量
private boolean flag ;
public DieLock(boolean flag){
this.flag = flag ;
}
@Override
public void run() {
//加入判断
if(flag){
//true
synchronized (MyDieLock.objA){ //objA锁
System.out.println("if objA"); //if ObjA
synchronized (MyDieLock.objB){
System.out.println("if objB");
}
}
}else{
//false
synchronized (MyDieLock.objB){ //t2
System.out.println("else objB"); //else objB
synchronized(MyDieLock.objA){
System.out.println("else objA");
}
}
}
}
}
生产者与消费者
生产者与消费者问题是多线程同步的一个经典问题。生产者和消费者同时使用一块缓冲区,生产者生产商品放入缓冲区,消费者从缓冲区中取出商品。我们需要保证的是,当缓冲区满时,生产者不可生产商品;当缓冲区为空时,消费者不可取出商品。
public class TestPAC {
public static void main(String[] args) {
final MyStack ms = new MyStack();
// ms.push("A");
// ms.push("B");
// ms.push("C");
// ms.push("D");
// ms.push("E");
// ms.push("E");
//
// ms.pop();
// ms.pop();
// ms.pop();
// ms.pop();
// ms.pop();
// ms.pop();
Thread t1 = new Thread() {
public void run() {
for(char ch = 'A'; ch <= 'Z'; ch++) {
ms.push(ch + "");
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 26; i++) {
ms.pop();
}
}
};
t1.start();
t2.start();
}
}
class MyStack{
private String[] values = new String[] {"","","","",""};
int size = 0;
public synchronized void push(String str) {
this.notify();//唤醒对方
while (values.length == size) {
System.out.println("满了");
try { this.wait();} catch (Exception e) {}//结束 ---> 暂停(等待)
}
System.out.println(str + "入栈");
values[size] = str;
size++;
}
public synchronized void pop() {
this.notify();//唤醒对方
while (size == 0) {
System.out.println("空了");
try {this.wait();} catch (Exception e) {}//结束 ---> 暂停(等待)
}
System.out.println(values[size-1] + "出栈");
values[size-1] = "";
size--;
}
}
import java.util.ArrayList;
import java.util.List;
public class TestProducerAndConsumer {
public static void main(String[] args) {
MyQueue mq = new MyQueue();
// mq.offer("A");
// mq.offer("B");
// mq.offer("C");
// mq.offer("D");
// mq.offer("E");
//
// System.out.println(mq.poll());
// System.out.println("以下是遍历内容:");
//
// mq.show();
Produce1 p1 = new Produce1(mq);
Produce2 p2 = new Produce2(mq);
Consumer c1 = new Consumer(mq);
p1.start();
p2.start();
c1.start();
System.out.println("main end");
}
}
class Consumer extends Thread{
MyQueue mq;
public Consumer(MyQueue mq) {
this.mq = mq;
}
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(mq.poll() + "被移除");
}
}
}
class Produce1 extends Thread{
MyQueue mq;
public Produce1(MyQueue mq) {
this.mq = mq;
}
public void run() {
System.out.println("Produce1启动");
for (char ch = 'A'; ch <= 'E'; ch++) {
mq.offer(ch);
}
System.out.println("Produce1结束");
}
}
class Produce2 extends Thread{
MyQueue mq;
public Produce2(MyQueue mq) {
this.mq = mq;
}
public void run() {
System.out.println("Produce2启动");
for (char ch = 'F'; ch <= 'J'; ch++) {
mq.offer(ch);
}
System.out.println("Produce2结束");
}
}
//我的队列
class MyQueue{
private List values = new ArrayList();
private int max = 4;
//存入队列
public synchronized void offer(Object o) {
if(values.size() == max) {
//进来线程,停下
try {
this.wait();
//唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "存入第" + (values.size() + 1) + "个元素");
values.add(o);
}
//从队列取出
public synchronized Object poll() {
if(values.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();//唤醒因mq对象而进入无限期等待的线程对象(一个)
return values.remove(0);
}
public void show() {
for (Object obj : values) {
System.out.println(obj);
}
}
}
龟兔赛跑问题
/**
* //需要又有个赛道:Race-----乌龟/兔子---共用赛道
*/
public class Race implements Runnable {
//定义变量 :胜利者
public static String winner ; //默认值:null
@Override
public void run() {
//定义一个for循环;模拟 1-100之间:步数
for(int x = 1 ; x<=100; x ++){
//需要让兔子模拟 它睡觉
//如果线程对象所在的名称是兔子 并且 是 每10步
if(Thread.currentThread().getName().equals("兔子")&& (x % 10==0)){
//睡眠50毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(x) ;//参数步数
if(flag){//true
break ;
}
System.out.println(Thread.currentThread().getName()+"跑了---->"+x+"步"); //比赛结束之后打印出来每一个线程跑了多少不
}
}
//定义一个比赛是否结束的方法
private boolean gameOver(int steps) {//步数
//情况1:如果当前已经存在胜利者---比赛结束
if(winner!=null){
return true ;
}{
//情况2
//如果steps>=100 :说明已经跑完了
if(steps>=100){
// //记录下一胜利者 :
winner = Thread.currentThread().getName() ;//胜利者
System.out.println("winner is--->"+winner);
return true ;
}
}
return false ;
}
//用户线程
public static void main(String[] args) {
//将赛道被共用
Race race = new Race() ;
//创建两条线程
Thread t1 = new Thread(race,"兔子") ;
Thread t2 = new Thread(race,"乌龟") ;
//启动线程
t1.start();
t2.start();
}
}
线程池
线程池概念
现有问题:
线程是宝贵的资源,单个线程约占1MB空间,过多分配易造成内存溢出。
频繁的创建及销毁线程会增加虚拟机的回收频率,资源开销,造成程序性能下降
线程池:
线程容器,可设定线程分配的数量上限
将预先创建的线程对象存入池中,并重用线程池中的线程对象
避免频繁的创建和销毁
线程池原理
将任务提交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程
获取线程池
常用的线程池接口和类(所在包java.util.concurrent);
Executor: 线程池顶级接口
ExecutorService: 线程池接口,可通过submit(Runnable task) 提交任务代码
Executors工厂类: 通过此类可以获得一个线程池
通过newFixedThreadPool(int nThreads) 获取固定数量的线程池,
参数: 指定线程池中线程的数量
通过newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,没有上限
void shutdown():关闭线程池中以前提交的异步任务!
public class TestThreadPool {
public static void main(String[] args) {
//线程池(引用) ---》Executors工具类(工厂类)
ExecutorService es = Executors.newFixedThreadPool(3);
Runnable task = new MyTask();
es.submit(task);
es.submit(task);
es.submit(task);
es.submit(task);
}
}
class MyTask implements Runnable{
@Override
public void run() {
for(int i = 1; i <= 1000; i++) {
System.out.println(Thread.currentThread().getName() + " MyTask:" + i);
}
}
}
Callable接口
Callable接口:
public interface Callable<V>{
public V call() throws Exception;
}
JDK5.0加入,与Runnable接口类似,实现之后代表一个线程任务
Callable具有泛型返回值,可以声明异常
Future接口
Future接口:
概念:异步接受ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值
方法:V get() 以阻塞形式等待Future中的异步处理结果(call()的返回值)
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//使用工厂类创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//提交异步任务
/* pool.submit(new MyRunnable()) ;
pool.submit(new MyRunnable()) ;*/
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
//获取结果
Integer result1 = f1.get();
Integer result2 = f2.get();
System.out.println(result1);
System.out.println(result2);
//关闭线程池
pool.shutdown();
}
}
import java.util.concurrent.Callable;
//两个线程分别进行计算结果:求和
public class MyCallable implements Callable<Integer> {
//声明变量
private int number ;
public MyCallable(int number){
this.number = number ;
}
@Override
public Integer call() throws Exception { //call方法的返回值和Callbe<类型>保持一致
int sum = 0 ; //结果变量
for(int x = 1 ; x<= number ; x ++){
sum += x ;
}
return sum;
}
}
线程安全的集合
CopyOnWriteArrayList:
线程安全的ArrayList,加强版读写分离
写有锁,读无锁,读写之间不阻塞,优于读写锁
写入时,先copy一个容器副本,再添加新元素,最后替换引用
使用方式与ArrayList无异
CopyOnWriteArraySet:
线程安全的Set,底层使用CopyOnWriteArrayList实现
唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
如存在元素,则不添加(扔掉副本)
ConcurrentHashMap:
初始容量默认为16段(segment) , 使用分段锁设计
不对整个Map加锁,而是为每个segment加锁
当多个对象存入同一个segment时,才需要互斥
最理想的状态为16个对象分别存入16个segment,并行数量为16
使用方式与HashMap无异
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<String>();//接口引用指向实现类对象,更容易更换实现
list.add("A");
Set<String> set = new CopyOnWriteArraySet<String>();
set.add("B");
Map<String,String> map = new ConcurrentHashMap<String,String>();
map.put("","");
}
}
Collections中的工具方法
Collections中的工具方法:
Collections工具类中提供了多个可以获得线程安全的方法
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
public static <T> List<T> synchronizedList(List<T> list)
public static <T> Set<T> synchronizedSet(Set<T> s)
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
public static <K,V> SortedMap(K,V) synchronizedSortedMap(SortedMap<K,V> m)
JDK1.2提供,接口统一,维护性高,但性能没有提升,均以synchronized实现
public class TestCollectionFonSyn {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
List<String> safeList = Collections.synchronizedList(list);
safeList.add("A");
safeList.add("A");
safeList.add("A");
safeList.add("A");
}
}
class SafeCollection<E>{
private Collection c = null;
final Object o = new Object();
public SafeCollection(Collection c) {
this.c = c;
}
public void add(E e) {
synchronized(o) {
c.add(e);
}
}
}
Queue接口(队列)
Queue接口(队列): Collection的子接口,表示队列FIFO(First In First Out)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jBY3x7Xk-1615108342170)(Collection体系集合.png)]
常用方法
抛出异常:
boolean add(E e) //顺序添加一个元素(到达上限后再添加,则会抛出异常)
E remove() // 获得第一个元素并移除(如果队列没有元素时,则会抛出异常)
E element() // 获得第一个元素但不移除(如果队列没有元素,则会抛出异常)
返回特殊值:推荐
boolean offer(E e) // 顺序添加一个元素(到达上限后再添加,则会返回false)
E poll() // 获得第一个元素并移除(如果队列没有元素,则会返回null)
E peek() // 获得第一个元素但不移除(如果队列没有元素时,则会返回null)
ConcurrentLinkedQueue
ConcurrentLinkedQueue:
线程安全,可高效读写的队列,高并发下性能最好的队列
无锁,CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
V:要更新的变量;E: 预期值;N:新值
只有当V = E时,V = N; 否则表示已被更新过,则取消当前操作
public class TestQueue {
public static void main(String[] args) {
// Queue q = new LinkedList();
Queue q = new ConcurrentLinkedQueue();
q.offer("A");
q.offer("B");
q.offer("C");
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());
}
}
BlockingQueue(阻塞队列)
BlockingQueue接口(阻塞队列)
Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法
方法:
void put(E e) //将指定元素插入此队列中,如果没有可用空间,则等待
E take() // 获取并移除此队列头部元素,如果没有可用元素,则等待
可用于解决生产者和消费者的问题
ArrayBlockingQueue
ArrayBlockingQueue:
数组结构实现,有界队列。(手工固定上限)
BlockingQueue<String> abq = new ArrayBlockingQueue<String>(10)
LinkedBlockingQueue
LinkedBlockingQueue:
链表结构实现,无界队列,(默认上限:Integer.MAX_VALUE)
BlockingQueue<String> lbq = new LinkedBlockingQueue<String>()
Timer定时器
java.util.Timer:定时器 (定时工具)
可安排任务执行一次,或者定期重复执行
构造方法
public Timer():创建一个定时器
成员方法
public void cancel()终止此计时器,丢弃所有当前已安排的任务
public void schedule(TimerTask task,Date time):在指定的日期时间时完成定时任务
参数1:为定时任务(定时业务...)
参数2:规定的日期时间(String:"2021-2-25 18:00:00")
public void schedule(TimerTask task,long delay,long period):
固定延迟的时间间隔来完成这个任务(重复执行的)
public void schedule(TimerTask task,long delay):在指定延迟时间后执行这个任务
TimerTask定时任务
定时任务:TimerTask :抽象类 ------> implements Runnable
public abstract void run() :指定计时器任务操作
public class ThreadDemo2 {
public static void main(String[] args) {
//创建一个定时器
Timer timer = new Timer() ;
//启用定时任务
//public void schedule(TimerTask task,long delay):在指定延迟时间后执行这个任务
// timer.schedule(new MyTask(timer),3000); //3秒后这个任务
//public void schedule(TimerTask task,long delay,long period):固定延迟的时间间隔来完成这个任务(重复执行的)
timer.schedule(new MyTask(),2000,3000); //2秒后执行这个定时任务,然后每经过3秒重复执行定时任务
}
}
//自定一个类 继承TimerTask
class MyTask extends TimerTask{
private Timer t ;
public MyTask(){
}
public MyTask(Timer t){
this.t = t ;
}
@Override
public void run() {
//for(int x = 0 ; x< 5 ;x ++){
// System.out.println("bom...");
// t.cancel(); //终止了
//}
System.out.println("bom");
}
}
题目
需求:在 "2021-2-25 18:00:00"---->删除 D盘下:
JavaEE_2011
day_2_25 中所有后缀为.java文件
描述这个文件----->java.io.File类
public void schedule(TimerTask task,Date time)
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Test05 {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
String str = "2021-2-26 08:36:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(str);
timer.schedule(new MyTask(),date);
}
}
class MyTask extends TimerTask {
private Timer t;
public MyTask(){}
public MyTask(Timer t){
this.t = t;
}
@Override
public void run() {
File file = new File("K://source//");
File[] files = file.listFiles();
if(files != null){
for(File f : files){
if(f.isFile()){
if(f.getName().endsWith(".java")){
System.out.println(f.delete());
}
}
}
}
}
}
设计模式
面向对象设计原则
面向对象思想设计原则
在实际的开发中,我们要想更深入的了解面向对象思想,就必须熟悉前人总结过的面向对象的思想的设计原则
单一职责原则
其实就是开发人员经常说的”高内聚,低耦合”
也就是说,每个类应该只有一个职责,对外只能提供一种功能,而引起类变化的原因应该只有一个。在设计模式中,所有的设计模式都遵循这一原则
开闭原则
核心思想是:一个对象对扩展开放,对修改关闭。
其实开闭原则的意思就是:对类的改动是通过增加代码进行的,而不是修改现有代码。 也就是说软件开发人员一旦写出了可以运行的代码,就不应该去改动它,而是要保证它能一直运行下去,如何能够做到 这一点呢?这就需要借助于抽象和多态,即把可能变化的内容抽象出来,从而使抽象的部分是相对稳定的,而具体的实现则 是可以改变和扩展的。
里氏替换原则
核心思想:在任何父类出现的地方都可以用它的子类来替代。
其实就是说:同一个继承体系中的对象应该有共同的行为特征。
依赖注入原则
核心思想:要依赖于抽象,不要依赖于具体实现。
其实就是说:在应用程序中,所有的类如果使用或依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他 类的具体类。为了实现这一原则,就要求我们在编程的时候针对抽象类或者接口编程,而不是针对具体实现编程。
接口分离原则
核心思想:不应该强迫程序依赖它们不需要使用的方法。
其实就是说:一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一 个接口中。
迪米特原则
核心思想:一个对象应当对其他对象尽可能少的了解
其实就是说:降低各个对象之间的耦合,提高系统的可维护性。在模块之间应该只通过接口编程,而不理会模块的内部 工作原理,它可以使各个模块耦合度降到最低,促进软件的复用
常见见设计模式
设计模式概述
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
好处:使用设计模式 是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
设计模式不是一种方法和技术,而是一种思想 设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设 计的程序可复用 学习设计模式能够促进对面向对象思想的理解,反之亦然。它们相辅相成
设计模式的分类
创建型模式:简单工厂模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式,单例模式。(6个)
结构型模式:外观模式、适配器模式、代理模式、装饰模式、桥接模式、组合模式、享元模式。(7个)\
行为型模式:模版方法模式、观察者模式、状态模式、职责链模式、命令模式、访问者模式、策略模式、备
忘录模式、迭代器模式、解释器模式。(10个)
简单工厂模式
简单工厂----(静态工厂方法模式)
提供一个工厂类:
提供静态的功能 (使用多态:提高扩展性):负责创建一些类的实例!
优点:不需要让具体类创建对象了
弊端:一旦有新的类型增加,需要修改工厂类代码,代码量增加了...
public class PatternDemo {
public static void main(String[] args) {
//没有使用设计模式之前:
//对象的创建
Dog d = new Dog() ;
d.eat();
d.sleep();
Cat c = new Cat() ;
c.eat();
c.sleep();
System.out.println("--------------------------");
//简单工厂
//需要通过一个工厂类:创建这些具体动物实例!
/*Cat cat = AnimalFactory.createCat();
cat.eat();
cat.sleep();
Dog dog = AnimalFactory.createDog();
dog.eat();
dog.sleep();
Pig pig = AnimalFactory.createPig();
pig.eat();
pig.sleep();*/
Animal animal = AnimalFactory.createAnimal("dog"); //new Dog() ;
animal.eat();
animal.sleep();
animal = AnimalFactory.createAnimal("cat"); //new Cat() ;
animal.eat();
animal.sleep();
animal = AnimalFactory.createAnimal("pig"); //new Pig() ;
animal.eat();
animal.sleep();
animal = AnimalFactory.createAnimal("monkey") ;
if(animal!=null){
animal.sleep();
animal.eat();
}else{
System.out.println("工厂类中没有提供该动物实例的创建...");
}
}
}
/**
* 工程类---创建具体动物实例
*/
public class AnimalFactory {
//里面一个功能静态的功能
//创建猫类
/* public static Cat createCat(){
return new Cat() ;
}
//创建狗类
public static Dog createDog(){
return new Dog() ;
}
//创建猪类对象
public static Pig createPig(){
return new Pig() ;
}*/
//返回值使用父类型
public static Animal createAnimal(String type){//参数:为当前类型
if("dog".equals(type)){
return new Dog() ; //Animal 对象名 = new Dog() ;//多态
}else if("cat".equals(type)){
return new Cat() ;
}else if("pig".equals(type)){
return new Pig() ;
}else{
return null ;
}
}
}
public class Animal {
public void eat(){
System.out.println("动物需要吃饭...");
}
public void sleep(){
System.out.println("动物都需要休息...");
}
}
//具体的狗类
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
@Override
public void sleep() {
System.out.println("狗躺着睡觉...");
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
@Override
public void sleep() {
System.out.println("猫趴着睡觉...");
}
}
public class Pig extends Animal {
@Override
public void eat() {
System.out.println("猪吃白菜...");
}
@Override
public void sleep() {
System.out.println("猪卧着睡觉...");
}
}
工厂方法模式
工厂方法模式
1)有一个抽象类:Animal----具体的子类:(Dog/Cat)
2)工厂接口----->创建具体类的实例 (抽象功能)
3)每一个具体的类----都对应一个该类的工厂(DogFactory,CatFactory)
优点:每一个具体的类都使用具体的工厂类创建该类实例
弊端: 代码量增加!!
public class PatternDemo {
public static void main(String[] args) {
Factory factory = new CatFactory() ; //接口多态
Animal animal = factory.createAnimal(); //new Cat() ;
animal.eat();
animal.sleep();
System.out.println("-----------------------");
factory = new DogFactory() ;
animal = factory.createAnimal() ;
animal.eat();
animal.sleep();
}
}
public abstract class Animal { //动物类
public abstract void eat() ;
public abstract void sleep() ;
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
@Override
public void sleep() {
System.out.println("狗趴着睡觉...");
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void sleep() {
System.out.println("猫侧着睡觉");
}
}
//工厂接口:创建具体的动物实例
public interface Factory {
//提供扩展性---使用多态
public Animal createAnimal() ;
}
//狗的工厂类
public class DogFactory implements Factory {
@Override
public Animal createAnimal() {
return new Dog() ;
}
}
//猫的工厂类
public class CatFactory implements Factory {
@Override
public Animal createAnimal() { //抽象类多态
return new Cat();
}
}
单例设计模式
概述
单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。
优点:在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系 统的性能。
缺点:没有抽象层,因此扩展很难。 职责过重,在一定程序上违背了单一职责
分类
饿汉式
饿汉式:(不会出现问题的单例!)
特点:在类加载的时候就创建该类实例!
1)在某个类中,成员变量位置:创建该类的静态实例
2)构造方法私有化
3)需要提供静态的功能,返回值是它本身 ---创建静态实例
例子:Runntime类就是单例,是饿汉式
public class Student {
//为了保证数据的安全性:加入私有修饰
private static Student s = new Student() ;
private Student(){}//构造方法私有化
//提供对外的公共访问---静态的
public static Student getStudent(){
return s ;
}
}
public class SinglePattern {
public static void main(String[] args) throws IOException {
//使用Student类
//外界:依然可以通过无参构造方法创建对象
/*Student s1 = new Student() ;
Student s2 = new Student() ;
System.out.println(s1==s2);*/
Student s1 = Student.getStudent();
Student s2 = Student.getStudent();
System.out.println(s1==s2);
System.out.println(s1);
System.out.println(s2);
System.out.println(s1.getClass() == s2.getClass());
//getClass()---->获取正在运行的Java类对象:Class字节码文件对象
//Class ----class com.qf.pattern_06.Student
System.out.println("---------------------------");
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc") ;//里面执行一些指令
runtime.exec("shutdown -s -t 3600") ;//里面执行一些指令
runtime.exec("shutdown -a") ;//取消关机
int count = runtime.availableProcessors();
System.out.println(count);//cpu核数
}
}
懒汉式
懒汉式
1)某个类中,构造方法私有化---目的:外界不能够直接通过构造方法创建对象
2)成员变量位置:声明一个静态变量: 该类型
3)提供对外的公共访问方法: 静态
需要判断:
如果当前变量为null,这个时候,创建一个对象!
懒汉式:可能出现问题的单例模式!
延迟加载
懒加载
都可能造成多线程安全问题
将当前这个静态的公共的方法---加入同步代码块---解决线程安全问题!
public class Teacher {
private static Teacher t ; //类型:Teacher
//构造方法私有化
private Teacher(){
}
//提供静态的对外的公共访方法
//t1,t2,t3 ----线程
/* public static Teacher getTeacher(){
//如果当前对象存在,一直使用;如果为null,就创建一个对象!
//判断
//每一个线程对象---都在访问资源(访问冲突)
synchronized (Teacher.class){
if(t == null){
t = new Teacher() ;
}
return t ;
}
}*/
//将synchronized定义在方法声明上
public synchronized static Teacher getTeacher(){ //锁对象:Teacher.class
//如果当前对象存在,一直使用;如果为null,就创建一个对象!
//判断
//每一个线程对象---都在访问资源(访问冲突)
if(t == null){
t = new Teacher() ;
}
return t ;
}
}
IO框架
File类
概念
java.io.File:文件和目录(文件夹)路径名的抽象表示形式。
构造方法
构造方法
public File(String pathname):直接传递字符串路径 (推荐....)
public File(File parent,String child):
参数1:File对象:某个目录(文件夹)File对象
参数2:当前指定的具体路径
public File(String parent,String child):分别指定父目录的路径以及子文件的路径
功能
创建功能
创建文件:
public boolean createNewFile() throws IOException
如果不存在,创建空文件,返回true;否则,false
必须存在目录,目录都不存在(系统找不到指定的路径。)
创建文件夹(目录)
public boolean mkdir():创建目录(文件夹)
public boolean mkdirs():创建此抽象路径名指定的目录,包括所有必需但不存在的父目录时,创建!
如果操作File所示的路径:没有带盘符----->文件/目录创建到哪里了?
将文件或者文件夹创建在当前项目路径下!
删除功能
删除功能:
public boolean delete():可以删除文件或者文件夹(目录),如果要删除目录,前提必须是空目录!
重命名功能
重命名功能:
public boolean renameTo(File dest)
重新命名此抽象路径名表示的文件
将D盘下这个文件进行重命名
情况1:
重名的时候:源文件/目标文件都是相同地址 :d盘下----只是重命名
情况2:
源文件/目标文件不再同一个路径下: 剪切+重命名
判断功能
判断功能:
public boolean isAbsolute() :是否为绝对路径
public boolean isDirectory():File所表示的路径是否指定的是一个目录
public boolean isFile():是否是文件
public boolean isHidden():是否是一个隐藏文件
public boolean canRead():是否可读
public boolean canWrite():是否可写
public boolean exists():是否存在
获取功能
获取功能
public String getAbsolutePath():获取绝对路径
public String getPath():获取路径
long lastModified():获取文件最后一个被修改的时间
public String getName():获取文件/目录的名称
public long length():获取文件的长度(单位是字节...)
public String[] list() :获取当前路径下所表示的文件或者文件夹的字符串数组
public File[] listFiles():获取某个路径的下所表示的文件或者文件夹的File数组
高级获取功能
高级获取功能
通过文件名称过滤器:获取某个路径下所有 的文件以及文件夹的String[]/File[]数组
public File[] listFiles(FilenameFilter filter) :
public String[] list(FilenameFilter filter) :
递归
递归:
方法调用方法本身的一种现象,并非方法嵌套方法!
Math.max(Math.max(10,20),35) ; 这个方法嵌套
递归的条件
递归的条件:
1)必须定义一个方法:通过方法调用方法本身的一种现象
2)必须有出口条件(结束条件),否则死递归
3)存在一定的规律...
注意:
构造方法:没有递归的!
阶乘
public class DiGuiDemo {
/* public DiGuiDemo(){ //构造方法
// DiGuiDemo() ;
}*/
public static void main(String[] args) {
//循环
//阶乘思想:最终结果变量
int jc = 1 ;
for(int x= 1; x <=5 ; x ++){
jc *= x ;
}
System.out.println("5的阶乘是:"+jc);
System.out.println("---------------------------------");
//递归的思想
System.out.println("5的阶乘是:"+getNum(5));
}
//定义一个方法
private static int getNum(int n) {//5//4 //3
//结束条件
if(n==1){
return 1 ;
}else{
return n*getNum(n-1) ;
}
}
}
不死神兔
/**
* 需求:[不死神兔],有一对兔子,三个月后每一个月产生一对兔子,小兔子长到第三个月又产生一对兔子
* 假设兔子都不死,第20个月,总共有多少对兔子!
*
*
* 分析:
* 第一个月:1
* 第二个月:1
* 第三个月:2
* 第四个月:3
* 第五个月:5
* 第六个月:8
* .....
*
* 规律:
* 已知两个数据:第一个月/第二个月:1
* 从第三个月开始:每个月兔子的对数:等于前两个月之和!
*
* 结束条件:第一个月和第二个都是1
*
* 解决方案:
* 1)数组方式
* 2)递归方式
*/
public class DiGuiTest {
public static void main(String[] args) {
//方式1:定义一个数组:动态初始化
int[] arr = new int[20] ;//角标:0开始,, 长度20
//第一个月和第二个月都是1
arr[0] = 1 ;
arr[1] = 1 ;
// 从第三个月开始:每个月兔子的对数:等于前两个月之和!
for(int x = 2 ;x < arr.length ; x++){
arr[x] = arr[x-1] + arr[x-2] ;
}
System.out.println("第二十个月兔子的对数有:"+arr[19]);//6765
System.out.println("----------------------------------------------");
//定义一个方法:getRabbitNum(20): 参数代表:第几个月
System.out.println("第二十个月兔子的对数有:"+getRabbitNum(20));
}
//n:表示的月份
private static int getRabbitNum(int n) {
//结束条件:第一个月/第二个月:1
if(n==1 || n== 2){
return 1 ;
}else{
//从第三个月开始:中间的月份:
// 每个月兔子的对数:等于前两个月之和!
//3 (3-1) 3-2
return getRabbitNum(n-1) + getRabbitNum(n-2) ;//6765
}
}
}
递归删除指定的目录
/**
* 需求:递归删除指定的目录
*
* 删除:当前 项目下的demo文件夹
* 分析:
*
* 1)封装文件夹(目录):File对象
* 2)定义一个删除文件夹方法
* deleteSrc(file对象)
*
* 3)
* 获取File对象所表示路径下的所有文件夹以及文件的File数组
* 4)判断数组不为空,遍历
* 获取到每一个File对象
* 判断:如果这个file对象代表的是文件夹isDirectory()
* 继续回到2)
* 如果不是文件夹,
* 直接删除文件,(查看删除了谁,获取文件名称)
*
* 5)直接将demo文件删除掉
*
*
*
*
*/
public class DiGuiTest2 {
public static void main(String[] args) {
//创建File对象:表示抽象路径
File srcFile = new File("demo") ;
//定义一个删除文件夹的方法
deleteSrc(srcFile) ;
}
//递归删除文件夹的方法
public static void deleteSrc(File srcFile) {
//获取这个srcFile下面的所有文件夹以及文件的file数组
File[] fileArray = srcFile.listFiles();
//防止空指针
if(fileArray!=null){
//遍历File数组
for(File file:fileArray){
//判断如果file对象是文件夹
if(file.isDirectory()){
//继续调用 deleteSrc方法来删除
deleteSrc(file);
}else{
//是文件
System.out.println(file.getName()+"---"+file.delete());
}
}
//删除demo文件
System.out.println(srcFile.getName()+"---"+srcFile.delete());
}
}
}
题目
/**
* 需求:
* 获取指定盘符下的文件并且该文件是以.
* jpg结尾的文件
*
* 分析:
* 1)使用File对象描述一个盘符:D盘
* 2) public File[] listFiles():获取D盘下的所有的文件以及文件夹的File数组
* 3)如果File[]不为空,然后遍历----获取到每一个File对象
* 3.1) 如果当前File对象所表示的是一个文件:boolean isFile()
* 3.2)如果是文件,要判断当前文件是以.jpg结尾的文件
* String类:endsWith(String xx)
* 获取文件名称...
*
*
*/
public class FileTest {
public static void main(String[] args) {
//1)使用File对象描述一个盘符:D盘
File srcFile = new File("D://") ;
//2) public File[] listFiles():获取D盘下的所有的文件以及文件夹的File数组
File[] fileArray = srcFile.listFiles();
//非空判断
if(fileArray!=null){
//遍历---获取到每一个File对象
for(File file: fileArray){
// 3.1) 如果当前File对象所表示的是一个文件:boolean isFile()
if(file.isFile()){
//是文件
//如果是文件,要判断当前文件是以.jpg结尾的文件
if(file.getName().endsWith(".jpg")){
//获取文件
System.out.println(file.getName());
}
}
}
}
}
}
/**
* 需求:
* 获取指定盘符下的文件并且该文件是以.
* jpg结尾的文件
*
* 分析:
* 1)使用File对象描述一个盘符:D盘
* 2) public File[] listFiles():获取D盘下的所有的文件以及文件夹的File数组
* 3)如果File[]不为空,然后遍历----获取到每一个File对象
* 3.1) 如果当前File对象所表示的是一个文件:boolean isFile()
* 3.2)如果是文件,要判断当前文件是以.jpg结尾的文件
* String类:endsWith(String xx)
* 获取文件名称...
*
*
* 上面这种可以,麻烦---- 获取到了所有的文件以及文件夹的File数组---->加入一系列判断!
*
* 在File对象(D盘符)获取的时候整个盘符下是文件以及文件夹File数组,就已经能够拿到每一个file对象?
* 文件名称过滤器:FilenameFilter
*
* 通过文件名称过滤器:获取某个路径下所有 的文件以及文件夹的String[]/File[]数组
* public File[] listFiles(FilenameFilter filter) :
* 形式参数:是一个接口类型:文件名称过滤器 -----匿名内部类/自定义一个子实现类
* boolean accept(File dir,String name)测试指定文件是否应该包含在某一文件列表中。
* 参数1:dir:指定的文件所在的目录
* 参数2:当前文件的名称
*
* 返回值:true/false取决于 当前是否能够将当前dir所在的目录中指定的文件名称添加在文件列表中
* true:添加到文件列表中
* false:不会添加到文件列表中
*/
public class FileTest2 {
public static void main(String[] args) {
//表示D盘
File srcFile = new File("D://") ;
//直接获取到文件列表
// public File[] listFiles(FilenameFilter filter) :
// public String[] listFiles(FilenameFilter filter) :
String[] strArray = srcFile.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// return false;
//加入自己的逻辑
//返回值为true:将文件名称所在的目录添加到列表中
//dir:目录 name:文件名称
File file = new File(dir,name) ;
// System.out.println(file);
// return true ;
//需要的是文件/以及以.jpg结尾的文件
/*boolean flag1 = file.isFile() ;//判断是文件
boolean flag2 = file.getName().endsWith(".jpg");
return flag1 && flag2 ;*/
return file.isFile() && file.getName().endsWith(".jpg") ;
//
}
});
for(String s : strArray){
System.out.println(s);
}
}
}
IO流
流的概念
概念:内存与存储设备之间传输数据的通道
流的用途
IO流:-----> 底层创建系统资源,在不同的设备之间进行数据传输的!
I: 输入---->磁盘上有一个文件,让通过流的方式将文件读出来!
O:输出 ---->在内存中—通过输出流,在某个磁盘下输出文件并且写入内容
流的分类
IO流的分为:
按流的方向
输入流:将存储设备中的内容读取到内存中
输出流:将内存中的内容写入到存储设备中
按流的类型:
字节流:以字节为单位,可以读取所有数据
字符流:以字符为单位,只能读写文本数据
字节流
字节流的抽象基类:
InputStream ,OutputStream。
xxxxInputStream
XXXXOutputStream
都是子类
字节输入流
InputStream : 字节输入流
public int read(){}
public int read(byte[] b){}
public int read(byte[] b, int off, int len){}
字节文件输入流
FileInputStream
读取 fos3.txt 文件
使用步骤:
1)创建具体的子类对象:FileInputStream
public FileInputStream(String name)
2)读取数据:一次读取一个字节/一次读取一个字节数组
public abstract int read()throws IOException
public abstract int read(byte[] bytes)throws IOException
3)释放资源
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
//创建文件输入流对象
FileInputStream fis = new FileInputStream("FileOutputStreamDemo.java") ;
//单独将带中文的文件---输出在控制台上,由于强制类型转换---一个中文(UTF-8)对应三个字节,跟前面的英文字符对应的字节拼接不上,所以造成乱码
//使用循环实现----不知道循环多少次,使用while
//结束条件:当前字节数为-1,流已经到末尾了
//一次读取一个字节
//将赋值,判断,以及获取写在一块
int by = 0 ; //字节数开始为0,没有开始读
while((by=fis.read())!=-1){
//将获取的都字节数---转换字符
System.out.print((char)by);
}
//释放资源
fis.close();
}
}
public class FileInputStreamDemo2 {
public static void main(String[] args) throws IOException {
//创建一个文件输入流对象
FileInputStream fis = new FileInputStream("FileOutputStreamDemo.java") ;
// public abstract int read(byte[] bytes)throws IOException :一次读取一个字节数组
//重复性代码:使用循环实现:while循环
//结束条件:如果流已经读取到末尾了,返回-1
//一次读取一个字节数组:模板代码
//一般情况:字节数组的长度:1024或者是1024的整数倍
//在使用new String(bytes)获取实际的内容,带上len的使用:实际的内容
//String(bytes[] bytes,0,len):每一次读取的时候,都是从角标0开始,读取实际长度!
//赋值,判断,获取都写在while循环中
byte[] bytes = new byte[1024] ; //构造字节缓冲区
int len = 0 ;//开始没有读取
while((len=fis.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
//资源释放
fis.close();
}
}
字节缓冲输入流
字节缓冲输入流对象: (高效的字节输入流)
BufferedInputStream(InputStream in) :默认大小的缓冲区(创建字节缓冲输入流)
public int read()
public int read(byte[] bytes)
public class BufferedInputStreamDemo {
public static void main(String[] args) throws IOException {
//创建字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("bos.txt")) ;
//读取数据
//一次读取一个字节
/* int by = 0 ;
while((by=bis.read())!=-1){
System.out.print((char)by);
}*/
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
// 资源关闭
bis.close();
}
}
字节输出流
OutputStream: 字节输出流
public void write(int n){}
public void write(byte[] b){}
public void write(byte[] b, int off, int len){}
字节文件输出流
FileOutputStream:
输出一个文本文件,并且写内容
使用步骤:
1)创建输出流对象
public FileOutputStream(String name)
throws FileNotFoundException
2)写入内容
写入的字节....
3)关闭资源
写入数据的功能
public void write(int b) throws IOException :写入一个字节
public void write(byte[] b) throws IOException:写入一个字节数组
public void write(byte[] b,int off,int len):写入当前字节数组的一部分
构造方法
public FileOutputStream(File file,boolean append) throws FileNotFoundException
参数1:File对象所表示的文件
参数2:是否进行末尾追加(true,文件末尾处追加) 在之前操作记录之上,继续追加!
如果输出文件写入内容需要进行换行操作:
换行符号
windows操作系统:
"\r\n"
Linux操作系统中:
"\n"
Mac系统:
"\r"
public class FileOutputStreamDemo3 {
public static void main(String[] args) throws IOException {
//File表示文件
File file = new File("fos3.txt") ;
//创建文件输出流对象
//public FileOutputStream(File file,boolean append) throws FileNotFoundException
FileOutputStream fos = new FileOutputStream(file,true) ;
//写入内容
for(int x = 0 ; x < 10 ; x ++){
fos.write(("hello"+x).getBytes());
//写入这个换行符号
fos.write("\r\n".getBytes());
}
//释放资源
fos.close();
}
}
异常处理
使用文件输出流的时候,加入异常处理:try...catch...finally
实际开发中:IO流/JDBC ---->加入异常处理,try...catch...finally:统一处理
public class FileOutputStreamDemo4 {
public static void main(String[] args) {
//方式1: 跟流对象相关的一一处理
// method1() ;
//方式2:统一处理
method2();
}
public static void method2() {
FileOutputStream fos = null ;
try {
fos = new FileOutputStream("fos4.txt") ;
//写入内容
fos.write("hello,FileOutputStream".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static void method1() {
//创建文件输出流对象
FileOutputStream fos = null ;
try{
fos = new FileOutputStream("fos4.txt") ;
}catch (FileNotFoundException e){
e.printStackTrace();
}
//写入内容
try {
fos.write("hello,FileOutputStream".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
//释放资源
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字节缓冲输出流
BufferedOutputStream:字节缓冲输出流(高效的字节流):是OutputStream的子类
作用:内部提供一个(默认大小)缓冲区
构造方法
public BufferedOutputStream(OutputStream out):创建一个默认大小的字节缓冲输出流对象,
形式参数:抽象类---实际参数--需要该抽象类的子类对象
BufferedInputStream/OutputStream:只是提供一个缓冲区,针对文件读/写的操作,还是
需要使用基本字节流:InputStream/OutputStream
源码
/*public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
byte[] buf = new byte[8192] ;长度*/
编码表
计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就
将各个国家的文字用数字来表示,并一一对应,形成一张表。
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表用一个字节的8位表示。
GB2312:中国的中文编码表。
GBK:中国的中文编码表升级,融合了更多的中文文字符号。
GB18030:GBK的取代版本BIG-5码 :通行于台湾、香港地区的一个繁体字编码方案,俗称“大五码”。
Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicodeUTF-8:最多用三个字节来表示一个字符。
UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:它将Unicode编码为00000000-0000007F的字符,用单个字节来表示,它将Unicode编码为00000080-000007FF的字符用两个字节表示,它将Unicode编码为00000800-0000FFFF的字符用3字节表示
public class TestEncoding {
public static void main(String[] args) throws UnsupportedEncodingException {
String s1 = "你好世界123abc喆";
byte[] bs = s1.getBytes("GBK");//GBK文本"编码"为二进制
String s2 = new String(bs,"BIG5");//二进制"解码"为GBK文本
System.out.println(s2);
byte[] bs2 = s2.getBytes("BIG5");
String s3 = new String(bs2,"GBK");
System.out.println(s3);
}
}
字符流
桥转换流
字符转换输出流
字符输出流的使用
OutputStreamWriter 是字符流通向字节流的桥梁
构造方法
构造方法
OutputStreamWriter(OutputStream out)
使用平台默认的编码集(UTF-8)--->对写入的内容进行编码 (字符转换输出流)
OutputStreamWriter(OutputStream out,String charsetName):执行编码操作
成员方法
void write(char[] cbuf)
写入字符数组。
abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分。
void write(int c)
写入单个字符。
void write(String str)
写入字符串。
void write(String str, int off, int len)
写入字符串的某一部分。
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
//创建字符转换输出流对象:
//字符流中指向字节流
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("osw.txt")) ;//UTF-8格式编码
//写入数据
// char[] chs = {'a','b','c','d','e'} ;
//void write(char[] cbuf)
// osw.write(chs);
//abstract void write(char[] cbuf, int off, int len)
// osw.write(chs,1,2);
//void write(int c)
// osw.write('a');
osw.write("我爱中国");
//刷新流
osw.flush();
//释放资源
osw.close();
}
}
字符转换输入流
字符转换输入流:InputStreamReader
是字节流通向字符流的桥梁: (解码:将流中的内容使用指定的字符集解码成字符!)
构造方法
public InputStreamReader(InputStream in):平台默认字符集
public InputStreamReader(InputStream in,String charsetName):指定的字符集
成员方法
int read()
读取单个字符。
int read(char[] cbuf)
将字符读入数组。
abstract int read(char[] cbuf, int off, int len)
将部分字符读入数组
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
//读取当前项目osw.txt
//指定字符集:GBK
// InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"),"GBK") ;
InputStreamReader isr = new InputStreamReader(
new FileInputStream("osw.txt")) ;//默认:UTF-8
//读取内容
//int read(char[] cbuf)
//一次读取一个字符数组
char[] chs = new char[1024] ;
int len = 0 ;//字符数
while((len=isr.read(chs))!=-1){
System.out.println(new String(chs,0,len));
}
//关闭资源
isr.close();
}
}
字符文件流
字符流操作文件两种方式
1)使用字符转换流操作:读写操作
2)使用FileReader/FileWriter:读写操作
构造方法
public FileReader(String fileName)
public FileWriter(String fileName)
public class CopyTest {
public static void main(String[] args) throws IOException {
//封装源文件
FileReader fr = new FileReader("d://FileOutputStreamDemo.java") ;
//封装目的地文件
FileWriter fw = new FileWriter("MySelf.java") ;
//读写操作
//一次读取字符数据
char[] chs = new char[1024] ;
int len = 0 ;
while((len=fr.read(chs))!=-1){
//读一个字符数组,写入字符数组
fw.write(new String(chs,0,len));
//刷新
fw.flush();
}
//释放资源
fw.close();
fr.close();
}
}
字符缓冲流
字符缓冲输出流
字符缓冲输出流:BufferedWriter
针对文本来进行高效的写入(高效的字符流)
只是提供缓冲区:针对文件的操作还需要使用基本流
构造方法
public BufferedWriter(Writer out):创建一个默认大小的字符缓冲输出流对象(默认值足够大了)
成员方法
void newLine()
写入一个行分隔符。
void write(char[] chs)
void write(char[] cbuf, int off, int len)
写入字符数组的某一部分。
void write(int c)
写入单个字符。
void write(String str)
void write(String s, int off, int len)
public class BuffferedWriterDemo {
public static void main(String[] args) throws IOException {
//创建一个字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt")) ; //默认大小:defaultBufferSize:8192
/* BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("bw.txt"))) ;*/
//直接写入字符串内容
bw.write("Hello");
//之前的换行:通过系统写入"\r\n"
// public void newLine()throws IOException
bw.newLine();
bw.write("JavaEE");
bw.newLine();
bw.write("World");
bw.newLine();
//刷新流
bw.flush();
//释放资源
bw.close();
}
}
字符缓冲输入流
字符缓冲输入流:BufferedReader
针对文本高效读取(高效的字符输入流)
构造方法
BufferedReader(Reader read):构造一个默认大小的缓冲输入流对象(默认值足够大)
成员方法
public String readLine()throws IOException :一次读取一行内容,返回值如果为null,表示当前读完了
public int read(int ch):读取一个字符
public int read(char[] chs):读取一个字符
public void read(String str/)
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException{
//创建一个字符缓冲输入流对象
// BufferedReader br = new BufferedReader(new FileReader("bw.txt")) ;
BufferedReader br = new BufferedReader(new FileReader("BuffferedWriterDemo.java")) ;
//读取:一次读取一个字符/一次读取一个字符数组
/* char[] chs = new char[1024] ;
int len = 0 ;//实际字符数
while((len=br.read(chs))!=-1){
System.out.println(new String(chs,0,len));
}*/
//public String readLine()throws IOException :一次读取一行内容,返回值如果为null,表示当前读完了
//使用循环,while循环
String line = null ;
while((line=br.readLine())!=null){
System.out.println(line);
}
//释放资源
br.close();
}
}
/**
* 需求:当前项目下的:BufferedWriterDemo.java文件
*
* 复制到D://copy.java文件中
*
* 源文件:
* BufferedReader:读取 BufferedWriterDemo.java文件 (推荐)
* 目标文件:
* BufferedWriter:写入D://copy.java文件
*/
public class CopyFile {
public static void main(String[] args) throws IOException {
//封装源文件/目标文件
BufferedReader br = new BufferedReader(new FileReader("BuffferedWriterDemo.java")) ;
BufferedWriter bw = new BufferedWriter(new FileWriter("D://copy.java")) ;
//读写复制操作:使用字符缓冲流的特有功能
String line = null ;
while((line=br.readLine())!=null){
//读一行,写一行
bw.write(line);
bw.newLine();
bw.flush(); //刷新
}
//关闭资源
bw.close();
br.close();
}
}
其他流
打印流
字节打印流
字节打印流:PrintStream
System.out---->标准"输出流"
public static final PrintStream out ;
打印各种数据表示形式(输出字节,如果是字符----转换成字节)
print(xxx);
println(xxx);
字符打印流
字符打印流:PrintWriter
只能操作目标文件
这个流:他可以启用自动刷新功能
public PrintWriter(Writer out,boolean autoFlush) :第二个参数为true:启用自动刷新
public void println():写入行的分隔符号,终止当前行
public class PinrtStreamDemo {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
out.println("hello");
out.println("-------------------------------------");
out.println(true) ;
char[] chs = {'a','b','c'} ;
out.println(chs) ;
out.println("-------------------------------------");
//创建一个字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("BuffferedWriterDemo.java")) ;
//创建字符打印流 public PrintWriter(Writer out,boolean autoFlush)
PrintWriter ps = new PrintWriter(new FileWriter("demo.java"),true) ;
//读写复制操作
//一次读取一行
String line = null ;
while((line=br.readLine())!=null){
ps.println(line); //给当前ps指向这个demo.java文件中打印内容
//不需要刷新:自动刷新
}
//释放资源
ps.close();
br.close();
}
}
字节输入流的逻辑串联
操作两个文件
构造方法
SequenceInputStream(InputStream s1,InputStream s2)
操作两个源文件...
/**
* SequenceInputStream:字节输入流的逻辑串联
*
* 构造方法
* SequenceInputStream(InputStream s1,InputStream s2)
* 操作两个源文件...
*
* 举例
* 将当前项目下的BuffferedWriterDemo.java+PinrtStreamDemo.java复制到
* 当前项目下的:copy.java文件中
*
*/
public class SequenceInputStreamDemo {
public static void main(String[] args) throws IOException {
//封装源文件
SequenceInputStream sis = new SequenceInputStream(new FileInputStream("BuffferedWriterDemo.java"),
new FileInputStream("PinrtStreamDemo.java")) ;
//封装目标文件
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.java")) ;
//读写操作
byte[] bytes = new byte[1024] ;
int len = 0 ;//字节数
while((len=sis.read(bytes))!=-1){
bos.write(bytes,0,len);
bos.flush(); //字节流中刷新:强制刷新缓冲区中字节数
}
bos.close();
sis.close();
}
}
操作两个文件以上
构造方法:
public SequenceInputStream(Enumeration<? extends InputStream> e):
针对两个以上的文件进行复制
/**
* public SequenceInputStream(Enumeration<? extends InputStream> e):
* 针对两个以上的文件进行复制
*
* 举例
*
* D://a.txt + c://b.txt + e://c.txt
*
* ----->当前项目demo.txt文件中
*/
public class SequenceInputStreamDemo2 {
public static void main(String[] args) throws IOException {
//BuffferedWriterDemo.java+PinrtStreamDemo.java+SequenceInputStreamDemo.java
//封装源文件
//创建Vector集合对象
Vector<InputStream> v = new Vector<InputStream>() ;
InputStream s1 = new FileInputStream("BuffferedWriterDemo.java") ;
InputStream s2 = new FileInputStream("PinrtStreamDemo.java") ;
InputStream s3 = new FileInputStream("SequenceInputStreamDemo.java") ;
//添加元素
v.add(s1) ;
v.add(s2) ;
v.add(s3) ;
//Vector------>获取特有的迭代器(Enumeration)
Enumeration<InputStream> en = v.elements(); //获取向量的枚举组件-------- iterator() ---->Iterator
SequenceInputStream sis = new SequenceInputStream(en) ;
//d://demo.java:目标文件
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D://demo.java")) ;
//读写复制操作
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=sis.read(bytes))!=-1){
bos.write(bytes,0,len);
bos.flush();
}
//释放资源
bos.close();
sis.close();
}
}
序列化和反序列化
序列化
序列化: ObjectOutputStream
将Java对象(任意Java类的对象) 对象 在网络中进行数据传输-----变成一种"流"数据,这个过程称为序列化
反序列化
反序列化 ObjectInputStream
又需要将流数据-----还原成对象
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化:
// myWrite();
//反序列化
myRead() ;
}
public static void myRead() throws IOException, ClassNotFoundException {
//ObjectInputStream
//构造方法
//public ObjectInputStream(InputStream in)throws IOException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("oos.txt")) ;
//public final Object readObject()throws IOException,ClassNotFoundException
Object object = ois.readObject();
System.out.println(object); //Person //Person{name='高圆圆', age=41}
//释放资源
ois.close();
}
public static void myWrite() throws IOException{
//对象 在网络中进行数据传输-----变成一种"流"数据
// public ObjectOutputStream(OutputStream out)throws IOException
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("oos.txt")) ;
//public final void writeObject(Object obj)
// throws IOException 将对象写入到流中
Person p = new Person("高圆圆",41) ;
oos.writeObject(p); //写入内容:编码集:GBK
//java.io.NotSerializableException: com.qf.objectstream_05.Person
//释放资源
oos.close();
}
}
import java.io.Serializable;
//只能将支持 java.io.Serializable 接口的对象写入流
//要能够启用序列化功能:自定义对象所在的类必须实现:Serializable
//类通过实现 java.io.Serializable 接口以启用其序列化功能
/**
* 实现了序列化接口(标记接口)的类----在内存中会产生一个序列化的版本Id: SerialVersionUID:唯一标识符
* 类----对应类的签名:就是一个唯一的id值
*
* 序列化---反序列化----如果现在手动更改成员信息----如果直接在进行反序列化 :
*
* java.io.InvalidClassException: com.qf.objectstream_05.Person; local class incompatible: stream classdesc serialVersionUID = 3971989068296701088,
* local class serialVersionUID = 5645013692103785236
*
* 该类的序列版本号与从流中读取的类描述符的版本号不匹配 :
*
* 序列化的时候:com.qf.objectstream.Person:会产生一个serialVersionUID
* 然后反序列化的时候:会读取当前Person在内存中的序列化版本ID(一致),获取Java对象
* 如果更改了成员信息,直接进行反序列化,序列化版本Id不一致,出现这个异常!
* 产生一个固定的序列化版本Id号(常量)或者针对某个成员加入这个关键字: transient:针对某个类的成员不会参与序列化/反序列化!
*
*
*/
public class Person implements Serializable {
private transient String name ;
int age ;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
属性集合类
属性集合类:Properties extends Hashtable<K,V>
Properties 类表示了一个持久的属性集。
可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
这个属性集合类:应该具备Map集合的功能
public class PerpertiesDemo {
public static void main(String[] args) {
//创建属性集合类对象
//使用Map接口的功能
Properties prop = new Properties() ;
//添加元素:直接使用put方法
prop.put("1","张三") ;
prop.put("2","高圆圆") ;
prop.put("3","赵又廷") ;
prop.put("4","文章") ;
//遍历Map集合的遍历几种方式:
//通用:keySet()
Set<Object> set = prop.keySet();
for(Object key :set){
Object value = prop.get(key);
System.out.println(key+"---"+value);
}
}
}
构造方法
public Properties():无参构造
特有功能
添加功能
添加元素:
public Object setProperty(String key,String value)
获取功能
获取:
public Set<String> stringPropertyNames():获取属性列表中所有的键集
public String getProperty(String key):通过属性列表中的属性名称-->获取属性值
public class PropertiesDemo2 {
public static void main(String[] args) {
//创建属性列表对象
Properties prop = new Properties() ;
System.out.println(prop);
//添加元素
prop.setProperty("高圆圆","赵又廷") ;
prop.setProperty("文章","马伊琍") ;
prop.setProperty("孙悟空","紫霞仙子") ;
System.out.println(prop);
//遍历public Set<String> stringPropertyNames():获取属性列表中所有的键集
Set<String> set = prop.stringPropertyNames();
for(String key :set){
String value = prop.getProperty(key);
System.out.println(key+"---"+value);
}
}
}
加载功能
加载功能:
public void load(Reader reader) throws IOException:
public void load(InputStream in)throws IOException
将某个文件的内容加载到当前属性列表中
保存功能
保存:
public void store(Writer writer,String comments)
将属性列表中的内容保存到指定的文件中...
public class PropertiesDemo3 {
public static void main(String[] args) throws IOException {
// myStore() ;
myLoad() ;
}
//将文件中的内容加载属性集合类中
private static void myLoad() throws IOException {
//创建属性集合列表对象
Properties prop = new Properties() ;
System.out.println(prop);
System.out.println("---------------------");
//从文本文件中获取信息加载属性集合类中
prop.load(new FileReader("user.txt"));
System.out.println(prop);
}
//将属性列表中的内容:保存到某个文件中
private static void myStore() throws IOException {
//创建一个属性列表集合对象
Properties prop = new Properties() ;
//添加内容
prop.setProperty("张三","30") ;
prop.setProperty("高圆圆","41") ;
prop.setProperty("李四","40") ;
prop.setProperty("文章","35") ;
// public void store(Writer writer,String comments)
//参数1:字符输出流
//参数2:给当前属性列表中添加描述信息
prop.store(new FileWriter("username.txt"),"student's list");
}
}
网络编程
计算机网络
网络
网络:由点和线构成,表示诸多对象间的相互联系
计算机网络
计算机网络:
为实现资源共享和信息传递,通过通信线路连接起来的若干主机(host)
互联网:Internet 点与点相连
万维网:WWW - World Wide Web 端与端相连
物联网:IoT - Internet of things 物与物相连
网络编程
网络编程:让计算机与计算机之间建立连接,进行通信
网络模型
网络模型
网络模型:
OSI:(Open System Interconnection) 开放式系统互联
开放系统A 开放系统B
应用层协议
应用层 <------------> 应用层
第七层:应用层负责文件访问和管理,可靠运输服务,远程操作服务(HTTP,FTP,SMTP)
表示层协议
表示层 <------------> 表示层
第六层:表示层负责定义转换数据格式及加密,允许选择以二进制或ASCII格式传输
会话层协议
会话层 <------------> 会话层
第五层:会话层负责使应用建立和维持会话。使通信在失效时继续恢复通信(断点续传)
传输层协议
传输层 <------------> 传输层
第四层:传输层负责是否选择差错恢复协议,数据流重用,错误顺序重排(TCP,UDP)
网络层 网络层
第三层:网络层负责定义了能够标识所有网络节点的逻辑地址(IP地址)
数据链路层 数据链路层
第二层:链路层在物理层上,通过规程或协议(差错控制)来控制传输数据的正确性(MAC)
物理层 物理层
第一层:物理层为设备之间的数据通信提供传输信号和物理介质(双绞线,光导纤维)
《---------通信介质(物理媒体)----------》
TCP/IP模型
TCP/IP模型:
一组用于实现网络互连的通信协议,将协议分为四个层次
TCP/IP模型:
应用层 第四层:应用层负责传送各种最终形态的数据,是直接与用户打交道的层,典型的协议是HTTP,FTP等
传输层 第三层:传输层负责传输文本数据,主要协议是TCP协议,UDP协议
网络层 第二层:网络层负责分配地址和传送二进制数据,主要协议是IP协议
网络接口层 第一层:接口层负责建立电路连接,是整个网络的物理基础,典型的协议包括以太网,ADSL等等
TCP/UDP
TCP
TCP协议:Transmission Control Protocol 传输控制协议
是一种面向连接的,可靠的,基于字节流的传输层通信协议,数据大小无限制,建立连接过程需要三次握手,断开连接过程需要四次挥手
UDP
UDP协议:User Datagram Protocol 用户数据报协议
是一种无连接的传输协议,提供面向事物的简单不可靠信息传送服务,每个包的大小是64KB
IP
IP协议
IP协议:Internet Protocol Address 互联网协议地址/网际协议地址
分配给互联网设备的数字标签(唯一标识)
IP地址
IPV4
IP地址分为两种:
IPV4:4字节32位整数,并分为4段8位二进制数,每8位之间用圆点隔开,每8位整数可以转换为一个0~255的十进制整数
格式:D.D.D.D 例如:255.255.255.255
IPV4的应用分类
A类:政府机构:1.0.0.1~126.255.255.254
B类:中型企业:128.0.0.1~191.255.255.254
C类:个人用户:192.0.0.1~223.255.255.254
D类:用于组播:224.0.0.1~239.255.255.254
E类:用于实验:240.0.0.1~255.255.255.254
回环地址:127.0.0.1 指本机,一般用于测试使用
查看命令:ipconfig
测试IP命令:ping D.D.D.D
IPV6
IPV6:16字节128位整数,并分为8段十六进制数,每16位之间用圆点隔开,每16位整数可以转换为一个0~65535的十进制数
格式:X.X.X.X.X.X.X.X 例如:FFFF.FFFF.FFFF.FFFF.FFFF.FFFF.FFFF.FFFF
端口(Port)
端口号
端口号:在通信实体上进行网络通讯的程序的唯一标识
端口分类
端口分类:
公认端口:0~1023
注册端口:1024~49151
动态或私有端口:49152~65535
常用端口
常用端口:
MySql: 3306
Oracle: 1521
Tomcat: 8080
SMTP: 25
web服务器:80
FTP服务器:21
InetAddress类
概念
表示互联网协议(IP)地址对象,封装了与该IP地址相关的所有信息,并提供获取信息的常用方法
方法
public static InetAddress getLocalHost()获得本地主机地址对象
public static InetAddress getByName(String host) 根据主机名称获得Ip地址对象
参数:要么是知道机器名称的情况/要么就是ip地址字符串形式
public static InetAddress[] getAllByName(String host)获得所有相关地址对象
public String getHostName():获取IP地址主机名称
public String getHostAddress():获取ip地址(字符串形式)
import java.net.InetAddress;
import java.net.UnknownHostException;
public class TestInetAddress {
public static void main(String[] args) throws UnknownHostException {
//获得本机IP地址对象
InetAddress localhost = InetAddress.getLocalHost();
//获得IP地址字符串
System.out.println(localhost.getHostAddress());
//获得IP地址主机名
System.out.println(localhost.getHostName());
System.out.println(localhost);
//获得任意主机的IP地址对象(IP、主机名、域名)
InetAddress baidu = InetAddress.getByName("www.baidu.com");
// System.out.println(baidu.getHostAddress());
// System.out.println(baidu.getHostName());
//获得任意域名所绑定的所有IP地址对象
InetAddress[] addrs = InetAddress.getAllByName("www.baidu.com");
for(InetAddress addr : addrs) {
System.out.println(addr.getHostAddress());
System.out.println(addr.getHostName());
}
}
}
基于UDP的网络编程
UDP发送端的使用步骤
UDP发送端的使用步骤:
1)创建发送端的Socket对象
DatagramSocket:此类表示用来发送和接收数据报包的套接字。
构造方法
public DatagramSocket() throws SocketException:
将任何端口号绑定当前这个数据包套接字上
2)创建数据报包对象:DatagramPacket此类表示数据报包。
构造方法
public DatagramPacket(byte[] buf, 包数据
int length, 包长度
InetAddress address, 目标地址
int port) 端口号
3)发送
public void send(DatagramPacket p) throws IOException
4)释放资源
关闭 public void close()
public class UDP_Send {
public static void main(String[] args) throws IOException {
// 创建发送端的Socket对象
DatagramSocket ds = new DatagramSocket() ;
//2)创建数据报包对象:DatagramPacket此类表示数据报包
/**
* public DatagramPacket(byte[] buf,包数据
* int length, 包长度
* InetAddress address, 目标地址
* int port) 端口号
*/
byte[] bytes = "hello,udp,我来了".getBytes() ;
/*int length = bytes.length ;
InetAddress inetAddress = InetAddress.getByName("10.12.156.196");
int port = 8888 ;*/
//DatagramPacket dp = new DatagramPacket(bytes,length,inetAddress,port) ;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,InetAddress.getByName("10.12.156.196"),8888);
//3)发送
ds.send(dp);
//4)释放资源
ds.close();
}
}
UDP接收端的使用步骤
UDP接收端的使用步骤
1)创建接收端的Socket
public DatagramSocket(int port) throws SocketException
2)创建一个接收容器:数据报包 (并非真实数据)
public DatagramPacket(byte[] buf,int length)
参数1:缓冲区大小:长度1024/1024的倍数
参数2:长度
3)使用接收容器:接收数据
public void receive(DatagramPacket p) throws IOException
4)从缓冲区中获取真实数据(解析)
public byte[] getData():实际字节数组
public int getLength():实际长度
5)展示...哪一个ip发送过来的数据 new String(实际字节数组,0,实际长度)
6)接收端---->关闭
接收端一般一直开启,不关闭并且-先启动接收端,在启动发送端!
public class UDP_Receive {
public static void main(String[] args) throws IOException {
//1)创建接收端的Socket
//public DatagramSocket(int port) throws SocketException
DatagramSocket ds = new DatagramSocket(8888) ;
//2)创建一个接收容器:数据报包 (并非真实数据)
byte[] bytes = new byte[1024] ;
int length = bytes.length ;
DatagramPacket dp = new DatagramPacket(bytes,length) ;
//3)接收数据
ds.receive(dp);
//4)解析容器中的数据
//public byte[] getData():实际字节数组
//public int getLength():实际长度
byte[] buffer = dp.getData();
int buffeLength = dp.getLength();
//获取传递过来的数据
String dataStr = new String(buffer,0,buffeLength) ;
//获取ip地址字符串形式
//public InetAddress getAddress()
//public String gethostAddress()
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from --"+ip+",data is-->"+dataStr);
//关闭
ds.close();
}
}
优化
优化:
需要发送端,键盘录入数据(BufferedReader的readLine())不断录入数据,并且自定义结束标记"886",
接数收端不关闭,不断的接收据
udp一个窗口进行聊天,ChatRoom类 (简易版)
开启两条线程
发送端线程
接收端的线程
public class SendTest {
public static void main(String[] args) {
//发送端的Socket
DatagramSocket ds = null ;
try {
ds = new DatagramSocket() ;
//数据报包:把数据放在包中
//利用BufferedReader的readLine
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)) ;
String line = null ;
while((line=br.readLine())!=null){
//结束标记
if("886".equals(line)){
break ;
}
//创建DatagramPacket
InetAddress inetAddress = InetAddress.getByName("10.12.156.196");
DatagramPacket dp = new DatagramPacket(line.getBytes(),line.getBytes().length,
inetAddress,10086) ;
//发送
ds.send(dp);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(ds!=null){
ds.close();
}
}
}
}
//接收端不关闭(真实场景)
public class ReceiveTest {
public static void main(String[] args) {
//创建接收端的Socket对象
try {
DatagramSocket ds = new DatagramSocket(10086) ;
while(true){
//创建接收容器
byte[] bytes = new byte[1024] ;
int length = bytes.length ;
DatagramPacket dp = new DatagramPacket(bytes,length) ;
//接收数据
ds.receive(dp);
//解析数据
String dataStr = new String(dp.getData(),0,dp.getLength()) ;
//获取ip地址
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from --->"+ip+",data is--->"+dataStr);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
// //不关闭...
}
}
}
基于TCP的网络编程
开发步骤
开发步骤:
建立通信连接(会话):
创建ServerSocket,指定端口号
调用accept等待客户端接入
客户端请求服务器:
创建Socket,指定服务器IP+端口号
使用输出流,发送请求数据给服务器
使用输入流,接受响应数据到客户端(等待)
服务器响应客户端
使用输入流,接受请求数据到服务器(等待)
使用输出流,发送响应数据给客户端
TCP客户端的使用步骤
TCP客户端(需要建立连接通道) 使用步骤
1)创建客户端所在的Socket对象
public Socket(String host,int port)throws UnknownHostException,IOException
参数1:主机名称/ip地址字符串表现形式
参数2:端口号
2)写数据到服务器端
public OutputStream getOutputStream() throws IOException:获取连接通道内的输出流对象
写数据write:字节
3)释放资源
关闭socket对象所在的系统资源
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1)创建客户端所在的Socket对象
Socket socket = new Socket("10.12.156.196",6666) ;
//2)获取连接通道内的输出流对象
// public OutputStream getOutputStream() throws IOException:
OutputStream out = socket.getOutputStream();
//写数据到流中
out.write("hello TCP,我来了".getBytes());
//3)释放资源
socket.close();
}
}
TCP服务端的使用步骤
TCP服务端的使用步骤:
1)创建服务器端所在的Socket:此类实现服务器套接字
public ServerSocket(int port) throws IOException:
创建服务器端socket对象同时监听某个端口
2)侦听客户端连接 (阻塞式方法:一旦客户端绑定的端口号和当前服务器端的端口不一致,永远等待..)
public Socket accept() throws IOException :返回值就是被侦听到的客户端对象
3)通过客户端获取通道内的输入流对象
public InputStream getInputStream() throws IOException返回此套接字的输入流。
要么一次读取一个字节/一次读取一个字节数组
4)展示数据并且释放资源
注意错误:
启动一次,它就等待监听客户端;
服务器端不要启动多次----BindException:绑定异常: Address already in user:端口号被占用!
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1)创建服务器端所在的Socket:此类实现服务器套接字
ServerSocket ss = new ServerSocket(6666) ;
//2)侦听客户端连接
Socket socket = ss.accept();
//一旦侦听到客户端--获取通道内的输入流对象
InputStream inputStream = socket.getInputStream();
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = inputStream.read(bytes); //获取字节数
//展示接收到的数据
String clientMsg = new String(bytes,0,len) ;
//获取客户端ip地址
//public InetAddress getInetAddress()
InetAddress inetAddress = socket.getInetAddress();
String ip = inetAddress.getHostAddress() ;
System.out.println("data from--->"+ip+",data is-->"+clientMsg);
//释放资源
ss.close();
}
}
加入响应
//客户端发送消息,服务器端接收消息之后展示,并且给客户端响应数据,客户端将响应的数据可以展示
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建一个客户端的Socket对象
Socket socket = new Socket("10.12.156.196",12306) ;
//获取通道内输出流,给服务器端写过去
OutputStream outputStream = socket.getOutputStream();
outputStream.write("ServerSocket,你好".getBytes());
//获取服务器响应的数据
//获取通道内的输入流对象
InputStream inputStream = socket.getInputStream();
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = inputStream.read(bytes) ;
String responseMsg = new String(bytes,0,len) ;
System.out.println("responseMsg:"+responseMsg);
//关闭资源
socket.close();
}
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器端的Socket对象
ServerSocket ss = new ServerSocket(12306) ;
//监听
Socket socket = ss.accept();
//获取到客户端的通道内输入流对象
InputStream in = socket.getInputStream();
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = in.read(bytes) ;
String sendMsg = new String(bytes,0,len) ;
System.out.println("data from--->"+socket.getInetAddress().getHostAddress()+",data is-->"+sendMsg);
//服务器端响应给客户端数据
//获取监听客户端所在的通道内输出流对象
OutputStream outputStream = socket.getOutputStream();
outputStream.write("socket,你好,数据已经是收到".getBytes());
//释放资源
ss.close();
}
}
UDP和TCP协议的区别
UDP是一种不可靠连接,不安全---执行效率高
UDP不需要建立连接通道,通过一种数据报包(DatagramPacket)的形式,发送数据/接收数据
UDP有文件大小限制
TCP是一种可靠连接,安全---执行效率低
TCP是需要建立连接通道,通过一种最基本的OutputStream/InputStream
TCP相对UDP没有具体的限制
题目
键盘录入…
//TCP客户端:不断键盘录入数据,服务器将数据复制到当前项目下的Copy.java文件中
public class ClientTest {
public static void main(String[] args) throws IOException {
//1)客户端Socket对象
Socket socket = new Socket("10.12.156.196",8888) ;
//2)键盘录入数据:使用BufferedReader的readLine:读取一行
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)) ;
//3)获取当前客户端所在通道内的输出流:OutputStream getOutputStream()
// OutputStream out = socket.getOutputStream();
//封装通道内的流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())) ;
String line = null ;
while((line=br.readLine())!=null){
if("over".equals(line)){
break ;
}
//录入一行数据,将数据写入到封装的输出流中
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
br.close();
socket.close();
}
}
public class ServerTest {
public static void main(String[] args) throws IOException {
//服务器端是Socket
ServerSocket ss = new ServerSocket(8888) ;
Socket socket = ss.accept();
//封装通道内输入流
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())) ;
//创建字符缓冲输出流对象,输出指定文件copy.java
BufferedWriter bw = new BufferedWriter(new FileWriter("copy.txt")) ;
//一次读取一行数据
String line = null ;
while((line=br.readLine())!=null){
//使用BufferedWriter写入到指定的文件中
bw.write(line);
bw.newLine();
bw.flush();
}
bw.close();
//服务器不关闭
}
}
操作文本文件遇到阻塞问题
/**
* TCP的客户端的操作ClientDemo.java文件
* 需要使用BufferedReader进行读取,将文件内容写入到通道内的输出流中
*
* 服务器端将文件内容获取到并且复制到当前项目下Copy.java文件中
*
* 条件:
* 文件复制完毕,服务器端给客户端进行反馈,客户端将反馈的消息打印出来!
*
*
* 问题:服务器端和客户端都正在执行,程序就是没有结束!
*
* 原因:
*
* 读取某个文本文件
* readLine() 返回值为null,表示文件已经读取完毕 阻塞式方法
* 返回值已经null,但是null不能用来判断通道内的输出是否已经写入完毕
*
*
* 文件已经读取完毕,服务器不知道!
*
* 针对服务器端:需要通过BufferedReader来进行读取复制文件内容,readLine():返回为null,文件复制完毕,
* 等待服务器反馈!
*
*
* 解决方案:
* 1)在客户端写入完毕,自定义结束标记"886"/"over",服务器端读到这个标记的,结束!
* (推荐) 2)public void shutdownOutput() throws IOException:在客户端写入完毕的时候,调用这个功能,告诉服务器,没有内容需要写入到流中
*
*
*
*
*
*/
public class ClientTest {
public static void main(String[] args) throws IOException {
//客户端Socket
Socket socket = new Socket("10.12.156.196",6666) ; //一旦创建对象创建完毕,已经和服务器端连接
//创建字符缓冲输入流,读取指定的文件
BufferedReader br = new BufferedReader(new FileReader("ClientDemo.java")) ;
//封装通道内 的输出流对象,将文件内容进行写入到流对象汇总
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())) ;
String line = null ;
while((line=br.readLine())!=null){ //readLine() 返回值为null,表示文件已经读取完毕 阻塞式方法
//返回值已经null,但是null不能用来判断通道内的输出是否已经写入完毕
bw.write(line);
bw.newLine();
bw.flush();
}
//告诉服务器端,文件写入完毕
//自定义标记
/* bw.write("over");
bw.newLine();
bw.flush();*/
//public void shutdownOutput() throws IOException
socket.shutdownOutput();
//接收服务器端的反馈
//获取通道的内的输入流,读取
//客户端也不知道服务器端是否将文件已经复制完毕
BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
String serverMsg = br2.readLine();
System.out.println("serverMsg:"+serverMsg);
//释放资源
br.close();
socket.close();
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerTest {
public static void main(String[] args) throws IOException {
//服务器端是Socket
ServerSocket ss = new ServerSocket(6666) ;
Socket socket = ss.accept();//阻塞式方法: 如果一旦某个客户端端口号跟主机的端口号一致,可以被服务器监听
//封装通道内输入流
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())) ;
//创建字符缓冲输出流对象,输出指定文件copy.java
BufferedWriter bw = new BufferedWriter(new FileWriter("copy.java")) ;
//一次读取一行数据
String line = null ;
while((line=br.readLine())!=null){ //阻塞式方法
//使用BufferedWriter写入到指定的文件中
//读取标记:
/*if("over".equals(line)){
break ;
}*/
bw.write(line);
bw.newLine();
bw.flush();
}
//服务器端的响应动作
//继续获取通道内的输出流,写给客户端
BufferedWriter bw2 = new BufferedWriter( //给客户端反馈
new OutputStreamWriter(socket.getOutputStream())) ;
bw2.write("服务器已经将数据复制完毕");
bw2.newLine();
bw2.flush();
bw.close();
//服务器不关闭
ss.close();
}
}
操作图片文件遇到图片缺失问题
/**
* TCP客户端的有一个图片文件高圆圆.jpg,
* 服务器端将图片内容进行复制---当前项目下:mv.jpg,并加入反馈操作(服务器端给客户端反馈!)
*
* 图片复制完毕,但是缺失,对于缓冲的字节数,通过字节流也可以刷新的
* public void flush()
* throws IOException :强制刷新此缓冲区字节数!
*
*/
public class UploadClient {
public static void main(String[] args) throws IOException {
//客户端
Socket socket = new Socket("10.12.156.196",2222) ;
//创建BufferedInputStream
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("高圆圆.jpg")) ;
//封装通道内的输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){
//将内容写入到通道内的输出流中
bos.write(bytes,0,len);
bos.flush();
}
//通知:服务器端:当前流数据没有可写内容了
socket.shutdownOutput();
//读取反馈
//获取通过内的输入流
InputStream in = socket.getInputStream();
byte[] bytes2 = new byte[1024] ;
int len2 = in.read(bytes2) ;
String severMsg = new String(bytes2,0,len2) ;
System.out.println("serverMsg:"+severMsg);
//释放资源
bis.close();
socket.close();
}
}
public class UploadServer {
public static void main(String[] args) throws IOException {
//服务器端的Socket
ServerSocket ss = new ServerSocket(2222) ;
Socket socket = ss.accept() ;
//封装通道内输入流对象
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()) ;
//创建输出流 -----mv.jpg
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mv.jpg")) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
bos.flush();
}
//反馈
//获取通道内输出流
OutputStream out = socket.getOutputStream();
out.write("图片已经复制完毕".getBytes());
bos.flush();
//释放资源
bos.close();
ss.close();
}
}
反射
类的加载
类对象
类的对象:基于某个类new出来的对象,也称为实例对象
类对象:类加载的产物,封装了一个类的所有信息(类名,父类,接口,属性,方法,构造方法)
类加载
类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象。
连接
验证是否有正确的内部结构,并和其他类协调一致 准备 负责为类的静态成员分配内存,并设置默认初始化值 解析 将类的二进制数据中的符号引用替换为直接引用
类初始化时机
创建类的实例
访问类的静态变量,或者为静态变量赋值
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
类加载器
类加载器
负责将.class文件加载到内在中,并为之生成对应的Class对象。
类加载器的组成
Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。 在JDK中JRE的lib目录下ext目录
Sysetm ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
什么是反射
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调 用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java代码经历的三个阶段
1)SORUCE:源码阶段
2)CLASS:编译阶段 ----->Class:字节码文件对象
3)RUNNABLE:运行阶段
获取一个字节码文件对象的方式有几种
三种:
Object类的getClass()---->Class<T>
任意Java类型的class属性--->Class<T>
Class类中静态功能:forName("类的全限定名称") ;
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//创建一个Person类对象
Person p1 = new Person() ;
//第一种方式获取字节码文件对象
System.out.println(p1.getClass());//class com.qf_reflect_08.Person
System.out.println(p1.getClass().getName());//com.qf_reflect_08.Person
System.out.println("--------------------------");
//第二种方式获取
Class<Person> c = Person.class ;
System.out.println(c);//class com.qf_reflect_08.Person
System.out.println("--------------------------");
//public static Class<?> forName(String className) throws ClassNotFoundException
//注意事项:参数为字符串:全限定名从:包名.类名
Class c2 = Class.forName("com.qf_reflect_08.Person") ;
System.out.println(c2);
System.out.println(c2.getName());//com.qf_reflect_08.Person
}
}
使用反射的方式创建某个类的实例
1)获取当前类的字节码文件对象:Class.forName("类的全限定名称")--->Class clazz
2)当前类的构造方法:无参/公共权限 :构造器对象所代表的的指定的公共的构造方法
Constructor con = clazz.getConstructor() ;
public 包名.类名类名()
//如果是私有的构造方法/默认的/受保护的
con.setAccessiable(true) ;
3)创建该类实例: con.newInstance()--->Object obj :当前该类的实例!
public Constructor<?>[] getConstructors() throws SecurityException:获取公共的构造器对象
public Constructor<?>[] getDeclaredConstructors():获取的是所有的,包含私有,公共,受保护的,默认的.
public Constructor<T> getConstructor(Class<?>... parameterTypes):获取指定的公共的构造器对象,
public T newInstance(Object... initargs)
返回值就是当前类的实例 ,参数参数:需要给构造器中传递的实际参数,没有参数,空参
public class RefectTest {
public static void main(String[] args) throws Exception {
//1)获取当前Person类所在的字节码文件对象
Class c = Class.forName("com.qf_reflect_08.Person") ;//class com.qf_reflect_08.Person
//2)public Constructor<?>[] getConstructors() throws SecurityException:获取公共的构造器对象
//public Constructor<?>[] getDeclaredConstructors():获取的是所有的,包含私有,公共,受保护的,默认的.
// Constructor[] con = c.getConstructors();
// Constructor[] con = c.getConstructors();
// Constructor[] con = c.getDeclaredConstructors() ;
//遍历
/* for(Constructor constructor:con){
System.out.println(constructor);*/
/*
公共的构造器对象
* public com.qf_reflect_08.Person(java.lang.String)
public com.qf_reflect_08.Person()
所有的
com.qf_reflect_08.Person(java.lang.String,int,java.lang.String)
private com.qf_reflect_08.Person(java.lang.String,int)
public com.qf_reflect_08.Person(java.lang.String)
public com.qf_reflect_08.Person()
* */
//}
//获取的单个的构造器对象: 公共的
//public Constructor<T> getConstructor(Class<?>... parameterTypes):获取指定的公共的构造器对象,
//参数:形式参数类型 的字节码文件对象:类名.class
// Constructor con = c.getConstructor(); //空参构造对象
Constructor con = c.getConstructor(String.class);
//System.out.println(con);//public com.qf_reflect_08.Person()
//通过构造器对创建Person类的实例
//public T newInstance(Object... initargs):
//返回值就是当前类的实例 ,参数参数:需要给构造器中传递的实际参数,没有参数,空参!
// Object obj = con.newInstance();
// System.out.println(obj);
Object obj = con.newInstance("高圆圆") ;
System.out.println(obj);
System.out.println("---------------------------");
Person p = new Person("高圆圆") ;
System.out.println(p);
}
}
访问私有成员
IllegalAccessException:非法访问异常:这个类不能访问Person私有的成员
AccessibleObject 类是 Field、Method 和 Constructor 对象的基类
public void setAccessible(boolean flag):参数为true:取消Java语言访问检查:暴力访问
/**
* 通过私有的构造方法创建Person类的实例,并且给成员信息赋值
*/
public class ReflectTest2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Person类的字节码对象
Class clazz = Class.forName("com.qf_reflect_08.Person");
//2)获取指定的构造器对象所代表的构造方法
//getDeclaredConstructor(Class<T>...parameterClass)
//private Person(String name,int age):
// Constructor con = clazz.getDeclaredConstructor(String.class,int.class) ;
Constructor con = clazz.getDeclaredConstructor(String.class,int.class,String.class) ;
// System.out.println(con);//private com.qf_reflect_08.Person(java.lang.String,int)
con.setAccessible(true);
//通过构造器对象所代表的指定的这个构造方法创建该类实例
Object obj = con.newInstance("高圆圆", 30,"鄠邑区");
//IllegalAccessException:非法访问异常:这个类不能访问Person私有的成员
//AccessibleObject 类是 Field、Method 和 Constructor 对象的基类
//public void setAccessible(boolean flag):参数为true:取消Java语言访问检查:暴力访问
System.out.println(obj);//实例
/**
* Person p = new Person("高圆圆",30) ; //编译通过不了,私有的构造方法
*
*/
}
}
通过反射获取Field对象所代表的成员变量,并且为其赋值
public Field[] getFields():获取当前类/接口的公共字段
public Field[] getDeclaredFields():获取当前类/接口所声明的所有的字段
public Field getDeclaredField(String name):获取指定的字段
public Field getField(String name):获取单个的公共的字段
形式参数:就是当前字段名称
public void set(Object obj,Object value): ----类似于:p.name = "高圆圆";
参数1:当前类的实例,参数2:当前这个赋的实际值
1)获取当前类的字节码文件对象:
Class.forName("类的全限定名称")--->Class clazz
//创建该类的实例
clazz.newInstance()---->Object obj ---->Person p = new Person() ;
2)clazz.getField("公共的字段名称") ;----->Field f1
3)直接赋值: f1.set(obj,"实际参数") ;
public class ReflectTest3 {
public static void main(String[] args) throws
ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException, NoSuchFieldException {
//1)获取当前Person类字节码文件对象
Class personClazz = Class.forName("com.qf_reflect_08.Person");
//2)public Field[] getFields():获取当前类/接口的公共字段
//public Field[] getDeclaredFields():获取当前类/接口所声明的所有的字段
// Field[] fields = personClazz.getFields();
// Field[] fields = personClazz.getDeclaredFields();
//
// for(Field field:fields){
// System.out.println(field);
//public java.lang.String com.qf_reflect_08.Person.address
/**
* private java.lang.String com.qf_reflect_08.Person.name
* int com.qf_reflect_08.Person.age
* public java.lang.String com.qf_reflect_08.Person.address
*/
//}
//获取公共的构造器对象所代表的构造方法:无参/公共的
Constructor con = personClazz.getConstructor();
//通过构造器对象创建该类的实例
Object obj = con.newInstance() ;//Person p = new Person() ;
System.out.println(obj);
//获取单个并且指定的构造方法:
//public Field getDeclaredField(String name):获取指定的字段
//public Field getField(String name):获取单个的公共的字段
//形式参数:就是当前字段名称
Field nameField = personClazz.getDeclaredField("name");
//public void set(Object obj,Object value): ----类似于:p.name = "高圆圆";
//参数1:当前类的实例,参数2:当前这个赋的实际值
nameField.setAccessible(true);//取消Java语言访问检查
nameField.set(obj,"高圆圆"); //name私有的字段
System.out.println(obj);
System.out.println("-----------------------------");
//获取指定的age字段
Field ageField = personClazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(obj,30);
System.out.println(obj);
System.out.println("-----------------------------");
//获取address字段:公共的
Field addressField = personClazz.getField("address");
addressField.set(obj,"鄠邑区");
System.out.println(obj);
System.out.println("---------------------------------------");
//类对象的创建----Class.forName("全限定名名称")----> Constructor 创建该类实例
//类对象的创建----Class.forName("全限定名名称")----> 直接创建该类实例
//public T newInstance()
Class clazz = Class.forName("com.qf_reflect_08.Person");
System.out.println(clazz.newInstance()); //Person{name='null', age=0, address='null'}
}
}
通过反射获取Method对象所代表的成员方法并调用这个方法
public Method[] getMethods():获取当前类/接口中的所有公共的成员方法,包括父类/父接口
public Method[] getDeclaredMethods():获取当前类/接口执行的成员方法:公共的,私有的,默认的,受保护的
获取单个并且指定的公共的构造方法并调用
public Method getMethod(String name,Class<?>... parameterTypes)
参数1:方法名称
参数2:当前这个方法的形式参数类型的class属性 举例:带一个String类型的参数:String.class
调用方法:底层的调用方式
public Object invoke(Object obj,Object... args)
参数1:当前类/接口的实例
参数2:是对方法的形式参数赋值
返回值:如果当前方法有返回结果,返回,没有的话,直接单独调用
1)获取当前类的字节码文件对象:
Class.forName("类的全限定名称")--->Class clazz
//创建该类的实例
clazz.newInstance()---->Object obj Person p = new Person() ;
2)获取指定的公共的Method对象所代表的的成员方法
//clazz.getMethod("方法名",形式参数类型的class) ---->p.show() ;
clazz.getMethod("show",String.class) ;---->Method m
3)调用这个方法:invoke(当前类的实例,如果有形式参数--赋值实际参数)
m.invoke(obj,"hello,javaEE") ; :如果当前成员方法有返回值类型,这个时候调用的时候
Object o = m.invoke(obj,"hello,javaEE") ;
public class ReflectTest4 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1)获取Person类的字节码文件对象
Class personClazz = Class.forName("com.qf_reflect_08.Person") ;
//2)public Method[] getMethods():获取当前类/接口中的所有公共的成员方法,包括父类/父接口
//2)public Method[] getDeclaredMethods():获取当前类/接口执行的成员方法:公共的,私有的,默认的,受保护的
// Method[] methods = personClazz.getMethods();
/* Method[] methods = personClazz.getDeclaredMethods();
for(Method method:methods){
System.out.println(method);
}*/
Constructor con = personClazz.getConstructor() ;
Object obj = con.newInstance() ;//Person p = new Person() ;
//获取单个并且指定的公共的构造方法并调用
//public Method getMethod(String name,Class<?>... parameterTypes)
//参数1:方法名称
//参数2:当前这个方法的形式参数类型的class属性 举例:带一个String类型的参数:String.class
Method m1 = personClazz.getMethod("show");//p.show() ;
//调用方法:底层的调用方式
//public Object invoke(Object obj,Object... args)
//参数1:当前类/接口的实例
//参数2:是对方法的形式参数赋值
//返回值:如果当前方法有返回结果,返回,没有的话,直接单独调用
m1.invoke(obj) ;
System.out.println("---------------------------------------------");
//调用method():私有的
Method m2 = personClazz.getDeclaredMethod("method", String.class);
// System.out.println(m2);
//取消Java语言访问检查
m2.setAccessible(true);
m2.invoke(obj,"Mysql..") ;
System.out.println("-------------------------------------");
//function()调用
Method m3 = personClazz.getMethod("function");
Object object = m3.invoke(obj);//返回的结果
System.out.println(object);
}
}
反射应用举例
1.通过配置文件(name.txt)运行类中的方法
1. 反射应用
Student类中有love方法
Teacher类中有love方法
随着需求不断变化---现有一个.txt的配置文件:name.txt
className=包名.类名
methodName=love
使用所学的东西将文件读取并获取文件中的键对应的值,然后使用反射创建该类实例,调用方法
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class Test02 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Properties prop = new Properties();
prop.load(new FileReader("name.txt"));
String classname = prop.getProperty("className");
String methodname = prop.getProperty("methodName");
Class clas = Class.forName(classname);
Constructor con = clas.getConstructor();
Object obj = con.newInstance();
Method m = clas.getDeclaredMethod(methodname);
m.invoke(obj);
}
}
public class Student {
public void love(){
System.out.println("love Java...");
}
}
public class Teacher {
public void love(){
System.out.println("love 高圆圆...");
}
}
2,通过配置文件(name.properties)运行类中的方法
2.读取name.properties配置文件,并获取文件中的键对应的值,然后使用反射创建该类实例,调用方法
xxx.properties/xxx.xml/xxx.yml(springboot)
都放在src目录下:类路径下
1)获取当前类的字节码文件对象
2)通过当前类的字节码文件对象:public ClassLoader getClassLoader() 当前这个类的加载器
3)ClassLoader:类加载器
public InputStream getResourceAsStream(String name):获取当前路径下中指定的资源文件的输入流对象
4)创建Properties属性集合列表对象
load(InputStream in)
5)获取key对应的value---反射创建实例,调用方法....
public class Test2 {
public static void main(String[] args) throws Exception {
// 1)在哪个类下去读取这个配置文件:获取当前类的字节码文件对象
Class clazz = Test2.class ;
// 2)通过当前类的字节码文件对象:public ClassLoader getClassLoader() 当前这个类的加载器
ClassLoader classLoader = clazz.getClassLoader();
//3) public InputStream getResourceAsStream(String name):获取当前路径下中指定的资源文件的输入流对象
InputStream inputStream = classLoader.getResourceAsStream("name.properties");
//4)创建Properties属性集合列表对象
Properties prop = new Properties() ;
//加载
prop.load(inputStream);
// System.out.println(prop);
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//反射创建该类实例
Class c = Class.forName(className) ;
Object obj = c.newInstance();
//获取Method对象--调用
Method method = c.getMethod(methodName);
method.invoke(obj) ;
}
}
3.ArrayList,有一些字符串元素,如何给ArrayList中添加Integer元素呢?
public class Test1 {
public static void main(String[] args) throws Exception {
//创建ArrayList集合对象
ArrayList<String> array = new ArrayList<String>() ; //明确数据类型:防止运行时期异常
array.add("hello") ;
array.add("world") ;
array.add("java") ;
//array.add(100) ; //直接添加不了
//通过反射的方式去完成
//获取ArrayList集合的字节码文件对象
Class clazz = array.getClass();
// System.out.println(clazz);//class java.util.ArrayList
//通过反射的方式:调用public boolean add(E e) {}
//通过反射获取Method对象所代表的的成员方法
//getMethod("方法名",形式参数类型的class属性)---->Method m
Method method = clazz.getMethod("add", Object.class) ;
//调用这个方法
method.invoke(array,100) ;
method.invoke(array,10) ;
method.invoke(array,30) ;
System.out.println(array);
}
}
动态代理
代理模式
代理模式:
静态代理----- Thread implements Runnable接口
真实角色和代理角色:实现同一个接口
动态代理----->
在程序执行过程中:产生的代理对象
代理角色---对当前真实角色的功能进行增强!
jdk动态代理:基于接口
cglib动态代理:基于子类
jdk动态代理
java.lang.reflect.Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。---->产生代理角色
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:代理类的加载器
参数2:代理类要实现的接口列表
参数3:代理实例的处理程序
返回结果:带有代理类的指定调用处理程序的代理实例
mybatis 里面动态代理---实质就是Proxy作为父类间接实现代理
public class Test {
public static void main(String[] args) {
//接口多态
UserDao ud = new UserDaoImpl() ; //目标角色
ud.add();
ud.delete();
ud.update();
ud.find();
System.out.println("---------------------------------");
//优化:需要使用jdk动态代理:在程序运行的时候,产生代理角色---UserDao的代理角色
//获取基于代理处理程序---产生的代理类
UserDao ud2 = new UserDaoImpl() ;
//接口多态
InvocationHandler handler = new MyInvocationHandler(ud2) ;
UserDao udProxy = (UserDao) Proxy.newProxyInstance(ud2.getClass().getClassLoader(),
ud2.getClass().getInterfaces(), handler);
//udProxy----代理角色
udProxy.add();
udProxy.delete();
udProxy.update();
udProxy.find();
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//代理的处理程序
public class MyInvocationHandler implements InvocationHandler {
//声明目标角色
private Object target ; //UserDao
public MyInvocationHandler(Object target){
this.target = target ;
}
//对真实角色进行增强
//method----基于代理的底层方法:add,delete,update,find...
//args:参数--->这些方法的 对象数组
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("权限校验...");
Object obj = method.invoke(target, args); //代理角色
System.out.println("产生日志记录");
return obj;
}
}
//持久层接口---用户操作
public interface UserDao {
void add() ;//添加
void update(); //修改
void delete() ;//删除
void find() ;//查询
}
//接口的实现层
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("添加用户...");
}
@Override
public void update() {
System.out.println("修改用户...");
}
@Override
public void delete() {
System.out.println("删除用户...");
}
@Override
public void find() {
System.out.println("查询用户...");
}
}
p.getProperty(“methodName”);
Class clas = Class.forName(classname);
Constructor con = clas.getConstructor();
Object obj = con.newInstance();
Method m = clas.getDeclaredMethod(methodname);
m.invoke(obj);
}
}
public class Student {
public void love(){
System.out.println("love Java...");
}
}
public class Teacher {
public void love(){
System.out.println("love 高圆圆...");
}
}
#### 2,**通过配置文件(name.properties)运行类中的方法**
```java
2.读取name.properties配置文件,并获取文件中的键对应的值,然后使用反射创建该类实例,调用方法
xxx.properties/xxx.xml/xxx.yml(springboot)
都放在src目录下:类路径下
1)获取当前类的字节码文件对象
2)通过当前类的字节码文件对象:public ClassLoader getClassLoader() 当前这个类的加载器
3)ClassLoader:类加载器
public InputStream getResourceAsStream(String name):获取当前路径下中指定的资源文件的输入流对象
4)创建Properties属性集合列表对象
load(InputStream in)
5)获取key对应的value---反射创建实例,调用方法....
public class Test2 {
public static void main(String[] args) throws Exception {
// 1)在哪个类下去读取这个配置文件:获取当前类的字节码文件对象
Class clazz = Test2.class ;
// 2)通过当前类的字节码文件对象:public ClassLoader getClassLoader() 当前这个类的加载器
ClassLoader classLoader = clazz.getClassLoader();
//3) public InputStream getResourceAsStream(String name):获取当前路径下中指定的资源文件的输入流对象
InputStream inputStream = classLoader.getResourceAsStream("name.properties");
//4)创建Properties属性集合列表对象
Properties prop = new Properties() ;
//加载
prop.load(inputStream);
// System.out.println(prop);
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//反射创建该类实例
Class c = Class.forName(className) ;
Object obj = c.newInstance();
//获取Method对象--调用
Method method = c.getMethod(methodName);
method.invoke(obj) ;
}
}
3.ArrayList,有一些字符串元素,如何给ArrayList中添加Integer元素呢?
public class Test1 {
public static void main(String[] args) throws Exception {
//创建ArrayList集合对象
ArrayList<String> array = new ArrayList<String>() ; //明确数据类型:防止运行时期异常
array.add("hello") ;
array.add("world") ;
array.add("java") ;
//array.add(100) ; //直接添加不了
//通过反射的方式去完成
//获取ArrayList集合的字节码文件对象
Class clazz = array.getClass();
// System.out.println(clazz);//class java.util.ArrayList
//通过反射的方式:调用public boolean add(E e) {}
//通过反射获取Method对象所代表的的成员方法
//getMethod("方法名",形式参数类型的class属性)---->Method m
Method method = clazz.getMethod("add", Object.class) ;
//调用这个方法
method.invoke(array,100) ;
method.invoke(array,10) ;
method.invoke(array,30) ;
System.out.println(array);
}
}
动态代理
代理模式
代理模式:
静态代理----- Thread implements Runnable接口
真实角色和代理角色:实现同一个接口
动态代理----->
在程序执行过程中:产生的代理对象
代理角色---对当前真实角色的功能进行增强!
jdk动态代理:基于接口
cglib动态代理:基于子类
jdk动态代理
java.lang.reflect.Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。---->产生代理角色
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:代理类的加载器
参数2:代理类要实现的接口列表
参数3:代理实例的处理程序
返回结果:带有代理类的指定调用处理程序的代理实例
mybatis 里面动态代理---实质就是Proxy作为父类间接实现代理
public class Test {
public static void main(String[] args) {
//接口多态
UserDao ud = new UserDaoImpl() ; //目标角色
ud.add();
ud.delete();
ud.update();
ud.find();
System.out.println("---------------------------------");
//优化:需要使用jdk动态代理:在程序运行的时候,产生代理角色---UserDao的代理角色
//获取基于代理处理程序---产生的代理类
UserDao ud2 = new UserDaoImpl() ;
//接口多态
InvocationHandler handler = new MyInvocationHandler(ud2) ;
UserDao udProxy = (UserDao) Proxy.newProxyInstance(ud2.getClass().getClassLoader(),
ud2.getClass().getInterfaces(), handler);
//udProxy----代理角色
udProxy.add();
udProxy.delete();
udProxy.update();
udProxy.find();
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//代理的处理程序
public class MyInvocationHandler implements InvocationHandler {
//声明目标角色
private Object target ; //UserDao
public MyInvocationHandler(Object target){
this.target = target ;
}
//对真实角色进行增强
//method----基于代理的底层方法:add,delete,update,find...
//args:参数--->这些方法的 对象数组
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("权限校验...");
Object obj = method.invoke(target, args); //代理角色
System.out.println("产生日志记录");
return obj;
}
}
//持久层接口---用户操作
public interface UserDao {
void add() ;//添加
void update(); //修改
void delete() ;//删除
void find() ;//查询
}
//接口的实现层
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("添加用户...");
}
@Override
public void update() {
System.out.println("修改用户...");
}
@Override
public void delete() {
System.out.println("删除用户...");
}
@Override
public void find() {
System.out.println("查询用户...");
}
}