Study Notes: Dark Horse Programmer Java-Advanced Chapter (2) (Part 5)

Java language introduction to mastery chapters

  1. Study notes: Java-Basics (Part 1)_ljtxy.love’s blog-CSDN blog
  2. Study Notes: Java-Intermediate (Part 2)_ljtxy.love’s Blog-CSDN Blog
  3. Study Notes: Java-Advanced Chapter (Part 3)_ljtxy.love’s Blog-CSDN Blog
  4. Study Notes: Java-Advanced Chapter (1) (Part 4)_ljtxy.love's Blog-CSDN Blog
  5. Study Notes: Java-Advanced Chapter (2) (Part 5)_ljtxy.love’s Blog-CSDN Blog
  6. Study Notes: New Features of Java8_ljtxy.love’s Blog-CSDN Blog

Article directory

25. Multithreading

Summary of notes: see each section

25.1 Overview

Summary of notes:

  1. Process: is a running program
  2. Thread: is a single sequential control flow in a process , an execution path
  3. Parallelism: At the same time, multiple instructions are executed simultaneously on multiple CPUs
  4. Concurrency: At the same time, multiple instructions are executed alternately on a single CPU
  5. Preemptive scheduling model: Prioritize threads with high priority to use the CPU
  6. Time-sharing scheduling model: All threads take turns using the CPU , and the time slices occupied by each thread on the CPU are evenly distributed.
  7. Daemon Thread is a special thread that provides a general service in the background

25.1.1 Processes and Threads

Process: is a running program

  • Independence: A process is a basic unit that can run independently, and it is also an independent unit for system allocation and scheduling.
  • Dynamics: The essence of a process is an execution process of a program. A process is dynamically generated and dies dynamically.
  • Concurrency: Any process can execute concurrently with other processes

Thread: is a single sequential control flow in a process, an execution path

  • Single-threaded: If a process has only one execution path, it is called a single-threaded program.

  • Multithreading: If a process has multiple execution paths, it is called a multithreaded program.

​ Thread is the smallest unit that the operating system can perform calculation scheduling. It is included in the process and is the actual operating unit in the process.

image-20230428111837338

25.1.2 Concurrency and Parallelism

  • Parallelism: At the same time, multiple instructions are executed simultaneously on multiple CPUs .

    image-20230428112044449

  • Concurrency: At the same time, multiple instructions are executed alternately on a single CPU .

    image-20230428112056574

25.1.3 Thread Scheduling

​ Preemptive scheduling model: Prioritize threads with higher priority to use the CPU. If the threads have the same priority, one will be randomly selected. The thread with higher priority will get relatively more CPU time slices.

image-20230429094213082

illustrate:

​ The probability of the CPU calling a thread is random, and the calling time is also uncertain

​ Time-sharing scheduling model: All threads take turns using the CPU, and the time slice occupied by each thread on the CPU is evenly distributed.

image-20230429094311054

illustrate:

The CPU calls threads sequentially, and the call duration is relatively fixed.

25.1.4 Daemon thread

In Java, a daemon thread (Daemon Thread) is a special thread that provides a general service in the background . When there are only daemon threads left in the Java Virtual Machine (JVM), the JVM will exit. Unlike user threads, daemon threads do not prevent the JVM from exiting.

image-20230429100500333

illustrate:

​While chatting, you can also transfer files

25.2 Implementation

Summary of notes:

  • Implementation method: inherit the Thread class and implement the Runnable and Callable interfaces
  • Usage: Receive the thread return result through thread object.start() or FutureTask<String> ft = new FutureTask<>(mc); . Please see each section for details

25.2.1 Overview

image-20230429090017292

  • Implement Runnable and Callable interfaces
    • Advantages: Strong scalability, you can also inherit other classes while implementing this interface
    • Disadvantages: programming is relatively complicated, and methods in the Thread class cannot be used directly
  • Inherit the Thread class
    • Advantages: programming is relatively simple, you can directly use the methods in the Thread class
    • Disadvantages: poor scalability, can no longer inherit other classes

25.2.2 Inherit the Thread class

25.2.2.1Construction method

method name illustrate
Thread(Runnable target) Allocate a new Thread object
Thread(Runnable target, String name) Allocate a new Thread object

25.2.2.2 Common member methods

method name illustrate
void run( ) After the thread is started, this method will be called and executed.
void start( ) Cause this thread to start execution, and the Java virtual machine calls the run method ()

25.2.2.3 Case - Printing Thread

Implementation steps:

  1. Define a class MyThread that inherits the Thread class
  2. Override the run() method in the MyThread class
  3. Create an object of MyThread class
  4. Start thread
public class MyThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        for(int i=0; i<100; i++) {
    
    
            System.out.println(i);
        }
    }
}
public class MyThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

//        my1.run();
//        my2.run();

        //void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();
    }
}

Notice:

  1. Override the run method in the class, because run() is used to encapsulate the code executed by the thread
  2. The difference between run() and start() methods is
    • run(): encapsulates the code executed by the thread and calls it directly, which is equivalent to calling an ordinary method.
    • start(): Start the thread; then the JVM calls the run() method of this thread

25.2.3 Implement Runnable interface

25.2.3.1 Common member methods

25.2.3.3 Case - Printing Thread

Implementation steps:

  1. Define a class MyRunnable to implement the Runnable interface
  2. Override the run() method in the MyRunnable class
  3. Create an object of MyRunnable class
  4. Create an object of the Thread class and use the MyRunnable object as a parameter of the constructor method
  5. Start thread
public class MyRunnable implements Runnable {
    
    
    @Override
    public void run() {
    
    
        for(int i=0; i<100; i++) {
    
    
            // 此处通过线程来进行获取名称,不能直接使用getName()方法
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class MyRunnableDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
        //Thread(Runnable target)
//        Thread t1 = new Thread(my);
//        Thread t2 = new Thread(my);
        //Thread(Runnable target, String name)
        Thread t1 = new Thread(my,"坦克");
        Thread t2 = new Thread(my,"飞机");

        //启动线程
        t1.start();
        t2.start();
    }
}

25.2.4 Implement the Callable interface

25.2.4.1 Common member methods

method name illustrate
V call() Calculates the result, or throws an exception if the result cannot be calculated
FutureTask(Callable callable) Creates a FutureTask that executes the given Callable once run
V get() If necessary, wait for the calculation to complete and then obtain its result

25.2.4.3 Case - Printing Thread

Implementation steps:

  1. Define a class MyCallable to implement the Callable interface
  2. Override the call() method in the MyCallable class
  3. Create a parameter object MyCallable class to be run by multiple threads
  4. Create an implementation class FutureTask object of the manager object Future for multi-thread running results , and use the MyCallable object as a parameter of the constructor method.
  5. Create a thread object Thread class, and use the FutureTask object as a parameter of the construction method
  6. Start thread
  7. Then call the get method to get the result after the thread ends
public class MyCallable implements Callable<String> {
    
    
    @Override
    public String call() throws Exception {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            System.out.println("跟女孩表白" + i);
        }
        //返回值就表示线程运行完毕之后的结果
        return "答应";
    }
}
public class Demo {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //线程开启之后需要执行里面的call方法
        MyCallable mc = new MyCallable();

        //Thread t1 = new Thread(mc);

        //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
        FutureTask<String> ft = new FutureTask<>(mc);

        //创建线程对象
        Thread t1 = new Thread(ft);

        String s = ft.get();
        //开启线程
        t1.start();

        //String s = ft.get();
        System.out.println(s);
    }
}

25.3 Common member methods

Summary of notes:

  • Thread object. Member methods commonly used in multi-threading usually include getName(),currentEhread()

image-20230812144055888

25.3.1 Overview

Thread object. Commonly used member methods in multi-threading

image-20230429094005433

Replenish:

Set the thread's priority range to 1-10

25.3.2setName(String name)

/* 格式:
	Thread对象.setName(String name)
	作用:为线程起名字*/
thred.setName("玥玥");

detail:

  • If we do not set a name for the thread, the thread also has a default name.

    格式:Thread-X(X序号,从0开始的)
    
  • If we want to set a name for the thread, we can use the set method to set it, or we can set it using the constructor method.

25.3.3getName()

/* 格式:
	Thread对象.getName()
	作用:获取线程的名字*/
thred.getName()

25.3.4currentThread()

/* 格式:
	Thread对象.currentThread()
	作用:获取对当前正在执行的线程对象的引用*/
thred.currentThread()

detail:

  • When the JVM virtual machine is started, multiple threads will be automatically started.
  • One of the threads is called the main thread.
  • ​ His role is to call the main method and execute the code inside
  • In the past, all the code we wrote actually ran in the main thread.

25.3.5sleep(long millis)

/6 格式:
	Thread对象.sleep(long millis)
	作用:使当前正在执行的线程停留(暂停执行)指定的毫秒数*/
thred.sleep(1000)

25.3.6getPriority()

/* 格式:
	Thread对象.getPriority()
	作用:返回此线程的优先级*/
thred.getPriority() // 5

detail:

  • The minimum priority of Java is 1, the default is 5, and the maximum is 10
  • image-20230429095035493

25.3.7setPriority(int newPriority)

/* 格式:
	Thread对象.setPriority(int newPriority)
	作用:更改此线程的优先级,范围1-10*/
thred.setPriority(10) // 5

illustrate:

The higher the priority, it does not mean that it must be executed first, but the probability of preemption is higher.

25.3.8setDaemon(boolean on)

/* 格式:
	Thread对象.setDaemon(boolean on)
	作用:将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出*/
thred.setDaemon(True) 

25.3.9yield()

/* 格式:
	Thread对象.yield()
	作用:让线程输出更均衡一点*/
thred.yield()

illustrate:

Polite thread, adding this method to the thread can make the thread output more balanced. Not commonly used, just understand it

25.3.10jion()

/* 格式:
	Thread对象.jion()
	作用:将此线程在某线程之前插队执行完成*/
thred.jion()

illustrate:

Insert a thread and add this method to the thread. This thread can be queued before a certain thread to complete the execution. Not commonly used, just understand

25.4 Life cycle

Note summary: Please check this section

image-20230429103533897

illustrate:

​Create a thread object and call start()methods. At this time, the thread will continue to grab the CPU until it has both execution qualifications and execution rights. At this time, the thread will execute the task. After the task is executed, the thread will become a thread without execution qualifications and execution rights. reciprocating cycle. If the total task execution is completed, the thread dies.

Replenish:

The sleep method will make the thread sleep. After the sleep time is up, will the following code be executed immediately? The answer is no, and the thread will enter the ready state at this time.

25.5 Thread safety issues

Summary of notes:

  1. The synchronized keyword implements code locking.

    static Object object = new Object(); // 注意:此时锁的对象需要确保唯一,否则加锁失效
    synchronized (object) {
           
            
    	……
    }
    
  2. Synchronization method:

    public synchronized boolean method() {
           
            // 此时锁对象不能够自定义
    	……
    }
    
  3. Lock lock:

    ReentrantLock lock = new ReentrantLock(); //创建锁对象
    lock.lock(); // 加锁
    lock.unlock(); // 释放锁
    
  4. Deadlock: Threads wait for each other , resulting in a mutually blocked state, making it impossible for the program to continue executing.

25.5.1 Synchronized code blocks

25.5.1.1 Overview

25.5.1.1.1Definition

​ Lock up code that operates on shared data

