Java重温之路(下卷)

文章目录

第11章 多线程

多线程基础

1、线程相关概念

1.1程序(program)?

是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码

1.2进程

1.进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
2.进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程

1.3什么是线程

在这里插入图片描述

1.线程由进程创建的,是进程的一个实体。
2.一个进程可以拥有多个线程。
1.4 其他相关概念
1.单线程:同一个时刻,只允许执行一个线程
2.多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
3.并发:同一个时刻,多个任务/指令在单个cpu上交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发。
4.并行:同一个时刻,多个任务/指令在多个cpu同时执行。可以说是并发和并行。

2、线程基本使用

2.1创建线程的两种方式

在这里插入图片描述
在这里插入图片描述
提示:可以使用“jconsole”命令查看进程的运行状况

2.2线程应用案例1-继承Thread类

package hsp.thread_;

//通过继承 Thread 类创建线程
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        //创建 Cat 对象,可以当做线程使用
        Cat cat = new Cat();
        cat.start();//启动线程->最终会执行cat的run方法
        //cat.run();//run 方法就是一个普通的方法, 没有真正的启动一个线程,就会把 run 方法执行完毕,才向下执行
        //说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行

        //下面主线程和子线程是交替执行
        for (int i = 1; i <= 60; i++) {
            System.out.println("我是主线程-->"+i+"\t线程名:"+Thread.currentThread().getName());
            Thread.sleep(1000);
        }
         /*源码:
             (1)
                public synchronized void start() {
                    start0();
                }
            (2)
                //start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现
                //真正实现多线程的效果, 是 start0(), 而不是 run
                private native void start0()
        */
    }
}

class Cat extends Thread {

    private int count = 0;

    //1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
    //2. 我们会重写 run 方法,写上自己的业务代码
    //3. run Thread 类 实现了 Runnable 接口的 run 方法
    /*源码:
    @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    */
    @SuppressWarnings({"all"})
    @Override
    public void run() {

        while (true){
            System.out.println("我是自定义线程-->"+(++count)+"\t线程名:"+Thread.currentThread().getName());
            try {
                //线程休眠1s,ctrl+alt+t
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (this.count==30){ //十次后让线程退出
                break;
            }
        }
    }
}

2.3线程应用案例2-实现Runnable接口

package hsp.thread_;

//通过实现接口Runnable来开发线程
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();

        //dog.start(); 这里不能调用 start
        //创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
        Thread thread = new Thread(dog);
        thread.start();
    }
}

class Dog implements Runnable{
    private int times = 0;
    @Override
    public void run() {
        while (true){

            try {
                Thread.sleep(1000);
                System.out.println("Runnable线程"+(++times)+"\t"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            if(times==6){
                break;
            }
        }
    }
}

2.4 线程使用应用案例-多线程执行

package hsp.thread_;

public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        new Thread(t1).start();//启动第 1 个线程
        new Thread(t2).start();//启动第 1 个线程
    }
}

class T1 implements Runnable{
private int cur = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("T1线程输出:hi"+(++cur)+"\t"+"线程名:"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(cur==15){
                break;
            }
        }
    }
}
class T2 implements Runnable{
private int cur = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("T2线程输出:say"+(++cur)+"\t"+"线程名:"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(cur==10){
                break;
            }
        }
    }
}

3、 继承Thread vs实现Runnable的区别

1)从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用实现Runnable接口方式

4、线程终止

4.1基本说明

1.当线程完成任务后,会自动退出。
2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

package hsp.thread_;

public class ThreadStop {
    public static void main(String[] args) throws InterruptedException {
        Person person = new Person();
        person.start();

        //终止
        //希望在主线程控制 Person 线程的终止,去修改loop即可
        //让期退出run()方法,从而达到终止的目的---->通知方式

        System.out.println("main线程休眠10s...");
        Thread.sleep(10 * 1000);
        person.exit(false);
    }
}

class Person extends Thread {
    private int count = 0;
    private boolean loop = true;//控制线程变量

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println((++count) + "Person运行中...");
        }
    }

    public void exit(boolean b) {
        this.loop = b;
    }
}

5、线程常用方法

在这里插入图片描述

5.1 常用方法第一组

1.setName //设置线程名称,使之与参数name相同
2. getName //返回该线程的名称
3. start //使该线程开始执行;Java虚拟机底层调用该线程的start0()方法
4.run //调用线程对象 run方法;
4. setPriority //更改线程的优先级(1-10)
6. getPriority //获取线程的优先级
7.sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8.interrupt //中断线程

5.2 注意事项和细节

1.start底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程
2.线程优先级的范围
3. interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
4.sleep:线程的静态方法,使当前线程休眠

package hsp.thread_;


class ThreadMethod extends Thread {

    public static void main(String[] args) throws InterruptedException {
        T td = new T();
        td.setName("老大");
        td.setPriority(Thread.MIN_PRIORITY);
        td.start();//启动子线程

        //主线程开始输出后,就中断子线程
        for (int i = 1; i <= 5; i++) {
            Thread.sleep(1000);
            System.out.println("主线程开始做事情~~~"+i);
        }
        td.interrupt();//中断T的休眠

        System.out.println("默认优先级=" + Thread.currentThread().getPriority());//测试interrupt

    }

}

class T extends Thread { //自定义线程类
    @Override
    public void run() {
        while (true){
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + "正在做事情~~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + "休眠中~~~");
                Thread.sleep(20 * 1000);

            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt方法时,就会catch一个 异常,可以加入自己的业务代码
                System.out.println(Thread.currentThread().getName() + "被interrupt了");
                break;
            }

        }
    }
}

5.4 常用方法第二组

在这里插入图片描述

package hsp.thread_;

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        Second second = new Second();
        second.start();

        //主线程
        for (int i = 1; i <= 20; i++) {
            Thread.sleep(500);
            System.out.println("main线程正在做事情~~~~" + i);
            if (i == 5) {
                System.out.println("======到第5次主线程暂停,让给子线程======");
                second.join();//线程插队,相当于让second先执行完毕
//                Thread.yield();//线程礼让,不一定礼让成功
                System.out.println("======子线程完事,主线程继续======");

            }
        }
    }
}

class Second extends Thread { //自定义线程类
    @Override
    public void run() {

        for (int i = 1; i <= 20; i++) {

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("子线程正在做事情~~~~" + i);
        }


    }
}
package hsp.thread_;

public class ThreadMethod04 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new TT());
        for (int i = 1; i <= 10; i++) {
            System.out.println("hi"+i);
            Thread.sleep(500);
            if(i==5){
                t.start();//启动子线程
                t.join();
            }
        }
    }
}

class TT implements Runnable{
    private int count = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("hello"+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(count==10){
                break;
            }
        }
    }
}


6、用户线程和守护线程

1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程(备胎线程):一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3.常见的守护线程:垃圾回收机制

package hsp.thread_;

public class ThreadDaemon {
    public static void main(String[] args) throws InterruptedException {
        MyGirlFriend myGirlFriend = new MyGirlFriend();

        //当main线程结束后,子线程自动结束,只需要将子线程设置为守护线程
        myGirlFriend.setDaemon(true);

        myGirlFriend.start();

        for (int i = 1; i < 11; i++) {
            System.out.println("main线程在持续工作"+i+".....");
            Thread.sleep(600);
        }
    }
}
class MyGirlFriend extends Thread{
    @Override
    public void run() {
        for (;;){//无线循环
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("子线程在持续工作....");
        }
    }
}

7、线程的生命周期

JDK 中用 Thread.State 枚举表示了线程的几种状态
在这里插入图片描述
线程状态转换图(重要)
在这里插入图片描述
在这里插入图片描述

package hsp.thread_;

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        State state = new State();
        Thread thread = new Thread(state);
        System.out.println(thread.getName()+"状态:"+thread.getState());
        thread.start();

        while (Thread.State.TERMINATED != thread.getState()){
            System.out.println(thread.getName()+"状态:"+thread.getState());
            Thread.sleep(800);

        }
        System.out.println(thread.getName()+"状态:"+thread.getState());

    }
}
class State implements Runnable{

    @Override
    public void run() {
        while (true){
            for (int i = 0; i < 11; i++) {
                System.out.println("ho"+i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            break;
        }
    }
}

8、 线程的同步(Synchronized)、线程安全

8.1 线程同步机制

1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
2.也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.

8.2 同步具体方法-Synchronized

1.同步代码块

synchronized(对象){/得到对象的锁,才能操作同步代码
	  //需要被同步代码;
}

2.synchronized还可以放在方法声明中,表示整个方法-为同步方法

public synchronized void m (String name){
	//需要被同步的代码
}

3.如何理解:
就好像上厕所前先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可在使用厕所了
4.使用synchronized解决售票问题

在这里插入图片描述

package hsp.thread_;

public class SellTicketSynchronized {
    public static void main(String[] args) {
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第 1 个线程-窗口
        new Thread(sellTicket03).start();//第 2个线程-窗口
        new Thread(sellTicket03).start();//第 3 个线程-窗口
    }
}

//实现接口方式,使用Synchronized实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean loop = true;

    public synchronized void sell() { //同步方法,同一时刻,只能有一个线程来执行,锁起来
        if (ticketNum <= 0) {
            System.out.println("售票结束...");
            loop = false;
            return;
        }
        //休眠 50 毫秒, 模拟
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() +
                " 售出一张票" + "" + " 剩余票数=" + (--ticketNum));
    }

    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}

9、互斥锁

1.Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,
表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法((非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6.同步方法(静态的)的锁为当前类本身。

package hsp.thread_;

public class SellTicketLock {
    public static void main(String[] args) {
        SellTicket04 sellTicket04 = new SellTicket04();
        new Thread(sellTicket04).start();//第 1 个线程-窗口
        new Thread(sellTicket04).start();//第 2个线程-窗口
        new Thread(sellTicket04).start();//第 3 个线程-窗口
    }
}

//实现接口方式,使用Synchronized实现线程同步
class SellTicket04 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean loop = true;

    Object obj = new Object();

    //同步方法(静态的)的锁为当前对象本身
    //1.public synchronized static void m1(){} 锁是加在SellTicket0.class
    //2.如果在静态方法中,实现一个同步代码块
    public synchronized static void m1() {

    }

    public static void m2() {
        synchronized (SellTicket04.class) {
            System.out.println("m2");
        }
    }

    //1.public synchronized void sell(){}  就是一个同步方法
    //2.也可以使用代码块写synchronized,同步代码块,互斥锁还是在this对象
    public /*synchronized*/ void sell() { //同步方法,同一时刻,只能有一个线程来执行
        synchronized (/*this*/ obj) {
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return;
            }
            //休眠 50 毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() +
                    " 售出一张票" + "" + " 剩余票数=" + (--ticketNum));
        }
    }

    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}

注意事项和细节
1.同步方法如果没有使用static修饰:默认锁对象为this
2.如果方法使用static修饰,默认锁对象:当前类.class
3.实现的落地步骤:
·需要先分析上锁的代码
·选择同步代码块或同步方法
·要求多个线程的锁对象为同一个即可

10、线程的死锁

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.

package hsp.thread_;

public class ThreadLock01 {
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A 线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B 线程");
        A.start();
        B.start();
    }
}

class DeadLockDemo extends Thread {
    static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }

    @Override
    public void run() {
        //下面业务逻辑的分析
        //1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
        //2. 如果线程 A 得不到 o2 对象锁,就会 Blocked
        //3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
        //4. 如果线程 B 得不到 o1 对象锁,就会 Blocked
        if (flag) {
            synchronized (o1) {//对象互斥锁, 下面就是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入 1");
                synchronized (o2) { // 这里获得 li 对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入 2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入 3");
                synchronized (o1) { // 这里获得 li 对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入 4");
                }
            }
        }
    }

}


11、释放锁

11.1下面操作会释放锁

1.当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
2.当前线程在同步代码块、同步方法中遇到break、return。
案例:没有正常的完事,经理叫他修改bug,不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束案例:没有正常的完事,发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去

11.2下面操作不会释放锁

1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方
法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示;应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

12、本章巩固练习

package hsp.thread_.lianxi;

import java.util.Scanner;

