当前位置: 首页 > article >正文

JAVA基础【第四篇】

文章目录

  • InetAddress类
      • 相关方法
      • 代码示例
  • Socket
      • 基本介绍
      • TCP网络通信编程
          • 基本介绍
          • 代码示例
      • UDP网络编程
          • 基本介绍
          • 基本流程
          • 常用方法
          • 代码示例
  • 反射机制
      • 基本介绍
      • 常用方法
      • 代码示例
      • 优缺点
      • class类
          • 介绍
          • 常用方法
          • 代码示例
      • 反射获取类的结构信息
          • java.lang.Class 类
          • java.lang.reflect.Field 类
          • java.lang.reflect.Method 类
          • java.lang.reflect.Constructor 类
          • 代码示例
      • 反射创建对象
          • 方式
          • 相关方法
          • 代码示例
      • 反射访问类的成员
          • **根据属性名获取Field对象**
          • **暴破**
          • **访问**
          • 代码示例
      • 反射访问方法
          • 前言
          • 代码示例
      • 获取class对象的方式
            • 代码示例
      • 类加载的五个阶段
          • 介绍
          • 加载时机
          • 加载阶段
            • 连接阶段-验证
            • 连接阶段-准备
            • 连接阶段-解析
            • Initialization(初始化)
  • 数据库
      • 常用命令
      • jdbc介绍
      • 快速入门
          • 环境搭建
          • 代码示例
      • 使用方式
          • 获取数据库的五种方式
            • 方式一
            • 方式二
            • 方式三
            • 方式四
            • 方式五
          • ResultSet[结果集]
            • 介绍
            • 代码示例
          • 预处理PreparedStatement
            • 介绍
            • 预处理的好处
            • 代码示例
            • PreparedStatement的DML示例
      • 事务处理
          • 介绍
          • 代码示例
      • 批处理
          • 介绍
          • 代码示例
      • 数据库连接池
          • 介绍
          • 种类
          • C3P0的两种连接方式示例
          • Druid代码示例
          • DBUTils
            • 介绍
            • 常用方法
            • 代码示例
          • BasicDao
            • 介绍
            • 代码示例
  • 正则表达式
      • 介绍
      • 底层源码分析
      • 语法
          • 元字符--字符匹配符
          • 元字符--选择匹配符
          • 元字符--限定符
          • 元字符--定位符
          • 分组
      • 三个常用类
          • Pattern类
          • Matcher类
          • PatternSyntaxException类
          • 代码示例
            • Pattern
          • 分组、捕获、反向引用
            • 相关介绍
            • 代码示例
          • 常用的正则表达式匹配

InetAddress类

  1. 相关方法

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

    1.  package com.hspedu.api;
       import java.net.InetAddress;
       import java.net.UnknownHostException;
      
       public class API_ {
           public static void main(String[] args) throws UnknownHostException {
      
               //1. 获取本机的InetAddress 对象
               InetAddress localHost = InetAddress.getLocalHost();
               System.out.println(localHost);//cong/192.168.72.1,这个的ip不是wlan的ip,而是虚拟网卡的ip
      
               //2. 根据指定主机名 获取 InetAddress对象
               InetAddress host1 = InetAddress.getByName("cong");
               System.out.println("host1=" + host1);//cong/192.168.72.1
      
               //3. 根据域名返回 InetAddress对象, 比如 www.baidu.com 对应
               InetAddress host2 = InetAddress.getByName("www.baidu.com");
               System.out.println("host2=" + host2);//www.baidu.com/183.2.172.42
      
               //4. 通过 InetAddress 对象,获取对应的地址
               String hostAddress = host2.getHostAddress();//IP 183.2.172.42
               System.out.println("host2 对应的ip = " + hostAddress);//183.2.172.42
      
               //5. 通过 InetAddress 对象,获取对应的主机名/或者的域名
               String hostName = host2.getHostName();
               System.out.println("host2对应的主机名/域名=" + hostName); // www.baidu.com
      
           }
       }
      