25.5.1.1.2 Features
  • The lock is open by default. If a thread enters, the lock is automatically closed.
  • All the code inside is executed, the thread comes out, and the lock is automatically opened.

25.5.1.2 Basic Use Cases

public class MyThread extends Thread {
    
    

    //表示这个类所有的对象,都共享ticket数据
    static int ticket = 0;//0 ~ 99

    @Override
    public void run() {
    
    
            while (true) {
    
    
                synchronized (MyThread.class) {
    
    
                //同步代码块
                if (ticket < 100) {
    
    
                    try {
    
    
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
                } else {
    
    
                    break;
                }
            }
        }
    }
}

detail:

Generally speaking, the bytecode file of a class object, namely MyThread.class, is unique. Therefore, meeting the lock object is the only condition

25.5.1.3 Common methods

/* 格式:
 synchronized (锁) {
	操作共享数据的代码} */
// 锁对象,一定需要是唯一的
static Object object = new Object();
synchronized (object) {
    
    
	……
}

detail:

The lock object in synchronized must be unique. If it is a different lock object at this time, then the lock added to this code is meaningless.

image-20230429110124857

25.5.1.4 Application Scenarios

image-20230429104133895

illustrate:

​ If multiple threads buy tickets at the same time, then when the ticket++ code is executed, the execution right of the CPU is taken away, and the soout method is not executed, which will lead to a logic error.

25.5.2 Synchronization methods

25.5.2.1 Overview

25.5.2.1.1Definition

The synchronization method in Java refers to a mechanism that can ensure the correctness of data when accessed by multiple threads .

25.5.2.1.2Features
  1. The synchronization method will automatically acquire the lock of the current object when executed.
  2. If a thread has acquired the lock of the current object, other threads must wait for the thread to complete execution and release the lock before they can acquire the lock and execute the synchronized method.
  3. Synchronization methods can ensure thread safety and avoid the problem of multiple threads modifying the same object at the same time.
  4. Synchronous methods may also cause thread blocking and performance degradation , so they need to be used with caution
  5. The lock object cannot be specified by yourself

25.5.2.4 Basic use cases

public class MyRunnable implements Runnable {
    
    

    int ticket = 0;

    @Override
    public void run() {
    
    
        //1.循环
        while (true) {
    
    
            //2.同步代码块(同步方法)
            if (method()) break;
        }
    }

    //this
    private synchronized boolean method() {
    
    
        //3.判断共享数据是否到了末尾,如果到了末尾
        if (ticket == 100) {
    
    
            return true;
        } else {
    
    
            //4.判断共享数据是否到了末尾,如果没有到末尾
            try {
    
    
                Thread.sleep(10);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
        }
        return false;
    }
}

25.5.2.5 Common methods

/* 格式:
修饰符 synchronized 返回值类型 方法名(方法参数) { 
	方法体;	} */

public synchronized boolean method() {
    
    
	……
}

detail:

  • Lock object cannot be specified by yourself
    • Static method: the lock object is the bytecode file of the current object
    • Non-static method: the lock object is this

25.5.2.6 Case-Ticket booking

public class MyRunnable implements Runnable {
    
    

    int ticket = 0;

    @Override
    public void run() {
    
    
        //1.循环
        while (true) {
    
    
            //2.同步代码块(同步方法)
            if (method()) break;
        }
    }

    //this
    private synchronized boolean method() {
    
    
        //3.判断共享数据是否到了末尾,如果到了末尾
        if (ticket == 100) {
    
    
            return true;
        } else {
    
    
            //4.判断共享数据是否到了末尾,如果没有到末尾
            try {
    
    
                Thread.sleep(10);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
        }
        return false;
    }
}
MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

t1.start();
t2.start();
t3.start();

illustrate:

At this time, the lock object passed in by each thread is unique and is mr. Therefore, it is guaranteed that the lock object in the static method of the synchronized method is unique

25.5.3Lock lock

25.5.3.1 Overview

25.5.3.1.1Definition

​ Lock Lock is a synchronization mechanism in Java concurrent programming that can be used to control access to shared resources.

25.5.3.1.2 Features
  1. Explicitly acquire and release locks: The synchronized keyword acquires and releases locks implicitly, while Lock locks require manual acquisition and release of locks.
  2. Fair locks and unfair locks can be implemented: the synchronized keyword is an unfair lock, that is, the order in which locks are acquired is uncertain. Lock locks can implement fair locks and unfair locks, that is, the order in which locks are acquired is predictable.
  3. Threads waiting for the lock can be interrupted: During the process of acquiring the lock, if the thread waits for too long, the thread waiting for the lock can be interrupted.
  4. Support multiple condition variables: Lock can support multiple condition variables, that is, different wake-up operations can be performed on threads under different conditions.
  5. Can improve throughput: In high concurrency scenarios, using Lock can improve the throughput of the system, which is more efficient than the synchronized keyword.

25.5.3.4 Basic use cases

public class MyThread extends Thread{
    
    

    static int ticket = 0;
	// 此处需要将Lock锁变为静态,以保证全局唯一,而不会创建多个Lock锁
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
    
    
        //1.循环
        while(true){
    
    
            //2.同步代码块
            //synchronized (MyThread.class){
    
    
            lock.lock(); //2 //3
            try {
    
    
                //3.判断
                if(ticket == 100){
    
    
                    break;
                    //4.判断
                }else{
    
    
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(getName() + "在卖第" + ticket + "张票!!!");
                }
                //  }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                lock.unlock();
            }
        }
    }
}

detail:

​ When the Lock is about to terminate, it is recommended to use try...catch...finally. Because the finally method will always be executed, it is more appropriate to put it here.

25.5.3.5 Common methods

/* 格式:
构造方法:ReentrantLock() 
加锁解锁方法:获得锁 void lock()、释放锁 void unlock()*/
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();

step:

  1. Create Lock object
  2. get lock
  3. release lock

25.5.4 Deadlock

​ Deadlock refers to a phenomenon in which two or more threads wait for each other due to competition for resources during execution . Without external force, they will be unable to continue execution. To put it simply, two or more threads are waiting for each other to release the lock first, resulting in a mutual blocking state, making the program unable to continue executing.

image-20230429134513180

illustrate:

When a boy and a girl hold a chopstick at the same time, the boy is waiting for the girl to put down the chopsticks, and the girl is waiting for the boy to put down the chopsticks

Example:

public class MyThread extends Thread {
    
    

    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
    
    
        //1.循环
        while (true) {
    
    
            if ("线程A".equals(getName())) {
    
    
                synchronized (objA) {
    
    
                    System.out.println("线程A拿到了A锁,准备拿B锁");//A
                    synchronized (objB) {
    
    
                        System.out.println("线程A拿到了B锁,顺利执行完一轮");
                    }
                }
            } else if ("线程B".equals(getName())) {
    
    
                if ("线程B".equals(getName())) {
    
    
                    synchronized (objB) {
    
    
                        System.out.println("线程B拿到了B锁,准备拿A锁");//B
                        synchronized (objA) {
    
    
                            System.out.println("线程B拿到了A锁,顺利执行完一轮");
                        }
                    }
                }
            }
        }
    }
}

illustrate:

​ When thread A gets the A lock, thread B gets the B lock. At the same time, thread A is waiting to acquire lock B, and thread B is waiting to acquire lock A, so the program will be stuck, which is called deadlock

To avoid deadlocks, don't use lock nested locks

25.6 Producers and consumers

Summary of notes:

  1. Overview: producer consumer is a common concurrency model used to solve data exchange problems between producers and consumers

  2. Common member methods:

    method name illustrate
    void wait() Causes the current thread to wait until another thread calls the object's notify() method or notifyAll() method
    void notify() Wake up a single thread that is waiting on an object monitor
    void notifyAll() Wake up all threads waiting on the object monitor

25.6.1 Overview

25.6.1.1 Definition

Producer-consumer is a common concurrency model used to solve data exchange problems between producers and consumers .

25.6.1.2 Producer-consumer model

  • Producer: Responsible for producing data and storing it in a shared data structure
  • Consumer: Responsible for obtaining data from the shared data structure and consuming it

25.6.1.3 Waiting for wake-up mechanism

image-20230429135922759

25.6.2 Commonly used member methods

method name illustrate
void wait() Causes the current thread to wait until another thread calls the object's notify() method or notifyAll() method
void notify() Wake up a single thread that is waiting on an object monitor
void notifyAll() Wake up all threads waiting on the object monitor

25.6.3 Case-Foodie and Chef

1. Create a table class

public class Desk {
    
    

    /*
    * 作用:控制生产者和消费者的执行
    *
    * */

    //是否有面条  0:没有面条  1:有面条
    public static int foodFlag = 0;

    //总个数
    public static int count = 10;

    //锁对象
    public static Object lock = new Object();

}

2. Create consumers (foodies)

public class Foodie extends Thread{
    
    