/*
*  (1)在main方法中启动两个线程
*  (2)第1个线程循环随机打印100以内的整数(3)直到第2个线程从键盘读取了“Q”命令。
* */
public class Test01 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B(a);//注意传入a对象
        a.start();
        b.start();
    }
}
class A extends Thread{
    private boolean loop = true;
    @Override
    public void run() {
        while (loop){
            System.out.println((int)(Math.random()*100+1));
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}
class B extends Thread{
    private A a;
    private Scanner scanner = new Scanner(System.in);

    public B(A a){//构造器直接传入A对象
        this.a = a;
    }

    @Override
    public void run() {
       //
        while (true){
            System.out.println("请输入你的指令(Q)才可以退出!!");
            char key = scanner.next().toUpperCase().charAt(0);

            if(key=='Q'){
                //以通知的方法结束线程A
                a.setLoop(false);
                System.out.println("b线程退出~~~");

                break;
            }
        }
    }
}
```java
package hsp.thread_.lianxi;

/*
* (1)有2个用户分别从同一个卡上取钱(总额:10000
* (2)每次都取1000,当余额不足时,就不能取款了
* (3)不能出现超取现象=》线程同步问题.
 */
public class Test02 {
    public static void main(String[] args) {

        T t = new T();
        Thread thread1 = new Thread(t);
        Thread thread2 = new Thread(t);
        thread1.setName("t1");
        thread2.setName("t2");

        thread1.start();
        thread2.start();

    }
}
class T implements Runnable{
private int money =10000;
    @Override
    public void run() {
        while (true){
            //使用synchronized,实现线程同步,谁在此刻争夺到this对象锁,谁就去执行,另一个则就阻塞等待,
            // 执行完了会释放this对象锁,两个准备继续夺到锁
            synchronized (this){
                if(money<1000){
                    System.out.println("余额不足");
                    break;
                }
                money-=1000;
                System.out.println(Thread.currentThread().getName()+"取出来1000,  当前余额:"+money);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

第12章 IO流

1、文件

1.1什么是文件

文件,对我们并不陌生,文件是保存数据的地方,比如大家经常使用的word文档,txt文件,excel文件…都是文件。它既可以保存一张图片,也可以保持视频,声音.…

1.2 文件流

在这里插入图片描述

2 常用的文件操作

2.1 创建文件对象相关构造器和方法

相关方法:
new File(String pathname) //根据路径构建一个File对象
new File(File parent,String child) //根据父目录文件+子路径构

package hsp.file;

import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;

public class FileCreate {
    public static void main(String[] args) {

    }

    //new File(String pathname)//根据路径构建一个File对象
    //new File(File parent,String child)//根据父目录文件+子路径构建

    //方式 2
    @Test
    public void create01() {
        String filePath = "E:\\new01.txt";
        File file = new File(filePath);

        try {
            file.createNewFile();
            System.out.println("文件1创建成功");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    //方式 2
    @Test
    public void create02(){
        String parentPath = "e:\\";
        String fileName ="new02.txt";
        //这里的 file 对象,在 java 程序中,只是一个对象
        //只有执行了 createNewFile 方法,才会真正的,在磁盘创建该文件
        File file = new File(parentPath, fileName);
        try {
            file.createNewFile();
            System.out.println("文件2创建成功");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.2 获取文件的相关信息

getName、getAbsolutePath、getParent、 length、exists、isFile、isDirectory
如何获取到文件的大小,文件名,路径,父File,是文件还是目录(自录本质也文件,一种特殊的文件).是否存在.

package hsp.file;

import org.testng.annotations.Test;

import java.io.File;

public class FileGetInfo {
    public static void main(String[] args) {

    }
    //获取文件信息
    @Test
    public void fileInformation (){
        //先创建文件对象
        File file = new File("e:\\new01.txt");
        //调用相应的方法,得到对应信息
        System.out.println("文件名字=" + file.getName());
        //getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
        System.out.println("文件绝对路径=" + file.getAbsolutePath());
        System.out.println("文件父级目录=" + file.getParent());
        System.out.println("文件大小(字节)=" + file.length());
        System.out.println("文件是否存在=" + file.exists());//T
        System.out.println("是不是一个文件=" + file.isFile());//T
        System.out.println("是不是一个目录=" + file.isDirectory());//F
    }
}

2.3目录的操作和文件删除

mkdir创建一级目录、mkdirs创建多级目录、delete删除空目录或文件

package hsp.file;

import org.testng.annotations.Test;

import java.io.File;

public class FileMkdir {
    public static void main(String[] args) {

    }
    //判断文件是否存在,并删除

    @Test
    public void m1() {
        String filePath = "e:\\new02.txt";
        File file = new File(filePath);
        if (file.exists()) {

            if (file.delete()) {
                System.out.println(filePath + "删除成功");
            } else {
                System.out.println(filePath + "删除失败");

            }

        } else {
            System.out.println(filePath + "文件不存在");

        }
    }

    //判断文件目录是否存在,并删除
    //java中,目录也当成文件
    @Test
    public void m2() {
        String filePath = "e:\\demo";
        File file = new File(filePath);
        if (file.exists()) {

            if (file.delete()) {
                System.out.println(filePath + "删除成功");
            } else {
                System.out.println(filePath + "删除失败");

            }

        } else {
            System.out.println(filePath + "目录不存在");

        }
    }

    @Test
    public void m3() {
        String filePath = "e:\\demo\\a\\b\\c";
        File file = new File(filePath);
        if (file.exists()) {
            System.out.println(filePath + "目录存在");
        } else {
            //创建一级目录使用mkdirs(),创建多级目录使用mkdirs()
           if (file.mkdirs()){
               System.out.println(filePath + "目录创建成功");
           }else {
               System.out.println(filePath + "目录创建失败");

           }

        }
    }


}

3、 IO流原理及流的分类

3.1 Java IO流原理

1.I/O是lnput/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输.
如读/写文件,网络通讯等。
2.Java程序中,对于数据的输入/输出操作以”流(stream)”的方式进行。
3. java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
4.输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
5.输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中

3.2 流的分类

在这里插入图片描述

4、IO 流体系图-常用的类

IO 流体系图
在这里插入图片描述

4.1 FileInputStream

package hsp.io_;

import org.testng.annotations.Test;

import java.io.FileInputStream;
import java.io.IOException;

//FileInputStream 的使用(字节输入流 文件--> 程序)

public class FileInputStream01 {
    public static void main(String[] args) {

    }
    /*
    * 单个字节的读取,效率低
    * */
    @Test
    public void readFile01() {
        String filePath = "e:\\hello.txt";
        int readData = 0;
        FileInputStream fileInputStream = null;
        try {
            //创建 FileInputStream 对象,用于读取 文件
            fileInputStream = new FileInputStream(filePath);
            //从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
            //如果返回-1 , 表示读取完毕
            while ((readData = fileInputStream.read()) != -1) {
                System.out.print((char)readData);//转成 char 显示
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭文件流,释放资源
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
/*
* 使用 read(byte[] b) 读取文件,提高效率
* */
    @Test
    public void readFile02() {
        String filePath = "e:\\hello.txt";
        int readData = 0;
        //字节数组
        byte[] buf = new byte[8]; //一次读取 8 个字节.
        int readLen = 0;
        FileInputStream fileInputStream = null;
        try {
            //创建 FileInputStream 对象,用于读取 文件
            fileInputStream = new FileInputStream(filePath);
            //从该输入流读取最多 b.length 字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
            //如果返回-1 , 表示读取完毕
            //如果读取正常, 返回实际读取的字节数
            while ((readLen = fileInputStream.read(buf)) != -1) {
                System.out.print(new String(buf, 0, readLen));//显示
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭文件流,释放资源
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

4.2 FileOutputStream

package hsp.io_;

import org.testng.annotations.Test;

import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStream01 {
    public static void main(String[] args) {

    }

    @Test
    public void writeFile() {
        String filePath = "e:\\a.txt";

        FileOutputStream fileOutputStream = null;

        try {
            //得到 FileOutputStream 对象
            //说明
            //1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
            //2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面

            //fileOutputStream = new FileOutputStream(filePath);
            fileOutputStream = new FileOutputStream(filePath ,true);

            //写入一个字节
            //fileOutputStream.write('H');

            //写入字符串
            String str = "hello,world!";
            //str.getBytes() 可以把 字符串-> 字节数组
            //fileOutputStream.write(str.getBytes());


            //write(byte[] b, int off, int len) 将 len 字节从位于偏移量 off 的指定字节数组写入此文件输出流
            fileOutputStream.write(str.getBytes(), 0, 5);


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

实现文件拷贝

package hsp.io_;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {
    public static void main(String[] args) {

        //1. 创建文件的输入流 , 将文件读入到程序
        //2. 创建文件的输出流, 将读取到的文件数据,写入到指定的文件

        String secFilePath =  "e:\\picture.jfif";
        String destFilePath =  "e:\\picture_copy.jfif";

        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;

        try {
             fileInputStream = new FileInputStream(secFilePath);
             fileOutputStream = new FileOutputStream(destFilePath);
            //定义一个字节数组,提高读取效果
            byte[] buf = new byte[1024];
            int readLength = 0;
            while ((readLength = fileInputStream.read(buf))!=-1){
                //读取到后,就写入到文件 通过 fileOutputStream
                //即,是一边读,一边写
                fileOutputStream.write(buf,0,readLength);// 一定要使用这个方法
            }
            System.out.println("拷贝 ok~");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null){
                    fileInputStream.close();
                }
                if(fileOutputStream != null){
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

5、FileReader 和 FileWriter 介绍

在这里插入图片描述

5.1 FileReader 相关方法:

  1. new FileReader(File/String)
  2. read:每次读取单个字符,返回该字符,如果到文件末尾返回-13) read(char[):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
    相关API:
  3. new String(char[1]:将char[]转换成String
  4. new String(char[.off,len):将char[的指定部分转换成String
package hsp.io_;

import java.io.FileReader;
import java.io.IOException;

/*
 * * 单个字符读取文件
 * */
public class FileReader01 {
    public static void main(String[] args) {
        String filePath = "E:\\test01.txt";
        FileReader fileReader = null;
        int data = 0;
        try {
            fileReader = new FileReader(filePath);
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }

    }
}

/*
 * 字符数组读取文件
 * */
class FileReader02 {
    public static void main(String[] args) {
        String filePath = "E:\\test01.txt";
        FileReader fileReader = null;
        int readLen = 0;
        char[] buf = new char[8];
        try {
            fileReader = new FileReader(filePath);
            //循环读取 使用 read(buf), 返回的是实际读取到的字符数
            //如果返回-1, 说明到文件结束
            while ((readLen = fileReader.read(buf)) != -1) {
                System.out.print(new String(buf, 0, readLen));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }

    }
}

5.2 FileWriter常用方法

  1. new FileWriter(File/String):覆盖模式,相当于流的指针在首端
  2. new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
  3. write(int):写入单个字符
  4. write(char[]):写入指定数组
  5. write(char[],off,len):写入指定数组的指定部分
  6. write (string):写入整个字符串
  7. write(string,off,len):写入字符串的指定部分
    相关API: String类:toCharArray:将String转换成char[]
    注意: FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入 不到指定的文件!
package hsp.io_;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriter01 {
    public static void main(String[] args) {

        String filePath = "e:\\note.txt";
        FileWriter fileWriter = null;
        char[] chars = {'a', 'b', 'c'};
        try {
            fileWriter = new FileWriter(filePath);
            // 3) write(int):写入单个字符
            fileWriter.write('H');
            // 4) write(char[]):写入指定数组
            fileWriter.write(chars);
            // 5) write(char[],off,len):写入指定数组的指定部分
            fileWriter.write("中华人民共和国".toCharArray(), 0, 3);
            // 6) write(string):写入整个字符串
            fileWriter.write(" 你好玉儿~");
            fileWriter.write("风雨之后,定见彩虹");
            // 7) write(string,off,len):写入字符串的指定部分
            fileWriter.write("上海天津", 0, 2);
            //在数据量大的情况下,可以使用循环操作
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                //对应 FileWriter , 一定要关闭流,或者 flush 才能真正的把数据写入到文件
                //fileWriter.flush();
                //关闭文件流,等价 flush() + 关闭
                fileWriter.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

6、 节点流和处理流

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.3 节点流和处理流的区别和联系

1.节点流是底层流/低级流,直接跟数据源相接。
2、处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。[源码理解]
3.处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连[模拟修饰器设计模式=》]

6.4处理流的功能主要体现在以下两个方面:

1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
2、操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便

6.5 处理流-BufferedReader和 BufferedWriter

BufferedReader 和 BufferedWriter属于字符流,是按照字符来读取数据的关闭时处理流,只需要关闭外层流即可。

package hsp.io_;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReader01 {
    public static void main(String[] args) throws IOException {
        String filePath = "e:\\aa.java";
        //创建 bufferedReader
        BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
        //读取
        String line; //按行读取, 效率高
        //说明
        //1. bufferedReader.readLine() 是按行读取文件
        //2. 当返回 null 时,表示文件读取完毕
        while ((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
        }
        //关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭节点流
        //FileReader。
        bufferedReader.close();
    }
}

package hsp.io_;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriter01 {
    public static void main(String[] args) throws IOException {
        String filePath = "e:\\ok.txt";

        //创建 BufferedWriter
        //说明:
        //1. new FileWriter(filePath, true) 表示以追加的方式写入
        //2. new FileWriter(filePath) , 表示以覆盖的方式写入
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
        bufferedWriter.write("床前明月光,");
        bufferedWriter.newLine();//插入一个和系统相关的换行
        bufferedWriter.write("疑是地上霜。");
        bufferedWriter.newLine();
        bufferedWriter.write("举头望明月,");
        bufferedWriter.newLine();
        bufferedWriter.write("低头思故乡。");
        bufferedWriter.newLine();
        //说明:关闭外层流即可 , 传入的 new FileWriter(filePath) ,会在底层关闭
        bufferedWriter.close();
    }
}

拷贝

package hsp.io_;

import java.io.*;

public class BufferCopy {
    public static void main(String[] args) {
	    //说明
		//1. BufferedReader 和 BufferedWriter 是安装字符操作
		//2. 不要去操作 二进制文件[声音,视频,doc, pdf ], 可能造成文件损坏
        String secFilePath = "e:\\aa.java";
        String destFilePath = "e:\\aa_copy.java";
        BufferedReader bufReader = null;
        BufferedWriter bufWriter = null;
        String line;
        try {

            bufReader = new BufferedReader(new FileReader(secFilePath));
            bufWriter = new BufferedWriter(new FileWriter(destFilePath));
            //readLine 读取一行内容,但是没有换行
            while ((line = bufReader.readLine()) != null) {
                //每读取一行,就写入
                bufWriter.write(line);
                //插入一个换行
                bufWriter.newLine();
            }
            System.out.println("拷贝完毕...");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                if (bufReader != null) {
                    bufReader.close();
                }
                if (bufWriter != null) {
                    bufWriter.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }


}


6.6 处理流-BufferedInputStream 和 BufferedOutputStream

在这里插入图片描述
在这里插入图片描述

package hsp.io_;

import java.io.*;
/*
* 演示使用 BufferedOutputStream 和 BufferedInputStream 使用
* 使用他们,可以完成二进制文件拷贝.
*字节流可以操作二进制文件,可以操作文本文件
* */
public class BufferInputOutputStreamCopy01 {
    public static void main(String[] args) {

        String srcFilePath = "e:\\picture.jfif";
        String destFilePath = "e:\\picture_copy.jfif";

        //创建 BufferedOutputStream 对象 BufferedInputStream 对象
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            //因为 FileInputStream 是 InputStream 子类
            bis = new BufferedInputStream(new FileInputStream(srcFilePath));
            bos = new BufferedOutputStream(new FileOutputStream(destFilePath));

            //循环的读取文件,并写入到 destFilePath
            byte[] buff = new byte[1024];
            int readLen = 0;

            //当返回 -1 时,就表示文件读取完毕
            while ((readLen = bis.read(buff)) != -1) {
                bos.write(buff, 0, readLen);
            }
            System.out.println("文件拷贝完毕~~~");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

6.7 对象流-ObjectInputStream 和 ObjectOutputStream

看一个需求
1.将int num = 100这个int数据保存到文件中,注意不是100数字,而是int 100,并且,能够从文件中直接恢复int 100
2.将Dog dog = new Dog(“小黄”,3)这个dog对象保存到文件中,并且能够从文件恢复.3.上面的要求,就是能够将基本数据类型或者对象进行序列化和反序列化操作

序列化和反序列化
1.序列化就是在保存数据时,保存数据的值和数据类型
2.反序列化就是在恢复数据时,恢复数据的值和数据类型
3.需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:

Serializable //这是一个标记接口,没有方法
Externalizable//该接口有方法需要实现,因此我们一般实现上面的 Serializable接口
在这里插入图片描述

6.8对象流介绍

功能:提供了对基本类型或对象类型的序列化和反序列化的方法:
ObjectOutputStream 提供 序列化功能
ObjectInputStream 提供 反序列化功能
![在这里插入图片描述](https://img-blog.csdnimg.cn/d2f83eae3b0944c5b3cc1e24270f5f11.png

package hsp.io_;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectOutStream01 {
    public static void main(String[] args) throws IOException {
        //序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
        String filePath = "e:\\data.dat";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
        //序列化数据到 e:\data.dat
        oos.writeInt(100);// int -> Integer (实现了 Serializable)
        oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
        oos.writeChar('a');// char -> Character (实现了 Serializable)
        oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
        oos.writeUTF("中国人");//String
        //保存一个 dog 对象
        oos.writeObject(new Dog("旺财", "白色", 10,"俄罗斯"));
        oos.close();
        System.out.println("数据保存完毕(序列化形式)");
    }
}

//需要序列化某个对象,实现Serializable
class Dog implements Serializable {
    private String name;
    private String color;
    private int age;
    //序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
    private transient String nation;
    //serialVersionUID序列化版本号,可以提高兼容性
    private static final long serialVersionUID = 1l;

    public Dog(String name, String color, int age,String nation) {
        this.name = name;
        this.color = color;
        this.age = age;
        this.nation =nation;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", age=" + age +
                '}'+nation;
    }
}
package hsp.io_;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputStream01 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 1.创建流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:\\data.dat"));
        // 2.读取, 注意顺序一致
        System.out.println(ois.readInt());
        System.out.println(ois.readBoolean());
        System.out.println(ois.readChar());
        System.out.println(ois.readDouble());
        System.out.println(ois.readUTF());
        Object dog = ois.readObject();
        System.out.println("运行类型:"+dog.getClass());
        System.out.println("obj信息:"+dog);

        //1.如果需要调用Dog的方法,需要向下转型
        //2.需要将Dog的定义,拷贝到可以引用的位置即可
        Dog dog2 = (Dog)dog;
        System.out.println(dog2.getName());
        // 3.关闭
        ois.close();
        System.out.println("以反序列化的方式读取(恢复)ok~");

    }
}

注意事项和细节说明:
1)读写顺序要一致
2)要求序列化或反序列化对象,需要实现Serializable
3)序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性
4)序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
5)序列化对象时,要求里面属性的类型也需要实现序列化接口
6)序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化

6.8 标准输入输出流

介绍类型默认设备
System.in标准输入lnputStream键盘
System.out标准输出PrintStream显示器

应用案例1
传统方法System.out.printIn(“”);是使用out对象将数据输出到显示器
应用案例2
传统的方法, Scanner是从标准输入键盘接收数据

package hsp.io_;

import java.util.Scanner;

public class SystemInputOut01 {
    public static void main(String[] args) {

        //System.in
        //编译类型:InputStream
        //运行类型:BufferedInputStream
        //表示标准输入  键盘
        System.out.println(System.in.getClass());//class java.io.BufferedInputStream
        
        //System.out
        //编译类型:PrintStream
        //运行类型:PrintStream
        //表示标准输出  显示器
        System.out.println(System.out.getClass());//class java.io.PrintStream

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入内容。。。");
        String next = scanner.next();
        System.out.println("next="+next);

    }
}

6.9转换流-InputStreamReader和 OutputStream

在这里插入图片描述

在这里插入图片描述

  1. InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成(转换)Reader(字符流)
  2. OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
  3. 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
  4. 可以在使用时指定编码格式(比如utf-8, gbk , gb2312, ISO8859-1等)
package hsp.io_;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

//乱码问题:指定编码
//* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
public class OutputStreamReader01 {
    public static void main(String[] args) throws IOException {
        String filePath = "e:\\ok.txt";
        //解读
        //1. 把 FileInputStream 转成 InputStreamReader
        //2. 指定编码 gbk
        //InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
        //3. 把 InputStreamReader 传入 BufferedReader
        //BufferedReader br = new BufferedReader(isr);
        //将 2 和 3 合在一起
        BufferedReader br = new BufferedReader(new InputStreamReader(
                new FileInputStream(filePath), "gbk"));

        //4. 读取
        String s = br.readLine();
        System.out.println("读取内容=" + s);
        //5. 关闭外层流
        br.close();
    }
}

package hsp.io_;

import java.io.*;

//* 将字节流 FileOutputStream 转成字符流 OutputStreamReader, 指定编码 gbk/utf-8
public class OutputStreamWriter01 {
    public static void main(String[] args) throws IOException {
        String filePath = "e:\\lxc.txt";
        String charSet = "utf-8";
        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
        writer.write("codeSE,中国人");
        writer.close();
        System.out.println("文件写入成功。。。");

    }
}

7、 打印流-PrintStream 和 PrintWriter

在这里插入图片描述

package hsp.io_;

/*
*PrintStream (字节打印流/输出流)
* */

import java.io.IOException;
import java.io.PrintStream;

public class PrintStream01 {
    public static void main(String[] args) throws IOException {
        PrintStream out = System.out;
        //在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器
        out.println("hello,你好");

        //因为 print 底层使用的是 write , 所以我们可以直接调用 write 进行打印/输出
        out.write("hello,你好".getBytes());
        out.close();

        //我们可以去修改打印流输出的位置/设备
        System.setOut(new PrintStream("e:\\f.txt"));
        System.out.println("日照香炉生紫烟");//就会输出到该文件下

        //1. 输出修改成到 "e:\\f1.txt"
        //2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt
        //3. public static void setOut(PrintStream out) {
        // checkIO();
        // setOut0(out); // native 方法,修改了 out
        // }

    }
}

package hsp.io_;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/*
* PrintWriter (字节打印流/输出流)
* */
public class PrintWriter01 {
    public static void main(String[] args) throws IOException {
        //PrintWriter printWriter = new PrintWriter(System.out);
        PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
        printWriter.print("hi, 玉儿你好~~~~");
        printWriter.close();//flush + 关闭流, 才会将数据写入到文件
    }
}

8、 Properties 类

8.1 看需求

在这里插入图片描述

package hsp.properties_;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Properties01 {
    public static void main(String[] args) throws IOException {
        //读取 mysql.properties 文件,并得到 ip, user 和 pwd
        BufferedReader bufferedReader = new BufferedReader(new FileReader("src\\hsp\\properties_\\mysql.properities"));
        String line = "";
        while ((line=bufferedReader.readLine())!=null){  //循环读取

            //System.out.println(line);
            String[] str = line.split("=");
            System.out.println(str[0]+"值是:"+str[1]);

            //如果我们要求指定的 ip 值
            if("ip".equals(str[0])) {
                System.out.println(str[0] + "值是: " + str[1]);
            }
        }
        bufferedReader.close();

    }
}


8.2基本介绍

1)专门用于读写配置文件的集合类
配置文件的格式:
键=值
键=值
2)注意:键值对不需要有空格,值不需要用引号一起来。默认类型是String
3) Properties的常见方法

--
load加载配置文件的键值对到Properties对象
list将数据显示到指定设备
getProperty(key)根据键获取值
setProperty(key,value)设置键值对到Properties对象
store将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,会存储为unicode码

(http://tool.chinaz.com/tools/unicode.aspx) unicode码查询工具

package hsp.properties_;

import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class Properties02 {
    public static void main(String[] args) throws IOException {
        //使用 Properties 类来读取 mysql.properties 文件
        //1. 创建 Properties 对象
        Properties properties = new Properties();
        //2. 加载指定配置文件
        properties.load(new FileReader("src\\hsp\\properties_\\mysql.properties"));
        //3. 把 k-v 显示控制台
        properties.list(System.out);
        //4. 根据 key 获取对应的值
        String user = properties.getProperty("user");
        String pwd = properties.getProperty("pwd");
        System.out.println("用户名=" + user);
        System.out.println("密码是=" + pwd);
    }
}

package hsp.properties_;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

public class Properties03 {
    public static void main(String[] args) throws IOException {
        //使用 Properties 类来创建 配置文件, 修改配置文件内容
        Properties properties = new Properties();
        //创建
        //1.如果该文件没有 key 就是创建
        //2.如果该文件有 key ,就是修改
        /*
        * Properties 父类是 Hashtable , 底层就是 Hashtable 核心方法
        * */
        properties.setProperty("charset","utf-8");
        properties.setProperty("user","kk中国");//注意保存时,是中文的 unicode 码值
        properties.setProperty("pwd","admin");
        properties.setProperty("pwd","admin123");
        //将 k-v 存储文件中即可
        properties.store(new FileOutputStream("src\\mysql.properties"),"this is a comments,这里是注释");
        System.out.println("保存配置文件成功。。。");
    }
}


本章练习

package hsp.properties_.lianxi;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/*
*(1)在判断e盘下是否有文件夹mytemp ,如果没有就创建mytemp
* (2)在e:\mytemp目录下,创建文件 hello.txt
* (3)如果hello.txt已经存在,提示该文件已经存在,就不要再重复创建了
* (4)并且在hello.txt 文件中,写入hello,world~
* */
public class HomeWork01 {
    public static void main(String[] args) throws IOException {
        String dirPath = "e:\\mytemp";
        File file = new File(dirPath);
        if (!(file.exists())){
            //创建
            if (file.mkdir()){
                System.out.println("创建 "+dirPath +" 创建成功");
            }else {
                System.out.println("创建 "+dirPath +" 创建失败");

            }
        }
        String filePath = dirPath+"\\hello.txt";
         file = new File(filePath);
        if (!file.exists()){
            if (file.createNewFile()){
                System.out.println(filePath+"创建成功");
                BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));
                bufferedWriter.write("hello,中国");
                bufferedWriter.close();
            }else {
                System.out.println(filePath+"创建失败");

            }
        }else {
            System.out.println("文件已经存在,就不要再重复创建了");
        }
    }
}

第13章 网络编程

1、网络的相关概念

1.1 网络通信

1.概念:两台设备之间通过网络实现数据传输
2.网络通信:将数据通过网络从一台设备传输到另一台设备
3.java.net包下提供了一系列的类或接口,供程序员使用,完成网络通信

1.2 网络

1.概念:两台或多台设备通过一定物
理设备连接起来构成了网络
2.根据网络的覆盖范围不同,对网络
进行分类:
局域网:覆盖范围最小,仅仅覆盖一个教室或一个机房
城域网:覆盖范围较大,可以覆盖一个城市
广域网:覆盖范围最大,可以覆盖全国,甚至全球,万维网是广域网的代表

1.3 ip 地址

1.概念:用于唯一标识网络中的每台计算机/主机2.查看ip地址: ipconfig
3. ip地址的表示形式:点分十进制XX.XX.XX.XX
4.每一个十进制数的范围:0~255
5. ip地址的组成=网络地址+主机地址,比如:192.168.16.69
6. iiPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界的每一粒沙子编上一个地址[1]。
7.由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍

1.4 ipv4 地址分类

在这里插入图片描述

类型范围
A0.0.0.0——127.255.255.255
B128.0.0.0——191.255.255.255
C192.0.0.0——223.255.255.255
D224.0.0.0——239.255.255.255
E240.0.0.0——247.255.255.255

1.5 域名

1.www.baidu.com
2.好处:为了方便记忆,解决记ip的困难
3.概念:将ip地址映射成域名,这里怎么映射上,HTTP协议
端口号
1.概念:用于标识计算机上某个特定的网络程序
2.表示形式:以整数形式,端口范围0~65535 [2个字节表示端口0-0~2^16-1]
3. 0~1024已经被占用,比如 ssh 22, ftp 21, smtp 25 http 80
4.常见的网络程序端口号:
tomcat :8080
mysql:3306
oracle:1521
sqlserver:1433

1.6 网络通信协议

在这里插入图片描述
协议(tcp/ip)
TCP/IP (Transmission ControlProtocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的。

图:

TCP和UDP
TCP协议:传输控制协议
1.使用TCP协议前,须先建立TCP连接,形成传输数据通道
2.传输前,采用"三次握手"方式,是可靠的
3. TCP协议进行通信的两个应用进程:客户端、服务端
4.在连接中可进行大数据量的传输
5.传输完毕,需释放已建立的连接,效率低

UDP协议:用户数据协议
1.将数据、源、目的封装成数据包,不需要建立连接
2.每个数据报的大小限制在64K内,不适合传输大量数据
3.因无需连接,故是不可靠的
4.发送数据结束时无需释放资源(因为不是面向连接的),速度快
5.举例:厕所通知:发短信

2 、InetAddress

2.1相关方法

1.获取本机InetAddress对象getLocalHost
2.根据指定主机名/域名获取ip地址对象getByName
3.获取InetAddress对象的主机名getHostName
4.获取InetAddress对象的地址getHostAddress

package hsp.network;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class API01 {
    public static void main(String[] args) throws UnknownHostException {
        //获取本机 InetAddress 对象 getLocalHost
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);//DESKTOP-O44F4A7/10.100.3.248


        //根据指定主机名/域名获取 ip 地址对象 getByName
        InetAddress host2 = InetAddress.getByName("DESKTOP-O44F4A7");
        System.out.println(host2);//
        InetAddress host3 = InetAddress.getByName("www.code.com");
        System.out.println(host3);//

        //获取 InetAddress 对象的主机名 getHostName
        String host3Name = host3.getHostName();
        System.out.println(host3Name);

        //获取 InetAddress 对象的地址 getHostAddress
        String host3Address = host3.getHostAddress();
        System.out.println(host3Address);
    }
}

3、 Sockt

3.1基本介绍

1.套接字(Socket)开发网络应用程序被广 泛采用,以至于成为事实 上的标准。
2.通信的两端都要有Socket, 是两台机器间通信的端点
3.网络通信其实就是Socket间的通信。
4.Socket允许程序把网络连接当成一 个流,数据在两个Socket间通过I0传输。
5.一般主动发起通信的应用程序属客户端,等待通信请求的为服务端

在这里插入图片描述

案例一:

package hsp.socket_;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
 * 服务端
 * */
public class SocketTCP01Server {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 在本机 的 9898 端口监听, 等待连接
        // 细节: 要求在本机没有其它服务在监听 9898
        // 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
        ServerSocket serverSocket = new ServerSocket(9898);
        System.out.println("服务端在9898端口监听,等待连接...");
        //2. 当没有客户端连接 9898 端口时,程序会 阻塞, 等待连接
        // 如果有客户端连接,则会返回 Socket 对象,程序继续
        Socket socket = serverSocket.accept();
        System.out.println("服务端socket=" + socket.getClass());
        //
        //3. 通过 socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
        InputStream inputStream = socket.getInputStream();
        //4. IO 读取
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println("服务端收到:"+new String(buf, 0, readLen));//根据读取到的实际长度,显示内容. }
            //5.关闭流和 socket
            inputStream.close();
            socket.close();
            serverSocket.close();//关闭
        }
    }
}

package hsp.socket_;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/*
* 客户端,发送 "hello, 我是客户端,发消息给你啦!!" 给服务端
* */
public class SocketTCP01Client {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 连接服务端 (ip , 端口)
        //解读: 连接本机的 9898 端口, 如果连接成功,返回 Socket 对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9898);
        System.out.println("客户端socket返回=" + socket.getClass());
        //2. 连接上后,生成 Socket, 通过 socket.getOutputStream()
        // 得到 和 socket 对象关联的输出流对象
        OutputStream outputStream = socket.getOutputStream();
        //3. 通过输出流,写入数据到 数据通道
        outputStream.write("hello, 我是客户端,发消息给你啦!!".getBytes());
        //4. 关闭流对象和 socket, 必须关闭
        outputStream.close();
        socket.close();
        System.out.println("客户端退出.....");
    }
}

案例二:

package hsp.socket_;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/*
* 服务端
* */
public class SocketTCP02Server {
    public static void main(String[] args) throws IOException {

        //1. 在本机 的 7878 端口监听, 等待连接
        // 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
        ServerSocket serverSocket = new ServerSocket(7878);
        System.out.println("服务端在7878端口监听,等待连接...");

        //2. 当没有客户端连接 9999 端口时,程序会 阻塞, 等待连接
        // 如果有客户端连接,则会返回 Socket 对象,程序继
        Socket socket = serverSocket.accept();
        System.out.println("服务端socket=" + socket.getClass());

        //3. 通过 socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
        InputStream inputStream = socket.getInputStream();

        //4. IO 读取
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println("服务端收到:"+new String(buf, 0, readLen));
        }
        //5. 获取 socket 相关联的输出流
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("我是服务端,收到,返回你啦".getBytes(StandardCharsets.UTF_8));
        // 设置结束标记
        socket.shutdownOutput();
        //6.关闭流和 socke
        inputStream.close();
        outputStream.close();
        socket.close();
        serverSocket.close();


    }
}

package hsp.socket_;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/*
* 客户端
* */
public class SocketTCP02Client {
    public static void main(String[] args) throws IOException {

        //1. 连接服务端 (ip , 端口)
        //解读: 连接本机的 9999 端口, 如果连接成功,返回 Socket
        Socket socket = new Socket(InetAddress.getLocalHost(), 7878);

         //2. 连接上后,生成 Socket, 通过 socket.getOutputStream()
         // 得到 和 socket 对象关联的输出流对象
        OutputStream outputStream = socket.getOutputStream();
        //3. 通过输出流,写入数据到 数据通道
        outputStream.write("hello,我是客户端".getBytes());
        // 设置结束标记
        socket.shutdownOutput();

        //4. 获取和 socket 关联的输入流. 读取数据(字节),并显示
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen=inputStream.read(buf))!=-1){
            System.out.println("客户端收到:"+new String(buf,0,readLen));
        }
        //5. 关闭流对象和 socket,必须关闭
        inputStream.close();
        outputStream.close();
        socket.close();
        System.out.println("客户端退出.....");
    }
}

案例三:


案例四:


4、 TCP网络通信编程

4.1 基本介绍

1.基于客户端一服务端的网络通信
2.底层使用的是TCP/IP协议
3.应用场景举例:客户端发送数据,服务端接受并显示控制台
4.基于Socket的TCP编程

4.2 netstat 指令

1.netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况
2.netstat -an | more 可以分页显示
3.要求在dos控制台下执行win+r
说明:
(1) Listening表示某个端口在监听
(2)如果有一个外部程序(客户端)连接到该端口,就会显示条连接信息
(3)可以输入ctrl + C退出指令

在这里插入图片描述

第14章 反射

1、反射机制

1.1 Java Reflection

1.反射机制允许程序在执行期借助于Reflection的API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
2.加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射
p对象 --> 类型Person类
Class对象cls —>类型Class类
在这里插入图片描述

1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时得到任意一个类所具有的成员变量和方法
4.在运行时调用任意一个对象的成员变量和方法
5.生成动态代理

1.2 反射相关的主要类

  1. java.lang.Class:代表一个类,Class对象表示某 个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
  3. java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
    这些类在java.lang.reflection
package hsp.reflection_;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class Reflection01 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException, IOException, InvocationTargetException {
        //1. 使用 Properties 类, 可以读写配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.properties"));
        System.out.println(properties);
        String classfullpath = properties.get("classfullpath").toString();//"hsp.test.Cat"
        String methodName = properties.get("method").toString();//"m1"
        System.out.println(classfullpath);
        System.out.println(methodName);

        //2. 使用反射机制解决
        //(1) 加载类, 返回 Class 类型的对象 cls
        Class cls = Class.forName("hsp.test.Cat");
        System.out.println(cls); //运行类型
        System.out.println(cls.getMethod("getName"));
        //(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
        Object o = cls.newInstance();
        System.out.println("o 的运行类型=" + o.getClass()); //运行类型
        //(3) 通过 cls 得到你加载的类 com.test.Cat 的 methodName"hi" 的方法对象
        // 即:在反射中,可以把方法视为对象(万物皆对象)
        Method method1 = cls.getMethod("hi");
        //(4) 通过 method1 调用方法: 即通过方法对象来实现调用方法
        System.out.println("=============================");
        method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象
        //java.lang.reflect.Field: 代表类的成员变量, Field 对象表示某个类的成员变量
        //得到 name 字段
        //getField 不能得到私有的属性
        //Field nameField = cls.getField("name"); //报错

        Field nameField = cls.getField("age"); //报错
        System.out.println("获取公有属性:"+nameField.get(o)); // 传统写法 对象.成员变量 , 反射 : 成员变量对象.get(对象)
        //java.lang.reflect.Constructor: 代表类的构造方法, Constructor 对象表示构造器
        Constructor constructor1 = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
        System.out.println("获取构造器:"+constructor1);//Cat()
        Constructor constructor2 = cls.getConstructor(String.class); //这里老师传入的 String.class 就是 String 类的Class 对象
        System.out.println(constructor2);//Cat(String name)

    }
}

package com.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//获得类的属性
public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.reflection.User");

        //获得类的名字
        System.out.println(c1.getName());//获得包名+类名
        System.out.println(c1.getSimpleName());//获得类名

        //获得属性名
        System.out.println("================================================================");
        Field[] fields = c1.getFields();//只能找到public属性

        fields = c1.getDeclaredFields();//找到全部的属性
        for(Field field:fields){
            System.out.println(field);
        }

        //获得指定属性
        Field name = c1.getDeclaredField("name");
        System.out.println("获得指定属性:"+name);

        //获得类的方法
        System.out.println("================================================================");

        Method[] methods = c1.getMethods();//获得本类和父类的所有
        for (Method method:methods){
            System.out.println("获得本类和父类的所有:"+method);
        }
        methods = c1.getDeclaredMethods();//获得本类的所有方法
        for (Method method:methods){
            System.out.println("获得本类的所有方法:"+method);

        }
        //获得指定方法
        System.out.println("================================================================");
        Method getName = c1.getMethod("getName");
        Method setName = c1.getMethod("setName", String.class);
        System.out.println("获得指定方法:"+getName);
        System.out.println("获得指定方法:"+setName);

        //获得构造器
        System.out.println("================================================================");
        Constructor[] constructors = c1.getConstructors();
        for(Constructor constructor:constructors){
            System.out.println("获得本类public构造器:"+getName);
        }
        constructors = c1.getDeclaredConstructors();
        for(Constructor constructor:constructors){
            System.out.println("获得本类全部构造器:"+getName);
        }
        //获得指定构造器
        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class,int.class,boolean.class);
        System.out.println("指定构造器:"+declaredConstructor);


    }
}

1.3 反射优点和缺点

1.优点:可以动态的创建和使用对象(也是框架底层核心,使用灵活没有反射机
制,框架技术就失去底层支撑。
2.缺点:使用反射基本是解释执行,对执行速度有影响.

1.4 反射调用优化-关闭访问检查

  1. Method和Field, Constructor对象都有setAccessible0方法
  2. setAccessible作用是启动和禁用访问安全检查的开关
  3. 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
package hsp.reflection_;

import hsp.test.Cat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/*
 *  测试反射调用的性能,和优化方案
 * */
public class Reflection02 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //Field
        //Method
        //Constructor
        m1();//传统
        m2();//反射
        m3();//反射优化
    }

    //传统方法来调用 hi
    public static void m1() {
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("m1() 耗时=" + (end - start));
    }

    //反射机制调用方法 hi
    public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, InvocationTargetException {
        Class cls = Class.forName("hsp.test.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射调用方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m2() 耗时=" + (end - start));
    }

    //反射调用优化 + 关闭访问检查
    public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("hsp.test.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        hi.setAccessible(true);//在反射调用方法时,取消访问检查
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射调用方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m3() 耗时=" + (end - start));
    }
}
/*
m1() 耗时=0
m2() 耗时=1500
m3() 耗时=1484
*/

2、Class 类

2.1 基本介绍

在这里插入图片描述
1.Class也是类,因此也继承Object类
2.Class类对象不是new出来的, 而是系统创建的
3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
4.每个类的实例都会记得自己是由哪个Class实例所生成
5.通过Class对象可以完整地得到一个类的完整结构, 通过系列API
6. Class对象是存放在堆的
7.类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等) https://www.zhihu.com/question/38496907

package hsp.reflection_;
/*
Class类的特点
*/
public class Class01 {
    public static void main(String[] args) throws ClassNotFoundException {
        //1. Class 也是类,因此也继承 Object 类
        //Class
        //2. Class 类对象不是 new 出来的,而是系统创建的
        //(1) 传统 new 对象
        /* ClassLoader 类
        public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
        }
        */
        //Cat cat = new Cat();
        //(2) 反射方式
        /*
            ClassLoader 类, 仍然是通过 ClassLoader 类加载 Cat 类的 Class 对象
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                return loadClass(name, false);
            }
        */
        Class cls1 = Class.forName("hsp.test.Cat");
        //3. 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
        Class cls2 = Class.forName("hsp.test.Cat");
        System.out.println(cls1.hashCode());//312714112
        System.out.println(cls2.hashCode());//312714112
        Class cls3 = Class.forName("hsp.test.Cat");
        System.out.println(cls3.hashCode());//312714112
    }
}

2.2 Class 类的常用方法

方法名功能说明
static Class forName(String name)返回指定类名name的Class对象
object newInstance()调用缺省构造函数,返回该Class对象的一 个实例
getName()返回此Class对象所表示的实体(类、接口、数组类、基本类型等)名称
Class [ ] getlnterfaces( )获取当前Class对象的接口
ClassLoader getClassLoader()返回该类的类加载器
Class getSuperclass0返回表示此Class所表示的实体的超类的Class
Constructor[ ] getConstructors()返回一个包含某些Constructor对象的数组
Field[ ] getDeclaredFields()返回Field对象的一个数组
Method getMethod()返回一个Method对象,此对象的形参类型为paramType
package hsp.reflection_;

import hsp.test.Car;

import java.lang.reflect.Field;

/*
 * Class常用方法
 * */
public class Class02 {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        String classAllPath = "hsp.test.Car";
        //1 . 获取到 Car 类 对应的 Class 对象
//<?> 表示不确定的 Java 类型
        Class<?> cls = Class.forName(classAllPath);
//2. 输出 cls
        System.out.println(cls); //显示 cls 对象, 是哪个类的 Class 对象 hsp.test.Car
        System.out.println(cls.getClass());//输出 cls 运行类型 java.lang.Class
//3. 得到包名
        System.out.println(cls.getPackage().getName());//包名 hsp.test
//4. 得到全类名
        System.out.println(cls.getName());//hsp.test.Car
//5. 通过 cls 创建对象实例
        Car car = (Car) cls.newInstance();
        System.out.println(car); // 调用car.toString()
//6. 通过反射获取属性 brand
        Field brand = cls.getField("brand");
        System.out.println(brand.get(car));//宝马
//7. 通过反射给属性赋值
        brand.set(car, "奔驰");
        System.out.println(brand.get(car));//奔驰
//8 我希望大家可以得到所有的属性(字段)
        System.out.println("=======所有的字段属性====");
        Field[] fields = cls.getFields();
        for (Field f: fields) {
            System.out.println(f.getName());//名称
        }
    }
}

2.3 获取Class类对象

1.前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法
forName()获取,可能抛出ClassNotFoundException
实例: Class cls1 = Class.forName( “java.lang.Cat” );
应用场景:多用于配置文件,读取类全路径,加载类.
2.前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
实例: Class cls2 = Cat.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象.
3.前提:已知某个类的实例,调用该实例的getClass(方法获取Class对象
实例:Class clazz =对象.getClassQ;//运行类型
应用场景:通过创建好的对象,获取Class对象.
4.其他方式
ClassLoader cl =对象.getClass0.getClassLoader()
Class clazz4 = cl.loadClass( "类的全类名”);
5.基本数据(int, charboolean,float,double,byte,long,short)按如下方式得到Class类对象
Class cls =基本数据类型.class
6.基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class cls = 包装类.TYPE

package hsp.reflection_;

import hsp.test.Car;

public class Class03 {
    public static void main(String[] args) throws ClassNotFoundException {
        //1. Class.forName
        String classAllPath = "hsp.test.Car"; //通过读取配置文件获取
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);
        //2. 类名.class , 应用场景: 用于参数传递
        Class cls2 = Car.class;
        System.out.println(cls2);
        //3. 对象.getClass(), 应用场景,有对象实例
        Car car = new Car();
        Class cls3 = car.getClass();
        System.out.println(cls3);
        //4. 通过类加载器【4 种】来获取到类的 Class 对象
        //(1)先得到类加载器 car
        ClassLoader classLoader = car.getClass().getClassLoader();
        //(2)通过类加载器得到 Class 对象
        Class cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);
        //cls1 , cls2 , cls3 , cls4 其实是同一个对象
        System.out.println(cls1.hashCode());// 312714112
        System.out.println(cls2.hashCode());// 312714112
        System.out.println(cls3.hashCode());// 312714112
        System.out.println(cls4.hashCode());// 312714112
        //5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到 Class 类对象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
        System.out.println(integerClass);//int

        //6. 基本数据类型对应的包装类,可以通过 .TYPE 得到 Class 类对象
        Class<Integer> type1 = Integer.TYPE;
        Class<Character> type2 = Character.TYPE; //其它包装类 BOOLEAN, DOUBLE, LONG,BYTE 等待
        System.out.println(type2);//char
        System.out.println(integerClass.hashCode());//692404036
        System.out.println(type1.hashCode());//692404036

    }
}

2.4 哪些类型有Class对象

如下类型有Class对象
1.外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2. interface :接口
3.数组
4.enum :枚举
5. annotation :注解
6.基本数据类型
7. void

package hsp.reflection_;

import java.io.Serializable;

public class Class04 {
    public static void main(String[] args) {
        Class<String> cls1 = String.class;//外部类
        Class<Serializable> cls2 = Serializable.class;//接口
        Class<Integer[]> cls3 = Integer[].class;//数组
        Class<float[][]> cls4 = float[][].class;//二维数组
        Class<Deprecated> cls5 = Deprecated.class;//注解
        //枚举
        Class<Thread.State> cls6 = Thread.State.class;
        Class<Long> cls7 = long.class;//基本数据类型
        Class<Void> cls8 = void.class;//void 数据类型
        Class<Class> cls9 = Class.class;//
        System.out.println(cls1);// class java.lang.String
        System.out.println(cls2);// interface java.io.Serializable
        System.out.println(cls3);// class [Ljava.lang.Integer;
        System.out.println(cls4);// class [[F
        System.out.println(cls5);// interface java.lang.Deprecated
        System.out.println(cls6);// class java.lang.Thread$State
        System.out.println(cls7);// long
        System.out.println(cls8);// void
        System.out.println(cls9);// class java.lang.Class
    }
}

3、类加载

3.1 基本说明

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。
1.静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
2.动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性

类加载时机
1.当创建对象时(new) //静态加载
2.当子类被加载时,父类也加载//静态加载
3.调用类中的静态成员时//静态加载
4.通过反射//动态加载
类加载过程图:
在这里插入图片描述

类加载各阶段完成任务:
在这里插入图片描述
加载阶段
在这里插入图片描述
连接阶段-验证
1.目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2.包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证[举例说明]
3. 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。

连接阶段_准备
1.JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如O、OL、null. false 等)。这些变量所使用的内存都将在方法区中进行分配

第15章 JDBC 和数据库连接

1 JDBC概述

1.1基本介绍

1.JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
2. Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
3.JDBC的基本原理图[重要!]
在这里插入图片描述在这里插入图片描述

1.2 JDBC API

在这里插入图片描述

2、JDBC程序编写步骤

1.注册驱动–加载Driver类
2.获取连接-得到Connection
3.执行增删改查-发送SQL给mysql执行
4.释放资源-关闭相关连接
注意:使用前需要将数据库依赖jar包放在目录下
例如:

package jdbc;


import com.mysql.cj.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/*
* 这是第一个 Jdbc 程序,完成简单的操作
* */
public class JDBC01 {
    public static void main(String[] args) throws SQLException {
        //前置工作: 在项目下创建一个文件夹比如 libs
        // 将 mysql.jar 拷贝到该目录下,点击 add to project ..加入到项目中
        //1. 注册驱动
        Driver driver = new Driver(); //创建 driver 对象
        //2. 得到连接
        //(1) jdbc:mysql:// 规定好表示协议,通过 jdbc 的方式连接 mysql
        //(2) localhost 主机,可以是 ip 地
        //(3) 3306 表示 mysql 监听的端口
        //(4) lxc_db01 连接到 mysql dbms 的哪个数据库
        //(5) mysql 的连接本质就是 socket 连接
        String url = "jdbc:mysql://localhost:3306/lxc_db01";
        //将 用户名和密码放入到 Properties 对象
        Properties properties = new Properties();
        //说明 user 和 password 是规定好,后面的值根据实际情况写
        properties.setProperty("user", "root");// 用户
        properties.setProperty("password", "123456"); //密码
        Connection connect = driver.connect(url, properties);
        //3. 执行 sql
        String sql = "insert into actor values(null, '朱茵', '女', '1975-11-11', '')";
        //String sql = "update actor set name='周星驰' where id = 1";
        //String sql = "delete from actor where id = 1";
        //statement 用于执行静态 SQL 语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql); // 如果是 dml 语句,返回的就是影响行数
        System.out.println(rows > 0 ? "成功" : "失败");
        //4. 关闭连接资源
        statement.close();
        connect.close();
    }
}

2.2 获取数据库连接 5 种方式

package jdbc;


import com.mysql.cj.jdbc.Driver;
import org.testng.annotations.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/*
 * 获取数据库连接 5 种方式
 * */
public class JDBC02 {
    //方式1:获取实Driver现类对象
    @Test
    public void connect01() throws SQLException, SQLException {
        Driver driver = new Driver(); //创建 driver 对象
        String url = "jdbc:mysql://localhost:3306/lxc_db01";
        //将 用户名和密码放入到 Properties 对象
        Properties properties = new Properties();
        //说明 user 和 password 是规定好,后面的值根据实际情况写
        properties.setProperty("user", "root");// 用户
        properties.setProperty("password", "123456"); //密码
        Connection connect = driver.connect(url, properties);
        System.out.println(connect);
    }

    //方式2:通过方式1会直接使用com.mysql.jdbc.Driver(),属于静态加载。灵活性差,依赖强
    // //使用反射加载 Driver 类 , 动态加载,更加的灵活,减少依赖性
    @Test
    public void connect02() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {

        Class<?> c = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver) c.newInstance();
        String url = "jdbc:mysql://localhost:3306/lxc_db01";
        //将 用户名和密码放入到 Properties 对象
        Properties properties = new Properties();
        //说明 user 和 password 是规定好,后面的值根据实际情况写
        properties.setProperty("user", "root");// 用户
        properties.setProperty("password", "123456"); //密码
        Connection connect = driver.connect(url, properties);
        System.out.println(connect);
    }
    
    //方式3:使用DriverManger替代Driver进行同一管理
    @Test
    public void connect03() throws SQLException, SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> c = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver) c.newInstance();
        String url = "jdbc:mysql://localhost:3306/lxc_db01";
        String user = "root";
        String password = "123456";
        DriverManager.registerDriver(driver);//注册Diver驱动
        Connection connect = DriverManager.getConnection(url,user,password);
        System.out.println(connect);
    }

    //方式4:使用Class.forName 自动注册,简化代码
    //推荐使用
    @Test
    public void connect04() throws SQLException, SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
         //使用反射加载了 Driver 类
        //在加载 Driver 类时,完成注册
        /*
        源码:
        1. 静态代码块,在类加载时,会执行一次.
        2. DriverManager.registerDriver(new Driver());
        3. 因此注册 driver 的工作已经完成
         static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                 throw new RuntimeException("Can't register driver!");
                }
          }
        */
        Class<?> c = Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/lxc_db01";
        String user = "root";
        String password = "123456";
        Connection connect = DriverManager.getConnection(url,user,password);
        System.out.println(connect);
    }
    //方式5:在方式 4 的基础上改进,增加配置文件,让连接 mysql 更加灵活
    @Test
    public void connect05() throws SQLException, SQLException, ClassNotFoundException, IOException {
        //通过 Properties 对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        Class.forName(driver);//建议写
        Connection connect = DriverManager.getConnection(url,user,password);
        System.out.println(connect);
    }
}

提示:
1.mysqL驱动5.1.6可以无需CLass . forName(“com.mysql.jdbc.Driver”);
2.从jdk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动而是自动调用驱动
jar包下META-INF\servicesVjava.sql.Driver文本中的类名称去注册
3.建议还是写上CLass . forName(“com.mysql.jdbc.Driver”),更加明确

3、 ResultSet【结果集】

3.1基本介绍

1.表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
2. ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
3. .next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false ,因此可以在while循环中使用循环来遍历结果集

package jdbc;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

/*
* select 语句返回 ResultSet ,并取出结果
* */
public class ResultSet01 {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {
        //通过 Properties 对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1.注册驱动
        Class.forName(driver);//建议写

        //2.得到连接
        Connection connect = DriverManager.getConnection(url,user,password);

        //3.得到Statement
        Statement statement = connect.createStatement();

        //4.组织sql
        String sql = "select id,name,sex,borndate from actor";
        ResultSet resultSet = statement.executeQuery(sql); //返回结果集

        //5.使用while取出数据
        while (resultSet.next()){ //next() 光标下移,没有更多行就返回false
            int id = resultSet.getInt(1);//获取改行的第1列
            String name = resultSet.getNString(2);//获取改行的第2列
            String sex = resultSet.getNString(3);//获取改行的第3列
            Date date = resultSet.getDate(4);//获取改行的第4列
            //String phone = resultSet.getNString(5);//获取改行的第5列
            System.out.println(id+"\t"+name+"\t"+sex+"\t"+date+"\t");

        }

        //6.关闭连接
        resultSet.close();
        statement.close();
        connect.close();

    }
}

4.Statement

4.1 基本介绍

1.Statement对象用于执行静态SQL语句并返回其生成的结果的对象
2.在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过Statement[存在SQL注入]
PreparedStatement[预处理]CallableStatement[存储过程]
3. Statement对象执行SQL语句,存在SQL注入风险
4.SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
5.要防范SQL注入,只要用 PreparedStatement(从Statement扩展而来)取代Statement就可以了

-- 演示 sql 注入
-- 创建一张表
CREATE TABLE admin ( -- 管理员表
NAME VARCHAR(32) NOT NULL UNIQUE,
 pwd VARCHAR(32) NOT NULL DEFAULT '') CHARACTER SET utf8; -- 添加数据
 
INSERT INTO admin VALUES('tom', '123'); -- 查找某个管理是否存在
SELECT *FROM adminWHERE NAME = 'tom' AND pwd;
-- SQL
-- 输入用户名 为 1' or
-- 输入万能密码 为 or '1'= '1
SELECT *
FROM admin
WHERE NAME = '1' OR' AND pwd = 'OR '1'= '1' SELECT *
package jdbc;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;

public class Statement01 {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {

        Scanner scanner = new Scanner(System.in);
        //用户输入用户名和密码
        System.out.print("请输入管理员的名字: "); //next(): 当接收到空格或者 '就是表示结束
        String admin_name = scanner.nextLine(); // 如果希望看到 SQL 注入,这里需要用 nextLine
        System.out.print("请输入管理员的密码: ");
        String admin_pwd = scanner.nextLine();

        //通过 Properties 对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1.注册驱动
        Class.forName(driver);//建议写
        //2.得到连接
        Connection connect = DriverManager.getConnection(url,user,password);
        //3.得到Statement
        Statement statement = connect.createStatement();
        //4.组织sql
        String sql = "select name , pwd from admin where name ='" + admin_name + "' and pwd = '" + admin_pwd + "'";
        ResultSet resultSet = statement.executeQuery(sql);
        if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
            System.out.println("恭喜, 登录成功");
        } else {
            System.out.println("对不起,登录失败");
        }

        //6.关闭连接
        resultSet.close();
        statement.close();
        connect.close();
    }
}

5、 PreparedStatemen

1.PreparedStatement执行的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数. setXxox()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值
2.调用executeQuery),返回ResultSet 对象
3.调用executeUpdate():执行更新,包括增、删、修改
预处理好处
1.不再使用+拼接sql语句,减少语法错误
2.有效的解决了sql注入问题!
3.大大减少了编译次数,效率较高

package jdbc;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;

@SuppressWarnings({"all"})
public class PreparedStatement01 {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {

        Scanner scanner = new Scanner(System.in);
        //用户输入用户名和密码
        System.out.print("请输入管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束
        String admin_name = scanner.nextLine(); // 老师说明,如果希望看到 SQL 注入,这里需要用 nextLine
        System.out.print("请输入管理员的密码: ");
        String admin_pwd = scanner.nextLine();

        //通过 Properties 对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1.注册驱动
        Class.forName(driver);//建议写
        //2.得到连接
        Connection connect = DriverManager.getConnection(url, user, password);

        //3. 得到 PreparedStatement
        //3.1 组织 SqL , Sql 语句的 ? 就相当于占位符
        String sql = "select name , pwd from admin where name = ? and pwd = ?";
        //3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
        PreparedStatement preparedStatement = connect.prepareStatement(sql);
        //3.3 给 ? 赋值
        preparedStatement.setString(1, admin_name);
        preparedStatement.setString(2, admin_pwd);
        //4. 执行 select 语句使用 executeQuery
        // 如果执行的是 dml(update, insert ,delete) executeUpdate()
        // 这里执行 executeQuery,不要再写sql了
        ResultSet resultSet = preparedStatement.executeQuery();
        if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
            System.out.println("恭喜, 登录成功");
        } else {
            System.out.println("对不起,登录失败");
        }

        //6.关闭连接
        resultSet.close();
        preparedStatement.close();
        connect.close();
    }
}

package jdbc;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/*
* PreparedStatement 在del的使用
* */
@SuppressWarnings({"all"})
public class PreparedStatement02 {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {

        Scanner scanner = new Scanner(System.in);
        //用户输入用户名和密码
        System.out.print("请输入管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束
        String admin_name = scanner.nextLine(); // 老师说明,如果希望看到 SQL 注入,这里需要用 nextLine
        System.out.print("请输入管理员的密码: ");
        String admin_pwd = scanner.nextLine();

        //通过 Properties 对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1.注册驱动
        Class.forName(driver);//建议写
        //2.得到连接
        Connection connect = DriverManager.getConnection(url, user, password);

        //3. 得到 PreparedStatement
        //添加记录 //修改记录  //删除记录
        //String sql = "insert into admin values(?,?)";
        String sql = "update admin set pwd  = ? where name = ? ";
        //String sql = "delete from   admin  where name = ? ";

        PreparedStatement preparedStatement = connect.prepareStatement(sql);
        preparedStatement.setString(1, admin_pwd);
        preparedStatement.setString(2, admin_name);


        //4. 执行 select 语句使用 executeQuery
        int rows = preparedStatement.executeUpdate();

        System.out.println(rows > 0 ? "执行成功" : "执行失败");


        //6.关闭连接
        preparedStatement.close();
        connect.close();
    }
}


6、阶段小结

在这里插入图片描述在这里插入图片描述

7、封装JDBCUtils【关闭连接,得到连接】

7.1 说明

在jdbc操作中,获取连接和释放资源是经常使用到,可以将其封装JDBC连接的工真类JDBCUtils

package jdbc;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

/*
 * 封装一个工具类,完成mysql的连接和关闭操作
 * */
@SuppressWarnings({"All"})
public class JDBCUtils {
    //定义相关属性(4个),只要一份,做出static即可
    private static String user;
    private static String password;
    private static String url;
    private static String driver;

    //静态代码块去初始化
    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
            //读取相关属性
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            //实际开发中,将编译异常转成运行异常,
            //1. 将编译异常转成 运行异常
            //2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便
            throw new RuntimeException(e);
        }
    }

    //连接数据库 返回Connection
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //关闭相关资源
    /*
     * ResultSet 结果集
     * Statement 或 PreparedStatement
     * Connection
     * 如果需要关闭资源,就传入对象,否则null
     * */

    public static void close(ResultSet resultSet, Statement statement, Connection connection) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

package jdbc;

import org.testng.annotations.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
 * 使用封装好的JDBCUtils工具类
 * */
public class JDBCUtils_use01 {
    @Test
    public void test1() {
        //1得到连接
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        //2.组织sql
        String sql = "update actor set name = ? where id = ? ";

        //3.创建preparedStatement对象
        try {
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            //给占位符
            preparedStatement.setString(1, "codeSE");
            preparedStatement.setInt(2, 1);
            //执行
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }
}

package jdbc;

import org.testng.annotations.Test;

import java.sql.*;

/*
 * 使用封装好的JDBCUtils工具类
 * */
public class JDBCUtils_use02 {
    @Test
    public void test1() {
        //1得到连接
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        //2.组织sql
        String sql = "select * from actor";

        //3.创建preparedStatement对象
        try {
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);

            //执行,得到结果集
           resultSet =  preparedStatement.executeQuery();
           //遍历
            while (resultSet.next()){
                int id = resultSet.getInt("id");
                String name = resultSet.getNString("name");
                String sex = resultSet.getNString("sex");
                Date borndate = resultSet.getDate("borndate");
                String phone = resultSet.getNString("phone");
                System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate+"\t"+phone);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtils.close(resultSet, preparedStatement, connection);
        }
    }
}

8 、事务

8.1 基本介绍

1.JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
2. JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
3.调用Connection的 setAutoCommit(false)可以取消自动提交事务
4.在所有的SQL语句都成功执行后,调用Connection 的commit();方法提交事务
5在其中某个操作失败或出现异常时,调用Connection的rollback();方法回滚事务

package jdbc;

import org.testng.annotations.Test;

import java.sql.*;

/*
 * 使用事务
 * */
public class Transaction01 {
    @Test
    public void test1() {
        //操作转账业务
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        String sql1 = "update account set balance = balance - 100 where id = 1";
        String sql2 = "update account set balance = balance + 100 where id = 2";


        try {
            connection = JDBCUtils.getConnection();//默认情况下,connection自动提交
            connection.setAutoCommit(false);//设置为不自动提交,相当开启了事务

            //执行1
            preparedStatement = connection.prepareStatement(sql1);
            preparedStatement.executeUpdate();

            //int i = 1 / 0; //人为制造异常

            //执行2
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate();

            //这里提交事务
            connection.commit();
        } catch (SQLException e) {
            //这里可以进行回滚,撤销执行的sql
            try {
                connection.rollback();//默认回滚到开始状态
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }
}

9、批处理

9.1 基本介绍

1.当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
2JDBC的批量处理语句包括下面方法:
addBatch():添加需要批量处理的SQL语句或参数executeBatch():执行批量处理语句;
clearBatch():清空批处理包的语句
3.JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数?rewriteBatchedStatements=true
4.批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高

package jdbc;

import org.testng.annotations.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
 * java批处理
 * */
public class Batch01 {

    // 传统方法,添加 5000条数据到admin表
    @Test
    public void test1() throws SQLException {
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into admin values(null,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        System.out.println("开始执行:···");
        long start = System.currentTimeMillis();
        for (int i = 1; i <= 5000; i++) {
            preparedStatement.setString(1, "codeSE" + i);
            preparedStatement.setString(2, "666666" + i);
            preparedStatement.executeUpdate();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方法耗时:" + (end - start) + "ms");//4504ms

        //关闭
        JDBCUtils.close(null, preparedStatement, connection);
    }
    //使用批处理
    @Test
    public void test2() throws SQLException {
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into admin2 values(null,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        System.out.println("开始执行:···");
        long start = System.currentTimeMillis();
        for (int i = 1; i <= 5000; i++) {
            preparedStatement.setString(1, "codeSE" + i);
            preparedStatement.setString(2, "666666" + i);
            //将sql加入批处理包中
            preparedStatement.addBatch();
            //当有积攒慢1000条,再批量执行
            if(i%1000==0){
                preparedStatement.executeBatch();
                preparedStatement.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("使用批处理耗时:" + (end - start) + "ms");//63ms

        //关闭
        JDBCUtils.close(null, preparedStatement, connection);
    }
}

10、数据库连接池

10.1 传统获取 Connection 问题

1.传统的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证IP地址,用户名和密码(0.05s ~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
2.每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
3.传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
4.解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)。

10.2 数据库连接池基本介绍

1.预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
2.数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
3.当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中

10.3 数据库连接池种类

1.JDBC的数据库连接池使用javax.sql.DataSource 来表示,DataSource只是一个接口,该接口通常由第三方提供实现[提供.jar]
2.C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate, spring)
3.DBCP数据库连接池,速度相对c3p0较快,但不稳定
4.Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
5.BoneCP数据库连接池,速度快
6. Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool,优点于一身的数据库连接池

<c3p0-config>
<!--数据源名称代表连接池-->
  <named-config name="hello"> 
<!-- 驱动类 -->
  <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
  <!-- url-->
  	<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/lxc_db01</property>
  <!-- 用户名 -->
  		<property name="user">root</property>
  		<!-- 密码 -->
  	<property name="password">123456</property>
  	<!-- 每次增长的连接数-->
    <property name="acquireIncrement">5</property>
    <!-- 初始的连接数 -->
    <property name="initialPoolSize">10</property>
    <!-- 最小连接数 -->
    <property name="minPoolSize">5</property>
   <!-- 最大连接数 -->
    <property name="maxPoolSize">10</property>

	<!-- 可连接的最多的命令对象数 -->
    <property name="maxStatements">5</property> 
    
    <!-- 每个连接对象可连接的最多的命令对象数 -->
    <property name="maxStatementsPerConnection">2</property>
  </named-config>
</c3p0-config>
package jdbc.connectionPool_;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.testng.annotations.Test;

import java.beans.PropertyVetoException;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/*
* c3p0的使用
* */
public class C3p0_01 {
    @Test
    //方式1:: 相关参数,在程序中指定 user, url , password
    public void test1() throws IOException, PropertyVetoException, SQLException {

        //1.创建一个对象,相关参数,在程序中指定 user, url , password
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        //2.通过Properties配置文件获取相关连接信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\jdbc\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //给数据源 comboPooledDataSource 设置相关参数
        //注意:连接管理是由 comboPooledDataSource 来管理
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setJdbcUrl(url);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);

        //设置初始化连接数
        comboPooledDataSource.setInitialPoolSize(10);
        //设置最大连接数
        comboPooledDataSource.setMaxPoolSize(50);

        //测试连接5000次的效率
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            Connection connection = comboPooledDataSource.getConnection();//这个方法就是从 DataSource 接口实现的
            //System.out.println("连接成功");
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("c3p0连接5000次耗时:"+(end-start)+"ms");//208ms



    }
    @Test
    public void test2() throws SQLException {
        //第二种方式 使用配置文件模板来完成
        //1. 将 c3p0 提供的 c3p0.config.xml 拷贝到 src 目录下
        //2. 该文件指定了连接数据库和连接池的相关参数
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hello");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            Connection connection = comboPooledDataSource.getConnection();
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("c3p0第二种方式连接5000次耗时:"+(end-start)+"ms");//188ms
    }
}

# druid.properties
#key=value
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/lxc_db01?rewriteBatchedStatements=true
username=root
password=123456
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=50
#max wait time (5000 mil seconds)
maxWait=5000
package jdbc.connectionPool_;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.testng.annotations.Test;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;

public class Druid_01 {
    @Test
    public void test1() throws Exception {
        //1. 加入 Druid jar 包
        //2. 加入 配置文件 druid.properties , 将该文件拷贝项目的 src 目录
        //3. 创建 Properties 对象, 读取配置文

        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\druid.properties"));

        //4.创建一个指定参数的数据库连接池,Druid连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        //测试500000次Druid连接
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            Connection connection = dataSource.getConnection();
            //System.out.println("连接成功");
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("Druid连接500000次耗时:"+(end-start)+"ms");//315ms
    }
}

10.4 将 JDBCUtils 工具类改成 Druid(德鲁伊)实现

package jdbc.connectionPool_;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/*
 * 将 JDBCUtils 工具类改成 Druid(德鲁伊)实现
 * */
public class JDBCUtilsDruid {
    private static DataSource ds;

    //静态代码块完成初始化
    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    //编写getConnection方法
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    //关闭连接, 强调: 在数据库连接池技术中,close 不是真的断掉连接
    // 而是把使用的 Connection 对象放回连接池

    public static void close(ResultSet resultSet, Statement statement, Connection connection) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

package jdbc.connectionPool_;

import org.testng.annotations.Test;

import java.sql.*;

public class JDBCUtilsDruid_use01 {
    @Test
    public void test1() {
        //1得到连接
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        //2.组织sql
        String sql = "select * from actor";

        //3.创建preparedStatement对象
        try {
            connection = JDBCUtilsDruid.getConnection();
            System.out.println(connection.getClass());//运行类型class com.alibaba.druid.pool.DruidPooledConnection
            preparedStatement = connection.prepareStatement(sql);

            //执行,得到结果集
            resultSet =  preparedStatement.executeQuery();
            //遍历
            while (resultSet.next()){
                int id = resultSet.getInt("id");
                String name = resultSet.getNString("name");
                String sex = resultSet.getNString("sex");
                Date borndate = resultSet.getDate("borndate");
                String phone = resultSet.getNString("phone");
                System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate+"\t"+phone);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtilsDruid.close(resultSet, preparedStatement, connection);
        }
    }
}

11、Apache—DBUtils

11.1 入门介绍

在这里插入图片描述

package jdbc.connectionPool_;

import java.util.Date;

/*
*
* Actor对象和actor表的记录对应
* */
public class Actor {
    private Integer id;
    private String nane;
    private String sex;
    private Date borndate;
    private String phone;

    public Actor(){}//一定给个无参构造器【反射需要】

    public Actor(Integer id, String nane, String sex, Date borndate, String phone) {
        this.id = id;
        this.nane = nane;
        this.sex = sex;
        this.borndate = borndate;
        this.phone = phone;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNane() {
        return nane;
    }

    public void setNane(String nane) {
        this.nane = nane;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBorndate() {
        return borndate;
    }

    public void setBorndate(Date borndate) {
        this.borndate = borndate;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Actor{" +
                "id=" + id +
                ", nane='" + nane + '\'' +
                ", sex='" + sex + '\'' +
                ", borndate=" + borndate +
                ", phone='" + phone + '\'' +
                '}';
    }
}

package jdbc.connectionPool_;

import org.testng.annotations.Test;

import java.sql.*;
import java.util.ArrayList;

public class JDBCUtilsDruid_use01 {

    //使用土方法来解决 ResultSet =封装=> Arraylist
    @Test
    public void test2() {
        //1得到连接
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        //2.组织sql
        String sql = "select * from actor";
        ArrayList<Actor> list = new ArrayList<>();
        //3.创建preparedStatement对象
        try {
            connection = JDBCUtilsDruid.getConnection();
            System.out.println(connection.getClass());//运行类型class com.alibaba.druid.pool.DruidPooledConnection
            preparedStatement = connection.prepareStatement(sql);

            //执行,得到结果集
            resultSet = preparedStatement.executeQuery();
            //遍历
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getNString("name");
                String sex = resultSet.getNString("sex");
                Date borndate = resultSet.getDate("borndate");
                String phone = resultSet.getNString("phone");

                //把得到的 resultSet 的记录,封装到 Actor 对象,放入到 list
                list.add(new Actor(id, name, sex, borndate, phone));
                System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
            }
            System.out.println("list集合数据:" + list);
            for (Actor actor : list
            ) {
                System.out.println("id=" + "\t" + actor.getId() + "name=" + actor.getNane() + "\t" + "sex=" + actor.getSex() + "\t" + "borndate=" + actor.getBorndate() + "\t" + "phone=" + actor.getPhone());
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            JDBCUtilsDruid.close(resultSet, preparedStatement, connection);
        }
    }
}

11.2 基本介绍

1.commons-dbutils 是 Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量[真的]。

DbUtils类
1.QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
2.使用QueryRunner类实现查询
3. ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式,

---
ArrayHandler把结果集中的第一行数据转成对象数组。
ArrayListHandler把结果集中的每一行数据都转成一个数组,再存放到List中.BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
BeanListHandler将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里
ColumnListHandler将结果集中某一列的数据存放到List中。
KeyedHandler(name)将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key
MapHandler将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
MapListHandler将结果集中的每一行数据都封装到一个Map里,然后再存放到List
package jdbc.connectionPool_;


import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.testng.annotations.Test;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/*
* 使用 apache-DBUtils 工具类 + druid
 * */
@SuppressWarnings({"All"})
public class DBUtils_use01 {

    /*
    * 返回结果是多行的情况
    * */
    @Test
    public void testQueryMany() throws SQLException {
        //1. 得到 连接 (druid)
        Connection connection = JDBCUtilsDruid.getConnection();
        //2. 使用 DBUtils 类和接口 , 先引入 DBUtils 相关的 jar , 加入到本 Project
        //3. 创建 QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4. 就可以执行相关的方法,返回 ArrayList结果集
        //String sql = "select * from actor where id >= ?";
        String sql = "select name, phone from actor where id >= ?";
        //解读:
        //(1) query 方法就是执行 sql 语句,得到 resultset ---封装到 -->ArrayList 集合中
        //(2) 返回集合
        //(3) connection: 连接
        //(4) sql : 执行的 sql 语句
        //(5) new BeanListHandler<>(Actor.class): 在将 resultset -> Actor 对象 -> 封装到 ArrayList
        // 底层使用反射机制 去获取 Actor 类的属性,然后进行封装
        //(6) 1 就是给 sql 语句中的? 赋值,可以有多个值,因为是可变参数 Object... params
        //(7) 底层得到的 resultset ,会在 query 关闭, 关闭 PreparedStatment
        List<Actor> actorList = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);

        for (Actor actor : actorList){
            System.out.println(actor);
        }

        //释放资源
        JDBCUtilsDruid.close(null,null,connection);
    }

    //演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)
    @Test
    public void testQuerySingle() throws SQLException {

        Connection connection = JDBCUtilsDruid.getConnection();
        QueryRunner queryRunner = new QueryRunner();
        String sql = "select * from actor where id = ?";
        //使用BeanHandler方法,返回单个对象
        Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 3);
        System.out.println(actor);

        //释放资源
        JDBCUtilsDruid.close(null,null,connection);
    }
    /*
    * apache-dbutils + druid 完成查询结果是单行单列-返回的就是 objec
    * */
    @Test
    public void testQueryCcalar() throws SQLException {

        Connection connection = JDBCUtilsDruid.getConnection();
        QueryRunner queryRunner = new QueryRunner();
        String sql = "select name from actor where id = ?";
        //使用ScalarHandler方法,返回单行单列,返回就是Object
        Object obj = queryRunner.query(connection, sql, new ScalarHandler<>(), 3);
        System.out.println(obj);

        //释放资源
        JDBCUtilsDruid.close(null,null,connection);
    }

    /*
    * apache-dbutils + druid 完成 dml (update, insert)
    * */

    @Test
    public void testQueryDML() throws SQLException {

        Connection connection = JDBCUtilsDruid.getConnection();
        QueryRunner queryRunner = new QueryRunner();
        //String sql = "insert into actor values (?,?,?,?,?)";
        String sql = "update actor set name = ? where id = ?";
        //(1) 执行 dml 操作是 queryRunner.update()
        //(2) 返回的值是受影响的行数 (affected: 受影响)
        //int affected = queryRunner.update(connection, sql, null,"周润发","男","1955-01-01","13567678888");
        int affected = queryRunner.update(connection,sql,"释小龙",1);
        System.out.println(affected>0?"执行成功":"执行没有影响到表");

        //释放资源
        JDBCUtilsDruid.close(null,null,connection);
    }
}


11.3 DAO和增删改查通用方法-BasicDao

11.1先分析一个问题
apache-dbutils+Druid简化了JDBC开发,但还有不足:;
1.SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
2对于select 操作,如果有返回值,返回类型不能固定,需要使用泛型
3.将来的表很多,业务需求复杂,不可能只靠一个Java类完成
4.引出–>BasicDAO画出示意图,看看在实际开发中,应该如何处理
在这里插入图片描述

第16章 正则表达式

1、正则表达式基本介绍

1.一个正则表达式,就是用某种模式去匹配字符串的一个公式。很多人因为它们看上去比较古怪而且复杂所以不敢去使用,不过,经过练习后,就觉得这些复杂的表达式写起来还是相当简单的,而且,一旦你弄懂它们,你就能把数小时辛苦而且易错的文本处理工作缩短在几分钟(甚至几秒钟)内完成
2.正则表达式不是只有java才有,实际上很多编程语言都支持正则表达式进行字符串操作!

2、正则表达式语法

2.1 基本介绍

各种元字符的功能,元字符从功能上大致分为:
1.限定符
2.选择匹配符
3.分组组合和反向引用符
4.特殊字符
5.字符匹配符
6.定位符

2.2元字符(Metacharacter)-转义号\

\符号说明:在我们使用正则表达式去检索某些特殊字符的时候,需要用到转义符号,否则检索不到结果,甚至会报错的。
在Java的正则表达式中,两个\代表其他语言中的一个\

需要用到转义符号的字符有以下: . * + () $ / \ ? [ ] ^ { }

2.3元字符-字符匹配符

符号含义事例解释匹配输入
[ ]可接收的字符列表[efgh]e、f、g、h中的任意1个字符
[^]不接收的字符列表[abc]除a、b、c之外的任意1个字符,包括数字和特殊符号
连字符A-z任意单个大写字母
.匹配除\n以外的任何字符a…b以a开头,b结尾,中间包括2个任意字符的长度为4的字符串aaab、aefb、a35b、a#*b
\\d匹配单个数字字符,相当于[O-9]\\d{3}(\\d)?包含3个或4个数字的字符串匹配单个非数字字符123、9876
\\D匹配非单个数字字符,相当于[^O-9]\\D(\\d)*以单个非数宁字符开头,后接任意个数字字符串a、A342
\\w匹配单个数字、大小写字母字符,相当手[0-9a-zA-Z]\\d{3}\\w{4}以3个数字字符开头的长度为7的数字字母字符串234abcd、12345Pe
\\W匹配单个非数字、大小写字母字相当于[^0-9a-zA-Z]\\W+\\d{2}以至少1个非数字字母字符开头,2个数字符结尾的字符串#29、#?@10
package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
* 转义字符的使用
* */
public class Regexp02 {
    public static void main(String[] args) {
        String content1 = "abcd$(abc(123(.";
        String content2 = "a11\nc8k_  abc.ABC&~";
        //匹配( ==>\\(
        //匹配. => \\.
        //String regStr = "\\.";
        //String regStr = "\\d\\d\\d";
        //String regStr = "\\d{3}";
        //String regStr = "[a-z]";//匹配 a-z之间任意一个字符
        //String regStr = "[A-Z]";//匹配 A-Z之间任意一个字符
        //String regStr = "abc";//匹配 abc字符串【默认区分大小写】
        //String regStr = "(?i)abc";//匹配 abc字符串【不区分大小写】
        //String regStr = "[0-9]";//匹配 0-9 之间任意一个字符
        //String regStr = "[^a-z]";//匹配 不在 a-z 之间任意一个字符
        //String regStr = "[^0-9]";//匹配 不在 0-9 之间任意一个字符
        //String regStr = "[abcd]";//匹配 在 abcd 中任意一个字符
        //String regStr = "\\D";//匹配 不在 0-9 的任意一个字符
        //String regStr = "\\w";//匹配 大小写英文字母, 数字,下划线
        //String regStr = "\\W";//匹配 等价于 [^a-zA-Z0-9_]
        //\\s 匹配任何空白字符(空格,制表符等)
        //String regStr = "\\s";
        //\\S 匹配任何非空白字符 ,和\\s 刚好相反
        //String regStr = "\\S";
        //. 匹配出 \n 之外的所有字符,如果要匹配.本身则需要使用 \\.
        String regStr = ".";

        //说明
        //1. 当创建 Pattern 对象时,指定 Pattern.CASE_INSENSITIVE, 表示匹配是不区分字母大小写.
        // Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE)
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content2);
        while (matcher.find()){
            System.out.println("找到:"+matcher.group(0));
        }
    }
}

2.4元字符-选择匹配符

在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以四配那个,这时你需要用到选择匹配符号

符号含义事例解释
|匹配“\”之前或之后的表达式ab|cdab或者cd
package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
* 选择匹配符使用
* */
public class Regexp03 {
    public static void main(String[] args) {
        String content2 = "abc.ABC& ~li  李四 力";
        String regStr = "li|李|里|力";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content2);
        while (matcher.find()){
            System.out.println("找到:"+matcher.group(0));
        }
    }
}

2.5元字符-限定符

用于指定其前面的字符和组合项连续出现多少次

符号含义事例说明匹配输入
*指定字符重复0次或n次(无要求)零到多(abc)*仅包含任意个abc的字符串,等效于\w*abc,abcabcabc
+指定字符重复1次或n次((至少-次)1到多m+(abc)*以至少1个m开头,后接任意个abc的字符串m、abc、mabca
指定字符重复0次或1次(最多-次)0到1m+abc?以至少1个m开头,后接ab或abc的字符串mab,mabc,mmmmabc,mmabc
{n}只能输入n个字符[abcd]{3}由abcd中字母组成的任意长度为3的字符串abc,dbc,adc
{n,}指定至少n个匹配[abcd]{3,}由abcd中字母组成的任意长度不小3的字符串aab,dbc,aaabdc
{n,m}指定至少n个但不多于m个匹配[abcd]{3,5}由abcd中字母组成的任意长度不小于3.不大于5的字符串abc,abcd,aaaaa,bcdab
package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
* 限定符的使用
* */
public class Regexp04 {
    public static void main(String[] args) {

        String content = "a1211111aaaaaahello11";
        //a{3},1{4},\\d{2}
        //String regStr = "a{3}";// 表示匹配 aaa
        //String regStr = "1{4}";// 表示匹配 1111
        //String regStr = "\\d{2}";// 表示匹配 两位的任意数字字符
        //a{3,4},1{4,5},\\d{2,5}

        //细节:java 匹配默认贪婪匹配,即尽可能匹配多的
        //String regStr = "a{3,4}"; //表示匹配 aaa 或者 aaaa
        //String regStr = "1{4,5}"; //表示匹配 1111 或者 11111
        //String regStr = "\\d{2,5}"; //匹配 2 位数或者 3,4,5位数

        //1+
        //String regStr = "1+"; //匹配一个 1 或者多个 1
        //String regStr = "\\d+"; //匹配一个数字或者多个数字
        //String regStr = "1*"; //匹配 0 个 1 或者多个 1
        //?的使用, 遵守贪婪匹配
        String regStr = "a1?"; //匹配 a 或者 a1
        Pattern pattern = Pattern.compile(regStr/*, Pattern.CASE_INSENSITIVE*/);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()) {
            System.out.println("找到 " + matcher.group(0));
        }
    }
}

2.6 正则定位符

符号含义事例说明匹配输入
^指定起始字符^[0-9]+[a-z]*以至少一个数字开头,后接任意小写字母的字符串123,6aa,555ede
$指定结束字符^[0-9]\\-[a-z]*+$以一个数字开头后接连字符“-”,并以至少1个小写字母结束的字符串1-a
\\b匹配目标字符串的边界code\\b这里说的是字符串的边界指的是字串有空格,或者时目标字符串的结束位置codeshunpp pcode nmcode
\\B匹配目标字符串的非边界code\\B和\b含义相反codeshunpp pcode nmcode
package regexp;


import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Regexp05 {
    public static void main(String[] args) {

        //String content = "123-abc";
        String content= "codeshun sscode hkcode";
        ///以至少 1 个数字开头,后接任意个小写字母的字符串
        //String regStr = "^[0-9]+[a-z]*";
        //以至少 1 个数字开头, 必须以至少一个小写字母结束
        //String regStr = "^[0-9]+\\-[a-z]+$";
        //表示匹配边界的 han[这里的边界是指:被匹配的字符串最后, // 也可以是空格的子字符串的后面]
        //String regStr = "code\\b";
        //和\\b 的含义刚刚相反
        String regStr  = "code\\B";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到 " + matcher.group(0));

        }
    }
}

2.7 分组

常用分组构造形式说明
(pattern)非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。
(?<name>pattern)命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如(?‘name’)
(?:pattern)匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符(|)组合模式部件的情况很有用。例如,"industr(?:y|ies)是比"industry|industries’更经济的表达式。
(?=pattern)它是一个非捕获匹配。例如,“Windows (?=95|98|NT|2000)’匹配"Windows 2000"中的"Windows”,但不匹配"Windows 3.1"中的"Windows"。
(!=pattern)该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,“Windows(?!95|98|NT|2000)”匹配"Windows 3.1"中的“Windows”,但不匹配"Windows 2000"中的"Windows"。
package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Regexp06 {
    public static void main(String[] args) {
        String content= "codeshun s6756 hk1009code";

        //非命名分组
        // 1. matcher.group(0) 得到匹配到的字符串
        // 2. matcher.group(1) 得到匹配到的字符串的第 1 个分组内容
        // 3. matcher.group(2) 得到匹配到的字符串的第 2 个分组内
        //String regStr  = "(\\d\\d)(\\d)(\\d)";//匹配四个数字的字符串

        //命名分组: 即可以给分组取名
        String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";//匹配 4 个数字的字符串

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            //System.out.println("找到: " + matcher.group(0));
            //System.out.println("第1分组: " + matcher.group(1));
            //System.out.println("第2分组: " + matcher.group(2));
            //System.out.println("第3分组: " + matcher.group(3));

            System.out.println("找到: " + matcher.group(0));
            System.out.println("第1分组: " + matcher.group(1));
            System.out.println("第1分组【通过组名】: " + matcher.group("g1"));
            System.out.println("第2分组: " + matcher.group(2));
            System.out.println("第2分组【通过组名】: " + matcher.group("g2"));

        }
    }
}

package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
* 非捕获分组,语法奇特
* */
public class Regexp07 {
    public static void main(String[] args) {
        String content= "code小明同学 s6756小明家长 hk小明对象";
        
        //String regStr = "小明同学|小明家长|小明对象";
        //上面的写法可以等价非捕获分组, 注意:不能 matcher.group(1)
        // String regStr = "小明(?:家长|对象|同学)";

        //找到 '小明' 这个关键字,但是要求只是查找小明同学和小明对象 中包含有的小明
        //下面也是非捕获分组,不能使用 matcher.group(1)
        //String regStr = "小明(?=对象|同学)";

        //找到 '小明' 这个关键字,但是要求只是查找 不是 (小明同学 和 小明对象) 中包含有的小明
        //下面也是非捕获分组,不能使用 matcher.group(1)
        String regStr = "小明(?!同学|对象)";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到: " + matcher.group(0));

        }
    }
}

应用实例

package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexpTest01 {
    /*
    *
    * 1.汉字
    * 2邮政编码
    * 要求:是1-9开头的一个六位数.比如:1238903.
    * 3.QQ号码
    *要求:是1-9开头的一个(5位数-10位数)比如:12389,1345687,187698765
    * 4.手机号码
    *要求:必须以13,14,15,18开头的11位数,比如13588889999
    *5.URL:https://www.bilibili.com/video/BV1fh411y7R8?from=search&seid=1831060912083761326
    * */
    public static void main(String[] args) {
        //1.汉字
        String content1 = "中华人民共和国";
        String regStr1 = "^[\u0391-\uffe5]+$";
        Pattern pattern1 = Pattern.compile(regStr1);
        Matcher matcher1 = pattern1.matcher(content1);
        if (matcher1.find()){
            System.out.println("汉字满足格式");
        }else{
            System.out.println("汉字不满足格式");
        }
        //2.邮政编码
        String content2 = "232124";
        String regStr2 = "^[1-9]\\d{5}$";
        Pattern pattern2 = Pattern.compile(regStr2);
        Matcher matcher2 = pattern2.matcher(content2);
        if (matcher2.find()){
            System.out.println("邮政编码满足格式");
        }else{
            System.out.println("邮政编码不满足格式");
        }

        //3.QQ号码
        String content3 = "124";
        String regStr3 = "^[1-9]\\d{4,9}$";
        Pattern pattern3 = Pattern.compile(regStr3);
        Matcher matcher3 = pattern3.matcher(content3);
        if (matcher3.find()){
            System.out.println("QQ号码满足格式");
        }else{
            System.out.println("QQ号码不满足格式");
        }

        //4.手机号码
        String content4 = "13528830843";
        String regStr4 = "^1[3|4|5|8]\\d{9}$";
        Pattern pattern4 = Pattern.compile(regStr4);
        Matcher matcher4 = pattern4.matcher(content4);
        if (matcher4.find()){
            System.out.println("手机号码满足格式");
        }else{
            System.out.println("手机号码不满足格式");
        }

        //5.url
        //String content5 = "https://www.bilibili.com/video/BV1fh411y7R8?from=search&seid=1831060912083761326";
        String content5 = "www.bilibili.com/video/BV1fh411y7R8?from=search&seid=1831060912083761326";
        /*
        * 1、确定开始以http://或者https://
        * 2.然后通过 ([\w-]+\.)+[\w-]+ 匹配 www.bilibili.com
        *  3. /video/BV1fh411y7R8?from=sear 匹配(\/[\w-?=&/%.#]*)?
        * */
        String regStr5 = "^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/%.#]*)?$";//注意:[. ? *]表示匹配就是.本身
        Pattern pattern5 = Pattern.compile(regStr5);
        Matcher matcher5 = pattern5.matcher(content5);
        if (matcher5.find()){
            System.out.println("url满足格式");
        }else{
            System.out.println("url不满足格式");
        }
    }
}

正则表达式三个常用类

java.util.regex 包主要包括以下三个类 Pattern类、Matcher类和PatternSyntaxException
Pattern类
pattern对象是一个正则表达式对象。Pattern 类没有公共构造方法。要创建一个 Pattern对象,调用其公共静态方法,它返回一个 Pattern对象。该方法接受一个正则表达式作为它的第一个参数,比如:Pattern r = Pattern.compile(pattern);
Matcher类
Matcher对象是对输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher 也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个 Matcher 对象
· PatternSyntaxException
PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

package regexp;

import java.util.regex.Pattern;

/*
* matches方法,用于整体匹配,在验证输入的字符串是否满足条件使用
*返回 true|false
* */
public class PatternMethod {
    public static void main(String[] args) {
        String content = "hello abcd hi,中国人民";
        //String regStr = "hello";
        String regStr = "hello.*";
        boolean matches = Pattern.matches(regStr, content);
        System.out.println("整体匹配="+matches);
    }
}

package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
*  Matcher 类的常用方法
* */
public class MatcherMethod {
    public static void main(String[] args) {
        String content = "hello edu jack codetom hello smith hello code code";
        String regStr = "hello";
        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()) {
            System.out.println("=================");
            System.out.println(matcher.start());
            System.out.println(matcher.end());
            System.out.println("找到: " + content.substring(matcher.start(), matcher.end()));
        }
        //整体匹配方法,常用于,去校验某个字符串是否满足某个规则
        System.out.println("整体匹配=" + matcher.matches());
        //完成如果 content 有 code 替换成 中国人
        regStr = "code";
        pattern = Pattern.compile(regStr);
        matcher = pattern.matcher(content);
        //注意:返回的字符串才是替换后的字符串 原来的 content 不变化
        String newContent = matcher.replaceAll("中国人");
        System.out.println("newContent=" + newContent);
        System.out.println("content=" + content);
    }
}

分组、捕获、反向引用

(\\d\\d)(\\d\\d)
1.分组
我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式/一个分组。
2捕获
把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式
3.反向引用
圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\分组号,外部反向引用$分组号

package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
* 分组、捕获、反向引用
* */
public class Regexp08 {
    public static void main(String[] args) {
        String content = "hello66666 jack333 tom11 jack22 xxx155134 yyy12321-333999111";
        //String regStr = "(\\d)\\1";//1.要匹配两个连续的相同数字:(\\d)\\1
        //String regStr = "(\\d)\\1{4}";//2.要匹配五个连续的相同数字:(\\d)\\1{4}
        //String regStr = "(\\d)(\\d)\\2\\1";//3.要匹配个位与千位相同,十位与百位相同的数5225,1551 (\\d)(\\d)\\2\\1
        String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}";//请在字符串中检索商品编号,形式如:12321-333999111这样的号码,要求满足前面是一个五位数,然后一个-号,然后是一个九位数,连续的每三位要相同

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到:"+matcher.group(0));
        }
    }
}

package regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Regexp09 {
    public static void main(String[] args) {

        String content = "我....我要....学学学学....编程 java!";
        //1. 去掉所有的.
        Pattern pattern = Pattern.compile("\\.");
        Matcher matcher = pattern.matcher(content);
        content = matcher.replaceAll("");
        System.out.println("content=" + content);
        //2. 去掉重复的字 我我要学学学学编程 java!
        // 思路
        //(1) 使用 (.)\\1+
        //(2) 使用 反向引用$1 来替换匹配到的内容
        // 注意:因为正则表达式变化,所以需要重置 matcher
//        pattern = Pattern.compile("(.)\\1+");
//        matcher = pattern.matcher(content);
//        while (matcher.find()) {
//            System.out.println("找到:" + matcher.group(0));
//        }
//        //使用 反向引用$1
//        content =  matcher.replaceAll("$1");
 //       System.out.println("content:" + content);

        //3. 使用一条语句 去掉重复的字 我我要学学学学编程 java!
        content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
        System.out.println("content=" + content);

    }
}

String类中使用正则表达式

package regexp;

public class StringReg {
    public static void main(String[] args) {
        String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布,几周后其" +
                "获得了Apple公司Mac OS X的工业标准的支持。2001年9月24日,J2EE1.3发布。" +
                "2002年2月26日,J2SE1.4发布。自此Java的计算能力有了大幅提升";
        //使用正则表达式方式,将JDK1.3和JDK1.4替换成JDK
        content = content.replaceAll("JDK1.3|JDK1.4", "JDK");
        System.out.println(content);

        //要求 验证一个 手机号, 要求必须是以 138 139 开头的
        content = "13888889999";
        if (content.matches("1(38|39)\\d{8}")) {
            System.out.println("验证成功");
        } else {
            System.out.println("验证失败");
        }


        //要求按照 # 或者 - 或者 ~ 或者 数字 来分割
        System.out.println("=======================================================");
        content = "hello#abc-jack12smith~北京";
        String[] split = content.split("#|-|~|\\d+");
        for (String s : split) {
            System.out.println(s);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦境之冢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值