Socket

  1. 基本介绍

    1. 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准。

    2. 通信的两端都要有Socket,是两台机器间通信的端点

    3. 网络通信其实就是Socket间的通信。

    4. Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。

    5. 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端

    6. 示意图

  2. TCP网络通信编程

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

      2. 编写一个服务端,和一个客户端

      3. 服务器端在9999端口监听

      4. 客户端连接到服务端,发送“hello,server”,并接收服务器端回发的"hello,client",再退出(使用字节流)

      5. 服务器端接收到客户端发送的信息,输出,并发送“hello,client”,再退出

        1.  服务端:
           package com.hspedu.socket;
          
           import java.io.IOException;
           import java.io.InputStream;
           import java.io.OutputStream;
           import java.net.ServerSocket;
           import java.net.Socket;
          
           /**
            * 服务端
            */
           @SuppressWarnings({"all"})
           public class SocketTCP02Server {
               public static void main(String[] args) throws IOException {
                   //思路
                   //1. 在本机 的9999端口监听, 等待连接
                   //   细节: 要求在本机没有其它服务在监听9999
                   //   细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
                   ServerSocket serverSocket = new ServerSocket(9999);
                   System.out.println("服务端,在9999端口监听,等待连接..");
                   //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("hello, client".getBytes());
                   //   设置结束标记
                   socket.shutdownOutput();
          
                   //6.关闭流和socket
                   outputStream.close();
                   inputStream.close();
                   socket.close();
                   serverSocket.close();//关闭
          
               }
           }
           =============================================================================================================
           客户端:
           package com.hspedu.socket;
          
           import java.io.IOException;
           import java.io.InputStream;
           import java.io.OutputStream;
           import java.net.InetAddress;
           import java.net.Socket;
          
           /**
            * 客户端,发送 "hello, server" 给服务端
            */
           @SuppressWarnings({"all"})
           public class SocketTCP02Client {
               public static void main(String[] args) throws IOException {
                   //思路
                   //1. 连接服务端 (ip , 端口)
                   //解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
                   Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
                   System.out.println("客户端 socket返回=" + socket.getClass());
                   //2. 连接上后,生成Socket, 通过socket.getOutputStream()
                   //   得到 和 socket对象关联的输出流对象
                   OutputStream outputStream = socket.getOutputStream();
                   //3. 通过输出流,写入数据到 数据通道
                   outputStream.write("hello, server".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("客户端退出.....");
               }
           }
           输出结果:
           服务端:
           服务端,在9999端口监听,等待连接..
           服务端 socket =class java.net.Socket
           hello, server
          
           客户端:
           客户端 socket返回=class java.net.Socket
           hello, client
           客户端退出.....
          
          1. 思考

            1. 服务段

              1. 服务器先创建一个ServerSocket​对象,绑定9999端口,然后使用serverSocket.accept()​进行监听他的返回
              2. 如果客户端有发送数据的话,服务器需要使用socket.getInputStream()​来监听客户端发送过来的字节流
              3. 发送数据就使用socket.getOutputStream()​的write方法即可
              4. 需要注意的是,每发送一个数据时,需要添加一个结束标志
              5. 需要关闭所有的流以及套接字编程
            2. 客户端

              1. Socket(InetAddress.getLocalHost(), 9999);​客户端通过创建一个对象来连接特定ip和端口,第一个参数可以填ip,例如Socket("127.0.0.1", 9999)
              2. 发送数据就使用socket.getOutputStream()​的write方法即可
              3. 注意使用结束标志,socket.shutdownOutput();
              4. 需要关闭所有的流以及套接字编程
      6. 代码示例二(字符流)

      7. 编写一个服务端,和一个客户端

      8. 服务器端在9999端口监听

      9. 客户端连接到服务端,发送“hello,server”,并接收服务器端回发的"hello,client",再退出(使用字符流)

      10. 服务器端接收到客户端发送的信息,输出,并发送“hello,client”,再退出

        1.  服务端:
           package com.hspedu.socket;
          
           import java.io.*;
           import java.net.ServerSocket;
           import java.net.Socket;
          
           /**
            * 服务端, 使用字符流方式读写
            */
           @SuppressWarnings({"all"})
           public class SocketTCP03Server {
               public static void main(String[] args) throws IOException {
                   //思路
                   //1. 在本机 的9999端口监听, 等待连接
                   //   细节: 要求在本机没有其它服务在监听9999
                   //   细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
                   ServerSocket serverSocket = new ServerSocket(9999);
                   System.out.println("服务端,在9999端口监听,等待连接..");
                   //2. 当没有客户端连接9999端口时,程序会 阻塞, 等待连接
                   //   如果有客户端连接,则会返回Socket对象,程序继续
          
                   Socket socket = serverSocket.accept();
          
                   System.out.println("服务端 socket =" + socket.getClass());
                   //3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
                   InputStream inputStream = socket.getInputStream();
                   //4. IO读取, 使用字符流, 老师使用 InputStreamReader 将 inputStream 转成字符流
                   BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                   String s = bufferedReader.readLine();
                   System.out.println(s);//输出
          
                   //5. 获取socket相关联的输出流
                   OutputStream outputStream = socket.getOutputStream();
                  //    使用字符输出流的方式回复信息
                   BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
                   bufferedWriter.write("hello client 字符流");
                   bufferedWriter.newLine();// 插入一个换行符,表示回复内容的结束
                   bufferedWriter.flush();//注意需要手动的flush
          
          
                   //6.关闭流和socket
                   bufferedWriter.close();
                   bufferedReader.close();
                   socket.close();
                   serverSocket.close();//关闭
          
               }
           }
           ========================================================
           客户端:
           package com.hspedu.socket;
          
           import java.io.*;
           import java.net.InetAddress;
           import java.net.Socket;
          
           /**
            * 客户端,发送 "hello, server" 给服务端, 使用字符流
            */
           @SuppressWarnings({"all"})
           public class SocketTCP03Client {
               public static void main(String[] args) throws IOException {
                   //思路
                   //1. 连接服务端 (ip , 端口)
                   //解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
                   Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
                   System.out.println("客户端 socket返回=" + socket.getClass());
                   //2. 连接上后,生成Socket, 通过socket.getOutputStream()
                   //   得到 和 socket对象关联的输出流对象
                   OutputStream outputStream = socket.getOutputStream();
                   //3. 通过输出流,写入数据到 数据通道, 使用字符流
                   BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
                   bufferedWriter.write("hello, server 字符流");
                   bufferedWriter.newLine();//插入一个换行符,表示写入的内容结束, 注意,要求对方使用readLine()!!!!
                   bufferedWriter.flush();// 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道
          
          
                   //4. 获取和socket关联的输入流. 读取数据(字符),并显示
                   InputStream inputStream = socket.getInputStream();
                   BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                   String s = bufferedReader.readLine();
                   System.out.println(s);
          
                   //5. 关闭流对象和socket, 必须关闭
                   bufferedReader.close();//关闭外层流
                   bufferedWriter.close();
                   socket.close();
                   System.out.println("客户端退出.....");
               }
           }
           输出结果:
           服务端:
           服务端,在9999端口监听,等待连接..
           服务端 socket =class java.net.Socket
           hello, server 字符流
          
           客户端:
           客户端 socket返回=class java.net.Socket
           hello client 字符流
           客户端退出.....
          
          1. 思考

            1. 以下只讲与字节流不同部分
            2. socket提供的是字节流的输入输出,使用字符流,所以我们需要用到转换流
            3. 输入和输出与字节流不同的是需要再添加一个转换流进行转换字符流,BufferedWriter(new OutputStreamWriter(outputStream));
            4. 需要注意的是,每次通话结束需要插入一个换行符来表示内容结束,bufferedWriter.newLine();
            5. 其次对方接受必须使用readLine()
            6. 其次需要注意的是每次写入数据完成之后需要使用bufferedWriter.flush()​来进行刷新,否则无法写入数据通道
            7. 最后完成记得关闭所有的流以及套接字
      11. 代码示例三(文件上传)

      12. 编写一个服务端,和一个客户端

      13. 服务器端在8888端口监听

      14. 客户端连接到服务端,发送一张图片e:\qie.png

      15. 服务器端接收到客户端发送的图片,保存到src下,发送“收到图片”再退出

      16. 客户端接收到服务端发送的“收到图片”,再退出

      17. 该程序要求使用StreamUtils.java,我们直接使用

        1.  工具类:
           package com.hspedu.upload;
          
           import java.io.BufferedReader;
           import java.io.ByteArrayOutputStream;
           import java.io.IOException;
           import java.io.InputStream;
           import java.io.InputStreamReader;
          
           /**
            * 此类用于演示关于流的读写方法
            *
            */
           public class StreamUtils {
           	/**
           	 * 功能:将输入流转换成byte[]
           	 * @param is
           	 * @return
           	 * @throws Exception
           	 */
           	public static byte[] streamToByteArray(InputStream is) throws Exception{
           		ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
           		byte[] b = new byte[1024];
           		int len;
           		while((len=is.read(b))!=-1){
           			bos.write(b, 0, len);
           		}
           		byte[] array = bos.toByteArray();
           		bos.close();
           		return array;
           	}
           	/**
           	 * 功能:将InputStream转换成String
           	 * @param is
           	 * @return
           	 * @throws Exception
           	 */
          
           	public static String streamToString(InputStream is) throws Exception{
           		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
           		StringBuilder builder= new StringBuilder();
           		String line;
           		while((line=reader.readLine())!=null){ //当读取到 null时,就表示结束
           			builder.append(line+"\r\n");
           		}
           		return builder.toString();
          
           	}
          
           }
           =======================================================================================
           服务端:
           package com.hspedu.upload;
          
           import java.io.*;
           import java.net.ServerSocket;
           import java.net.Socket;
          
           /**
            * 文件上传的服务端
            */
           public class TCPFileUploadServer {
               public static void main(String[] args) throws Exception {
          
                   //1. 服务端在本机监听8888端口
                   ServerSocket serverSocket = new ServerSocket(8888);
                   System.out.println("服务端在8888端口监听....");
                   //2. 等待连接
                   Socket socket = serverSocket.accept();
          
          
                   //3. 读取客户端发送的数据
                   //   通过Socket得到输入流
                   BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
                   byte[] bytes = StreamUtils.streamToByteArray(bis);
                   //4. 将得到 bytes 数组,写入到指定的路径,就得到一个文件了
                   String destFilePath = "src\\abc.mp4";
                   BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
                   bos.write(bytes);
                   bos.close();
          
                   // 向客户端回复 "收到图片"
                   // 通过socket 获取到输出流(字符)
                   BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                   writer.write("收到图片");
                   writer.flush();//把内容刷新到数据通道
                   socket.shutdownOutput();//设置写入结束标记
          
                   //关闭其他资源
                   writer.close();
                   bis.close();
                   socket.close();
                   serverSocket.close();
               }
           }
           =======================================================================================
           客户端:
           package com.hspedu.upload;
          
           import java.io.*;
           import java.net.InetAddress;
           import java.net.Socket;
          
          
           /**
            * 文件上传的客户端
            */
           public class TCPFileUploadClient {
               public static void main(String[] args) throws Exception {
          
                   //客户端连接服务端 8888,得到Socket对象
                   Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
                   //创建读取磁盘文件的输入流
                   //String filePath = "e:\\qie.png";
                   String filePath = "e:\\abc.mp4";
                   BufferedInputStream bis  = new BufferedInputStream(new FileInputStream(filePath));
          
                   //bytes 就是filePath对应的字节数组
                   byte[] bytes = StreamUtils.streamToByteArray(bis);
          
                   //通过socket获取到输出流, 将bytes数据发送给服务端
                   BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
                   bos.write(bytes);//将文件对应的字节数组的内容,写入到数据通道
                   bis.close();
                   socket.shutdownOutput();//设置写入数据的结束标记
          
                   //=====接收从服务端回复的消息=====
          
                   InputStream inputStream = socket.getInputStream();
                   //使用StreamUtils 的方法,直接将 inputStream 读取到的内容 转成字符串
                   String s = StreamUtils.streamToString(inputStream);
                   System.out.println(s);
          
          
                   //关闭相关的流
                   inputStream.close();
                   bos.close();
                   socket.close();
          
               }
           }
          
          1. 思考

            1. 服务器通过字节流的方式发送图片,并通过字符流的方式发送“收到图片”
  3. UDP网络编程

    1. 基本介绍
      1. 类DatagramSocket和 DatagramPacket[数据包/数据报]实现了基于UDP 协议网络程序。
      2. UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
      3. DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
      4. UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接
    2. 基本流程
      1. 核心的两个类/对象 DatagramSocket与DatagramPacket
      2. 建立发送端,接收端(没有服务端和客户端概念)
      3. 发送数据前,建立数据包/报DatagramPacket对象
      4. 调用DatagramSocket的发送、接收方法
      5. 关闭DatagramSocket
    3. 常用方法
      1. DatagramSocket类的常用方法

        1. DatagramSocket(int port)​: 创建数据报套接字并绑定指定端口
        2. send(DatagramPacket p)​: 发送数据报包
        3. receive(DatagramPacket p)​: 接收数据报包
      2. DatagramPacket类的常用方法

        1. getPort()​: 获取发送方或接收方的端口号
        2. DatagramPacket(byte[] buf, int length)​: 创建接收数据报包
        3. DatagramPacket(byte[] buf, int length, InetAddress address, int port)​: 创建发送数据报包
        4. getLength()​: 获取实际接收到的数据长度
        5. getData()​: 获取数据报包中的数据
        6. getAddress()​: 获取发送方或接收方的IP地址
      3. InetAddress类的常用方法

        1. getByName(String host)​: 根据主机名或IP地址字符串获取InetAddress对象
        2. getLocalHost()​: 获取本地主机的InetAddress对象
        3. getHostAddress()​: 获取IP地址字符串
        4. getHostName()​: 获取主机名
    4. 代码示例
      1. 编写一个接收端A,和一个发送端B

      2. 接收端A在9999端口等待接收数据(receive)

      3. 发送端B向接收端A发送数据“hello,明天吃火锅~”

      4. 接收端A接收到发送端B发送的数据,回复“好的,明天见”,再退出

      5. 发送端接收回复的数据,再退出

        1.  接受方:
           package com.hspedu.udp;
          
           import java.io.IOException;
           import java.net.DatagramPacket;
           import java.net.DatagramSocket;
           import java.net.InetAddress;
           import java.net.SocketException;
          
           /**
            * UDP接收端
            */
           public class UDPReceiverA {
               public static void main(String[] args) throws IOException {
                   //1. 创建一个 DatagramSocket 对象,准备在9999接收数据
                   DatagramSocket socket = new DatagramSocket(9999);
                   //2. 构建一个 DatagramPacket 对象,准备接收数据
                   //   在前面讲解UDP 协议时,老师说过一个数据包最大 64k
                   byte[] buf = new byte[1024];
                   DatagramPacket packet = new DatagramPacket(buf, buf.length);
                   //3. 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
                   //   填充到 packet对象
                   //老师提示: 当有数据包发送到 本机的9999端口时,就会接收到数据
                   //   如果没有数据包发送到 本机的9999端口, 就会阻塞等待.
                   System.out.println("接收端A 等待接收数据..");
                   socket.receive(packet);
          
                   //4. 可以把packet 进行拆包,取出数据,并显示.
                   int length = packet.getLength();//实际接收到的数据字节长度
                   byte[] data = packet.getData();//接收到数据
                   String s = new String(data, 0, length);
                   System.out.println(s);
          
          
                   //===回复信息给B端
                   //将需要发送的数据,封装到 DatagramPacket对象
                   data = "好的, 明天见".getBytes();
                   //说明: 封装的 DatagramPacket对象 data 内容字节数组 , data.length , 主机(IP) , 端口
                   packet =
                           new DatagramPacket(data, data.length, InetAddress.getByName("192.168.41.1"), 9998);
          
                   socket.send(packet);//发送
          
                   //5. 关闭资源
                   socket.close();
                   System.out.println("A端退出...");
          
               }
           }
          
           =========================================================================================================================
           发送方:
           package com.hspedu.udp;
          
           import java.io.IOException;
           import java.net.*;
          
           /**
            * 发送端B ====> 也可以接收数据
            */
           @SuppressWarnings({"all"})
           public class UDPSenderB {
               public static void main(String[] args) throws IOException {
          
                   //1.创建 DatagramSocket 对象,准备在9998端口 发送数据
                   DatagramSocket socket = new DatagramSocket(9998);
          
                   //2. 将需要发送的数据,封装到 DatagramPacket对象
                   byte[] data = "hello 明天吃火锅~".getBytes(); //
          
                   //说明: 封装的 DatagramPacket对象 data 内容字节数组 , data.length , 主机(IP) , 端口
                   DatagramPacket packet =
                           new DatagramPacket(data, data.length, InetAddress.getByName("192.168.41.1"), 9999);
          
                   socket.send(packet);
          
                   //3.=== 接收从A端回复的信息
                   //(1)   构建一个 DatagramPacket 对象,准备接收数据
                   //   在前面讲解UDP 协议时,老师说过一个数据包最大 64k
                   byte[] buf = new byte[1024];
                   packet = new DatagramPacket(buf, buf.length);
                   //(2)    调用 接收方法, 将通过网络传输的 DatagramPacket 对象
                   //   填充到 packet对象
                   //老师提示: 当有数据包发送到 本机的9998端口时,就会接收到数据
                   //   如果没有数据包发送到 本机的9998端口, 就会阻塞等待.
                   socket.receive(packet);
          
                   //(3)  可以把packet 进行拆包,取出数据,并显示.
                   int length = packet.getLength();//实际接收到的数据字节长度
                   data = packet.getData();//接收到数据
                   String s = new String(data, 0, length);
                   System.out.println(s);
          
                   //关闭资源
                   socket.close();
                   System.out.println("B端退出");
               }
           }
           输出结果:
           接受方:
           接收端A 等待接收数据..
           hello 明天吃火锅~
           A端退出...
          
           发送方:
           好的, 明天见
           B端退出
          
          1. 思考

            1. 这个原理就是创建一个DatagramSocket​对象,这个对象具有发送和接受方法,对应发送方和接受方
            2. DatagramPacket​对象用于网络传输,他常用于适配DatagramSocket​对象的send方法和receive方法

反射机制

  1. 基本介绍

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

    1. 获取Class对象的方法:

      • Class.forName(classfullpath)​: 通过类的完整路径获取Class对象
    2. Class类的常用方法:

      • newInstance()​: 创建对象实例
      • getMethod(methodName)​: 获取指定名称的公共方法
      • getField(fieldName)​: 获取指定名称的公共成员变量
      • getConstructor(parameterTypes...)​: 获取指定参数类型的公共构造器
    3. Method类的常用方法:

      • invoke(Object obj, Object... args)​: 调用方法,第一个参数是调用该方法的对象,后面是方法参数
    4. Field类的常用方法:

      • get(Object obj)​: 获取指定对象的该字段值
    5. Constructor类的常用方法:

      • newInstance(Object... args)​: 使用此构造器创建实例
     * ​`getDeclaredMethod()`​: 获取所有声明的方法,包括私有方法
     * ​`getDeclaredField()`​: 获取所有声明的字段,包括私有字段
     * ​`getDeclaredConstructor()`​: 获取所有声明的构造器,包括私有构造器
     * ​`setAccessible(true)`​: 设置访问权限,使得可以访问私有成员,设置为`true`​可以稍微优化下性能
    
  3. 代码示例

    1.  配置文件re.properties:
       classfullpath=com.hspedu.Cat
       method=cry
       =================================================================================================
       Cat.java:
       package com.hspedu;
      
       public class Cat {
      
           private String name = "招财猫";
           public int age = 10; //public的
      
           public Cat() {} //无参构造器
      
           public Cat(String name) {
               this.name = name;
           }
      
           public void hi() { //常用方法
               //System.out.println("hi " + name);
           }
           public void cry() { //常用方法
               System.out.println(name + " 喵喵叫..");
           }
      
       }
       =================================================================================================
       Reflection01.java:
       package com.hspedu.reflection;
      
       import java.io.FileInputStream;
       import java.io.FileNotFoundException;
       import java.lang.reflect.Constructor;
       import java.lang.reflect.Field;
       import java.lang.reflect.Method;
       import java.util.Properties;
      
       public class Reflection01 {
      
           public static void main(String[] args) throws Exception {
      
      
      
               //1. 使用Properties 类, 可以读写配置文件
               Properties properties = new Properties();
               properties.load(new FileInputStream("src\\re.properties"));
               String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat"
               String methodName = properties.get("method").toString();//"hi"
      
      
               //2. 使用反射机制解决
               //(1) 加载类, 返回Class类型的对象cls
               Class cls = Class.forName(classfullpath);
               //(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
               Object o = cls.newInstance();
               System.out.println("o的运行类型=" + o.getClass()); //运行类型
               //(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi"  的方法对象
               //    即:在反射中,可以把方法视为对象(万物皆对象)
               Method method1 = cls.getMethod(methodName);
               //(4) 通过method1 调用方法: 即通过方法对象来实现调用方法
               System.out.println("=============================");
               method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)
      
               //java.lang.reflect.Field: 代表类的成员变量, Field对象表示某个类的成员变量
               //得到name字段
               //getField不能得到私有的属性
               Field nameField = cls.getField("age"); //
               System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 :  成员变量对象.get(对象)
      
               //java.lang.reflect.Constructor: 代表类的构造方法, Constructor对象表示构造器
               Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
               System.out.println(constructor);//Cat()
      
               Constructor constructor2 = cls.getConstructor(String.class); //这里老师传入的 String.class 就是String类的Class对象
               System.out.println(constructor2);//Cat(String name)
           }
       }
      
       输出结果:
       o的运行类型=class com.hspedu.Cat
       =============================
       招财猫 喵喵叫..
       10
       public com.hspedu.Cat()
       public com.hspedu.Cat(java.lang.String)
      
      1. 思考:

        1. 如果不是用反射的话只能通过new对象来实现调用方法,需要更改源代码才能调用相应的方法
        2. 使用反射机制的好处在于可以通过修改配置文件来实现调用Cat对象具体的方法
        3. Properties​类是用于读取配置文件,具体使用请看Properties 类
        4. 因为如果我们要实现不同方法的切换使用,总不能使用修改字符串.对象这种方式吧?所以就得引入反射机制
        5. Method method1 = cls.getMethod(methodName);​然后method1.invoke(o);​其中methodName​为方法的字符串,o为相应的对象示例,这几句代码是实现o对象的method1方法(cry方法)
  4. 优缺点

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

    1. 介绍
      1. Class也是类,因此也继承Object类[类图]
      2. Class类对象不是new出来的,而是系统创建的[演示]
      3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次[演示]
      4. 每个类的实例都会记得自己是由哪个Class实例所生成
      5. 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
      6. Class对象是存放在堆的
      7. 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,
        变量名,方法名,访问权限等等)
    2. 常用方法
      1. Class.forName(String className)

        • 根据类的全路径名获取对应的Class对象
      2. getClass()

        • 获取Class对象的运行时类型
      3. getPackage().getName()

        • 获取类的包名
      4. getName()

        • 获取类的完整类名(包含包名)
      5. newInstance()

        • 通过Class对象创建该类的实例对象
        • 调用无参构造器
      6. getField(String name)

        • 获取指定名称的public属性
        • 返回Field对象
      7. getFields()

        • 获取所有public属性
        • 返回Field数组
      8. Field类的相关方法:

        • get(Object obj)​: 获取指定对象该字段的值
        • set(Object obj, Object value)​: 设置指定对象该字段的值
        • getName()​: 获取字段名
    3. 代码示例
      1.  Car.java
         package com.hspedu;
        
         public class Car {
             public String brand = "宝马";//品牌
             public int price = 500000;
             public String color = "白色";
        
             @Override
             public String toString() {
                 return "Car{" +
                         "brand='" + brand + '\'' +
                         ", price=" + price +
                         ", color='" + color + '\'' +
                         '}';
             }
         }
         ==========================================================================================
         Class02.java:
         package com.hspedu.reflection.class_;
        
         import com.hspedu.Car;
        
         import java.lang.reflect.Field;
        
         /**
          * 演示Class类的常用方法
          */
         public class Class02 {
             public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        
                 String classAllPath = "com.hspedu.Car";
                 //1 . 获取到Car类 对应的 Class对象
                 //<?> 表示不确定的Java类型
                 Class<?> cls = Class.forName(classAllPath);
                 //2. 输出cls
                 System.out.println(cls); //显示cls对象, 是哪个类的Class对象 com.hspedu.Car
                 System.out.println(cls.getClass());//输出cls运行类型 java.lang.Class
                 //3. 得到包名
                 System.out.println(cls.getPackage().getName());//包名
                 //4. 得到全类名
                 System.out.println(cls.getName());
                 //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());//名称
                 }
        
        
             }
         }
         输出结果:
         class com.hspedu.Car
         class java.lang.Class
         com.hspedu
         com.hspedu.Car
         Car{brand='宝马', price=500000, color='白色'}
         宝马
         奔驰
         =======所有的字段属性====
         brand
         price
         color
        
        1. 总结

          1. 本代码通过反射获取对象的相关信息,并了解calss的常用方法

          2. 首先展示了class的常用的方法,如getClass​获取对象运行类型,getName​获取对象类名,newInstance​实例化对象

          3. 然后展示了Field这个类,通过示例化的类来展示了如何获取类的所有的属性值,设置方法名

          4. Class<?> cls = Class.forName(classAllPath);​通过这句代码将字符串来获取Car类

          5. 然后通过newInstance​方法进行对象示例

          6. Field brand = cls.getField("brand");

            • 这行代码通过反射获取名为"brand"的字段(属性)
            • cls.getField()​ 方法接收一个字符串参数,这个字符串就是要获取的字段名称
            • 返回的 Field​ 对象代表了 Car 类中的 brand 字段
          7. System.out.println(brand.get(car));

            • brand.get(car)​ 用于获取指定对象(car)中该字段(brand)的值
            • car 是我们之前创建的 Car 类的实例
            • 因为 brand 字段的值是"宝马",所以这里会打印出"宝马"
          8. Field[] fields = cls.getFields();​这句代码将所有car类的属性值放入到一个数组里,可以进行遍历获取

  6. 反射获取类的结构信息

    1. java.lang.Class 类
      1. getName:获取全类名
      2. getSimpleName:获取简单类名
      3. getFields:获取所有public修饰的属性,包含本类以及父类的
      4. getDeclaredFields:获取本类中所有属性
      5. getMethods:获取所有public修饰的方法,包含本类以及父类的
      6. getDeclaredMethods:获取本类中所有方法
      7. getConstructors:获取本类所有public修饰的构造器
      8. getDeclaredConstructors:获取本类中所有构造器
      9. getPackage:以Package形式返回包信息
      10. getSuperClass:以Class形式返回父类信息
      11. getlnterfaces:以Class[]形式返回接口信息
      12. getAnnotations:以Annotation[]形式返回注解信息
    2. java.lang.reflect.Field 类
      1. getModifiers:以int形式返回修饰符
        [说明:默认修饰符是0,public 是1,private是2,protected是4, static是8,final是16],public(1)+static(8)=9
      2. getType:以Class形式返回类型 3.getName:返回属性名
    3. java.lang.reflect.Method 类
      1. getModifiers:以int形式返回修饰符
        [说明:是0, public是1, private是2, protectec是4, static是8,final是16]
      2. getReturnType:以Class形式获取返回类型
      3. getName:返回方法名
      4. getParameterTypes:以Class[]返回参数类型数组
    4. java.lang.reflect.Constructor 类
      1. getModifiers:以int形式返回修饰符
      2. getName:返回构造器名(全类名)
      3. getParameterTypes:以Class[]返回参数类型数组
    5. 代码示例
      1.  package com.hspedu.reflection;
        
         import org.junit.jupiter.api.Test;
        
         import java.lang.annotation.Annotation;
         import java.lang.reflect.Constructor;
         import java.lang.reflect.Field;
         import java.lang.reflect.Method;
        
         /**
          * 演示如何通过反射获取类的结构信息
          */
         public class ReflectionUtils {
             public static void main(String[] args) {
        
             }
        
             @Test
             public void api_02() throws ClassNotFoundException, NoSuchMethodException {
                 //得到Class对象
                 Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
                 //getDeclaredFields:获取本类中所有属性
                 //规定 说明: 默认修饰符 是0 , public  是1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16
                 Field[] declaredFields = personCls.getDeclaredFields();
                 for (Field declaredField : declaredFields) {
                     System.out.println("本类中所有属性=" + declaredField.getName()
                             + " 该属性的修饰符值=" + declaredField.getModifiers()
                             + " 该属性的类型=" + declaredField.getType());
                 }
        
                 //getDeclaredMethods:获取本类中所有方法
                 Method[] declaredMethods = personCls.getDeclaredMethods();
                 for (Method declaredMethod : declaredMethods) {
                     System.out.println("本类中所有方法=" + declaredMethod.getName()
                             + " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
                             + " 该方法返回类型" + declaredMethod.getReturnType());
        
                     //输出当前这个方法的形参数组情况
                     Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
                     for (Class<?> parameterType : parameterTypes) {
                         System.out.println("该方法的形参类型=" + parameterType);
                     }
                 }
        
                 //getDeclaredConstructors:获取本类中所有构造器
                 Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
                 for (Constructor<?> declaredConstructor : declaredConstructors) {
                     System.out.println("====================");
                     System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名
        
                     Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
                     for (Class<?> parameterType : parameterTypes) {
                         System.out.println("该构造器的形参类型=" + parameterType);
                     }
        
        
        
                 }
        
             }
        
             //第一组方法API
             @Test
             public void api_01() throws ClassNotFoundException, NoSuchMethodException {
        
                 //得到Class对象
                 Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
                 //getName:获取全类名
                 System.out.println(personCls.getName());//com.hspedu.reflection.Person
                 //getSimpleName:获取简单类名
                 System.out.println(personCls.getSimpleName());//Person
                 //getFields:获取所有public修饰的属性,包含本类以及父类的
                 Field[] fields = personCls.getFields();
                 for (Field field : fields) {//增强for
                     System.out.println("本类以及父类的属性=" + field.getName());
                 }
                 //getDeclaredFields:获取本类中所有属性
                 Field[] declaredFields = personCls.getDeclaredFields();
                 for (Field declaredField : declaredFields) {
                     System.out.println("本类中所有属性=" + declaredField.getName());
                 }
                 //getMethods:获取所有public修饰的方法,包含本类以及父类的
                 Method[] methods = personCls.getMethods();
                 for (Method method : methods) {
                     System.out.println("本类以及父类的方法=" + method.getName());
                 }
                 //getDeclaredMethods:获取本类中所有方法
                 Method[] declaredMethods = personCls.getDeclaredMethods();
                 for (Method declaredMethod : declaredMethods) {
                     System.out.println("本类中所有方法=" + declaredMethod.getName());
                 }
                 //getConstructors: 获取所有public修饰的构造器,包含本类
                 Constructor<?>[] constructors = personCls.getConstructors();
                 for (Constructor<?> constructor : constructors) {
                     System.out.println("本类的构造器=" + constructor.getName());
                 }
                 //getDeclaredConstructors:获取本类中所有构造器
                 Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
                 for (Constructor<?> declaredConstructor : declaredConstructors) {
                     System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名
                 }
                 //getPackage:以Package形式返回 包信息
                 System.out.println(personCls.getPackage());//com.hspedu.reflection
                 //getSuperClass:以Class形式返回父类信息
                 Class<?> superclass = personCls.getSuperclass();
                 System.out.println("父类的class对象=" + superclass);//
                 //getInterfaces:以Class[]形式返回接口信息
                 Class<?>[] interfaces = personCls.getInterfaces();
                 for (Class<?> anInterface : interfaces) {
                     System.out.println("接口信息=" + anInterface);
                 }
                 //getAnnotations:以Annotation[] 形式返回注解信息
                 Annotation[] annotations = personCls.getAnnotations();
                 for (Annotation annotation : annotations) {
                     System.out.println("注解信息=" + annotation);//注解
                 }
        
        
             }
         }
        
         class A {
             public String hobby;
        
             public void hi() {
        
             }
        
             public A() {
             }
        
             public A(String name) {
             }
         }
        
         interface IA {
         }
        
         interface IB {
        
         }
        
         @Deprecated
         class Person extends A implements IA, IB {
             //属性
             public String name;
             protected static int age; // 4 + 8 = 12
             String job;
             private double sal;
        
             //构造器
             public Person() {
             }
        
             public Person(String name) {
             }
        
             //私有的
             private Person(String name, int age) {
        
             }
        
             //方法
             public void m1(String name, int age, double sal) {
        
             }
        
             protected String m2() {
                 return null;
             }
        
             void m3() {
        
             }
        
             private void m4() {
        
             }
         }
         //编译成功
        
  7. 反射创建对象

    1. 方式
      1. 方式一:调用类中的public修饰的无参构造器
      2. 方式二:调用类中的指定构造器
    2. 相关方法
      1. Class类相关方法

        1. newlnstance:调用类中的无参构造器,获取对应类的对象
        2. getConstructor(Class.clazz):根据参数列表,获取对应的public构造器对象
        3. getDecalaredConstructor(Class.clazz):根据参数列表,获取对应的所有构造器 对象
      2. Constructor类相关方法

        1. setAccessible:暴破
        2. newInstance(Object…obj):调用构造器
    3. 代码示例
      1. 测试1:通过反射创建某类的对象,要求该类中必须有public的无参构造
      2. 测试2:通过调用某个特定构造器的方式,实现创建某类的对象
      3.  package com.hspedu.reflection;
        
         import java.lang.reflect.Constructor;
         import java.lang.reflect.InvocationTargetException;
        
         /**
          * 演示通过反射机制创建实例
          */
         public class ReflecCreateInstance {
             public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        
                 //1. 先获取到User类的Class对象
                 Class<?> userClass = Class.forName("com.hspedu.reflection.User");
                 //2. 通过public的无参构造器创建实例
                 Object o = userClass.newInstance();
                 System.out.println(o);
                 //3. 通过public的有参构造器创建实例
                 /*
                     constructor 对象就是
                     public User(String name) {//public的有参构造器
                         this.name = name;
                     }
                  */
                 //3.1 先得到对应构造器
                 Constructor<?> constructor = userClass.getConstructor(String.class);
                 //3.2 创建实例,并传入实参
                 Object hsp = constructor.newInstance("hsp");
                 System.out.println("hsp=" + hsp);
                 //4. 通过非public的有参构造器创建实例
                 //4.1 得到private的构造器对象
                 Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
                 //4.2 创建实例
                 //暴破【暴力破解】 , 使用反射可以访问private构造器/方法/属性, 反射面前,都是纸老虎
                 constructor1.setAccessible(true);
                 Object user2 = constructor1.newInstance(100, "张三丰");
                 System.out.println("user2=" + user2);
             }
         }
        
         class User { //User类
             private int age = 10;
             private String name = "韩顺平教育";
        
             public User() {//无参 public
             }
        
             public User(String name) {//public的有参构造器
                 this.name = name;
             }
        
             private User(int age, String name) {//private 有参构造器
                 this.age = age;
                 this.name = name;
             }
        
             public String toString() {
                 return "User [age=" + age + ", name=" + name + "]";
             }
         }
         输出结果:
         User [age=10, name=韩顺平教育]
         hsp=User [age=10, name=hsp]
         user2=User [age=100, name=张三丰]
        
  8. 反射访问类的成员

    1. 根据属性名获取Field对象
      1. Field f=clazz对象.getDeclaredField(属性名);
    2. 暴破
      1. f.setAccessible(true);/f是Field
    3. 访问
      1. f.set(o,值);/o表示对象
      2. syso(f.get(o);/o表示对象
      3. 注意:如果是静态属性,则set和get中的参数o,可以写成null
    4. 代码示例
      1.  package com.hspedu.reflection;
        
         import java.lang.reflect.Field;
        
         /**
          * 演示反射操作属性
          */
         public class ReflecAccessProperty {
             public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        
                 //1. 得到Student类对应的 Class对象
                 Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");
                 //2. 创建对象
                 Object o = stuClass.newInstance();//o 的运行类型就是Student
                 System.out.println(o.getClass());//Student
                 //3. 使用反射得到age 属性对象
                 Field age = stuClass.getField("age");
                 age.set(o, 88);//通过反射来操作属性
                 System.out.println(o);//
                 System.out.println(age.get(o));//返回age属性的值
        
                 //4. 使用反射操作name 属性
                 Field name = stuClass.getDeclaredField("name");
                 //对name 进行暴破, 可以操作private 属性
                 name.setAccessible(true);
                 //name.set(o, "老韩");
                 name.set(null, "老韩~");//因为name是static属性,因此 o 也可以写出null
                 System.out.println(o);
                 System.out.println(name.get(o)); //获取属性值
                 System.out.println(name.get(null));//获取属性值, 要求name是static
        
             }
         }
        
         class Student {//类
             public int age;
             private static String name;
        
             public Student() {//构造器
             }
        
             public String toString() {
                 return "Student [age=" + age + ", name=" + name + "]";
             }
         }
         输出结果:
         class com.hspedu.reflection.Student
         Student [age=88, name=null]
         88
         Student [age=88, name=老韩~]
         老韩~
         老韩~
        
  9. 反射访问方法

    1. 前言
      1. 根据方法名和参数列表获取Method方法对象:Method m=
        clazz.getDeclaredMethod(方法名,XX.class);/得到本类的所有方法
      2. 获取对象:Object o=clazz.newlnstance();
      3. 暴破:m.setAccessible(true);
      4. 访问:Object returnValue=m.invoke(o,实参列表);/o就是对象
      5. 注意:如果是静态方法,则invoke的参数o,可以写成null!
    2. 代码示例
      1.  package com.hspedu.reflection;
        
         import java.lang.reflect.InvocationTargetException;
         import java.lang.reflect.Method;
        
         /**
          * 演示通过反射调用方法
          */
         public class ReflecAccessMethod {
             public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        
                 //1. 得到Boss类对应的Class对象
                 Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");
                 //2. 创建对象
                 Object o = bossCls.newInstance();
                 //3. 调用public的hi方法
                 //Method hi = bossCls.getMethod("hi", String.class);//OK
                 //3.1 得到hi方法对象
                 Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
                 //3.2 调用
                 hi.invoke(o, "韩顺平教育~");
        
                 //4. 调用private static 方法
                 //4.1 得到 say 方法对象
                 Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
                 //4.2 因为say方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样
                 say.setAccessible(true);
                 System.out.println(say.invoke(o, 100, "张三", '男'));
                 //4.3 因为say方法是static的,还可以这样调用 ,可以传入null
                 System.out.println(say.invoke(null, 200, "李四", '女'));
        
                 //5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
                 Object reVal = say.invoke(null, 300, "王五", '男');
                 System.out.println("reVal 的运行类型=" + reVal.getClass());//String
        
        
                 //在演示一个返回的案例
                 Method m1 = bossCls.getDeclaredMethod("m1");
                 Object reVal2 = m1.invoke(o);
                 System.out.println("reVal2的运行类型=" + reVal2.getClass());//Monster
        
        
             }
         }
        
         class Monster {}
         class Boss {//类
             public int age;
             private static String name;
        
             public Boss() {//构造器
             }
        
             public Monster m1() {
                 return new Monster();
             }
        
             private static String say(int n, String s, char c) {//静态方法
                 return n + " " + s + " " + c;
             }
        
             public void hi(String s) {//普通public方法
                 System.out.println("hi " + s);
             }
         }
         输出结果:
         hi 韩顺平教育~
         100 张三 男
         200 李四 女
         reVal 的运行类型=class java.lang.String
         reVal2的运行类型=class com.hspedu.reflection.Monster
        
  10. 获取class对象的方式

    1. 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法
      forName()获取,可能抛出ClassNotFoundException,实例:Class cls1= Class.forName(“java.lang.Cat”);
      应用场景:多用于配置文件,读取类全路径,加载类.

    2. 前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高实例:Class cls2=Cat.class;
      应用场景:多用于参数传递,比如通过反射得到对应构造器对象

    3. 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象,实例:Class clazz=对象.getClass();/运行类型
      应用场景:通过创建好的对象,获取Class对象.

    4. 其他方式

      1. ClassLoader cl=对象.getClass().getClassLoader();
      2. Class clazz4=cl.loadClass(“类的全类名”);
    5. 基本数据(int,char,boolean,float,double,byte,long,short)按如下方式得到Class类 对象

      1. Class cls=基本数据类型.class
    6. 基本数据类型对应的包装类,可以通过.TYPE得到Class类对象

      1. Class cls=包装类.TYPE
    7. 代码示例
      1.  package com.hspedu.reflection.class_;
        
         import com.hspedu.Car;
        
         /**
          * 演示得到Class对象的各种方式(6)
          */
         public class GetClass_ {
             public static void main(String[] args) throws ClassNotFoundException {
        
                 //1. Class.forName
                 String classAllPath = "com.hspedu.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());
                 System.out.println(cls2.hashCode());
                 System.out.println(cls3.hashCode());
                 System.out.println(cls4.hashCode());
        
                 //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(type1);
        
                 System.out.println(integerClass.hashCode());//?
                 System.out.println(type1.hashCode());//?
        
             }
         }
         输出结果:
         class com.hspedu.Car
         class com.hspedu.Car
         class com.hspedu.Car
         class com.hspedu.Car
         1639705018
         1639705018
         1639705018
         1639705018
         int
         int
         1627674070
         1627674070
        
  11. 类加载的五个阶段

    1. 介绍
      1. 反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。

        1. 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
        2. 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性
    2. 加载时机
      1. 当创建对象时(new)/静态加载
      2. 当子类被加载时,父类也加载/静态加载
      3. 调用类中的静态成员时/静态加载
      4. 通过反射/动态加载
    3. 加载阶段
      1. JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class 对象

      2. 连接阶段-验证
        1. 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
        2. 包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证[举例说明]
        3. 可以考虑使用—Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
      3. 连接阶段-准备
        1. JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、OL、null、false等)。这些变量所使用的内存都将在方法区中进行分配
        2. public int n1=10;​ n1是实例属性,不是静态变量,因此在准备阶段,是不会分配内存
        3. public static int n2=20;​ n2是静态变量,分配内存n2是默认初始化0,而不是20
        4. public static final int n3=30;​n3是staticfinal是常量,他和静态变量不一样,因为一旦赋值就不变n3=30
      4. 连接阶段-解析
        1. 虚拟机将常量池内的符号引用替换为直接引用的过程。
      5. Initialization(初始化)
        1. 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程。
        2. ()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
        3. 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕[debug源码]