    @Override
    public void run() {
    
    
        /*
        * 1. 循环
        * 2. 同步代码块
        * 3. 判断共享数据是否到了末尾(到了末尾)
        * 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
        * */

        while(true){
    
    
            synchronized (Desk.lock){
    
    
                if(Desk.count == 0){
    
    
                    break;
                }else{
    
    
                    //先判断桌子上是否有面条
                    if(Desk.foodFlag == 0){
    
    
                        //如果没有,就等待
                        try {
    
    
                            Desk.lock.wait();//让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                    }else{
    
    
                        //把吃的总数-1
                        Desk.count--;
                        //如果有,就开吃
                        System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!!!");
                        //吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        //修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

detail:

​ When the consumer is waiting, it needs to call Desk.lock.wait();the method of the intermediary lock object. The purpose is to bind the current thread to the lock to facilitate the Desk.lock.notifyAll();wake-up operation of this thread.

3. Create a producer (chef)

public class Cook extends Thread{
    
    

    @Override
    public void run() {
    
    
        /*
         * 1. 循环
         * 2. 同步代码块
         * 3. 判断共享数据是否到了末尾(到了末尾)
         * 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
         * */

        while (true){
    
    
            synchronized (Desk.lock){
    
    
                if(Desk.count == 0){
    
    
                    break;
                }else{
    
    
                    //判断桌子上是否有食物
                    if(Desk.foodFlag == 1){
    
    
                        //如果有,就等待
                        try {
    
    
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                    }else{
    
    
                        //如果没有,就制作食物
                        System.out.println("厨师做了一碗面条");
                        //修改桌子上的食物状态
                        Desk.foodFlag = 1;
                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

detail:

Same as above

4.Use

public class ThreadDemo {
    
    
    public static void main(String[] args) {
    
    
       /*
       *
       *    需求:完成生产者和消费者(等待唤醒机制)的代码
       *         实现线程轮流交替执行的效果
       *
       * */

        //创建线程的对象
        Cook c = new Cook();
        Foodie f = new Foodie();

        //给线程设置名字
        c.setName("厨师");
        f.setName("吃货");

        //开启线程
        c.start();
        f.start();
    }
}

25.7 Blocking Queue

Note summary: View this summary overview in detail

25.7.1 Overview

25.7.1.1 Definition

​ A blocking queue is a special queue that has the characteristic of blocking and waiting when inserting and deleting elements . Blocking queues are often used in multi-threaded programming as a data exchange channel between different threads

25.7.1.2 Inheritance structure

image-20230429143415418

illustrate:

  • ArrayBlockingQueue: The bottom layer is an array, bounded

  • LinkedBlockingQueue: The bottom layer is a linked list, unbounded. But it is not really unbounded, the maximum is the maximum value of int

25.7.2 Case-Foodie and Chef

Step 1: Create consumers (foodies)

public class Foodie extends Thread{
    
    

    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
    
    
        this.queue = queue;
    }


    @Override
    public void run() {
    
    
        while(true){
    
    
                //不断从阻塞队列中获取面条
                try {
    
    
                    String food = queue.take();
                    System.out.println(food);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
        }
    }
}

illustrate:

When using a blocking queue, use the take() method of the object to get data from the blocking queue

Step 2: Create a producer (chef)

public class Cook extends Thread{
    
    

    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
    
    
        this.queue = queue;
    }

    @Override
    public void run() {
    
    
        while(true){
    
    
            //不断的把面条放到阻塞队列当中
            try {
    
    
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

illustrate:

​ When using a blocking queue, use the put() method of the object to insert data from the blocking queue

Step 3: Demo

ublic class ThreadDemo {
    
    
    public static void main(String[] args) {
    
    
       /*
       *
       *    需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
       *    细节:
       *           生产者和消费者必须使用同一个阻塞队列
       *
       * */

        //1.创建阻塞队列的对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);


        //2.创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        //3.开启线程
        c.start();
        f.start();

    }
}

illustrate:

​ Create a blocking object and pass it in to producers and consumers. The purpose is to unify the blocking queues used by chefs (producers) and foodies (consumers).

25.8 Thread status

Note summary: View this section in detail

In Java, the state of a thread refers to the different states of a thread object during its life cycle. Java thread status includes:

image-20230429145626702

  1. NEW: The newly created thread has not yet started execution.
  2. RUNNABLE: Running status, can run, or may be waiting for the CPU time slice.
  3. BLOCKED: The thread is blocked, waiting for a lock or other synchronization resource.
  4. WAITING: The thread is in a waiting state, waiting for other threads to perform an operation or waiting for a timeout.
  5. TIMED_WAITING: The thread is in a time-limited waiting state, such as Thread.sleep().
  6. TERMINATED: The thread has completed execution and exited the thread life cycle.

image-20230429145603204

illustrate:

​ The terms “qualification to execute” and “right to execute” in this diagram are added for ease of understanding. In Java, only the remaining six states will be coordinated by Java. When there is execution qualification and execution rights, Java will no longer take over.

25.9 Thread stack

Note summary: Please see the overview of this section for details

25.9.1 Overview

​ Thread Stack is a memory area allocated to threads by the operating system and is used to store information such as thread method calls and local variables . Each thread has its own thread stack . The thread stacks are independent and will not affect each other .

25.9.2 Memory Map

image-20230429154834528

illustrate:

Whenever a thread is created, various stacks are created in memory

25.9.3 Case-Lottery Pool

1. Create a thread

public class MyThread extends Thread {
    
    

    ArrayList<Integer> list;

    public MyThread(ArrayList<Integer> list) {
    
    
        this.list = list;
    }

    @Override
    public void run() {
    
    
        ArrayList<Integer> boxList = new ArrayList<>();//1 //2
        while (true) {
    
    
            synchronized (MyThread.class) {
    
    
                if (list.size() == 0) {
    
    
                    System.out.println(getName() + boxList);
                    break;
                } else {
    
    
                    //继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    boxList.add(prize);
                }
            }
            try {
    
    
                Thread.sleep(10);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

        }
    }
}

2.Demonstration

public class Test {
    
    
    public static void main(String[] args) {
    
    
        /*
            有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
            创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
            随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
            每次抽的过程中,不打印,抽完时一次性打印(随机)    在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
                分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
            在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
                分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
        */

        //创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);

        //创建线程
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);


        //设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");


        //启动线程
        t1.start();
        t2.start();

    }
}

25.10 Case-Lottery Pool

1. Create a thread

public class MyCallable implements Callable<Integer> {
    
    

    ArrayList<Integer> list;

    public MyCallable(ArrayList<Integer> list) {
    
    
        this.list = list;
    }

    @Override
    public Integer call() throws Exception {
    
    
        ArrayList<Integer> boxList = new ArrayList<>();//1 //2
        while (true) {
    
    
            synchronized (MyCallable.class) {
    
    
                if (list.size() == 0) {
    
    
                    System.out.println(Thread.currentThread().getName() + boxList);
                    break;
                } else {
    
    
                    //继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    boxList.add(prize);
                }
            }
            Thread.sleep(10);
        }
        //把集合中的最大值返回
        if(boxList.size() == 0){
    
    
            return null;
        }else{
    
    
            return Collections.max(boxList);
        }
    }
}

illustrate:

  • The shuffle(); method in the Collections tool class is used here to shuffle the data in the collection.
  • The max method in the Collections tool class is used here to obtain the maximum value in the collection

2.Demonstration

public class Test {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        /*
            有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
            创建两个抽奖箱(线程)设置线程名称分别为    "抽奖箱1", "抽奖箱2"
            随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:

            在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300
        	    最高奖项为300元,总计额为932元

            在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5,50,200,800,80,700
            	最高奖项为800元,总计额为1835元

            在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
            核心逻辑:获取线程抽奖的最大值(看成是线程运行的结果)


            以上打印效果只是数据模拟,实际代码运行的效果会有差异
        */

        //创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);

        //创建多线程要运行的参数对象
        MyCallable mc = new MyCallable(list);

        //创建多线程运行结果的管理者对象
        //线程一
        FutureTask<Integer> ft1 = new FutureTask<>(mc);
        //线程二
        FutureTask<Integer> ft2 = new FutureTask<>(mc);

        //创建线程对象
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);

        //设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        //开启线程
        t1.start();
        t2.start();

        Integer max1 = ft1.get();
        Integer max2 = ft2.get();

        System.out.println(max1);
        System.out.println(max2);

        //在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
        if(max1 == null){
    
    
            System.out.println("在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为"+max2+"元");
        }else if(max2 == null){
    
    
            System.out.println("在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为"+max1+"元");
        }else if(max1 > max2){
    
    
            System.out.println("在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为"+max1+"元");
        }else if(max1 < max2){
    
    
            System.out.println("在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为"+max2+"元");
        }else{
    
    
            System.out.println("两者的最大奖项是一样的");
        }
    }
}

25.11 Thread Pool

Summary of notes:

​ Thread pool is a mechanism to implement multi-threading . It can create a certain number of threads when the application starts . After the task is processed, the threads will not be destroyed.

	   // 1.创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
     ExecutorService executorService = Executors.newCachedThreadPool();//Executors --- 可以帮助我们创建线程池对象
     // 2.ExecutorService --- 可以帮助我们控制线程池
     executorService.submit(()->{
     
     
         System.out.println(Thread.currentThread().getName() + "在执行了");
     });
     // 3.销毁线程池
     executorService.shutdown();

25.11.1 Overview

25.11.1.1 Meaning

In Java, thread pool is a mechanism to implement multi-threading . It can create a certain number of threads when the application starts and save them in the thread pool. Then when a task needs to be processed, an idle thread is taken out from the thread pool to process the task. After the task is processed, the thread is not blocked. Destroy, but return to the thread pool and wait for the arrival of the next task

25.11.1.2 Main core principles

image-20230429162100379

25.11.1.3 Advantages

  1. Improve system performance: The thread pool can automatically control the number of threads based on the actual situation of system resources, making better use of the system's CPU and memory resources, thereby improving system performance.
  2. Improve thread reusability: Threads in the thread pool can be reused, which can avoid the overhead caused by thread creation and destruction, thereby improving thread reusability.
  3. Improve thread manageability: Threads in the thread pool can be managed uniformly, and the number of threads can be controlled by configuring parameters to prevent insufficient system resources caused by too many threads.
  4. Improve the reliability of the program: The thread pool can create new threads as needed, and the threads in the thread pool can automatically recover to avoid the entire program crashing due to thread hang-up.

25.11.2 Basic use cases

package com.itheima.mythreadpool;


//static ExecutorService newCachedThreadPool()   创建一个默认的线程池
//static newFixedThreadPool(int nThreads)	    创建一个指定最多线程数量的线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadPoolDemo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    

        //1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Executors --- 可以帮助我们创建线程池对象
        //ExecutorService --- 可以帮助我们控制线程池

        executorService.submit(()->{
    
    
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        //Thread.sleep(2000);

        executorService.submit(()->{
    
    
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });
		
        //销毁线程池
        executorService.shutdown();
    }
}

illustrate:

The method of the tool class Executors class is used here

  • Get the static method newCachedThreadPool() of the object
  • Submit task member method submit()
  • Shudown() method to shut down the thread pool

25.12 Custom thread pool

Summary of notes:

Custom thread pool object: new ThreadPoolExecutorobject

ThreadPoolExecutor pool = new ThreadPoolExecutor(
 3,  //核心线程数量,能小于0
 6,  //最大线程数,不能小于0,最大数量 >= 核心线程数量
 60,//空闲线程最大存活时间
 TimeUnit.SECONDS,//时间单位
 new ArrayBlockingQueue<>(3),//任务队列
 Executors.defaultThreadFactory(),//创建线程工厂
 new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);

25.12.1 Overview

In Java, you can manage the number of threads in the thread pool, the execution mode of the threads, etc. by customizing the thread pool object . The creation process of custom thread pool objects needs ThreadPoolExecutorto be implemented using classes

25.12.2 Construction method

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
  • corePoolSize: The number of threads maintained in the thread pool , which will not be destroyed even if the thread is idle.
  • maximumPoolSize: The maximum number of threads allowed in the thread pool . When the number of threads in the thread pool reaches this value, subsequent tasks will be blocked.
  • keepAliveTime: The survival time of idle threads in the thread pool .
  • unit: The time unitkeepAliveTime of the parameter .
  • workQueue: Task queue in thread pool .
  • threadFactory: Create a factory object for threads .
  • handler: Rejection strategy , how to handle new tasks when the task queue is full and can no longer accept new tasks.

image-20230429184636853

25.12.3 Basic Use Cases

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    3,  //核心线程数量,能小于0
    6,  //最大线程数,不能小于0,最大数量 >= 核心线程数量
    60,//空闲线程最大存活时间
    TimeUnit.SECONDS,//时间单位
    new ArrayBlockingQueue<>(3),//任务队列
    Executors.defaultThreadFactory(),//创建线程工厂
    new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);

25.12.4 Deny policy

image-20230429185553188

25.13 Frequently asked questions about threads

Summary of notes:

  • Maximum number of parallelism: The maximum number of parallelism refers to the maximum number of threads or tasks running simultaneously
  • Thread pool size: Adjust the initial size of the corresponding thread pool according to the hardware performance of the deployment machine , such as CPU, memory, etc.
  • Expanded knowledge: This chapter will not expand or expand the content of this chapter. It will be added in the future!

25.13.1 Maximum number of parallelism

25.13.1.1 Overview

​Maximum parallelism refers to the maximum number of threads or tasks running simultaneously

25.13.1.2 Basic use cases

public class MyThreadPoolDemo2 {
    
    
    public static void main(String[] args) {
    
    
        //向Java虚拟机返回可用处理器的数目
        int count = Runtime.getRuntime().availableProcessors();
        System.out.println(count);
    }
}

illustrate:

Get the number of processors currently allowed in the environment

25.13.2 Thread pool size

image-20230429191228915

25.13.3 Additional extended knowledge of thread pool

image-20230429192123966

illustrate:

​ This chapter will not expand or expand the content of this lady. It will be added in the future!

25.13 Tools

Summary of notes:

Overview: Multi-threads usually use the Executors tool class to create and use thread pools

25.13.1 Overview

Executors class

Executors: The tool class of the thread pool returns different types of thread pool objects by calling methods.

25.13.2 Construction method

image-20230429162534879

26. Network programming

Summary of notes:

  1. Overview:
    • Definition: Network programming allows data transmission and communication between different computers through the network , allowing remote computers to interact with each other and share information .
    • Software architecture: B/S, C/S
    • Three elements of programming: IP, port, protocol
  2. InetAddresskind:
    • Overview: A class representing a network address . It provides a set of methods for getting hostnames and IP addresses and resolving domain names to IP addresses
    • Common member methods: getByName, getHostName, getHostAddress
  3. UDP communication program:
    • Overview: UDP protocol is an unreliable network protocol
    • Construction method:new DatagramSocket()
    • Common member methods: send, receive, close, getData
    • Multicast and broadcast implementation: just understand it, and go into more depth when you use it
  4. TCP communication program:
    • Overview: TCP protocol is a reliable network protocol with three-way handshake and four-way wave communication guarantee.
    • Construction method:new Socket()
    • Common member methods: accept, getInputStream, close

26.1 Overview

26.1.1 Definition

In Java, network programming refers to the process of using the Java programming language and related class libraries to implement network communication. Network programming allows data transmission and communication between different computers through the network , allowing remote computers to interact with each other and share information.

Under the network communication protocol, programs running on different computers can transmit data.

26.1.2 Software architecture

The software architecture is divided into B/S and C/S

image-20230429193232208

Advantages and Disadvantages of BS/CS Architecture

image-20230429193354559

26.1.3 Three elements of network programming

image-20230429193739128

26.2InetAddress class

26.2.1 Overview

InetAddress is a class used to represent network addresses in Java. It provides a set of methods for getting hostnames and IP addresses and resolving domain names to IP addresses

26.2.2 Common member methods

method name illustrate
static InetAddress getByName(String host) Determine the IP address of the hostname. The host name can be a machine name or an IP address
String getHostName() Get the hostname of this IP address
String getHostAddress() Returns the IP address string in the text display

26.2.3 Case - Obtain basic information

public class InetAddressDemo {
    
    
    public static void main(String[] args) throws UnknownHostException {
    
    
		//InetAddress address = InetAddress.getByName("itheima");
        InetAddress address = InetAddress.getByName("192.168.1.66");

        //public String getHostName():获取此IP地址的主机名
        String name = address.getHostName();
        //public String getHostAddress():返回文本显示中的IP地址字符串
        String ip = address.getHostAddress();

        System.out.println("主机名:" + name);
        System.out.println("IP地址:" + ip);
    }
}

26.3UDP communication program

26.3.1 Overview

26.3.1.1 Definitions

​ The UDP protocol is an unreliable network protocol. It establishes a Socket object at each end of the communication. However, these two Sockets are only objects for sending and receiving data. Therefore, for both parties communicating based on the UDP protocol, it does not matter. The concept of client and server

​ Java provides the DatagramSocket class as a Socket based on the UDP protocol

26.3.1.2UDP three communication methods

  • Unicast

    Unicast is used for end-to-end communication between two hosts

  • multicast

    Multicast is used to communicate to a specific group of hosts

  • broadcast

    Broadcast is used for data communication from one host to all hosts on the entire LAN.

image-20230430100827221

26.3.2 Construction method

method name illustrate
DatagramSocket() Create a datagram socket and bind it to any available port on the local machine address
DatagramPacket(byte[] buf, int len) Create a DatagramPacket to receive data packets of length len
DatagramPacket(byte[] buf,int len,InetAddress add,int port) Create a data packet and send a data packet of length len to the specified port of the specified host

detail:

  • Empty parameter construction: use a random one of all available ports
  • Parameterized construction: Specify the port number for binding

26.3.3 Common member methods

method name illustrate
void send(DatagramPacket p) Send datagram packet
void close() Close datagram socket
void receive(DatagramPacket p) Accept datagram packets from this socket
byte[] getData() Return data buffer
int getLength() Returns the length of data to be sent or the length of data to be received

26.3.4 Basic use case - sending data

step:

  1. Create the Socket object (DatagramSocket) of the sending end – create a courier company
  2. Create data and package the data – create a package
  3. 调用DatagramSocket对象的send方法发送数据–快递公司发送数据
  4. 调用DatagramSocket对象的close方法释放资源–摧毁快递公司
public class SendMessageDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        //发送数据

        //1.创建DatagramSocket对象(快递公司)
        //细节:
        //绑定端口,以后我们就是通过这个端口往外发送
        //空参:所有可用的端口中随机一个进行使用
        //有参:指定端口号进行绑定
        DatagramSocket ds = new DatagramSocket();

        //2.打包数据
        String str = "你好威啊!!!";
        byte[] bytes = str.getBytes();
        InetAddress address = InetAddress.getByName("127.0.0.1");
        int port = 10086;

        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        //3.发送数据
        ds.send(dp);

        //4.释放资源
        ds.close();
    }
}

说明:

  • 在创建DatagramSocket时,指定的端口号是发送方端口号
  • 在创建DatagramPacket时,指定的端口号是接收方端口号

26.3.5基本用例-接收数据

步骤:

  1. 创建接收端的Socket对象(DatagramSocket)–创建快递公司
  2. 创建一个数据包,用于接收数据–创建空包裹
  3. 调用DatagramSocket对象的receive方法接收数据–快递公司接收数据
  4. 解析数据包,并把数据在控制台显示–拆包裹
  5. 调用DatagramSocket对象的close方法释放资源–摧毁快递公司
public class ReceiveMessageDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        //接收数据

        //1.创建DatagramSocket对象(快递公司)
        //细节:
        //在接收的时候,一定要绑定端口
        //而且绑定的端口一定要跟发送的端口保持一致
        DatagramSocket ds = new DatagramSocket(10086);

        //2.接收数据包
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

        //该方法是阻塞的
        //程序执行到这一步的时候,会在这里死等
        //等发送端发送消息
        System.out.println(11111);
        ds.receive(dp);
        System.out.println(2222);

        //3.解析数据包
        byte[] data = dp.getData();
        int len = dp.getLength();
        InetAddress address = dp.getAddress();
        int port = dp.getPort();

        System.out.println("接收到数据" + new String(data,0,len));
        System.out.println("该数据是从" + address + "这台电脑中的" + port + "这个端口发出的");

        //4.释放资源
        ds.close();
    }
}

注意:

  • 在创建DatagramSocket时,指定的端口号是接收方端口号,注意此端口号需要跟发送的端口号保持一致
  • 在创建DatagramPacket时,无需指定IP地址或者端口号,只需要创建空包裹即可

26.3.6案例-UDP组播实现

26.3.6.1基本用例-发送数据

// 发送端
public class ClinetDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        // 1. 创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
        String s = "hello 组播";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10000;
        // 2. 创建数据,并把数据打包(DatagramPacket)
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        // 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
        ds.send(dp);
        // 4. 释放资源
        ds.close();
    }
}

细节:

​ 在发送数据时,创建的包裹指定IP,需要填写指定组播的地址

26.3.6.2基本用例-接收数据

// 接收端
public class ServerDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        // 1. 创建接收端Socket对象(MulticastSocket)
        MulticastSocket ms = new MulticastSocket(10000);
        // 2. 创建一个箱子,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
        ms.joinGroup(InetAddress.getByName("224.0.1.0"));
        // 4. 将数据接收到箱子中
        ms.receive(dp);
        // 5. 解析数据包,并打印数据
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 6. 释放资源
        ms.close();
    }
}

细节:

​ 需要把当前计算机绑定一个组播地址,表示添加到这一组中

26.3.7案例UDP广播实现

26.3.7.1基本用例-发送数据

// 发送端
public class ClientDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
      	// 1. 创建发送端Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
		// 2. 创建存储数据的箱子,将广播地址封装进去
        String s = "广播 hello";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("255.255.255.255");
        int port = 10000;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
		// 3. 发送数据
        ds.send(dp);
		// 4. 释放资源
        ds.close();
    }
}

细节:

​ 发送数据,发送IP指定为 255.255.255.255

26.3.7.2基本用例-接收数据

// 接收端
public class ServerDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        // 1. 创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(10000);
        // 2. 创建一个数据包,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 调用DatagramSocket对象的方法接收数据
        ds.receive(dp);
        // 4. 解析数据包,并把数据在控制台显示
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 5. 关闭接收端
        ds.close();
    }
}

26.4TCP通信程序

26.4.1概述

26.4.1.1定义

​ Java中的TCP通信程序是一种基于Socket套接字的网络通信方式,其中客户端和服务器通过TCP协议进行数据的传输和交互

image-20230502200915057

​ 在Socket进行发送与读取数据时,在底层会进行三次握手与四次挥手的协议,确保所传输的数据是能够被正常接收到

26.4.1.2三次握手

image-20230502203937784

26.4.1.3四次挥手

image-20230502204103146

26.4.2基本用例-发送数据

public class Client {
    
    
    public static void main(String[] args) throws IOException {
    
    
        //TCP协议,发送数据

        //1.创建Socket对象
        //细节:在创建对象的同时会连接服务端
        //      如果连接不上,代码会报错
        Socket socket = new Socket("127.0.0.1",10000);


        //2.可以从连接通道中获取输出流
        OutputStream os = socket.getOutputStream();
        //写出数据
        os.write("aaa".getBytes());

        //3.释放资源
        os.close();
        socket.close();
    }
}

26.4.3基本用例-接收数据

public class Server {
    
    
    public static void main(String[] args) throws IOException {
    
    
        //TCP协议,接收数据

        //1.创建对象ServerSocker
        ServerSocket ss = new ServerSocket(10000);

        //2.监听客户端的链接
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流读取数据
        InputStream is = socket.getInputStream();
        // InputStreamReader isr = new InputStreamReader(is);
        // BufferedReader br = new BufferedReader(isr); // 将字符输入流用缓冲字符输入流进行读取,可加快读取的速度
        int b;
        while ((b = is.read()) != -1){
    
    
            System.out.println((char) b);
        }

        //4.释放资源
        socket.close();
        ss.close();

    }
}

细节:

​ 在接收中文数据时,会产生中文乱码,是由于IDEA平台默认使用UTF-8的编码方式。InputStream在读取字节流时默认是用一个字节一个字节的读,而不能根据编码表的方式进行读取。因此,使用字符流可以根据编码进行读取,解决中文乱码。

27.反射

笔记小结:

  1. 概述:反射允许封装字段方法构造函数的信息进行编程访问
  2. 作用:可以结合配置文件,动态的创建对象并调用方法,就像Java设计模式中的工程模式创建就会运用
  3. 获取构造方法:getDeclaredConstructors()、getConstructors()
  4. 获取成员变量:getDeclaredFields()、getFields()
  5. 获取成员方法:getDeclaredMethods()、getMethods()

27.1概述

27.1.1定义

​ 反射允许对封装类的字段,方法和构造函数的信息进行编程访问

image-20230502204715017

27.1.2作用

  • 获取一个类里面所有的信息,获取到了之后,再执行其他的业务逻辑
  • 结合配置文件,动态的创建对象并调用方法

27.2获取字节码方式

27.2.1概述

三种方式:

  • Class这个类里面的静态方法forName(“全类名”)(最常用)
  • 通过class属性获取
  • 通过对象获取字节码文件对象

image-20230502205052410

27.2.2基本用例-获取字节码

//1. 第一种方式
//全类名 : 包名 + 类名
//最为常用的
Class clazz1 = Class.forName("com.itheima.myreflect1.Student");

//2. 第二种方式
//一般更多的是当做参数进行传递
Class clazz2 = Student.class;


//3.第三种方式
//当我们已经有了这个类的对象时,才可以使用。
Student s = new Student();
Class clazz3 = s.getClass();

说明:

第一种方式Class.forName(“xxx”)的方式是常用方式

27.3获取构造方法

27.3.1概述

方法名 说明
Constructor<?>[] getConstructors() 获得所有的构造(只能public修饰)
Constructor<?>[] getDeclaredConstructors() 获得所有的构造(包含private修饰)
Constructor getConstructor(Class<?>… parameterTypes) 获取指定构造(只能public修饰)
Constructor getDeclaredConstructor(Class<?>… parameterTypes) 获取指定构造(包含private修饰

说明:

​ 获取构造方法名称解析,get获取,Constructors表示构造方法,Declared表示所有权限,s表示多个

常用成员方法

方法名 说明
getModifiers 获取修饰符。返回表示该成员或构造函数的Java语言修饰符的整数。可以使用 Modifier 类来解码这个整数中包含的修饰符。
getParameters 获取此方法的形式参数。返回一个包含 Parameter 对象的数组,每个 Parameter 对象描述一个参数。如果该方法没有参数,则返回长度为 0 的数组。
setAccessible 将此对象的可访问标志设置为指示的布尔值。值为 true 表示反射对象在使用时应该取消 Java 语言访问检查。

27.3.2基本用例-获取构造方法

//1.获得整体(class字节码文件对象)
Class clazz = Class.forName("com.itheima.reflectdemo.Student");

//2.获取构造方法对象

//2.1获取所有构造方法(public)
Constructor[] constructors1 = clazz.getConstructors();

System.out.println("=======================");

//2.2获取所有构造(带私有的)
Constructor[] constructors2 = clazz.getDeclaredConstructors();

System.out.println("=======================");

//2.3获取指定的空参构造
Constructor con1 = clazz.getConstructor();
Constructor con2 = clazz.getConstructor(String.class,int.class);

System.out.println("=======================");

//2.4获取指定的构造(所有构造都可以获取到,包括public包括private)
Constructor con3 = clazz.getDeclaredConstructor();

//3.获取构造方法的权限修饰符
int modifiers = con3.getModifiers();

//4.获取构造方法的参数信息
Parameter[] parameters = con4.getParameters();

//5.暴力反射
con4.setAccessible(true);
Student stu = (Student) con4.newInstance("张三", 23);

说明:

​ 当通过字节码文件,获取到Java中的构造方法后,可以更深入的获取构造方法的详细信息

27.4获取成员变量

27.4.1概述

方法名 说明
Field[] getFields() 返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields() 返回所有成员变量对象的数组,存在就能拿到
Field getField(String name) 返回单个成员变量对象(只能拿public的)
Field getDeclaredField(String name) 返回单个成员变量对象,存在就能拿到

说明:

​ 获取成员变量解析,get获取,Constructors表示构造方法,Declared表示所有权限,s表示多个

方法名称 说明
getModifiers 返回该方法的修饰符,如public、static、final等
getName 返回该方法的名称
getType 返回该方法参数类型的Class对象的数组
setAccessible 设置是否允许访问该方法,若设置为true,则可访问private修饰符的方法。

27.4.2基本用例-获取成员变量

//1.获取class字节码文件的对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");

//2.获取成员变量
//2.1获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();

//2.2获取单个的成员变量
Field name = clazz.getDeclaredField("name");
System.out.println(name);

//2.3获取权限修饰符
int modifiers = name.getModifiers();
System.out.println(modifiers);

//2.4获取成员变量的名字
String n = name.getName();
System.out.println(n);

//2.5获取成员变量的数据类型
Class<?> type = name.getType();
System.out.println(type);

//3.获取成员变量记录的值
Student s = new Student("zhangsan",23,"男");
name.setAccessible(true);
String value = (String) name.get(s);
System.out.println(value);

//4.修改对象里面记录的值
name.set(s,"lisi");
System.out.println(s);

说明:

​ 可对获取到的成员变量进行更细一步的操作,例如获取变量修饰符、变量名、数据类型、变量值,修改变量值等

27.5获取成员方法

27.5.1概述

方法名 说明
Method[] getMethods() 返回所有成员方法对象的数组(只能拿public的)
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象,存在就能拿到
说明:

​ 获取成员方法解析,get获取,Constructors表示构造方法,Declared表示所有权限,s表示多个

方法名 说明
getModifiers 返回当前方法的修饰符的整数表示,修饰符包括public、private、protected、static、final等等
getName 返回当前方法的名称
getParameters 返回当前方法的参数列表,以Parameter数组形式返回
getExceptionTypes 返回当前方法抛出的异常类型数组
invoke 通过反射调用当前方法

27.5.2基本用例-获取成员方法

//1. 获取class字节码文件对象
Class clazz = Class.forName("com.itheima.myreflect4.Student");

//2.获取方法对象
//2.1获取里面所有的方法对象(包含父类中所有的公共方法)
Method[] methods = clazz.getMethods();

//2.2获取里面所有的方法对象(不能获取父类的,但是可以获取本类中私有的方法)
Method[] methods = clazz.getDeclaredMethods();

//2.3获取指定的单一方法
Method m = clazz.getDeclaredMethod("eat", String.class);
System.out.println(m);

//3.获取方法的修饰符
int modifiers = m.getModifiers();
System.out.println(modifiers);

//4.获取方法的名字
String name = m.getName();
System.out.println(name);

//5.获取方法的形参
Parameter[] parameters = m.getParameters();

//6.获取方法的抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();


//7.方法运行
/*Method类中用于创建对象的方法
        Object invoke(Object obj, Object... args):运行方法
        参数一:用obj对象调用该方法
        参数二:调用方法的传递的参数(如果没有就不写)
        返回值:方法的返回值(如果没有就不写)*/

Student s = new Student();
m.setAccessible(true);
//参数一s:表示方法的调用者
//参数二"汉堡包":表示在调用方法的时候传递的实际参数
String result = (String) m.invoke(s, "汉堡包");
System.out.println(result);

28.动态代理

笔记小结:

  1. 概述:动态代理是一种机制,它允许在运行时创建一个代理对象,代理对象能够拦截并处理被代理对象的方法调用。跟IP的代理不一样,动态代理是用于增强被代理对象的功能
  2. 作用:代理可以无侵入式的给对象增强其他的功能
  3. 基本用例:使用Proxy类提供的newProxyInstance方法,进行动态的代理

28.1概述

28.1.1定义

​ 在 Java 中,动态代理是一种机制,它允许在运行时创建一个代理对象,代理对象能够拦截并处理被代理对象的方法调用

28.1.2作用

​ 代理可以无侵入式的给对象增强其他的功能

image-20230503082534273

补充:

​ 通过接口保证,后面的对象和代理需要实现同一个接口,接口中就是被代理的所有方法

28.2基本用例

步骤一:创建接口

说明:

​ 我们可以把所有想要被代理的方法定义在接口当中

public interface Star {
    
    

    //唱歌
    public abstract String sing(String name);

    //跳舞
    public abstract void dance();

}

说明:

​ 此接口,需要各个被代理对象代理共同实现。目的是让代理能够调用被代理对象内的方法

步骤二:创建被代理对象

  • 创建实体类
public class BigStar implements Star {
    
    
    private String name;


    public BigStar() {
    
    
    }

    public BigStar(String name) {
    
    
        this.name = name;
    }

    //唱歌
    @Override
    public String sing(String name){
    
    
        System.out.println(this.name + "正在唱" + name);
        return "谢谢";
    }

    //跳舞
    @Override
    public void dance(){
    
    
        System.out.println(this.name + "正在跳舞");
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
    
    
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
    
    
        this.name = name;
    }

    public String toString() {
    
    
        return "BigStar{name = " + name + "}";
    }
}

步骤三:创建代理

说明:

​ 给一个明星的对象,创建一个代理

/*
* 类的作用:
*       创建一个代理
* */
public class ProxyUtil {
    
    

    /*
    * 方法的作用:
    *       给一个明星的对象,创建一个代理
    *  形参:
    *       被代理的明星对象
    *  返回值:
    *       给明星创建的代理
    * 需求:
    *   外面的人想要大明星唱一首歌
    *   1. 获取代理的对象
    *      代理对象 = ProxyUtil.createProxy(大明星的对象);
    *   2. 再调用代理的唱歌方法
    *      代理对象.唱歌的方法("只因你太美");
    * */
    public static Star createProxy(BigStar bigStar){
    
    
        /* java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:

        public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        参数一:用于指定用哪个类加载器,去加载生成的代理类
        参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
        参数三:用来指定生成的代理对象要干什么事情*/
        Star star = (Star) Proxy.newProxyInstance(
            ProxyUtil.class.getClassLoader(),//参数一:用于指定用哪个类加载器,去加载生成的代理类
            new Class[]{
    
    Star.class},//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
            //参数三:用来指定生成的代理对象要干什么事情
            new InvocationHandler() {
    
    
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                    /*
                        * 参数一:代理的对象
                        * 参数二:要运行的方法 sing
                        * 参数三:调用sing方法时,传递的实参
                        * */
                    if("sing".equals(method.getName())){
    
    
                        System.out.println("准备话筒,收钱");
                    }else if("dance".equals(method.getName())){
    
    
                        System.out.println("准备场地,收钱");
                    }
                    //去找大明星开始唱歌或者跳舞
                    //代码的表现形式:调用大明星里面唱歌或者跳舞的方法
                    return method.invoke(bigStar,args);
                }
            }
        );
        return star;
    }
}

说明:

  • 此代理对象,需要用到Proxy类中的newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

步骤四:演示

public class Test {
    
    
    public static void main(String[] args) {
    
    
        
    /*
        需求:
            外面的人想要大明星唱一首歌
             1. 获取代理的对象
                代理对象 = ProxyUtil.createProxy(大明星的对象);
             2. 再调用代理的唱歌方法
                代理对象.唱歌的方法("只因你太美");
     */


        //1. 获取代理的对象
        BigStar bigStar = new BigStar("鸡哥");
        Star proxy = ProxyUtil.createProxy(bigStar);

        //2. 调用唱歌的方法
        String result = proxy.sing("只因你太美");
        System.out.println(result);

    }
}

29.日志

笔记小结:

  1. 概述:日志技术,可以便于记录重要的信息,将这些详细信息保存到文件和数据库中
  2. 日志级别:TRACE < DEBUG < INFO < WARN < ERROR
  3. 补充:@Sf4j注解也可以输出到本地,详细请参考springboot项目使用slf4j控制台输出日志+输出日志文件到本地_org.slf4j.loggerfactory 日志路径_余额一个亿的博客-CSDN博客

29.1概述

29.1.1定义

​ 在Java中,日志在合适的时候加入,用于记录重要的信息。便于运维进行管理

29.1.2作用

  • 跟输出语句一样,可以把程序在运行过程中的详细信息都打印在控制台上
  • 利用log日志还可以把这些详细信息保存到文件和数据库中

image-20230503085534646

29.1.3日志级别

TRACE, DEBUG, INFO, WARN, ERROR

日志级别从小到大的关系:

​ TRACE < DEBUG < INFO < WARN < ERROR

29.1.4体系结构

image-20230503085923394

  • 日志规范:一些接口,提供给日志的实现框架设计的标准
  • 日志框架:牛人或者第三方公司已经做好的日志记录实现代码,后来者直接可以拿去使用

29.2Logback日志框架

29.2.1概述

Logback是基于slf4j的日志规范实现的框架,性能比之前使用的log4j要好

说明:

​ 官方网站:https://logback.qos.ch/index.html

29.2.2模块

Logback主要分为三个技术模块:

  • logback-core:该模块为其他两个模块提供基础代码,必须有。
  • logback-classic:完整实现了slf4j API的模块。
  • logback-access模块与Tomcat和Jetty 等Servlet容器集成,以提供HTTP 访问日志功能

29.3Logback基本用例

步骤一:导入jar包,并添加依赖库

image-20230503090641330

步骤二:将Logback的核心配置文件logback.xml直接拷贝到src目录下(必须是src下)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target>
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{
    
    yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{
    
    yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{
    
    36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>C:/code/itheima-data.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                       class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>C:/code/itheima-data2-%d{
    
    yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--

    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALLOFF
   , 默认debug
    <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
    -->
    <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE" />
    </root>
</configuration>

步骤三:在代码中获取日志的对象

public static final Logger LOGGER= LoggerFactory.getLdgger("类对象");

步骤四:使用日志对象的方法记录系统的日志信息

29.4Logback配置文件

Logback日志输出位置、格式设置:

  • 通过logback.xml中的标签可以设置输出位置和日志信息的详细格式。
  • 通常可以设置2个日志输定位置:一个是控制台、一个是系统文件中

输出到控制台的配置标志:

<appender name="CONSOLE" class="ch.qos.logback.core.consoleAppender">

输出到系统文件的配置标志:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"">

30.注解

笔记小结:

  1. 概述:在Java中,注解(Annotation)是一种元数据机制,它允许在源代码、编译时以及运行时为程序元素(类、方法、字段等)添加信息、标记和说明

  2. 常用注解:@Override:表示方法的重写、@Deprecated:表示修饰的方法已过时、@SuppressWarnings(“all”):压制警告

  3. 自定义注解:

    说明:自己编写注解,通常来说与反射一并使用。

    // 定义格式
    public @interface 注解名称{
           
           
    	public 属性类型 属性名() default 默认值;
    }
    
  4. 元注解:

    • @Retention:用来标识注解的生命周期(有效范围)
    • @Target:用来标识注解使用的位置

30.1概述

30.1.1定义

​ 在Java中,注解(Annotation)是一种元数据机制,它允许在源代码、编译时以及运行时为程序元素(类、方法、字段等)添加信息、标记和说明。注解可以帮助开发人员在代码中添加关键信息,以便在后续的编译、构建、部署和运行过程中使用

@RestController
@RequestMapping("order")
public @interface MyAnnotation {
    
    
    String value();
    int priority() default 0;
}

说明:

​ 以@开头的代码行即为注解

30.1.2作用

​ Java的注解通过**@符号**来表示,紧接着是注解的名称。注解可以附加到类、方法、字段、参数等程序元素上,以提供额外的元数据。注解的元数据信息可以被编译器、工具和运行时框架使用,用于自动生成代码、配置应用程序行为,以及实现一些特定的功能

30.1.3注释和注解的区别

  • 共同点:都可以对程序进行解释说明

  • 不同点:注释,是给程序员看的。只在Java中有效。在class文件中不存在注释

说明:

  • 当编译之后,会进行注释擦除
  • 注解,是给虚拟机看的。当虚拟机看到注解之后,就知道要做什么事情了

30.2基本用例

说明:

​ 注解的一般使用

步骤一:认识注解

@Override

说明:

​ 明白这个注解的作用。在以前看过注解@Override,当子类重写父类方法的时候,在重写的方法上面写@Override。当虚拟机看到@Override的时候,就知道下面的方法是重写的父类的。检查语法,如果语法正确编译正常,如果语法错误,就会报错。

步骤二:添加注解

@Override
public PageResult search(GlobalSearchDTO requestParams) {
    
    
    ……
}

说明:

注解通常添加在类中的方法上

补充:

​ 也有添加在@interface注解之上的,这个叫源注解

30.3常用注解

JVM提供的注解:

  • @Override:表示方法的重写

  • @Deprecated:表示修饰的方法已过时

  • @SuppressWarnings(“all”):压制警告

第三方框架注解

  • Junit:
    • @Test 表示运行测试方法
    • @Before 表示在Test之前运行,进行数据的初始化
    • @After 表示在Test之后运行,进行数据的还原

30.4自定义注解

30.4.1概述

​ 自定义注解就是自己做一个注解来使用。自定义注解单独存在是没有什么意义的,一般会跟反射结合起来使用,会用发射去解析注解。

注意:

​ JVM不会对自定义的注解添加任何逻辑,怎样处理完全由Java代码决定。也就是说光定义了注解是不足以使用的,需要通过反射等手法来实现实现注解的逻辑

30.4.2基本用例

说明:

  • 定义注解格式
public @interface 注解名称{
     
     
	public 属性类型 属性名() default 默认值;
}
  • 属性类型可以为:基本数据类型、String、Class、注解等
  • 使用注解格式
@注解名称(属性名1 =1,属性名2 =2)
  • 使用自定义注解时要保证注解每个属性都有值。注解可以使用默认值

步骤一:定义注解

public @interface Annotation {
    
    
    public abstract String value();
}

步骤二:使用注解

@Annotation(value = "")
private int myField;

说明:

  • value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写
  • 但是加里右多个属性日多个属性沿右默认值,那么value名称是不能省略

步骤三:获取注解

public class MyClass implements MyInterface {
    
    
    @Annotation("My Class")
    private int myField;

    @Override
    public void myMethod() {
    
    }

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
    
    
		// 1.获取成员变量
        Field field = MyClass.class.getDeclaredField("myField");
        // 2.获取成员变量上的注解
        Annotation annotation = field.getAnnotation(Annotation.class);
        // 3.获取该成员变量注解上的值
        System.out.println(annotation.value()); 
    }
}

补充:获取方法上的注解

Method method = MyClass.class.getMethod("myMethod");
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); 

30.5元注解

30.5.1概述

​ 在Java中,元注解(Meta-annotation)是用于注解其他注解的注解,也就是说,元注解用于对其他注解进行注解。元注解为开发者提供了定义和控制注解行为的能力,可以影响注解在代码中的使用和解释方式

​ 简单的说,就是写在注解上的注解

30.5.2分类

  1. @Retention:用于指定注解的保留策略,即注解在什么级别保存(源代码、类文件、运行时)。
  2. @Target:用于指定注解可以应用的目标元素类型,如类、方法、字段等。
  3. @Documented:用于指定注解是否包含在Java文档中。
  4. @Inherited:用于指定注解是否被子类继承

30.5.3@Target

​ 用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。

​ 可使用的值定义在ElementType枚举类中,常用值如下

  • TYPE,类,接口
  • FIELD, 成员变量
  • METHOD, 成员方法
  • PARAMETER, 方法参数
  • CONSTRUCTOR, 构造方法
  • LOCAL_VARIABLE, 局部变量

30.5.4@Retention

​ 用来标识注解的生命周期(有效范围)

​ 可使用的值定义在RetentionPolicy枚举类中,常用值如下

  • SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
  • CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
  • RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段

30.算法(目前了解,日后补充)

​ 数据结构是数据存储的方式,算法是数据计算的方式。所以在开发中,算法和数据结构息息相关。今天的讲义中会涉及部分数据结构的专业名词,如果各位铁粉有疑惑,可以先看一下哥们后面录制的数据结构,再回头看算法。

30.1查找算法

30.1.1. 基本查找

​ 也叫做顺序查找

​ 说明:顺序查找适合于存储结构为数组或者链表。

基本思想:顺序查找也称为线形查找,属于无序查找算法。从数据结构线的一端开始,顺序扫描,依次将遍历到的结点与要查找的值相比较,若相等则表示查找成功;若遍历结束仍没有找到相同的,表示查找失败。

示例代码:

public class A01_BasicSearchDemo1 {
    
    
    public static void main(String[] args) {
    
    
        //基本查找/顺序查找
        //核心:
        //从0索引开始挨个往后查找

        //需求:定义一个方法利用基本查找,查询某个元素是否存在
        //数据如下:{131, 127, 147, 81, 103, 23, 7, 79}


        int[] arr = {
    
    131, 127, 147, 81, 103, 23, 7, 79};
        int number = 82;
        System.out.println(basicSearch(arr, number));

    }

    //参数:
    //一:数组
    //二:要查找的元素

    //返回值:
    //元素是否存在
    public static boolean basicSearch(int[] arr, int number){
    
    
        //利用基本查找来查找number在数组中是否存在
        for (int i = 0; i < arr.length; i++) {
    
    
            if(arr[i] == number){
    
    
                return true;
            }
        }
        return false;
    }
}

30.1.2. 二分查找

​ 也叫做折半查找

说明:元素必须是有序的,从小到大,或者从大到小都是可以的。

如果是无序的,也可以先进行排序。但是排序之后,会改变原有数据的顺序,查找出来元素位置跟原来的元素可能是不一样的,所以排序之后再查找只能判断当前数据是否在容器当中,返回的索引无实际的意义。

基本思想:也称为是折半查找,属于有序查找算法。用给定值先与中间结点比较。比较完之后有三种情况:

  • 相等

    说明找到了

  • 要查找的数据比中间节点小

    说明要查找的数字在中间节点左边

  • 要查找的数据比中间节点大

    说明要查找的数字在中间节点右边

代码示例:

package com.itheima.search;

public class A02_BinarySearchDemo1 {
    
    
    public static void main(String[] args) {
    
    
        //二分查找/折半查找
        //核心:
        //每次排除一半的查找范围

        //需求:定义一个方法利用二分查找,查询某个元素在数组中的索引
        //数据如下:{7, 23, 79, 81, 103, 127, 131, 147}

        int[] arr = {
    
    7, 23, 79, 81, 103, 127, 131, 147};
        System.out.println(binarySearch(arr, 150));
    }

    public static int binarySearch(int[] arr, int number){
    
    
        //1.定义两个变量记录要查找的范围
        int min = 0;
        int max = arr.length - 1;

        //2.利用循环不断的去找要查找的数据
        while(true){
    
    
            if(min > max){
    
    
                return -1;
            }
            //3.找到min和max的中间位置
            int mid = (min + max) / 2;
            //4.拿着mid指向的元素跟要查找的元素进行比较
            if(arr[mid] > number){
    
    
                //4.1 number在mid的左边
                //min不变,max = mid - 1;
                max = mid - 1;
            }else if(arr[mid] < number){
    
    
                //4.2 number在mid的右边
                //max不变,min = mid + 1;
                min = mid + 1;
            }else{
    
    
                //4.3 number跟mid指向的元素一样
                //找到了
                return mid;
            }

        }
    }
}

30.1.3. 插值查找

在介绍插值查找之前,先考虑一个问题:

​ 为什么二分查找算法一定要是折半,而不是折四分之一或者折更多呢?

其实就是因为方便,简单,但是如果我能在二分查找的基础上,让中间的mid点,尽可能靠近想要查找的元素,那不就能提高查找的效率了吗?

二分查找中查找点计算如下:

mid=(low+high)/2, 即mid=low+1/2*(high-low);

我们可以将查找的点改进为如下:

mid=low+(key-a[low])/(a[high]-a[low])*(high-low),

这样,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次数。

基本思想:基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。

**细节:**对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。

代码跟二分查找类似,只要修改一下mid的计算方式即可。

30.1.4. 斐波那契查找

在介绍斐波那契查找算法之前,我们先介绍一下很它紧密相连并且大家都熟知的一个概念——黄金分割。

黄金比例又称黄金分割,是指事物各部分间一定的数学比例关系,即将整体一分为二,较大部分与较小部分之比等于整体与较大部分之比,其比值约为1:0.619或1.619:1。

0.619被公认为最具有审美意义的比例数字,这个数值的作用不仅仅体现在诸如绘画、雕塑、音乐、建筑等艺术领域,而且在管理、工程设计等方面也有着不可忽视的作用。因此被称为黄金分割。

在数学中有一个非常有名的数学规律:斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…….

(从第三个数开始,后边每一个数都是前两个数的和)。

然后我们会发现,随着斐波那契数列的递增,前后两个数的比值会越来越接近0.619,利用这个特性,我们就可以将黄金比例运用到查找技术中。

image-20230812214900679

基本思想:也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。

斐波那契查找也是在二分查找的基础上进行了优化,优化中间点mid的计算方式即可

代码示例:

public class FeiBoSearchDemo {
    
    
    public static int maxSize = 20;

    public static void main(String[] args) {
    
    
        int[] arr = {
    
    1, 8, 10, 89, 1000, 1234};
        System.out.println(search(arr, 1234));
    }

    public static int[] getFeiBo() {
    
    
        int[] arr = new int[maxSize];
        arr[0] = 1;
        arr[1] = 1;
        for (int i = 2; i < maxSize; i++) {
    
    
            arr[i] = arr[i - 1] + arr[i - 2];
        }
        return arr;
    }

    public static int search(int[] arr, int key) {
    
    
        int low = 0;
        int high = arr.length - 1;
        //表示斐波那契数分割数的下标值
        int index = 0;
        int mid = 0;
        //调用斐波那契数列
        int[] f = getFeiBo();
        //获取斐波那契分割数值的下标
        while (high > (f[index] - 1)) {
    
    
            index++;
        }
        //因为f[k]值可能大于a的长度,因此需要使用Arrays工具类,构造一个新法数组,并指向temp[],不足的部分会使用0补齐
        int[] temp = Arrays.copyOf(arr, f[index]);
        //实际需要使用arr数组的最后一个数来填充不足的部分
        for (int i = high + 1; i < temp.length; i++) {
    
    
            temp[i] = arr[high];
        }
        //使用while循环处理,找到key值
        while (low <= high) {
    
    
            mid = low + f[index - 1] - 1;
            if (key < temp[mid]) {
    
    //向数组的前面部分进行查找
                high = mid - 1;
                /*
                  对k--进行理解
                  1.全部元素=前面的元素+后面的元素
                  2.f[k]=k[k-1]+f[k-2]
                  因为前面有k-1个元素没所以可以继续分为f[k-1]=f[k-2]+f[k-3]
                  即在f[k-1]的前面继续查找k--
                  即下次循环,mid=f[k-1-1]-1
                 */
                index--;
            } else if (key > temp[mid]) {
    
    //向数组的后面的部分进行查找
                low = mid + 1;
                index -= 2;
            } else {
    
    //找到了
                //需要确定返回的是哪个下标
                if (mid <= high) {
    
    
                    return mid;
                } else {
    
    
                    return high;
                }
            }
        }
        return -1;
    }
}

30.1.5. 分块查找

当数据表中的数据元素很多时,可以采用分块查找。

汲取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找

分块查找适用于数据较多,但是数据不会发生变化的情况,如果需要一边添加一边查找,建议使用哈希查找

分块查找的过程:

  1. 需要把数据分成N多小块,块与块之间不能有数据重复的交集。
  2. 给每一块创建对象单独存储到数组当中
  3. 查找数据的时候,先在数组查,当前数据属于哪一块
  4. 再到这一块中顺序查找

代码示例:

package com.itheima.search;

public class A03_BlockSearchDemo {
    
    
    public static void main(String[] args) {
    
    
        /*
            分块查找
            核心思想:
                块内无序,块间有序
            实现步骤:
                1.创建数组blockArr存放每一个块对象的信息
                2.先查找blockArr确定要查找的数据属于哪一块
                3.再单独遍历这一块数据即可
        */
        int[] arr = {
    
    16, 5, 9, 12,21, 19,
                     32, 23, 37, 26, 45, 34,
                     50, 48, 61, 52, 73, 66};

        //创建三个块的对象
        Block b1 = new Block(21,0,5);
        Block b2 = new Block(45,6,11);
        Block b3 = new Block(73,12,19);

        //定义数组用来管理三个块的对象(索引表)
        Block[] blockArr = {
    
    b1,b2,b3};

        //定义一个变量用来记录要查找的元素
        int number = 37;

        //调用方法,传递索引表,数组,要查找的元素
        int index = getIndex(blockArr,arr,number);

        //打印一下
        System.out.println(index);



    }

    //利用分块查找的原理,查询number的索引
    private static int getIndex(Block[] blockArr, int[] arr, int number) {
    
    
        //1.确定number是在那一块当中
        int indexBlock = findIndexBlock(blockArr, number);

        if(indexBlock == -1){
    
    
            //表示number不在数组当中
            return -1;
        }

        //2.获取这一块的起始索引和结束索引   --- 30
        // Block b1 = new Block(21,0,5);   ----  0
        // Block b2 = new Block(45,6,11);  ----  1
        // Block b3 = new Block(73,12,19); ----  2
        int startIndex = blockArr[indexBlock].getStartIndex();
        int endIndex = blockArr[indexBlock].getEndIndex();

        //3.遍历
        for (int i = startIndex; i <= endIndex; i++) {
    
    
            if(arr[i] == number){
    
    
                return i;
            }
        }
        return -1;
    }


    //定义一个方法,用来确定number在哪一块当中
    public static int findIndexBlock(Block[] blockArr,int number){
    
     //100


        //从0索引开始遍历blockArr,如果number小于max,那么就表示number是在这一块当中的
        for (int i = 0; i < blockArr.length; i++) {
    
    
            if(number <= blockArr[i].getMax()){
    
    
                return i;
            }
        }
        return -1;
    }



}

class Block{
    
    
    private int max;//最大值
    private int startIndex;//起始索引
    private int endIndex;//结束索引


    public Block() {
    
    
    }

    public Block(int max, int startIndex, int endIndex) {
    
    
        this.max = max;
        this.startIndex = startIndex;
        this.endIndex = endIndex;
    }

    /**
     * 获取
     * @return max
     */
    public int getMax() {
    
    
        return max;
    }

    /**
     * 设置
     * @param max
     */
    public void setMax(int max) {
    
    
        this.max = max;
    }

    /**
     * 获取
     * @return startIndex
     */
    public int getStartIndex() {
    
    
        return startIndex;
    }

    /**
     * 设置
     * @param startIndex
     */
    public void setStartIndex(int startIndex) {
    
    
        this.startIndex = startIndex;
    }

    /**
     * 获取
     * @return endIndex
     */
    public int getEndIndex() {
    
    
        return endIndex;
    }

    /**
     * 设置
     * @param endIndex
     */
    public void setEndIndex(int endIndex) {
    
    
        this.endIndex = endIndex;
    }

    public String toString() {
    
    
        return "Block{max = " + max + ", startIndex = " + startIndex + ", endIndex = " + endIndex + "}";
    }
}

30.1.6. 哈希查找

哈希查找是分块查找的进阶版,适用于数据一边添加一边查找的情况。

一般是数组 + 链表的结合体或者是数组+链表 + 红黑树的结合体

在课程中,为了让大家方便理解,所以规定:

  • 数组的0索引处存储1~100
  • 数组的1索引处存储101~200
  • 数组的2索引处存储201~300
  • 以此类推

但是实际上,我们一般不会采取这种方式,因为这种方式容易导致一块区域添加的元素过多,导致效率偏低。

更多的是先计算出当前数据的哈希值,用哈希值跟数组的长度进行计算,计算出应存入的位置,再挂在数组的后面形成链表,如果挂的元素太多而且数组长度过长,我们也会把链表转化为红黑树,进一步提高效率。

具体的过程,大家可以参见B站阿玮讲解课程:从入门到起飞。在集合章节详细讲解了哈希表的数据结构。全程采取动画形式讲解,让大家一目了然。

在此不多做阐述。

30.1.7. 树表查找

本知识点涉及到数据结构:树。

建议先看一下后面阿玮讲解的数据结构,再回头理解。

基本思想:二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。 这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。

二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree),具有下列性质的二叉树:

1)若任意节点左子树上所有的数据,均小于本身;

2)若任意节点右子树上所有的数据,均大于本身;

二叉查找树性质:对二叉查找树进行中序遍历,即可得到有序的数列。

基于二叉查找树进行优化,进而可以得到其他的树表查找算法,如平衡树、红黑树等高效算法。

具体细节大家可以参见B站阿玮讲解课程:从入门到起飞。在集合章节详细讲解了树数据结构。全程采取动画形式讲解,让大家一目了然。

在此不多做阐述。

​ 不管是二叉查找树,还是平衡二叉树,还是红黑树,查找的性能都比较高

30.2排序算法

30.2.1. 冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。

它重复的遍历过要排序的数列,一次比较相邻的两个元素,如果他们的顺序错误就把他们交换过来。

这个算法的名字由来是因为越大的元素会经由交换慢慢"浮"到最后面。

当然,大家可以按照从大到小的方式进行排列。

30.2.1.1 算法步骤