数据库

  1. 常用命令

    1. mysql命令操作
  2. jdbc介绍

    1. JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
    2. java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
  3. 快速入门

    1. 环境搭建
      1.  create table actor(
         	id int primary key AUTO_INCREMENT,
         	NAME varchar(32) NOT NULLL DEFAULT '',
         	sex char(1) NOT NULLL DEFAULT '女',
         	borndate DATETIME,
         	phone varchar(12));
        
    2. 代码示例
      1.  package com.hspedu.jdbc;
        
         import com.mysql.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) hsp_db02 连接到mysql dbms 的哪个数据库
                 //(5) mysql的连接本质就是前面学过的socket连接
                 String url = "jdbc:mysql://localhost:3306/hsp_db02";
                 //将 用户名和密码放入到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, '刘德华', '男', '1970-11-11', '110')";
                 //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();
        
             }
         }
        
  4. 使用方式

    1. 获取数据库的五种方式
      1. 方式一
        1.  Driver driver = new Driver(); //创建driver对象
           String url = "jdbc:mysql://localhost:3306/hsp_db02";
           //将 用户名和密码放入到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.  //使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性
           Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
           Driver driver = (Driver)aClass.newInstance();
          
           String url = "jdbc:mysql://localhost:3306/hsp_db02";
           //将 用户名和密码放入到Properties 对象
           Properties properties = new Properties();
           //说明 user 和 password 是规定好,后面的值根据实际情况写
           properties.setProperty("user", "root");// 用户
           properties.setProperty("password", "hsp"); //密码
          
           Connection connect = driver.connect(url, properties);
           System.out.println("方式2=" + connect);
          
      3. 方式三
        1.  //使用反射加载Driver
           Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
           Driver driver = (Driver) aClass.newInstance();
          
           //创建url 和 user 和 password
           String url = "jdbc:mysql://localhost:3306/hsp_db02";
           String user = "root";
           String password = "hsp";
          
           DriverManager.registerDriver(driver);//注册Driver驱动
          
           Connection connection = DriverManager.getConnection(url, user, password);
           System.out.println("第三种方式=" + connection);
          
      4. 方式四
        1.  //使用反射加载了 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.forName("com.mysql.jdbc.Driver");
          
           //创建url 和 user 和 password
           String url = "jdbc:mysql://localhost:3306/hsp_db02";
           String user = "root";
           String password = "hsp";
           Connection connection = DriverManager.getConnection(url, user, password);
          
           System.out.println("第4种方式~ " + connection);
          
      5. 方式五
        1.  //通过Properties对象获取配置文件的信息
           Properties properties = new Properties();
           properties.load(new FileInputStream("src\\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 connection = DriverManager.getConnection(url, user, password);
          
           System.out.println("方式5 " + connection);
           ---------
           mysql.properties:
           user=root
           password=123456
           url=jdbc:mysql://localhost:3306/hsp_db02?rewriteBatchedStatements=true
           driver=com.mysql.jdbc.Driver
          
    2. ResultSet[结果集]
      1. 介绍
        1. 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
        2. ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
        3. next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集
      2. 代码示例
        1.  package com.hspedu.jdbc.statement_;
          
           import java.io.FileInputStream;
           import java.io.FileNotFoundException;
           import java.sql.*;
           import java.util.Properties;
           import java.util.Scanner;
          
           /**
            * 演示statement 的注入问题
            */
           @SuppressWarnings({"all"})
           public class Statement_ {
               public static void main(String[] args) throws Exception {
          
                   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\\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 connection = DriverManager.getConnection(url, user, password);
          
                   //3. 得到Statement
                   Statement statement = connection.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("对不起,登录失败");
                   }
          
                   //关闭连接
                   resultSet.close();
                   statement.close();
                   connection.close();
          
               }
           }
           sql注入结果:
           请输入管理员的名字: 123
           请输入管理员的密码: 'or '1' = '1
           恭喜, 登录成功
          
    3. 预处理PreparedStatement
      1. 介绍
        1. PreparedStatement 执行的SQL语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx()方法来设置这些参数.setXxx()方法有 两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值
        2. 调用 executeQuery(),返回 ResultSet 对象
        3. 调用 executeUpdate():执行更新,包括增、删、修改
      2. 预处理的好处
        1. 不再使用+拼接sql语句,减少语法错误
        2. 有效的解决了sql注入问题!
        3. 大大减少了编译次数,效率较高
      3. 代码示例
        1.  package com.hspedu.jdbc.preparedstatement_;
          
           import java.io.FileInputStream;
           import java.io.FileNotFoundException;
           import java.sql.*;
           import java.util.Properties;
           import java.util.Scanner;
          
           /**
            * 演示PreparedStatement使用
            */
           @SuppressWarnings({"all"})
           public class PreparedStatement_ {
               public static void main(String[] args) throws Exception {
          
                   //看 PreparedStatement类图
          
                   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\\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 connection = 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 = connection.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("对不起,登录失败");
                   }
          
                   //关闭连接
                   resultSet.close();
                   preparedStatement.close();
                   connection.close();
          
               }
           }
           输出结果:
           请输入管理员的名字: tom
           请输入管理员的密码: 'or '1'='1
           对不起,登录失败
          
          1. 思考

            1. 这段代码数据库会对传入的sql先进行解析和编译其结构部分,不包括数据,生成一个执行计划,这个执行计划是基于sql语句的固定结构的,不会根据用户的输入而改变其结构
            2. String sql = "select name , pwd from admin where name =? and pwd = ?";​这里的问号是占位符,通过preparedStatement.setString(1, admin_name);​方法进行传参
            3. 第55行中的执行sql语句ResultSet resultSet = preparedStatement.executeQuery();​不要填在sql这个字符串,因为这个是没有改变的字符串
      4. PreparedStatement的DML示例
        1.  package com.hspedu.jdbc.preparedstatement_;
          
           import java.io.FileInputStream;
           import java.sql.Connection;
           import java.sql.DriverManager;
           import java.sql.PreparedStatement;
           import java.sql.ResultSet;
           import java.util.Properties;
           import java.util.Scanner;
          
           /**
            * 演示PreparedStatement使用 dml语句
            */
           @SuppressWarnings({"all"})
           public class PreparedStatementDML_ {
               public static void main(String[] args) throws Exception {
          
                   //看 PreparedStatement类图
          
                   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\\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 connection = DriverManager.getConnection(url, user, password);
          
                   //3. 得到PreparedStatement
                   //3.1 组织SqL , Sql 语句的 ? 就相当于占位符
                   //添加记录
                   //String sql = "insert into admin values(?, ?)";
                   //String sql = "update admin set pwd = ? where name = ?";
                   String sql = "delete from  admin where name = ?";
                   //3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
                   PreparedStatement preparedStatement = connection.prepareStatement(sql);
                   //3.3 给 ? 赋值
                   preparedStatement.setString(1, admin_name);
          
                   //preparedStatement.setString(2, admin_name);
          
                   //4. 执行 dml 语句使用  executeUpdate
                   int rows = preparedStatement.executeUpdate();
                   System.out.println(rows > 0 ? "执行成功" : "执行失败");
                   //关闭连接
                   preparedStatement.close();
                   connection.close();
          
          
               }
           }
          
    4. 事务处理

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

        2.  package com.hspedu.jdbc.transaction_;
          
           import com.hspedu.jdbc.utils.JDBCUtils;
           import org.junit.jupiter.api.Test;
          
           import java.sql.Connection;
           import java.sql.PreparedStatement;
           import java.sql.SQLException;
          
           /**
            * 演示jdbc 中如何使用事务
            */
           public class Transaction_ {
          
               //没有使用事务.
               @Test
               public void noTransaction() {
          
                   //操作转账的业务
                   //1. 得到连接
                   Connection connection = null;
                   //2. 组织一个sql
                   String sql = "update account set balance = balance - 100 where id = 1";
                   String sql2 = "update account set balance = balance + 100 where id = 2";
                   PreparedStatement preparedStatement = null;
                   //3. 创建PreparedStatement 对象
                   try {
                       connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
                       preparedStatement = connection.prepareStatement(sql);
                       preparedStatement.executeUpdate(); // 执行第1条sql
          
                       int i = 1 / 0; //抛出异常
                       preparedStatement = connection.prepareStatement(sql2);
                       preparedStatement.executeUpdate(); // 执行第3条sql
          
          
                   } catch (SQLException e) {
                       e.printStackTrace();
                   } finally {
                       //关闭资源
                       JDBCUtils.close(null, preparedStatement, connection);
                   }
               }
          
               //事务来解决
               @Test
               public void useTransaction() {
          
                   //操作转账的业务
                   //1. 得到连接
                   Connection connection = null;
                   //2. 组织一个sql
                   String sql = "update account set balance = balance - 100 where id = 1";
                   String sql2 = "update account set balance = balance + 100 where id = 2";
                   PreparedStatement preparedStatement = null;
                   //3. 创建PreparedStatement 对象
                   try {
                       connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
                       //将 connection 设置为不自动提交
                       connection.setAutoCommit(false); //开启了事务
                       preparedStatement = connection.prepareStatement(sql);
                       preparedStatement.executeUpdate(); // 执行第1条sql
          
                       int i = 1 / 0; //抛出异常
                       preparedStatement = connection.prepareStatement(sql2);
                       preparedStatement.executeUpdate(); // 执行第3条sql
          
                       //这里提交事务
                       connection.commit();
          
                   } catch (SQLException e) {
                       //这里我们可以进行回滚,即撤销执行的SQL
                       //默认回滚到事务开始的状态.
                       System.out.println("执行发生了异常,撤销执行的sql");
                       try {
                           connection.rollback();
                       } catch (SQLException throwables) {
                           throwables.printStackTrace();
                       }
                       e.printStackTrace();
                   } finally {
                       //关闭资源
                       JDBCUtils.close(null, preparedStatement, connection);
                   }
               }
           }
          
          1. 思考

            1. 当我们进行转账时,一方会在数据库减少金钱,另一方会在数据库中增加金钱,如果中间出现了语句的语法错误1,导致程序无法进行下去,就会导致一个局面–一方金钱减少,另一方金钱未动
            2. 这是事务就发挥重要作用了
            3. 只要在开始时添加connection.setAutoCommit(false);​和在结束时添加 connection.commit();​来提交事务即可
    5. 批处理

      1. 介绍
        1. 当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
        2. JDBC的批量处理语句包括下面方法:
          addBatch():添加需要批量处理的SQL语句或参数executeBatch():执行批量处理语句;
          clearBatch():清空批处理包的语句
        3. JDBC连接MySQL时,如果要使用批处理功能,请再url中加参34
          ?rewriteBatchedStatements=true
        4. 批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高
      2. 代码示例
        1.  package com.hspedu.jdbc.batch_;
          
           import com.hspedu.jdbc.utils.JDBCUtils;
           import org.junit.jupiter.api.Test;
          
           import java.sql.Connection;
           import java.sql.PreparedStatement;
           import java.sql.SQLException;
          
           /**
            * 演示java的批处理
            */
           public class Batch_ {
          
               //传统方法,添加5000条数据到admin2
          
               @Test
               public void noBatch() throws Exception {
          
                   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 = 0; i < 5000; i++) {//5000执行
                       preparedStatement.setString(1, "jack" + i);
                       preparedStatement.setString(2, "666");
                       preparedStatement.executeUpdate();
                   }
                   long end = System.currentTimeMillis();
                   System.out.println("传统的方式 耗时=" + (end - start));//传统的方式 耗时=10702
                   //关闭连接
                   JDBCUtils.close(null, preparedStatement, connection);
               }
          
               //使用批量方式添加数据
               @Test
               public void batch() throws Exception {
          
                   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 = 0; i < 5000; i++) {//5000执行
                       preparedStatement.setString(1, "jack" + i);
                       preparedStatement.setString(2, "666");
                       //将sql 语句加入到批处理包中 -> 看源码
                       /*
                       //1. //第一就创建 ArrayList - elementData => Object[]
                       //2. elementData => Object[] 就会存放我们预处理的sql语句
                       //3. 当elementData满后,就按照1.5扩容
                       //4. 当添加到指定的值后,就executeBatch
                       //5. 批量处理会减少我们发送sql语句的网络开销,而且减少编译次数,因此效率提高
                       public void addBatch() throws SQLException {
                           synchronized(this.checkClosed().getConnectionMutex()) {
                               if (this.batchedArgs == null) {
          
                                   this.batchedArgs = new ArrayList();
                               }
          
                               for(int i = 0; i < this.parameterValues.length; ++i) {
                                   this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
                               }
          
                               this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
                           }
                       }
          
                        */
                       preparedStatement.addBatch();
                       //当有1000条记录时,在批量执行
                       if((i + 1) % 1000 == 0) {//满1000条sql
                           preparedStatement.executeBatch();
                           //清空一把
                           preparedStatement.clearBatch();
                       }
                   }
                   long end = System.currentTimeMillis();
                   System.out.println("批量方式 耗时=" + (end - start));//批量方式 耗时=108
                   //关闭连接
                   JDBCUtils.close(null, preparedStatement, connection);
               }
           }
          
          1. 思考

            1. 当我们有进程需要对数据库发起大量连接时,我们可以通过将其数据先添加到指定的值在一起提交即可
            2. preparedStatement.addBatch();​这里可以利用预处理中的addBatch()​函数来添加数据存储
            3. 然后使用1executeBatch​函数执行,最后使用clearBatch​函数请你原来这个值的存储
    6. 数据库连接池

      1. 介绍
        1. 数据库连接池就像是一个存放数据库连接的 “池子”。当应用程序需要和数据库交互,比如查询数据或者更新数据时,就可以从这个 “池子” 里获取一个已经建立好的数据库连接。它的关键在于,这些连接是预先创建好并且被管理起来的。而不是每次应用程序需要连接数据库的时候,都重新去建立一个连接,使用完就直接关闭。通过连接池,可以重复利用这些连接,减少了频繁创建和销毁连接的开销,就像有一个工具库,需要工具的时候直接拿,用完放回去下次还能用,大大提高了数据库访问的效率。
      2. 种类
        1. JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource 只是一个接口,该接口通常由第三方提供实现[提供.jar]
        2. C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate,spring)
        3. DBCP数据库连接池,速度相对c3p0较快,但不稳定
        4. Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点5.
        5. BoneCP数据库连接池,速度快6.
        6. Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool 优点于一身的数据库连接池
      3. C3P0的两种连接方式示例
        1.  c3p0-config.xml配置文件:
           <c3p0-config>
               <!-- 数据源名称代表连接池 -->
             <named-config name="hsp_edu">
           <!-- 驱动类 -->
             <property name="driverClass">com.mysql.jdbc.Driver</property>
             <!-- url-->
             	<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/hsp_db02</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">50</property>
          
           	<!-- 可连接的最多的命令对象数 -->
               <property name="maxStatements">5</property> 
             
               <!-- 每个连接对象可连接的最多的命令对象数 -->
               <property name="maxStatementsPerConnection">2</property>
             </named-config>
           </c3p0-config>
           --------------------------------------------------------------------------
           C3P0_.java:
           package com.hspedu.jdbc.datasource;
           import com.mchange.v2.c3p0.ComboPooledDataSource;
           import org.junit.jupiter.api.Test;
           import java.io.FileInputStream;
           import java.sql.Connection;
           import java.sql.SQLException;
           import java.util.Properties;
          
           /**
            * 演示c3p0的使用
            */
           public class C3P0_ {
          
               //方式1: 相关参数,在程序中指定user, url , password等
               @Test
               public void testC3P0_01() throws Exception {
          
                   //1. 创建一个数据源对象
                   ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
                   //2. 通过配置文件mysql.properties 获取相关连接的信息
                   Properties properties = new Properties();
                   properties.load(new FileInputStream("src\\mysql.properties"));
                   //读取相关的属性值
                   String user = properties.getProperty("user");
                   String password = properties.getProperty("password");
                   String url = properties.getProperty("url");
                   String driver = properties.getProperty("driver");
          
                   //给数据源 comboPooledDataSource 设置相关的参数
                   //注意:连接管理是由 comboPooledDataSource 来管理
                   comboPooledDataSource.setDriverClass(driver);
                   comboPooledDataSource.setJdbcUrl(url);
                   comboPooledDataSource.setUser(user);
                   comboPooledDataSource.setPassword(password);
          
                   //设置初始化连接数
                   comboPooledDataSource.setInitialPoolSize(10);
                   //最大连接数
                   comboPooledDataSource.setMaxPoolSize(50);
                   //测试连接池的效率, 测试对mysql 5000次操作
                   long start = System.currentTimeMillis();
                   for (int i = 0; i < 5000; i++) {
                       Connection connection = comboPooledDataSource.getConnection(); //这个方法就是从 DataSource 接口实现的
                       //System.out.println("连接OK");
                       connection.close();
                   }
                   long end = System.currentTimeMillis();
                   //c3p0 5000连接mysql 耗时=211
                   System.out.println("c3p0 5000连接mysql 耗时=" + (end - start));
          
               }
          
               //第二种方式 使用配置文件模板来完成
          
               //1. 将c3p0 提供的 c3p0.config.xml 拷贝到 src目录下
               //2. 该文件指定了连接数据库和连接池的相关参数
               @Test
               public void testC3P0_02() throws SQLException {
          
                   ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hsp_edu");
          
                   //测试5000次连接mysql
                   long start = System.currentTimeMillis();
                   System.out.println("开始执行....");
                   for (int i = 0; i < 500000; i++) {
                       Connection connection = comboPooledDataSource.getConnection();
                       //System.out.println("连接OK~");
                       connection.close();
                   }
                   long end = System.currentTimeMillis();
                   //c3p0的第二种方式 耗时=922
                   System.out.println("c3p0的第二种方式(500000) 耗时=" + (end - start));//1917
          
               }
           }
          
      4. Druid代码示例
        1.  druid.java配置文件:
           #key=value
           driverClassName=com.mysql.jdbc.Driver
           url=jdbc:mysql://localhost:3306/hsp_db02?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
           ----------------------------------------------------------------------------
           Druid.java:
           package com.hspedu.jdbc.datasource;
           import com.alibaba.druid.pool.DruidDataSourceFactory;
           import org.junit.jupiter.api.Test;
          
           import javax.sql.DataSource;
           import java.io.FileInputStream;
           import java.io.FileNotFoundException;
           import java.io.IOException;
           import java.sql.Connection;
           import java.util.Properties;
          
           /**
            * 测试druid的使用
            */
           public class Druid_ {
          
               @Test
               public void testDruid() 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);
          
                   long start = System.currentTimeMillis();
                   for (int i = 0; i < 5000; i++) {
                       Connection connection = dataSource.getConnection();
                       System.out.println(connection.getClass());
                       //System.out.println("连接成功!");
                       connection.close();
                   }
                   long end = System.currentTimeMillis();
                   //druid连接池 操作5000 耗时=412
                   System.out.println("druid连接池 操作5000 耗时=" + (end - start));//289
          
          
               }
           }
          
      5. DBUTils
        1. 介绍
          1. commons-dbutils 是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装 使用dbutils能极大简化jdbc编码的工作量[真的]。
          2. QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
          3. 使用QueryRunner类实现查
          4. ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式,
        2. 常用方法
          1. ArrayHandler:把结果集中的第一行数据转成对象数组。
          2. ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
          3. BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
          4. BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
          5. ColumnListHandler:将结果集中某一列的数据存放到List中。
          6. KeyedHandler(name) :将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key。
          7. MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
          8. MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
        3. 代码示例
          1. 使用apache-DBUtils 工具类 + druid 完成对表的crud操作

            1.  //使用apache-DBUtils 工具类 + druid 完成对表的crud操作
               @Test
               public void testQueryMany() throws SQLException { //返回结果是多行的情况
              
                   //1. 得到 连接 (druid)
                   Connection connection = JDBCUtilsByDruid.getConnection();
                   //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
                   //3. 创建 QueryRunner
                   QueryRunner queryRunner = new QueryRunner();
                   //4. 就可以执行相关的方法,返回ArrayList 结果集
                   //String sql = "select * from actor where id >= ?";
                   //   注意: sql 语句也可以查询部分列
                   String sql = "select id, name 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
                   /**
                    * 分析 queryRunner.query方法:
                    * public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
                    *         PreparedStatement stmt = null;//定义PreparedStatement
                    *         ResultSet rs = null;//接收返回的 ResultSet
                    *         Object result = null;//返回ArrayList
                    *
                    *         try {
                    *             stmt = this.prepareStatement(conn, sql);//创建PreparedStatement
                    *             this.fillStatement(stmt, params);//对sql 进行 ? 赋值
                    *             rs = this.wrap(stmt.executeQuery());//执行sql,返回resultset
                    *             result = rsh.handle(rs);//返回的resultset --> arrayList[result] [使用到反射,对传入class对象处理]
                    *         } catch (SQLException var33) {
                    *             this.rethrow(var33, sql, params);
                    *         } finally {
                    *             try {
                    *                 this.close(rs);//关闭resultset
                    *             } finally {
                    *                 this.close((Statement)stmt);//关闭preparedstatement对象
                    *             }
                    *         }
                    *
                    *         return result;
                    *     }
                    */
                   List<Actor> list =
                           queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
                   System.out.println("输出集合的信息");
                   for (Actor actor : list) {
                       System.out.print(actor);
                   }
              
              
                   //释放资源
                   JDBCUtilsByDruid.close(null, null, connection);
              
               }
              
          2. 演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)

            1.  @Test
               public void testQuerySingle() throws SQLException {
              
                   //1. 得到 连接 (druid)
                   Connection connection = JDBCUtilsByDruid.getConnection();
                   //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
                   //3. 创建 QueryRunner
                   QueryRunner queryRunner = new QueryRunner();
                   //4. 就可以执行相关的方法,返回单个对象
                   String sql = "select * from actor where id = ?";
                   // 老韩解读
                   // 因为我们返回的单行记录<--->单个对象 , 使用的Hander 是 BeanHandler
                   Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 2);
                   System.out.println(actor);
              
                   // 释放资源
                   JDBCUtilsByDruid.close(null, null, connection);
              
               }
              
          3. 演示apache-dbutils + druid 完成查询结果是单行单列-返回的就是object

            1.  public void testScalar() throws SQLException {
              
                   //1. 得到 连接 (druid)
                   Connection connection = JDBCUtilsByDruid.getConnection();
                   //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
                   //3. 创建 QueryRunner
                   QueryRunner queryRunner = new QueryRunner();
              
                   //4. 就可以执行相关的方法,返回单行单列 , 返回的就是Object
                   String sql = "select name from actor where id = ?";
                   //老师解读: 因为返回的是一个对象, 使用的handler 就是 ScalarHandler
                   Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4);
                   System.out.println(obj);
              
                   // 释放资源
                   JDBCUtilsByDruid.close(null, null, connection);
               }
              
          4. 演示apache-dbutils + druid 完成 dml (update, insert ,delete)

            1.  public void testDML() throws SQLException {
              
                   //1. 得到 连接 (druid)
                   Connection connection = JDBCUtilsByDruid.getConnection();
                   //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
                   //3. 创建 QueryRunner
                   QueryRunner queryRunner = new QueryRunner();
              
                   //4. 这里组织sql 完成 update, insert delete
                   //String sql = "update actor set name = ? where id = ?";
                   //String sql = "insert into actor values(null, ?, ?, ?, ?)";
                   String sql = "delete from actor where id = ?";
              
                   //老韩解读
                   //(1) 执行dml 操作是 queryRunner.update()
                   //(2) 返回的值是受影响的行数 (affected: 受影响)
                   //int affectedRow = queryRunner.update(connection, sql, "林青霞", "女", "1966-10-10", "116");
                   int affectedRow = queryRunner.update(connection, sql, 1000 );
                   System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表");
              
                   // 释放资源
                   JDBCUtilsByDruid.close(null, null, connection);
              
               }
              
      6. BasicDao
        1. 介绍
          1. DAO:data access object数据访问对象
          2. 这样的通用类,称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。
          3. 在BaiscDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如Customer表—Customer.java类(javabean)-CustomerDao.java
        2. 代码示例
          1. com.hspedu.dao_.utils 工具类
          2. com.hspedu.dao .domain javabean
          3. com.hspedu.dao_.dao 存放XxxDAO和BasicDAO
          4. com.hspedu.dao_.tes 写测试类
          5.  BasicDAO.java:
             package com.hspedu.dao_.dao;
            
             import com.hspedu.dao_.utils.JDBCUtilsByDruid;
             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 java.sql.Connection;
             import java.sql.SQLException;
             import java.util.List;
            
             /**
              * 开发BasicDAO , 是其他DAO的父类
              */
             public class BasicDAO<T> { //泛型指定具体类型
            
                 private QueryRunner qr =  new QueryRunner();
            
                 //开发通用的dml方法, 针对任意的表
                 public int update(String sql, Object... parameters) {
            
                     Connection connection = null;
            
                     try {
                         connection = JDBCUtilsByDruid.getConnection();
                         int update = qr.update(connection, sql, parameters);
                         return  update;
                     } catch (SQLException e) {
                        throw  new RuntimeException(e); //将编译异常->运行异常 ,抛出
                     } finally {
                         JDBCUtilsByDruid.close(null, null, connection);
                     }
            
                 }
            
                 //返回多个对象(即查询的结果是多行), 针对任意表
            
                 /**
                  *
                  * @param sql sql 语句,可以有 ?
                  * @param clazz 传入一个类的Class对象 比如 Actor.class
                  * @param parameters 传入 ? 的具体的值,可以是多个
                  * @return 根据Actor.class 返回对应的 ArrayList 集合
                  */
                 public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) {
            
                     Connection connection = null;
                     try {
                         connection = JDBCUtilsByDruid.getConnection();
                         return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
            
                     } catch (SQLException e) {
                         throw  new RuntimeException(e); //将编译异常->运行异常 ,抛出
                     } finally {
                         JDBCUtilsByDruid.close(null, null, connection);
                     }
            
                 }
            
                 //查询单行结果 的通用方法
                 public T querySingle(String sql, Class<T> clazz, Object... parameters) {
            
                     Connection connection = null;
                     try {
                         connection = JDBCUtilsByDruid.getConnection();
                         return  qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);
            
                     } catch (SQLException e) {
                         throw  new RuntimeException(e); //将编译异常->运行异常 ,抛出
                     } finally {
                         JDBCUtilsByDruid.close(null, null, connection);
                     }
                 }
            
                 //查询单行单列的方法,即返回单值的方法
            
                 public Object queryScalar(String sql, Object... parameters) {
            
                     Connection connection = null;
                     try {
                         connection = JDBCUtilsByDruid.getConnection();
                         return  qr.query(connection, sql, new ScalarHandler(), parameters);
            
                     } catch (SQLException e) {
                         throw  new RuntimeException(e); //将编译异常->运行异常 ,抛出
                     } finally {
                         JDBCUtilsByDruid.close(null, null, connection);
                     }
                 }
            
             }
             ======================================================================================================
             Actor.java:
             package com.hspedu.dao_.dao;
            
             import com.hspedu.dao_.domain.Actor;
            
             public class ActorDAO extends BasicDAO<Actor> {
                 //1. 就有 BasicDAO 的方法
                 //2. 根据业务需求,可以编写特有的方法.
             }
             ======================================================================================================
             Actor.java:
             package com.hspedu.dao_.domain;
            
             import java.util.Date;
            
             /**
              * Actor 对象和 actor表的记录对应
              *
              */
             public class Actor { //Javabean, POJO, Domain对象
            
                 private Integer id;
                 private String name;
                 private String sex;
                 private Date borndate;
                 private String phone;
            
                 public Actor() { //一定要给一个无参构造器[反射需要]
                 }
            
                 public Actor(Integer id, String name, String sex, Date borndate, String phone) {
                     this.id = id;
                     this.name = name;
                     this.sex = sex;
                     this.borndate = borndate;
                     this.phone = phone;
                 }
            
                 public Integer getId() {
                     return id;
                 }
            
                 public void setId(Integer id) {
                     this.id = id;
                 }
            
                 public String getName() {
                     return name;
                 }
            
                 public void setName(String name) {
                     this.name = name;
                 }
            
                 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 "\nActor{" +
                             "id=" + id +
                             ", name='" + name + '\'' +
                             ", sex='" + sex + '\'' +
                             ", borndate=" + borndate +
                             ", phone='" + phone + '\'' +
                             '}';
                 }
             }
             ======================================================================================================
             Test.java:
             package com.hspedu.dao_.test;
            
             import com.hspedu.dao_.dao.ActorDAO;
             import com.hspedu.dao_.domain.Actor;
             import org.junit.jupiter.api.Test;
            
             import java.util.List;
            
             public class TestDAO {
            
                 //测试ActorDAO 对actor表crud操作
                 @Test
                 public void testActorDAO() {
            
                     ActorDAO actorDAO = new ActorDAO();
                     //1. 查询
                     List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1);
                     System.out.println("===查询结果===");
                     for (Actor actor : actors) {
                         System.out.println(actor);
                     }
            
                     //2. 查询单行记录
                     Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 7);
                     System.out.println("====查询单行结果====");
                     System.out.println(actor);
            
                     //3. 查询单行单列
                     Object o = actorDAO.queryScalar("select name from actor where id = ?", 7);
                     System.out.println("====查询单行单列值===");
                     System.out.println(o);
            
                     //4. dml操作  insert ,update, delete
                     int update = actorDAO.update("insert into actor values(null, ?, ?, ?, ?)", "张无忌", "男", "2000-11-11", "999");
            
                     System.out.println(update > 0 ? "执行成功" : "执行没有影响表");
            
            
            
            
                 }
             }
            
             输出结果:
             ===查询结果===
            
             Actor{id=2, name='刘德华', sex='男', borndate=1970-11-11 00:00:00.0, phone='110'}
            
             Actor{id=4, name='张三非', sex='女', borndate=1966-10-10 00:00:00.0, phone='116'}
            
             Actor{id=7, name='张无嫉', sex='男', borndate=2000-11-11 00:00:00.0, phone='999'}
            
             Actor{id=8, name='张无忌', sex='男', borndate=2000-11-11 00:00:00.0, phone='999'}
             ====查询单行结果====
            
             Actor{id=7, name='张无嫉', sex='男', borndate=2000-11-11 00:00:00.0, phone='999'}
             ====查询单行单列值===
             张无嫉
             执行成功
            

正则表达式

  1. 介绍

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

    1.  package com.hspedu.regexp;
      
       import java.util.regex.Matcher;
       import java.util.regex.Pattern;
      
       /**
        * 分析java的正则表达式的底层实现(重要.)
        */
       public class RegTheory {
           public static void main(String[] args) {
      
               String content = "1998年12月8日,第二代Java平台的企业版J2EE发布。1999年6月,Sun公司发布了" +
                       "第二代Java平台(简称为Java2)的3个版本:J2ME(Java2 Micro Edition,Java2平台的微型" +
                       "版),应用于移动、无线及有限资源的环境;J2SE(Java 2 Standard Edition,Java 2平台的" +
                       "标准版),应用于桌面环境;J2EE(Java 2Enterprise Edition,Java 2平台的企业版),应" +
                       "用3443于基于Java的应用服务器。Java 2平台的发布,是Java发展过程中最重要的一个" +
                       "里程碑,标志着Java的应用开始普及9889 ";
               //目标:匹配所有四个数字
               //说明
               //1. \\d 表示一个任意的数字
               String regStr = "(\\d\\d)(\\d\\d)";
               //2. 创建模式对象[即正则表达式对象]
               Pattern pattern = Pattern.compile(regStr);
               //3. 创建匹配器
               //说明:创建匹配器matcher, 按照 正则表达式的规则 去匹配 content字符串
               Matcher matcher = pattern.matcher(content);
      
               //4.开始匹配
               /**
                * matcher.find() 完成的任务 (考虑分组)
                * 什么是分组,比如  (\d\d)(\d\d) ,正则表达式中有() 表示分组,第1个()表示第1组,第2个()表示第2组...
                * 1. 根据指定的规则 ,定位满足规则的子字符串(比如(19)(98))
                * 2. 找到后,将 子字符串的开始的索引记录到 matcher对象的属性 int[] groups;
                *    2.1 groups[0] = 0 , 把该子字符串的结束的索引+1的值记录到 groups[1] = 4
                *    2.2 记录1组()匹配到的字符串 groups[2] = 0  groups[3] = 2
                *    2.3 记录2组()匹配到的字符串 groups[4] = 2  groups[5] = 4
                *    2.4.如果有更多的分组.....
                * 3. 同时记录oldLast 的值为 子字符串的结束的 索引+1的值即35, 即下次执行find时,就从35开始匹配
                *
                * matcher.group(0) 分析
                *
                * 源码:
                * public String group(int group) {
                *         if (first < 0)
                *             throw new IllegalStateException("No match found");
                *         if (group < 0 || group > groupCount())
                *             throw new IndexOutOfBoundsException("No group " + group);
                *         if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
                *             return null;
                *         return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
                *     }
                *  1. 根据 groups[0]=31 和 groups[1]=35 的记录的位置,从content开始截取子字符串返回
                *     就是 [31,35) 包含 31 但是不包含索引为 35的位置
                *
                *  如果再次指向 find方法.仍然安上面分析来执行
                */
               while (matcher.find()) {
                   //小结
                   //1. 如果正则表达式有() 即分组
                   //2. 取出匹配的字符串规则如下
                   //3. group(0) 表示匹配到的子字符串
                   //4. group(1) 表示匹配到的子字符串的第一组字串
                   //5. group(2) 表示匹配到的子字符串的第2组字串
                   //6. ... 但是分组的数不能越界.
                   System.out.println("找到: " + matcher.group(0));
                   System.out.println("第1组()匹配到的值=" + matcher.group(1));
                   System.out.println("第2组()匹配到的值=" + matcher.group(2));
               }
           }
       }
       输出结果:
       找到: 19981()匹配到的值=192()匹配到的值=98
       找到: 19991()匹配到的值=192()匹配到的值=99
       找到: 34431()匹配到的值=342()匹配到的值=43
       找到: 98891()匹配到的值=982()匹配到的值=89
      
      1. 思考

        1. 要进行正则匹配,先用正则表达式填写模版Pattern.compile​,在放入匹配器中Pattern.compile​,最后通过find方法输出

        2. 底层源码分析,未分组情况

          1. 按照字符串进行匹配,则首先匹配到的是1998,那么groups[0]=0,group[1]=4.
          2. 接着匹配到第二组,1999,那么groups[0]=32,group[1]=36.
        3. 分组情况

          1. 按照字符串进行匹配,则首先匹配到的是1998,那么groups[0]=0,group[1]=4,groups[2]=0,group[3]=2,groups[4]=2,group[5]=4
          2. 这个规则是根据groups[group*2]或者groups[group*2+1],注意的是groups与group是不同的
  3. 语法

    1. 元字符–字符匹配符
      1. 符号含义示例说明匹配输入
        []可接受的字符列表[efgh]e,f,g,h任意一个字符e,f,g,h
        [^]不接受的字符列表[^efgh]除e,f,g,h以外任意一个字符,
        包括数字和特殊字符
        a,c
        -连字符-任意单个大写字母A,B,C
        .匹配除\n以外的任何字符a…b以a开头,b结尾,中间包括2个
        任意字母长度符合4的字符串
        aaab,aefb
        a35b,a#*b
        \d匹配单个数学字符,相当于[0-9]\\d{3}(\\d)?包括3个或4个数字的字符串123,9387
        \D匹配单个非数学字符,相当于[^0-9]\\D{\\d}*以单个非数字字符开头,后接
        任意个数字字符串
        a,A12323
        \w匹配单个数字,大小写字母字符,相当于
        [0-9a-zA-Z]
        \\d{3}\\w{4}以3个数字字符开头的长度为7的
        数字字符串
        234sfdf,
        345sdfg
        \W匹配单个非数字,大小写字母字符,相当于
        [^0-9a-zA-Z]
        \\W+\\d{2}以至少1个非数字字母字符开头,2
        个数字字符结尾的字符串
        #29,#?@10
        \s匹配任何空白字符(空格,制表符等)
        \S匹配任何非空白字符(空格,制表符等)
    2. 元字符–选择匹配符
      1. 在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以匹配那个,这时你需要用到选择匹配符号
      2. 符号含义示例说明匹配输入
        ****匹配"|"之前或之后的表达式ab|cdab或者cd
    3. 元字符–限定符
      1. 用于指定其前面的字符和组合项连续出现多少次
      2. 符号含义示例说明匹配输入
        *****指定字符重复0次或n次
        0到多
        (abc)*仅包含任意个abc的字符串,
        等效与\w*
        abc,abcweqwe
        +指定字符重复0次或n次
        (至少一次)0到多
        m+(abc)*以至少1个m开头,后接任意个
        abc的字符串
        m,mabc,
        mabcabc
        ?指定字符重复0次或n次
        (最多一次)0到1
        m+abc?以至少1个m开头,后接ab或者
        abc的字符串
        mab,mabc,
        mmabc,mmmab
        {n}只能输入n个字符[abcd]{3}由abcd中字母组成的任意长度
        为3的字符串
        abc,dbc
        {n,}指定至少m个匹配[abcd]{3,}由abcd中字母组成的任意长度
        至少不小于3个的字符串
        aab,dbc
        aaabcd
        {n,m}指定至少n个但不多于
        m个匹配
        [abcd]{3,5}由abcd中字母组成的任意长度不小于
        3不大于5的字符串
        abc,abcd
        aaaa,bcdab
    4. 元字符–定位符
      1. 定位符, 规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置,这个也是相当有用的,必须掌握
      2. 符号含义示例说明匹配输入
        ^指定起始字符1+[a-z]*以至少1个数字开头,后接
        任意个小写字母的字符串
        123,6aa
        555edf
        $指定结束字符2\\-[a-z]+$以1个数字开头后接连字符"-",并
        以至少1个小写字母结尾的字符串
        1-a
        \\b匹配目标字符串的边界han\\b这里说的字符串的边界指的是字符串
        间有空格,或者是目标字符串的结束位置
        handhauidh sphan nnhan
        \\B匹配目标字符串的非边界han\\B和\b的含义刚刚相反hanfsdsfs sphan mmhan
    5. 分组
      1. 常用分组构造形式说明
        (pattern)非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。
        (?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”.
  4. 三个常用类

    1. Pattern类
      1. pattern对象是一个正则表达式对象。Pattern类没有公共构造方法。要创建一个Pattern对象,调用其公共静态方法,它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数
    2. Matcher类
      1. Matcher 对象是对输入字符串进行解释和匹配的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher对象
    3. PatternSyntaxException类
      1. PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
    4. 代码示例
      1. Pattern
        1.  package com.hspedu.regexp;
          
           import java.util.regex.Pattern;
          
           /**
            * 演示matches方法,用于整体匹配, 在验证输入的字符串是否满足条件使用
            */
           public class PatternMethod {
          
               public static void main(String[] args) {
                   String content = "hello abc hello, 韩顺平教育";
                   //String regStr = "hello";
                   String regStr = "hello.*";
          
                   boolean matches = Pattern.matches(regStr, content);
                   System.out.println("整体匹配= " + matches);
               }
           }
           输出结果:
           整体匹配= true
          
      2. Matcher

        1.  package com.hspedu.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 hspedutom hello smith hello hspedu hspedu";
                   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 有 hspedu 替换成 韩顺平教育
                   regStr = "hspedu";
                   pattern = Pattern.compile(regStr);
                   matcher = pattern.matcher(content);
                   //注意:返回的字符串才是替换后的字符串 原来的 content 不变化
                   String newContent = matcher.replaceAll("韩顺平教育");
                   System.out.println("newContent=" + newContent);
                   System.out.println("content=" + content);
          
               }
           }
           输出结果:
           =================
           0
           5
           找到: hello
           =================
           25
           30
           找到: hello
           =================
           37
           42
           找到: hello
           整体匹配=false
           newContent=hello edu jack 韩顺平教育tom hello smith hello 韩顺平教育 韩顺平教育
           content=hello edu jack hspedutom hello smith hello hspedu hspedu
          
    5. 分组、捕获、反向引用
      1. 相关介绍
        1. 分组

          1. 我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式/一个分组。
        2. 捕获

          1. 把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式
        3. 反向引用

          1. 圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\分组号,外部反向引用$分组号
        4. 代码示例
          1.  package com.hspedu.regexp;
            
             import java.util.regex.Matcher;
             import java.util.regex.Pattern;
            
             public class RegExp13 {
                 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+");//分组的捕获内容记录到$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);
            
                 }
             }
             输出结果:
             content=我要学编程java!
            
    6. 常用的正则表达式匹配
      1.  一、校验数字的表达式
         1 数字:^[0-9]*$
         2 n位的数字:^\d{n}$
         3 至少n位的数字:^\d{n,}$
         4 m-n位的数字:^\d{m,n}$
         5 零和非零开头的数字:^(0|[1-9][0-9]*)$
         6 非零开头的最多带两位小数的数字:^([1-9][0-9]*)(.[0-9]{1,2})?$
         7 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
         8 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
         9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
         10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
         11 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
         12 非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
         13 非负整数:^\d+$ 或 ^[1-9]\d*|0$
         14 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
         15 非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
         16 非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
         17 正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
         18 负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
         19 浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$
         二、校验字符的表达式
         1 汉字:^[\u4e00-\u9fa5]{0,}$
         2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
         3 长度为3-20的所有字符:^.{3,20}$
         4 由26个英文字母组成的字符串:^[A-Za-z]+$
         5 由26个大写英文字母组成的字符串:^[A-Z]+$
         6 由26个小写英文字母组成的字符串:^[a-z]+$
         7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
         8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
         9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
         10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
         11 可以输入含有^%&’,;=?KaTeX parse error: Can't use function '\"' in math mode at position 1: \̲"̲等字符:`[^%&',;=?\x22]+12 禁止输入含有~的字符:[^~\x22]+三、特殊需求表达式 1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)KaTeX parse error: Undefined control sequence: \s at position 108: …`[a-zA-z]+://[^\̲s̲]*` 或 `^http://…4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
         5 电话号码(“XXX-XXXXXXX”、“XXXX-XXXXXXXX”、“XXX-XXXXXXX”、“XXX-XXXXXXXX”、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
         6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
         7 身份证号(15位、18位数字):^\d{15}|\d{18}$
         8 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
         9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
         10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
         11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
         12 日期格式:^\d{4}-\d{1,2}-\d{1,2}
         13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
         14 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
         15 钱的输入格式:
         16 1.有四种钱的表示形式我们可以接受:“10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]*$
         17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
         18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
         19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
         20 5.必须说明的是,小数点后面至少应该有1位数,所以"10.“是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$
         21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
         22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
         23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
         24 备注:这就是最终结果了,别忘了”+“可以用”"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
         25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
         26 中文字符的正则表达式:[\u4e00-\u9fa5]
         27 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
         28 空白行的正则表达式:\n\s*\r (可以用来删除空白行)
         29 HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
         30 首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
         31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
         32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
         33 IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
        


  1. 0-9 ↩︎

  2. 0-9 ↩︎