  1. 相邻的元素两两比较,大的放右边,小的放左边
  2. 第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推
  3. 如果数组中有n个数据,总共我们只要执行n-1轮的代码就可以

30.2.1.2 动图演示

在这里插入图片描述

30.2.1.3 代码示例

public class A01_BubbleDemo {
    
    
    public static void main(String[] args) {
    
    
        /*
            冒泡排序:
            核心思想:
            1,相邻的元素两两比较,大的放右边,小的放左边。
            2,第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推。
            3,如果数组中有n个数据,总共我们只要执行n-1轮的代码就可以。
        */


        //1.定义数组
        int[] arr = {
    
    2, 4, 5, 3, 1};

        //2.利用冒泡排序将数组中的数据变成 1 2 3 4 5

        //外循环:表示我要执行多少轮。 如果有n个数据,那么执行n - 1 轮
        for (int i = 0; i < arr.length - 1; i++) {
    
    
            //内循环:每一轮中我如何比较数据并找到当前的最大值
            //-1:为了防止索引越界
            //-i:提高效率,每一轮执行的次数应该比上一轮少一次。
            for (int j = 0; j < arr.length - 1 - i; j++) {
    
    
                //i 依次表示数组中的每一个索引:0 1 2 3 4
                if(arr[j] > arr[j + 1]){
    
    
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }

        printArr(arr);




    }

    private static void printArr(int[] arr) {
    
    
        //3.遍历数组
        for (int i = 0; i < arr.length; i++) {
    
    
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

30.2.2. 选择排序

30.2.2.1 算法步骤

  1. 从0索引开始,跟后面的元素一一比较
  2. 小的放前面,大的放后面
  3. 第一次循环结束后,最小的数据已经确定
  4. 第二次循环从1索引开始以此类推
  5. 第三轮循环从2索引开始以此类推
  6. 第四轮循环从3索引开始以此类推。

30.2.2.2 动图演示

在这里插入图片描述

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

        /*
            选择排序:
                1,从0索引开始,跟后面的元素一一比较。
                2,小的放前面,大的放后面。
                3,第一次循环结束后,最小的数据已经确定。
                4,第二次循环从1索引开始以此类推。

         */


        //1.定义数组
        int[] arr = {
    
    2, 4, 5, 3, 1};


        //2.利用选择排序让数组变成 1 2 3 4 5
       /* //第一轮:
        //从0索引开始,跟后面的元素一一比较。
        for (int i = 0 + 1; i < arr.length; i++) {
            //拿着0索引跟后面的数据进行比较
            if(arr[0] > arr[i]){
                int temp = arr[0];
                arr[0] = arr[i];
                arr[i] = temp;
            }
        }*/

        //最终代码:
        //外循环:几轮
        //i:表示这一轮中,我拿着哪个索引上的数据跟后面的数据进行比较并交换
        for (int i = 0; i < arr.length -1; i++) {
    
    
            //内循环:每一轮我要干什么事情?
            //拿着i跟i后面的数据进行比较交换
            for (int j = i + 1; j < arr.length; j++) {
    
    
                if(arr[i] > arr[j]){
    
    
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }


        printArr(arr);


    }
    private static void printArr(int[] arr) {
    
    
        //3.遍历数组
        for (int i = 0; i < arr.length; i++) {
    
    
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

}

30.2.3. 插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过创建有序序列和无序序列,然后再遍历无序序列得到里面每一个数字,把每一个数字插入到有序序列中正确的位置。

插入排序在插入的时候,有优化算法,在遍历有序序列找正确位置时,可以采取二分查找

30.2.3.1 算法步骤

将0索引的元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。

遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。

N的范围:0~最大索引

30.2.3.2 动图演示

在这里插入图片描述

package com.itheima.mysort;


public class A03_InsertDemo {
    
    
    public static void main(String[] args) {
    
    
        /*
            插入排序:
                将0索引的元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。
                遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。
                N的范围:0~最大索引

        */
        int[] arr = {
    
    3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};

        //1.找到无序的哪一组数组是从哪个索引开始的。  2
        int startIndex = -1;
        for (int i = 0; i < arr.length; i++) {
    
    
            if(arr[i] > arr[i + 1]){
    
    
                startIndex = i + 1;
                break;
            }
        }

        //2.遍历从startIndex开始到最后一个元素,依次得到无序的哪一组数据中的每一个元素
        for (int i = startIndex; i < arr.length; i++) {
    
    
            //问题:如何把遍历到的数据,插入到前面有序的这一组当中

            //记录当前要插入数据的索引
            int j = i;

            while(j > 0 && arr[j] < arr[j - 1]){
    
    
                //交换位置
                int temp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = temp;
                j--;
            }

        }
        printArr(arr);
    }

    private static void printArr(int[] arr) {
    
    
        //3.遍历数组
        for (int i = 0; i < arr.length; i++) {
    
    
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

}

30.2.4. 快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。

快速排序又是一种分而治之思想在排序算法上的典型应用。

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!

它是处理大数据最快的排序算法之一了。

30.2.4.1 算法步骤

  1. 从数列中挑出一个元素,一般都是左边第一个数字,称为 “基准数”;
  2. 创建两个指针,一个从前往后走,一个从后往前走。
  3. 先执行后面的指针,找出第一个比基准数小的数字
  4. 再执行前面的指针,找出第一个比基准数大的数字
  5. 交换两个指针指向的数字
  6. 直到两个指针相遇
  7. 将基准数跟指针指向位置的数字交换位置,称之为:基准数归位。
  8. 第一轮结束之后,基准数左边的数字都是比基准数小的,基准数右边的数字都是比基准数大的。
  9. 把基准数左边看做一个序列,把基准数右边看做一个序列,按照刚刚的规则递归排序

30.2.4.2 动图演示

在这里插入图片描述

package com.itheima.mysort;

import java.util.Arrays;

public class A05_QuickSortDemo {
    
    
   public static void main(String[] args) {
    
    
       System.out.println(Integer.MAX_VALUE);
       System.out.println(Integer.MIN_VALUE);
     /*
       快速排序:
           第一轮:以0索引的数字为基准数,确定基准数在数组中正确的位置。
           比基准数小的全部在左边,比基准数大的全部在右边。
           后面以此类推。
     */

       int[] arr = {
    
    1,1, 6, 2, 7, 9, 3, 4, 5, 1,10, 8};


       //int[] arr = new int[1000000];

      /* Random r = new Random();
       for (int i = 0; i < arr.length; i++) {
           arr[i] = r.nextInt();
       }*/


       long start = System.currentTimeMillis();
       quickSort(arr, 0, arr.length - 1);
       long end = System.currentTimeMillis();

       System.out.println(end - start);//149

       System.out.println(Arrays.toString(arr));
       //课堂练习:
       //我们可以利用相同的办法去测试一下,选择排序,冒泡排序以及插入排序运行的效率
       //得到一个结论:快速排序真的非常快。

      /* for (int i = 0; i < arr.length; i++) {
           System.out.print(arr[i] + " ");
       }*/

   }


   /*
    *   参数一:我们要排序的数组
    *   参数二:要排序数组的起始索引
    *   参数三:要排序数组的结束索引
    * */
   public static void quickSort(int[] arr, int i, int j) {
    
    
       //定义两个变量记录要查找的范围
       int start = i;
       int end = j;

       if(start > end){
    
    
           //递归的出口
           return;
       }



       //记录基准数
       int baseNumber = arr[i];
       //利用循环找到要交换的数字
       while(start != end){
    
    
           //利用end,从后往前开始找,找比基准数小的数字
           //int[] arr = {1, 6, 2, 7, 9, 3, 4, 5, 10, 8};
           while(true){
    
    
               if(end <= start || arr[end] < baseNumber){
    
    
                   break;
               }
               end--;
           }
           System.out.println(end);
           //利用start,从前往后找,找比基准数大的数字
           while(true){
    
    
               if(end <= start || arr[start] > baseNumber){
    
    
                   break;
               }
               start++;
           }



           //把end和start指向的元素进行交换
           int temp = arr[start];
           arr[start] = arr[end];
           arr[end] = temp;
       }

       //当start和end指向了同一个元素的时候,那么上面的循环就会结束
       //表示已经找到了基准数在数组中应存入的位置
       //基准数归位
       //就是拿着这个范围中的第一个数字,跟start指向的元素进行交换
       int temp = arr[i];
       arr[i] = arr[start];
       arr[start] = temp;

       //确定6左边的范围,重复刚刚所做的事情
       quickSort(arr,i,start - 1);
       //确定6右边的范围,重复刚刚所做的事情
       quickSort(arr,start + 1,j);

   }
}

30.补充(目前了解,日后补充)

30.1类加载器

30.1.1概述

类加载器∶负责将.class文件(存储的物理文件)加载在到内存中

image-20230503092447479

30.1.2类加载的时机

  1. 创建类的实例(对象)
  2. 调用类的类方法
  3. 访问类或者接口的类变量,或者为该类变量赋值
  4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  5. 初始化某个类的子类
  6. 直接使用java.exe命令来运行某个主类

总结:用到就加载,不用就不加载

30.1.3类加载的过程

  1. 加载

    • 通过包名 + 类名,获取这个类,准备用流进行传输
    • 在这个类加载到内存中
    • 加载完毕创建一个class对象

    image-20230813163913390

  2. 链接

    • 验证:确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全(文件中的信息是否符合虚拟机规范有没有安全隐患)

    image-20230813163933800

    • 准备:负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值(初始化静态变量)

    image-20230813163944592

    • 解析:将类的二进制数据流中的符号引用替换为直接引用(本类中如果用到了其他类,此时就需要找到对应的类)

    image-20230813163949122

  3. 初始化:根据程序员通过程序制定的主观计划去初始化类变量和其他资源(静态变量赋值以及初始化其他资源)

    image-20230813163957701

小结:

  • 当一个类被使用的时候,才会加载到内存
  • 类加载的过程: 加载、验证、准备、解析、初始化

30.1.4类加载的分类

分类

  • Bootstrap class loader:虚拟机的内置类加载器,通常表示为null ,并且没有父null
  • Platform class loader:平台类加载器,负责加载JDK中一些特殊的模块
  • System class loader:系统类加载器,负责加载用户类路径上所指定的类库

30.1.5双亲委派模型

​ 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

image-20230813164004023

30.1.6类加载器常用方法

方法名 说明
public static ClassLoader getSystemClassLoader() 获取系统类加载器
public InputStream getResourceAsStream(String name) 加载某一个资源文件

示例:

public class ClassLoaderDemo2 {
    
    
    public static void main(String[] args) throws IOException {
    
    
        //static ClassLoader getSystemClassLoader() 获取系统类加载器
        //InputStream getResourceAsStream(String name)  加载某一个资源文件

        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        //利用加载器去加载一个指定的文件
        //参数:文件的路径(放在src的根目录下,默认去那里加载)
        //返回值:字节流。
        InputStream is = systemClassLoader.getResourceAsStream("prop.properties");

        Properties prop = new Properties();
        prop.load(is);

        System.out.println(prop);

        is.close();
    }
}

注意:

prop.properties文件,需要放在src下,才能被读取并加载

30.2XML

30.2.1三种配置文件的优缺点

image-20230503095022066

30.2.2概述

  • XML的全称为(EXtensible Markup Language),是一种可扩展标记语言
  • 标记语言: 通过标签来描述数据的一门语言(标签有时我们也将其称之为元素)
  • 可扩展:标签的名字是可以自定义的,XML文件是由很多标签组成的,而标签名是可以自定义的

例如:

image-20230503095153933

  • 作用

    • 用于进行存储数据和传输数据
    • 作为软件的配置文件
  • 作为配置文件的优势

    • 可读性好
    • 可维护性高

30.2.3使用

XML的创建

  • 就是创建一个XML类型的文件,要求文件的后缀必须使用xml,如hello_world.xml

image-20230503095331129

XML的基本语法

  • A label consists of a pair of angle brackets followed by a legal identifier

    <student>
    
  • Tags must appear in pairs

    <student> </student>
    前边的是开始标签,后边的是结束标签
    
  • Special tags can be unpaired, but must have a closing tag

    <address/>
    
  • Attributes can be defined in tags. The attributes and tag names are separated by spaces. The attribute values ​​must be enclosed in quotation marks.

    <student id="1"> </student>
    
  • Tags need to be nested correctly

    这是正确的: <student id="1"> <name>张三</name> </student>
    这是错误的: <student id="1"><name>张三</student></name>
    

30.2.4 Document constraints

30.2.4.1 Overview

30.2.4.2 Classification

30.3 Unit testing

30.3.1 Overview

30.3.2 Quick Start

image-20230503101517610

Guess you like

Origin blog.csdn.net/D_boj/article/details/132257812
Recommended