http://www.kler.cn/a/382995.html

相关文章:

  • MySQL 数据”丢失”事件之 binlog 解析应用
  • 【LuaFramework】服务器模块相关知识
  • FastAPI 与 SQLModel 分页功能实现指南
  • 使用 OpenCV 绘制线条和矩形
  • 《计算机组成及汇编语言原理》阅读笔记:p86-p115
  • 微服务篇-深入了解 XXL-JOB 分布式任务调度的具体使用(XXL-JOB 的工作流程、框架搭建)
  • 在docker中搭建redis哨兵环境
  • 编译器优化乌龙——记一次死循环不进入问题
  • Elasticsearch Interval 查询:为什么它们是真正的位置查询,以及如何从 Span 转换
  • aitrader产品规划:数据自动下载,策略,因子挖掘,实盘对接,gui界面(源码+数据)
  • 重构代码之移动方法
  • 数据挖掘(六)
  • CentOS 9 Stream 上安装 Nginx
  • 【c知道】Hadoop工作原理。
  • 前后端交互接口(三)
  • 设置服务器ssh连接超时时间
  • 51单片机学习心得2(基于STC89C52):串口通信(UART)
  • 动态库实现lua网络请求GET, POST, 下载文件
  • 龙蜥副理事长张东:加速推进 AI+OS 深度融合,打造最 AI 的服务器操作系统
  • Java 中的 `wait()` 与 `sleep()`:深入解析两者的不同
  • SQLite数据库是什么?DB Browser for SQLite是什么?
  • Python 在PDF中绘制形状(线条、矩形、椭圆形等)
  • keep-alive - 2024最新版前端秋招面试短期突击面试题【100道】
  • A02、JVM性能监测调优
  • Docker学习—Docker核心概念总结
  • 机器学习—矩阵乘法