• 第02篇_Java进阶

    第01章_数据库编程(JDBC)

    第一节 JDBC的基本使用

    1. JDBC简介

    JDBC(Java DataBase Connectivity)是官方定义的一套操作关系数据库的接口,各数据库厂商实现了这套接口,提供相应的驱动包

    注意:

    1. Oracle和SqlServer等数据库由于版权原因,无法从Maven中央仓库下载驱动包,需手动安装。

      • 常见的Oracle驱动包版本有:ojdbc14-10.2.0.5.0.jar、ojdbc6-11.2.0.4.jar、ojdbc8-12.2.0.1.jar、ojdbc8-21.1.0.0.jar。

      • 常见的SqlServer驱动包版本有:sqljdbc4-4.0.jar、mssql-jdbc-9.2.1.jre8.jar。

     

    2. 基本增删改查

    1) 直接执行SQL

    SQL脚本如下:

     

    2) 预编译执行SQL

     

    3) 调用存储过程

    SQL脚本如下:

     

    4) 调用存储函数

    SQL脚本如下:

     

    5) JDBC批处理

     

    3. JDBCUtils

     

     

    第二节 相关接口详解

    1. DriverManager

    DriverManager为JDBC客户端管理一组可用的驱动(Driver)实现,主要功能有两个:

     

    2. DataSource

    DataSource是另一种获取连接的方式,主要如下三类:

    注意:

    1. DataSource相关接口并未定义close()方法,但具体连接池一般会扩展类似接口。

     

    3. Connection

    Connection表示通过JDBC驱动与数据源建立的连接,数据源可以是关系型数据库、文件系统或者其他通过JDBC驱动访问的数据。

     

    4. Statement

    Statement接口中定义了直接执行SQL语句的方法,其子类PreparedStatement扩展了设置SQL参数的方法,其孙类CallableStatement 又扩展了调用存储过程/函数的相关方法。

     

    5. ResultSet

    ResultSet提供了检索和操作SQL执行结果相关的方法,有3种不同的类型:

     

    6. DatabaseMetaData

    DatabaseMetaData接口用于获取数据源的相关元信息。

     

    7. ResultSetMetaData

    ResultSetMetaData用于获取结果集的相关元信息。

     

     

    第三节 数据库连接池

    数据库连接池是一个存储数据库连接的资源池,使用它可以更高效的使用数据库连接。

     

    1. C3P0连接池

    C3P0是一个开源的数据库连接池,使用方式如下:

     

    2. Druid连接池

    Druid是阿里巴巴开源的一个数据库连接池实现,不仅效率高,而且可以很好的监控DB池连接和SQL的执行情况,使用方式如下:

    下面是一个基于Duird连接池的JDBC工具类:

     

     

    第02章_网络编程(TCP&UDP)

    第一节 网络基础知识

    1. 网络通信三要素

    网络程序之间通信必须明确三要素:通信协议IP地址端口号

    上述“通信协议+IP地址+端口号”的组合,唯一标识了网络中的某个进程以及与其的通信方式,基于此就可以实现进程间网络通信了。

     

    2. 网络分层

    网络根据不同的规范可分为OSI七层模型TCP/IP四层模型

    在这里插入图片描述

     

    3. TCP协议

    TCP全名为传输控制协议 (Transmission Control Protocol),是一种面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。

    TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过三次握手,以保证连接的可靠:

     

    4. UDP协议

    UDP全称为用户数据报协议(User Datagram Protocol)。它是面向无连接的通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

    由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。

     

    5. HTTP协议

    HTTP全名为超文本传输协议(HyperText Transfer Protocol),是一种用于分布式、协作式和超媒体信息系统的应用层协议。

     

     

    第二节 简单的TCP程序

    1. Socket简介

    Socket简称套接字,是进程间通信的一种方式,在TCP程序中分为客户端Socket服务端Socket

    注意:

    1. 客户端Socket返回的输入流、输出流与Socket所关联的通道为同一个,关闭任何一个,其它对象所关联的通道也将关闭。

     

    2. 一个简单的TCP程序示例

     

    3. 文件上传案例(C/S)

     

    4. web服务器案例(B/S)

    简单的Web服务器程序,在浏览器输入http://127.0.0.1:8888/abc.html就可以访问服务器中WEB_ROOT目录下的abc.html及关联文件了。

     

     

    第三节 简单的UDP程序

    1. DatagramSocket简介

    在UDP通信中,分为数据发送者接收者,两者都是通过DatagramSocket来发送和接收数据,使用DatagramPacket进行数据打包。

     

    2. 简单UDP发送/接收程序

     

     

    第03章_Web编程(JavaWeb)

    第一节 快速入门

    1. JavaWeb简介

    Java Web是指使用Java语言进行Web开发的技术体系。

     

    2. 入门案例

    2.1 初始工程建立

    新建Maven工程(File->New->Project->Maven->Next->Finish),并补全和标识必要目录。

    image-20220929111128459

     

    2.2 引入相关依赖

     

    2.3 创建src/main/webapp/WEB-INF/web.xml文件

     

    2.4 创建src\main\webapp\index.jsp文件

     

    2.5 编写HelloServlet

     

    2.6 配置Tomcat Server

    新建Tomcat Server(工具栏 -> Add Configurations -> + -> Tomcat Server -> Local),进行如下一些配置,然后启动进行测试。

    image-20220929111619364

    image-20220929111645807

     

     

     

    第二节 Servlet

    1. 什么是Servlet?

    Servlet(server applet)指运行在服务器端的小程序,用来处理服务器收到的请求。

     

    2. 基本使用

    入门案例已介绍了如何使用XML配置Servlet,下面案例通过注解配置Servlet。

     

    3. HttpServlet

    HttpServlet继承自Servlet,并根据HTTP请求方式将service()方法进行了分发

     

     

    第二节 ServletRequest

    1. 什么是ServletRequest?

    ServletRequest/HttpServletRequest是一个描述Servlet请求的接口,由应用服务器(Tomcat)创建,封装了请求相关信息。

     

    2. 获取请求信息

    请求信息乱码问题:

    • get方式:如果使用tomcat 8+版本,则不会出现乱码。

    • post方式:在获取参数前,设置request的编码:request.request.setCharacterEncoding("utf-8");

     

    3. 请求转发

    请求转发是一种在服务器内部的资源跳转方式,主要特点如下:

     

    4. 请求域数据共享

    在请求转发过程中,可以使用request对象来进行数据共享。Requst域,代表一次请求的范围,一般用于请求转发的多个资源中共享数据。

     

    5. 使用案例

     

     

    第三节 ServletResponse

    1. 什么是ServletResponse?

    ServletResponse/HttpServletResponse是一个描述Servlet响应的接口,由应用服务器(Tomcat)创建,封装了响应相关信息。

     

    2. 设置响应信息

    响应信息乱码问题:

    响应时默认流编码为ISO-8859-1,我们需要在获取流之前设置该流的默认编码,即建议浏览器解析响应体使用的编码。

     

    3. 响应重定向

    重定向[redirect]是一种跨服务器的资源跳转方式,主要特点如下:

     

    4. 使用案例

     

     

    第四节 ServletContext

    1. 什么是ServletContext?

    ServletContext是一个描述Servlet应用上下文的接口,由应用服务器(Tomcat)创建,封装了应用上下文相关信息。

     

    2. 常用方法简介

     

    3. 使用案例

     

     

    1. 什么是Cookie?

    Cookie是一种客户端会话技术,用来将少量、不太敏感的数据保存到客户端,得以完成一些特殊功能。如在不登录的情况下,完成服务器对客户端的身份识别。

     

    2. 基本使用

     

    3. 实现原理

    Cookie是基于响应头和请求头实现的。服务器通过响应头set-cookie设置客户端Cookie后,浏览器在后续发送请求时,都会在请求头cookie中携带所有的Cookie键值对。

    image-20220930174721173

     

    4. 使用案例

     

    第六节 Session

    1. 什么是Session?

    Session/HttpSession是一种服务器端会话技术,可以将关键数据保存在服务器中,实现一次会话的多次请求间共享数据。

     

    会话的概念:会话指一个终端用户与交互系统进行通讯的过程。当客户端第一次给服务器发送资源请求时,就新建立了一个会话,直到有一方断开,则会话结束。

     

    2. Session和Cookie的区别

     

    3. 基本使用

     

    4. 实现原理

    Session是基于Cookie实现的,在第一次请求时,服务器会设置一个名为JSESSIONID的Cookie,在后续请求过程中,如果客户端携带了该Cookie,则表示处于会话中。

    image-20220930175928895

    Session会在下面几种情况下失效:

     

    5. 使用案例

     

     

    第七节 Filter

    1. 什么是Filter?

    Filter表示过滤器,当访问服务器的资源时,将请求拦截下来,完成一些特殊的功能,如登录验证、统一编码处理、敏感字符过滤等。

     

    2. 基本使用

     

    如果不使用注解配置,也可以在web.xml中配置如下:

     

    3. 过滤器链

    某个请求可以被多个过滤器进行拦截,执行顺序为:过滤器1->过滤器2->资源执行->过滤器2->过滤器1。

     

    4. 使用案例

     

     

     

    第八节 Listener

    1. 什么是Listener?

    Listener是JavaWeb三大组件之一。常用的Listener有ServletContextListener等,其用来监听ServletContext对象的创建和销毁!

     

    2. 基本使用

     

     

    第九节 JSP

    1. 什么是JSP?

    JSP(Java Server Pages,Java服务器端页面)可以理解为一个既可以写HTML标签,又可以写Java代码的特殊页面,其本质是一个Servlet。

    image-20220930190439209

     

    2. 指令标签

    JSP指令标签用于配置JSP页面,导入资源文件等。

    上文中的500.jsp如下,isErrorPage为true表示该页面是一个错误页面,并且可以在错误页面使用exception对象

     

    3. 脚本标签

    脚本标签用于定义Java代码,在jsp转换为Servlet后,分别放在不同的位置。

     

    4. 内置对象

    在jsp页面中不需要获取和创建,可以直接使用的对象,jsp一共有9个内置对象:

    变量名真实类型作用
    pageContextPageContext当前页面共享数据,还可以获取其他八个内置对象
    requestHttpServletRequest一次请求访问的多个资源间共享数据(转发)
    sessionHttpSession一次会话的多个请求间共享数据
    applicationServletContext所有用户间共享数据
    responseHttpServletResponse响应对象
    pageObject当前页面(Servlet)的对象 this
    outJspWriter字符输出流对象,将数据输出到页面上
    configServletConfigServlet的配置对象
    exceptionThrowable异常对象

     

    5. 使用案例

     

     

    第十节 EL表达式

    1. 什么是EL表达式?

    EL(Expression Language)指表达式语言,用于替换和简化JSP页面中Java代码的编写,格式为:${表达式}

    2. 内置对象

    pageContext对应于JSP页面中的pageContext对象,用于获取jsp其他八个内置对象
    pageScope代表page域中用于保存属性的Map对象
    requestScope代表request域中用于保存属性的Map对象
    sessionScope代表session域中用于保存属性的Map对象
    applicationScope代表application域中用于保存属性的Map对象
    param表示一个保存了所有请求参数的Map对象
    paramValues表示一个保存了所有请求参数的Map对象,它对于某些请求参数,返回的是一个string[]
    header表示一个保存了所有http请求头字段的Map对象
    headerValues同上,返回string[]数组。注意:如果头里面有"-" ,例Accept-Encoding,则要headerValues["Accept-Encoding"]
    cookie表示一个保存了所有cookie的Map对象
    initParam表示一个保存了所有web应用初始化参数的map对象

     

    3. 域对象取值

     

    4.运算符

    算数运算符+、 -、 *、 /(div)、 %(mod)
    比较运算符>、<、>=、<=、==、!=
    逻辑运算符&&(and)、||(or)、!(not)
    空运算符empty,用于判断字符串、集合、数组对象是否为null或者长度是否为0

     

    5. 忽略EL表达式

    JSP是默认支持EL表达式的,如果要忽略EL表达式,有两种方案:

     

     

    第十一节 JSTL

    1. 什么是JSTL?

    JSTL(JavaServer Pages Tag Library,JSP标准标签库)是由Apache组织提供的开源的免费的jsp标签,用于简化和替换jsp页面上的java代码。

     

    2. 基本使用

    JSTL的使用步骤如下:

     

    3. 常用标签

     

    4. 使用案例

     

     

    第十二节 补充: HTTP协议

    1. 什么是HTTP协议?

    HTTP(Hyper Text Transfer Protocol)指超文本传输协议,定义了客户端和服务器端通信时,发送数据的格式,它是对TCP/IP协议的一层封装,默认端口为80。

    2. HTTP协议的特点

    3. 请求消息格式

    HTTP请求消息是客户端发送给服务端的数据,先来看一个简单的HTTP请求示例:

    可以观察到,HTTP请求消息可分为下面四部分:

    4. 响应消息格式

    HTTP响应消息指服务器端发送给客户端的数据,先来看一个简单的示例:

    可以观察到,HTTP应答消息也分为四部分:

     

     

    第04章_Java虚拟机

    第一节 Java运行参数

    1. 标准参数

    注意:

    1. 标准参数在未来的JVM版本基本不会改变,而以-X-XX开头的扩展参数则可能修改。

     

    2. 堆内存相关参数

     

    3. GC相关参数

     

    4. 其它参数

     

     

    第二节 JDK常用工具

    1. 查看进程信息(jps -l)

     

    2. 查看运行参数(jinfo <pid>)

     

    3. 查看运行状态( jstat)

    jstat命令可以查看堆内存的各种统计信息,使用格式如下:

     

    1) 查看类加载统计

    使用jstat -class <进程ID>可以查看类加载统计。

    如上,表示加载了18454个类,共占用32302.9字节空间,共耗时17.09秒,剩余4个类未进行加载,总大小为3.6字节。

     

    2) 查看编译统计

    使用jstat -compiler <进程ID>可以查看编译统计。

    如上,表示编译了13399个类,失败0个,无效类0个,共耗时4.45秒。

     

    3) 查看垃圾回收统计

    使用jstat ‐gc <进程ID>可以查看垃圾回收统计,也可以使用jstat ‐gc <进程ID> <间隔n毫秒> <查询次数>连续多次查询。

     

    4. 查看内存使用情况(jmap)

    1) 查看内存使用情况

    使用jmap -heap <进程ID>可以查看堆内存的使用情况。

     

    2) 查看内存中对象数量及大小

    使用jmap -histo <进程ID> | more查看内存中的所有对象数量和大小。

     

    3) 将内存使用情况dump到文件中

    使用jmap -dump:format=b,file=<文件名> <进程ID>将内存的使用情况 dump 到文件中,然后使用其它工具进行分析。

     

    4) 使用jhat命令分析内存dump文件

    内存dump文件是二进制文件,可以借助jhat命令启动内置web服务器进行浏览,命令格式如下:jhat -port <端口> <dump文件名>

    web服务启动后,就可以通过浏览器进行查看了( http://127.0.0.1:1234/ )。

    image-20211228201626860

    在最下方有OQL查询窗口,可以查询需要的信息。如查询长度>1000的字符串。

    image-20211228202401237

     

     

    5) 使用mat工具分析内存dump文件

    MAT(Memory Analyzer Tool)是一个基于Eclipse的JAVA堆内存分析工具,下载地址:https://www.eclipse.org/mat/downloads.php 。打开后通过点击File->Open Heap Dump...导入内存dump文件,一般选择Leak Suspects Report选项进行打开,得到分析结果如下。

    image-20211228202457552

    可以点击相关链接查看关注的信息。

     

     

    5. 查看线程状态

    1) 线程的状态图

    在 Java中线程的状态一共被分成6种,下面是线程状态之间的流转图。

    image-20211228203816846

     

    2) 线程运行信息(jstack)

    jstack可以将正在运行的jvm的线程情况进行快照,并且打印出来,格式为:jstack <进程ID>

    如下为一个两个互相死锁的线程信息:

    Thread‐1持有0x00000000f655dc50锁,等待0x00000000f655dc40锁,而Thread‐0持有0x00000000f655dc40锁,等待0x00000000f655dc50锁。

     

     

    6. JDK可视化分析工具

    1) JConsole

    JConsole 是基于 JMX 的可视化监视和管理工具,适合快速查看 JVM 的基本运行状态,例如内存使用、线程状态等。

    image-20250401200522453

     

    2) VisualVM

    VisualVM 基于 NetBeans 平台开发,适合需要进行深入性能分析、内存泄漏排查、线程问题诊断等复杂场景(远程配置方式同上)。

    image-20211228204537155

    扩展:

    1. 如需更进一步的分析,可选择 MATJProfiler 等专业工具。

     

    7. 常见JVM故障

     

    参考:

     

     

    第三节 垃圾回收机制

    1. 运行时内存区域

    在执行 Java 程序的过程中,Java 虚拟机会把它管理的内存划分成若干个不同的数据区域。

    image-20250324214850113

    1) 堆内存

    堆内存是虚拟机管理的最大的一块内存,被所有线程共享,在虚拟机启动时创建,用于存放对象实例

    从垃圾回收的角度,又可细分为新生代(Eden、S0、S1)老年代(Tenured),目的是更好地回收内存,或者更快地分配内存。

    image-20250325202303668

    扩展:

    1. 堆内存最常见的错误是OutOfMemoryError 错误,如:

      • 在执行垃圾回收很难回收到堆内存空间时:java.lang.OutOfMemoryError: GC Overhead Limit Exceeded。

      • 在创建新对象但堆内存空间不足时:java.lang.OutOfMemoryError: Java heap space。

    2. 为什么要分为新生代和老年代?

      • 主要从GC层面考虑的,新生代适合复制算法,只需复制较少的存活对象。

      • 老年代的对象存活几率是比较高的,适合标记清除法或标记压缩算法。

     

    2) 方法区

    方法区存储已加载的类信息字段信息方法信息常量静态变量即时编译器编译后的代码缓存等数据。

    在JDK1.7中,由JVM管理的PermGen(永久代) 作为方法区,在JDK1.8及之后,被迁移至本地内存中的 Metaspace(元空间)

    注意:

    1. 当元空间溢出时会得到如下错误:java.lang.OutOfMemoryError: MetaSpace

     

    3) 常量池

    注意:

    1. 在JDK1.6中,字符串常量池存在永久代中,但是由于GC麻烦,在JDK1.7时,就迁移到了堆内存中。

     

    4) 程序计数器

    程序计数器是一块较小的内存空间,记录当前线程所执行的字节码指令的地址(执行native方法时为空),从而实现代码的流程控制。

    扩展:

    1. 程序计数器随线程的创建而创建,随线程的结束而销毁,唯一一个不会出现 OutOfMemoryError 的内存区域。

     

    5) 虚拟机栈

    虚拟机栈用于执行Java方法,方法的调用和结束(return或抛异常)分别对应入栈出栈

    由一个个栈帧组成,遵循先进后出原则,每个栈帧包括:局部变量表、操作数栈、动态链接、方法返回地址等。

    扩展:

    1. 虚拟机栈也随线程的创建而创建,随线程的结束而销毁,可能出现StackOverFlowErrorOutOfMemoryError错误。

     

    6) 本地方法栈

    虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务,两者基本相似。

     

    7) 直接内存

    直接内存是一种特殊的内存缓冲区,通过ByteBuffer.allocateDirect()本地内存分配,主要用于高性能的I/O操作。

    注意:

    1. 堆外内存是一个更广泛的概念,包括直接内存以及其它非堆内存,可通过sun.misc.Unsafe类或其它第三方类库分配,长常用于存储大型缓存数据、日志数据等,以减轻堆内存GC压力

     

     

    2. 垃圾回收算法

    1) 引用计数法

    引用计数法是最早期的垃圾回收算法,当对象被引用时计数器+1,释放引用时计数器-1,当计数器为0时,则进行回收。

    注意:

    1. 引用计数法的循环引用的问题可通过 Recycler 算法解决!

     

    2) 标记清除法

    标记清除法使用可达性分析算法,从根节点标记那些不再被任何引用链所指向的对象,然后对未标记的对象进行清除

    image-20250326205117485

    扩展:

    1. 根节点对象包括:栈中引用的对象、方法区中静态属性和常量引用的对象、被同步锁持有的对象、JNI 引用的对象等。

     

    3) 标记复制算法

    标记复制算法将内存分为两个区域,通常是大小相等的两个半区(From区和To区)。每次只使用其中一个区域来分配对象,当这个区域用完时,将其中还存活的对象复制到另一个区域,然后清空当前区域,再将To区和From区的角色互换。

    image-20250325215901032

    注意:

    1. 触发GC后,Eden区中所有存活的对象都会被复制到"To"区,而"From"区中存活的对象会根据年龄复制到"To"区或“老年代”。

     

    4) 标记压缩算法

    标记压缩法是标记清除法的改进,在清除阶段,将存活的对象压缩到内存的一端,然后直接清理掉端边界以外的内存空间。

    image-20211229114116703

     

    5) 分代垃圾回收

    分代垃圾回收指根据内存区域的特点,选用不同的垃圾回收算法,如新生代存活对象少,适合复制算法,老年代垃圾回收频率较低,则适用于标记清除算法或标记压缩算法。

     

     

    3. 垃圾收集器

    1) 垃圾收集日志

    为了方便查看GC的执行过程,可以通过下面一些参数打开GC日志。

    如果选择文件形式输出,则还可以上传到GC Easy网站,生成GC报告。

    image-20211229202551052

     

    2) 串行垃圾收集器

    串行收集器(Serial GC)使用单线程进行垃圾回收,适用于单核处理器的机器和小内存应用。在垃圾回收过程中,会暂停用户线程STW,Stop-The-World),直到垃圾回收完成。

    image-20250326215159793

    注意:

    1. 对于交互性较强的应用而言,这种垃圾收集器是不能够接受的,一般在JavaWeb应用中是不会采用该收集器的。

     

    3) 并行垃圾收集器

    并行收集器(Parallel GC) 也称为吞吐量优先收集器,虽然同样会暂停用户线程,但使用多个线程进行垃圾回收,可以缩短垃圾回收时间,提高吞吐量,适合对吞吐量要求较高的场景,例如服务器端应用

    image-20250326215351106

    image-20250326215605333

    注意:

    1. JDK1.8 默认使用的是 Parallel Scavenge + Parallel Old

    2. 如果指定了-XX:+UseParallelGC 参数,则默认指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 来禁用该功能。

     

    4) 并发垃圾收集器

    并发收集器(Concurrent GC)的垃圾回收线程可以与用户线程并发执行减少了垃圾回收过程中的停顿时间,适合响应速度要求较高的场景,例如交互式应用

    image-20211229200154738

    1. 初始化标记 (CMS-initial-mark) ,标记根对象,会导致STW

    2. 并发标记 (CMS-concurrent-mark),与用户线程同时运行,标记可达对象

    3. 预清理( CMS-concurrent-preclean),与用户线程同时运行;

    4. 重新标记 (CMS-remark) ,修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,会导致STW

    5. 并发清除 (CMS-concurrent-sweep),与用户线程同时运行,对未标记的区域做清扫;

    6. 调整堆大小,设置 CMS在清理之后进行内存压缩,目的是清理内存中的碎片;

    7. 并发重置状态等待下次 CMS的触发(CMS-concurrent-reset),与用户线程同时运行;

     

    5) G1收集器

    G1 (Garbage-First) 是一款面向服务器分区垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。

    扩展:

    1. G1收集器在 JDK1.7 中正式使用,在 JDK9 中修改为默认垃圾收集器,以替代 CMS 收集器。

    2. G1 收集器根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)。

    3. 避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小,固定年轻代的大小会覆盖暂停时间目标。

    G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。

    image-20211229203524071

    开发人员只需要简单的三步即可完成G1垃圾收集器的调优

    1. 第一步,开启G1垃圾收集器

    2. 第二步,设置堆的最大内存

    3. 第三步,设置最大的停顿时间

     

    6) ZGC 垃圾收集器

    Z Garbage Collector是一种低延迟的垃圾收集器,支持更大的堆内存(可达数TB)。它通过使用染色指针等技术,在垃圾回收过程中几乎不会暂停用户线程,从而实现极低的停顿时间,适合对延迟要求极高的场景,例如金融交易系统等。

     

     

    4. 面试扩展

    1) GC类型有哪些?

    HotSpot VM 的GC分类主要有如下两种:

     

    2) 对象可回收时一定会被回收吗?

    不一定。垃圾回收器会根据内存压力垃圾回收策略对象的引用类型等因素来决定是否回收对象。

     

    3) 对象的引用类型有哪些?
    引用类型特点使用场景
    强引用对象不会被垃圾回收器回收,除非显式设置为null或超出作用域普通对象引用
    软引用对象在内存不足时可能会被回收,但在内存足够时仍然可以保留内存敏感的高速缓存
    弱引用对象在下一次垃圾回收时一定会被回收类似缓存的功能,但比软引用更激进
    虚引用对象和没有引用一样,必须和引用队列(ReferenceQueue)联合使用跟踪对象被垃圾回收的活动

     

     

    第05章_JDK新特性

    第一节 JDK11新特性

    1. 模块化

    引入了模块化系统(Java Platform Module System,JPMS),通过 module 关键字来定义模块,并提供了更好的封装和可重用性。

     

    2. JShell

    引入了交互式的 Java 编程工具JShell,可以在命令行上直接编写和执行Java 代码片段,用于快速开发和学习。

     

    3. 接口私有实例方法

    接口中可以定义私有实例方法,用private关键字修饰,用于在接口内部共享代码

     

    4. 改进try-with-resources

    try-with-resources 语句可以使用资源的最终变量(final或等效于final的变量),不需要显式声明为final。

     

    5. 改进<>操作符

    支持在匿名内部类的实例化中使用钻石操作符<>,可以省略泛型类型的重复声明。

     

    6. 增强String类

    String类新增了一些方法:

     

    7. 增强List/Set/Map接口

    List、Set和Map接口中新增了一些静态工厂方法,用于创建不可变集合的实例。

     

    8. 推断局部变量类型

    引入了var关键字,支持在局部变量声明时进行类型推断

     

    9. 增强Stream API

    Stream API引入了一些新的方法,如takeWhile()dropWhile(),用于在满足特定条件之前或之后对流进行截断。

     

    10. 增强Optional类

    Optional类引入了一些新的方法,如stream()ifPresentOrElse(),提供更多的操作和处理 Optional 对象的方式。

     

    11. 增强CompletableFuture类

    CompletableFuture类增加了一些新的方法,如orTimeout()和completeOnTimeout(),用于处理异步任务的超时情况。

     

    12. 引入HTTP/2客户端

    引入了新的HTTP/2标准的客户端API,以提供更高效和性能更好的HTTP通信。

     

    13. 增强启动器

    允许直接运行单个源代码文件,而不需要将其编译为类文件。例如:java HelloWorld.java

     

    14. ZGC垃圾收集器

    一个实验性的垃圾收集器,目标是实现低延迟高吞吐量

     

     

    第二节 JDK17新特性

    1. 增强switch语句

    支持匹配多个值范围值不同类型的值,以及用作表达式等。

    注意:

    1. 特别的,在JDK17中,如果case后面只有一条语句,可以省略break。

     

    2. 增强String类

    String类新增一些方法:

     

    3. 文本块

    使用三个双引号 """ 括起来的文本块可以更自然的定义多行字符串

     

    4. 密封类

    密封类提供了一种新的方式来控制类的继承,通过使用sealed关键字,我们可以定义一个类只能有哪些特定的子类,这有助于我们创建更加清晰和可维护的代码

     

    5. 改进instanceof

    在检查对象类型的同时进行类型转换,并且可以直接在条件表达式的结果中使用这个转换后的对象。

     

    6. 记录类

    记录类是一种特殊的不可变类,一般用作数据传输对象,也被称作“数据类”。

     

     

    第三节 JDK21新特性

    1. 虚拟线程

    1) 虚拟线程简介

    虚拟线程(Virtual Thread)是由 JDK 实现的轻量级线程(Lightweight Process,LWP),和平台线程(操作系统线程)是多对一的关系。

    虚拟线程、平台线程和系统内核线程的关系

    优点:使用较少的资源,大大的提高了并发量,且简化了异步编程。

    缺点:不适用计算密集型任务;兼容性稍差(不兼容一些依赖平台线程特性的第三方库。

     

    2) 创建虚拟线程

     

    2. 分代ZGC

    扩展了原有的Z Garbage Collector(ZGC),为年轻对象和老对象维护单独的代,从而提高应用程序的性能。

     

    3. 序列集合

    引入了一套新的接口来标识各类集合是否有序

     

    4. 记录模式

    支持在 switch 表达式和 if 语句中使用简化的语法来匹配和解构记录类型

     

     

    第06章_其它补充

    第一节 代码组织机制

    1. 包

    1) 包的概念

    在Java中,使用来组织多个源文件,以及避免命名冲突等。包类似于文件夹的概念,包名以.号分隔表示层次结构,如常用的String类,位于java.lang包下。

    注意:

    1. Java API中所有的类和接口都位于包javajavax下,java是标准包,javax是扩展包。

     

    2) 声明类所在的包

    默认情况下,类位于默认包下,但使用默认包是不建议的,定义类的时候,应该先使用关键字package,声明其包名,如下所示:

    包名和文件目录结构必须匹配,否则会提示编译错误。如源文件的根目录为D:\src,则上面的Hello类对应的全路径就应该是D:\src\com\huangyuanxin\Hello.java。

     

    3) 通过包使用类

    同一个包下的类可以直接引用,不同包下的类需要进行导包或通过全限定类名进行引用。

    注意:

    1. java.lang包下的类可以直接使用,无需导包或使用全限定类名,如String、System等。

    2. 在一个类中,对其他类的引用必须是唯一确定的,不能有重名的类。

    3. 如果确实需要在同一个类中引用多个重名类,则可以通过import导入其中最常用的一个,其它的通过全限定类名使用。

     

    4) 关于包范围可见性

    对于类、变量和方法,都支持设置一个可见性修饰符(public/private/protected)。这个修饰符可以省略(default),表示在包可见性,即同一个包内的其他类可以访问,但其他包内的类不可以访问。

    注意:

    1. 在Java中,无特殊说明时,同一个包一般都不包括其子包

    2. protected修饰符的可见性包括包可见性,也就是说,不仅子类可以访问,同一个包内的其它类也可以访问,即使不是子类。

     

    5) 生成jar包

    为方便使用第三方代码或将自己的代码给其他人使用,需要进行打包操作,即将编译后的多个字节码文件等打包为一个压缩文件。

    在Java中,打包命令如下:jar -cvf <包名>.jar <最上层目录名>,打包后的文件后缀为.jar,称之为Jar包

    如上述Hello类的打包,先切换到源码根目录D:\src,再使用 jar -cvf hello.jar com 命令进行打包,将会在根目录下生成一个hello.jar包。

     

    6) 程序的编译与连接

    Java类库和第三方类库等都是通过jar包形式提供的,只要将jar包加入到类路径(classpath)中即可进行使用。那类路径又是什么呢?

    从Java源代码到运行的程序,有编译运行两个步骤,编译指将源代码文件变成一种后缀为.class的字节码文件,运行是指根据引用到的类加载相应的字节码并执行。编译和运行时都需要指定一个类路径,在编译时用于确定所引用类的全限定类名(需结合import语句),在运行时,根据已确定的全限定类名寻找并加载类。

    类路径可以有多个(以;或:分隔),对于直接的class文件,类路径就是class文件的源码根目录;对于jar包,类路径是jar包的完整名称(包括路径和jar包名),Jar包会在内存中进行解压,然后再寻找和加载对应的类。

     

    2. 模块(JDK9+)

     

     

    第二节 类加载机制

    1. 类文件结构

    1) 概览

    类文件结构可通过javap -c -v HelloWorld.class命令查看,其组成如下:

    image-20250327203601731

     

    2) 分段说明

     

    3) 字节码示例

     

    2. 类加载过程

    类加载过程指将类的字节码文件(.class文件)加载到Java虚拟机(JVM)中,并将其转换为java.lang.Class对象的过程。

    扩展:

    1. 类卸载类对象被垃圾回收的过程,除了不可达之外,还需保证该类的所有实例都被回收且其类加载器也被回收

     

    3. 类加载器

    1) 基本概念

    类加载器(ClassLoader)用于将字节码文件加载到 JVM 内存,创建Class对象,作用于类加载过程的加载阶段

    JVM 中内置了三个重要的 ClassLoader

    注意:

    1. 在JDK9中,扩展类加载器被修改为平台类加载器,除java.base.*外的类几乎都是由它加载。

    2. 每个 Java 类都有一个引用指向加载它的 ClassLoader,但数组除外。

    3. 两个类是否相同,不仅要保证它们类名相同,还要保证类加载器也相同。

     

    2) 双亲委派机制

    双亲委派机制优先使用父加载器加载类,当父加载器无法加载时,才会使用当前类加载器进行加载,这样可以避免类的重复加载防止核心 API 被篡改

    注意:

    1. 双亲委派机制中,所有的类加载最终都会传递给 BootstrapClassLoader 优先进行尝试。

     

    3) 自定义类加载器

    自定义类加载器可以实现灵活加载热部署、应用的模块化和相互隔离等功能。

    注意:

    1. 自定义类加载器可以实现 loadClass 来打破双亲委派机制,或实现 findClass 遵循双亲委派机制。

     

     

    第三节 虚拟机对象

    1. 创建对象过程

     

    2. 对象的内存布局

    对象在内存中的布局可以分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

     

    3. 对象的访问定位

    Java 程序通过栈上的 reference 数据来操作堆上的具体对象,主流的访问方式有:句柄直接指针

     

     

    第三节 动态代理

    1. 动态代理简介

    动态代理是一种强大的功能,它可以在运行时动态的创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态为该类的对象添加方法和修改行为,这些特性使得它广泛应用于各种框架和库中,比如Spring、Hibernate、MyBatis、Guice等。

     

    2. 静态代理

    学习动态代理前,我们先了解下静态代理。静态代理在编码阶段显示的写出代理类代码,通过其对象来操作被代理对象。

    注意:区分代理模式和装饰器模式

    1. 它们的背后都有一个实际对象,都是通过组合的方式指向该对象。

    2. 装饰器的特点在于增强,不修改原有对象的行为而添加额外功能,是继承的替代,如JDK的IO流。

    3. 代理模式的特点在于隔离,对原有对象进行控制,不暴露给外部对象,如Spring的事务管理。

     

    3. 动态代理-基于接口

    了解了静态代理后,我们再来看动态代理。动态代理主要有两种实现方式,一种是JDK提供的基于接口的代理,另外一种是第三方库cglib提供的基于子类的代理。

     

    1) 基本使用

    在静态代理中,代理类是直接定义在代码中的,而在动态代理中,代理类是动态生成的,JDK提供的生成方法如下:

    下面是一个简单示例:

     

    2) 实现原理

    上面例子中创建proxyService的代码可以用如下代码代替:

    其中Proxy.getProxyClass会动态生成一个类,类名以$Proxy开头,后跟一个数字。上面例子生成的代理类如下:

    Proxy$0类的代码与被代理对象毫无关系,与InvocationHandler的具体实现也毫无关系,它只会实现参数传入的所有接口并重写接口方法,重写的方法和Object中的hashcode、equals、toString方法都将直接转发到InvocationHandler的invoke方法,后续在invoke方法中针对不同业务方法进行不同处理,这由传入的InvocationHandler实现类决定。

    扩展:我们是怎么知道$Proxy0的定义的呢? 对于Oracle的JVM,可以配置java的一个属性:

    以上命令会把动态生成的代理类$Proxy0保存到文件​$Proxy0.class中,通过一些反编译器工具比如JD-GUI就可以得到源码。

     

    3) 动态代理的优点

    使用动态代理,可以编写通用的代理逻辑,用于各种类型的被代理对象,而不需要为每个被代理的类型都创建一个静态代理类。

     

    4. 动态代理-基于子类

    JDK动态代理是基于接口的代理,只能代理接口中的方法,代理对象也只能转换到某个接口类型,如果一个类没有接口,或者希望代理非接口中定义的方法,可以使用第三方的类库cglib

     

    1) 基本使用

    使用cglib前需导入依赖:

    一个简单的使用示例如下:

     

    2) 实现原理

    cglib动态代理是基于继承实现的,也是动态的创建一个类,但这个类的父类是被代理的类,代理类重写了父类的所有public且非final方法,改为调用Callback中的相关方法,在上例中,调用SimpleInterceptor的intercept方法。

     

    3) 与JDK代理对比

    JDK代理面向的是一组接口,它为这些接口动态的创建一个实现类,实现类中接口方法都将转发到InvocationHandler的invoke方法。这里需要注意,JDK动态代理的背后不一定有实际对象,也可能有多个实际对象,根据情况动态选择。

    cglib代理面向的是一个具体的类,它也动态的创建一个新类,并继承了该类,重写了其方法进行转发。

    从代理的角度看,JDK代理的是对象,需要先有一个实际对象,自定义的InvocationHandler引用该对象,然后创建一个代理类和代理对象,客户端访问的是代理对象,代理对象最后再调用实际对象的方法,cglib代理的是类,创建的对象只有一个。

    如果目的都是为一个类的方法增强功能,JDK代理要求该类必须有接口,且只能处理接口中的方法,cglib没有这个限制,只要方法是public且非final即可。

     

    5. 简单AOP框架

    动态代理是面向切面编程(AOP - Aspect Oriented Programming)的基础,切面的例子有日志、性能监控、权限检查、数据库事务等,它们在程序的很多地方都会用到,代码都差不多,但与某个具体的业务逻辑关系也不太密切,如果在每个用到的地方都写,代码会很冗余,也难以维护,AOP将这些切面与主体逻辑相分离,代码简单优雅的多,下面我们自己实现一个简单的AOP框架。

    详见:https://www.cnblogs.com/swiftma/p/6869790.html

     

     

     

    第四节 单元测试

    单元测试(Unit Testing)又称为模块测试 ,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。

     

    1. Junit简介

    JUnit是Java中最常用的单元测试框架之一,使用它需要添加如下依赖:

    下面是一个简单的使用案例:

    更多关于单元测试的操作请参阅:https://www.w3cschool.cn/junit/fegu1hv3.html

     

     

    第五节 日志框架

    1. 日志框架简介

    Java最早的日志框架是Apache提出的log4j,随后Sun公司为制定Java日志标准,在JDK4中内置了JUL(java.util.logging),同时期,其它优秀的日志框架也相继涌现,如simplelog等。

    随着日志框架越来越多,为了统一日志接口,Apache提出了日志门面JCL(commons-logging)。后面log4j的作者觉得不好用,又开发了另一套日志门面SLF4J(simple logging facade for Java),并还开发了一个基于SLF4J的日志框架实现logback,与此同时,又继续维护并升级了log4j,形成了其升级版本log4j2。此时Java日志体系如下:

    img

     

     

    2. 基本使用

    为了方便的切换日志实现,用户应选用某一种日志门面(JCL或SLF4J),而不是具体的日志实现。在使用时,需要关注日志门面依赖日志实现依赖以及它们的桥接依赖是否引入。

     

    2.1JCL

     

    2.2 SLF4J

     

    2.3 JCL->Log4j

     

    2.4 SLF4J->Log4j

     

    2.5 SLF4J->Logback

     

    2.6 JCL/SLF4J->Log4j2

     

     

    3. Log4j配置详解

    Log4j有三个主要的组件,分别是Loggers(记录器)Appenders(输出源)Layout(布局)

     

    3.1 Loggers

    Loggers(记录器)可以配置日志类别所对应的日志级别。日志类别指日志信息的分类,一般为调用类的全类名。日志级别按照重要程度可分为DEBUG < INFO < WARN < ERROR < FATAL五类。每条日志信息都有其日志级别,只有>=其类别所配置的日志级别时才会被输出。

     

    3.2 Appenders

    Appenders(输出源)用于配置日志的输出位置,常使用的类如下:

     

    3.3 Layout

    Layout(布局)用来配置日志输出格式,常使用的类如下:

     

    3.4 配置案例

     

    4. Log4j2配置详解

    4.1 配置案例

     

    5. Logback配置详解

    5.1 配置案例

    提示:有关Logback的更多信息可参考:https://blog.csdn.net/qq_41946216/article/details/124983469?spm=1001.2014.3001.5502

     

     

    第五节 XML解析

    1. XML基本概念

    XML(Extensible Markup Language)的中文名为可扩展标记语言,主要用来存储数据,如做项目的配置文件或作为网络文件传输数据等。

     

     

    2. XML约束

    XML约束规定了xml文档的书写规则,主要有DTDSchema两种方式。

     

    1) DTD方式

    DTD是一种简单的XML约束技术,有三种使用方式:

    下面是一个引用了DTD约束的XML文件:

    在同目录下的student.dtd文件如下:

     

    2) Schema方式

    Schema是一种复杂的XML约束技术,其引入步骤如下:

    下面是是一个引了了Schema约束的XML文件:

    在同目录下的student.xsd文件如下:

     

     

    3. XML解析(Jsoup)

    XML解析主要有两种方式:

    上述两种解析方式分别有一些代表性的解析器工具:

    解析器说明解析方式
    JAXP由sun公司提供,支持dom和sax两种解析方式DOM/SAX
    DOM4J一个非常优秀的解析器DOM
    Jsoup一款HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,
    可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据
    DOM
    PULLAndroid操作系统内置的解析器,sax方式的SAX

    下面我们以JSoup为例介绍下XML文件的解析。

     

    1) Jsoup的基本使用

    JSoup使用前需要先导入相关依赖:

    解析XML文件的简单示例如下:

    也可以从字符串或URL进行解析:

     

    2) Document对象

    Document(文档对象)表示内存中的DOM树,一般用来获取Element对象,常用方法有:

    使用示例如下:

     

    3) Elements/Element对象

    Elements表示Element的集合,常用方法与ArrayList<Element>类似,其中Element表示元素对象,常用方法如下:

    简单示例如下:

     

    4) Node对象

    Node表示节点对象,是Document和Element的父类。

     

    5) selector快捷查询

    Jsoup的快捷查询有两种,一种为selector选择器方式,直接调用Document的成员方法Elements select(String cssQuery)即可。

     

    6) XPath快捷查询

    JSoup的另一种快捷查询的方式为XPath,即XML路径语言,它是一种用来确定XML文档中某部分位置的语言。使用前需要导入相关依赖:

    使用示例如下:

     

     

    第六节 Json解析

    1. Json的基本概念

    1) 什么是Json?

    Json(JavaScript Object Notation)译为JavaScript对象表示法,多用于存储和交换文本信息,比XML更小更快更易解析。基本语法如下:

    1. Json是由花括号内括键值对构成的,键用引号(单双都行)引起来,一般情况下不省略。

    2. Json的值可以是数字(整数和浮点数)、字符串(在双引号中)、逻辑值(true或false)以及null。

    3. Json可以嵌套,即Json的值可以是另一个Json。

    4. Json的值可以是以上类型的数组,如:{"persons":[{},{}]}

    5. 键和值用冒号分隔,不同键值对之间用逗号分隔。

     

    2) 从Json中获取数据(JS)

     

    3) Json遍历获取所有值(JS)

     

     

    2. Json解析器(Jackson)

    1) Jackson简介

    Jackson 是一个 Java 语言编写的,可以进行 JSON 处理的开源工具库。它有三个核心包:

    大多数情况下我们只需要添加 jackson-databind 依赖项即可,它会同时引入另外两个依赖:

     

    2) 基本使用

    Jackson的常用API如下:

    下面是一些使用案例:

     

    3) 自定义扩展
    忽略某些属性

    在标准JDK序列化中,会忽略被 transient标记的字段。而在 Jack-son 中, 也可以使用@JsonIgnore/@JsonIgnoreProperties注解进行标记,被标记的字段或属性将不会被序列化,同时,反序列化时也不会赋值(即使输入源存在字段值)。

     

    忽略未知字段

    在标准JDK反序列化时,如果流中存在类中没有的字段,将会被忽略,而Jackson默认情况下抛异常:

    可以通过配置ObjectMapper或在类上添加注解来忽略该情形:

     

    修改字段名称

    有时,我们希望修改输出的名称,可以使用@JsonProperty注解进行配置。

     

    日期格式化

    默认情况下,日期的序列化格式为一个长整数,可以使用@JsonFormat注解配置日期格式。

     

    指定构造方法

    如果类中没有默认构造方法,则反序列化时会抛异常,此时可以使用@JsonCreator@Json-Property指定一个构造方法。

     

    其它配置

     

     

    3. Json解析器(FastJson)

    1) FastJson简介

    FastJson是阿里巴巴的的开源库,用于对JSON格式的数据进行解析和打包,Maven依赖如下。

     

    2) 常用API简介

     

    3) 常用注解

    @JSONField注解可以对序列化和反序列化进行一些配置,常用的属性如下:

     

     

    第七节 正则表达式

    正则表达式一串字符,它描述了一个文本模式,利用它可以方便的处理文本,包括文本的查找、替换、验证、切分等。

     

    1. 匹配字符

    1) 普通字符

    普通字符就是用字符本身表示,比如字符03a等。

     

    2) 特殊字符

    特殊字符一般以反斜杠\开头,有如下一些:

    特殊字符说明
    元字符为正则语法中的特殊字符,如:斜杠\,用\\表示,其它还有\.\*\?\+等。
    ASCII码转义字符ASCII码表定义的转义字符,如:制表符\t、换行符\n、回车符\r等。
    八进制字符\0开头,后跟1~3位八进制数字表示一个字符,如:\0141,ASCII码为97,表示字符 'a'。
    十六进制字符\x开头,后跟两位十六进制数字表示一个字符,如:\x6A,ASCII码为106,表示字符 'j'。
    Unicode字符\u开头,后跟四位十六进制数字表示一个字符,如:\u9A6C,表示中文字符“马”。
    特殊的,对于编号在0xFFFF以上的Unicode字符,使用\x{....}形式,如:\x{1f48e}表示字符'💎'。

     

    3) 字符组

    字符组是多个字符的集合,使用[][^]-等定义,主要有如下两种:

    在字符组中,如果字符是连续的,可以使用连字符-表示,如:[0-9][a-z][0-9a-zA-Z_]

    在字符组中,-是一个元字符,如果要匹配它自身,可以使用转义,即\-,或者把它放在字符组的最前面,如[-0-9]

    在字符组中,除了[]^-\外,其它元字符不再具备特殊含义,变成了普通字符,比如[.*]就是匹配.或者*本身。

    在字符组中,^只有在字符组的开头才是元字符,如果不在开头,就是普通字符,匹配它自身,如[a^b]就是匹配字符a, ^或b。

    在字符组中,还可以包含其它字符组,如:[[abc][def]]等同于[abcdef],[a-z&&[^de]]表示匹配的字符是a到z,且不能是d或e。

    注意:

    1. 排除字符组表示匹配组外的任意一个字符,而不是不能匹配,要表达不能匹配的含义,需要使用后文介绍的环视语法。

     

    4) 预定义字符组

    正则语法预定义了一些常用的字符组,简化了其书写,一般以\开头:

    字符组类型预定义字符组含义
    可选字符组.默认匹配模式下,匹配除\r\n外的任意一个字符,如a.f可匹配abf,acf等,但不能匹配a\nf
     \dd表示digit,匹配任意一个数字字符,等同于[0-9]
     \ww表示word,匹配任意一个单词字符,等同于[a-zA-Z_0-9],类似于Java语言的标识符
     \ss表示space,匹配任意一个空白字符,等同于[ \t\n\x0B\f\r]
    排除字符组\D匹配任意一个非数字字符,等同于[^0-9][^\d]
     \W匹配任意一个非单词字符,等同于[^\w]
     \S匹配任意一个非空白字符,等同于[^\s]

     

    5) POSIX字符组

    POSIX字符组是POSIX标准定义的一些字符组,在Java中,这些字符组的形式是\p{...},比如:

    POSIX字符组含义
    \p{Lower}小写字母,等同于[a-z]
    \p{Upper}大写字母,等同于[A-Z]
    \p{Digit}数字,等同于[0-9]
    \p{Punct}标点符号,匹配!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~中的一个

    POSIX字符组比较多,本文就不列举了。

     

    2. 字符量词

    1) 通用量词

    字符量词表示左边的字符/字符组/字符分组出现的次数,格式为{m,n},表示最少出现m次,最多出现n次。特殊的,如果n没有限制,可以省略为{m,},如果m和n一样,可以简写为{m}

    通用量词含义
    ab{1,10}cb可以出现1~10次
    ab{3}cb必须是出现3次,即只能匹配abbbc
    ab{0,1}cb最多出现1次
    ab{1,}cb最少出现一次
    ab{0,}cb可以出现任意次

    注意:

    1. 字符量词语法必须是严格的{m,n}形式,逗号左右不能有空格

     

    2) 预定义量词

    正则语法预定义了一些常用的量词:

    预定义量词含义
    ?表示最多出现1次,等同于ab{0,1}c,如ab?c,既能匹配abc,也能匹配ac,但不能匹配abbc
    +表示最少出现1次,等同于ab{1,}c,如ab+c,既能匹配abc,也能匹配abbc,但不能匹配ac
    *表示出现任意次,等同于ab{0,}c,如ab*c,既能匹配abc,能匹配ac,也能匹配abbbc

    注意:

    1. 如果要匹配?*+{等元字符,需要使用\转义,比如a\*b匹配字符串"a*b"。

    2. 如果这些量词出现在字符组中,就不是元字符,比如表达式[?*+{]就是匹配其中一个字符本身。

     

    3) 贪婪与懒惰原则

    字符量词默认是贪婪匹配的,即每次尽可能的匹配更多的字符,如<a>.*</a>会匹配整个<a>first</a><a>second</a>字符串,而不是先匹配前面的<a>first</a>部分,再匹配后面的<a>second</a>部分。

    如果希望匹配时一旦满足就立即停止,开始新的下一次匹配,应该使用懒惰量词,格式为在量词的后面加一个问号?,即<a>.*?</a>

    其它量词也是类似,都有对应的懒惰形式,如:x??x*?x+?x{m,n}?等。

     

    3. 字符分组

    1) 分组语法

    可以将若干个字符/字符组使用小括号()括起来,表示一个字符分组,如a(bc)d,其中bc就是一个字符分组。它具有以下一些特性:

     

    2) 分组选项

    前面章节介绍,使用中括号[]定义字符组,用于匹配可选列表中的任意一个字符。与之类似,也可以定义分组内的多个可选项,匹配其中的任意一个,如(http|ftp|file),使用竖线|分隔后,表示可匹配http或ftp或file。

    注意:

    1. 注意区分分组选项和字符组,中括号[]和竖线|一起使用并没有特殊含义,如[a|b]的含义不是匹配a或b,而是a或|或b。

    2. 特殊的,整个字符串代表第0个分组,因此ab|cd可匹配ab或cd,无需小括号。

     

    3) 回溯引用

    在正则表达式中,可以使用斜杠\分组编号引用前面捕获的分组,称之为回溯引用,如<(\w+)>(.*)</\1>,其中\1匹配之前的第一个分组(\w+),这个表达式可以匹配类似如下字符串:<title>bc</title>,这里的第一个分组为title。

     

    4. 字符边界

    1) 特殊边界匹配(边界符)

    在正则表达式中,除了可以匹配字符,还可以匹配字符的边界。如下字符串a cat\n,在每个字符的两边都是边界,在整个字符串两边也有字符串边界,即字符串的开始边界和字符串的结束边界。

    image-20230311104243095

    对于不匹配特定边界的表达式abc,可两次匹配字符串abcabc,分别为前三个字符和后三个字符,我们可以为它加上一些边界匹配:

    边界符含义
    ^\A默认匹配模式下,表示整个字符串的开始,如^abc匹配字符串开头处的abc,对于字符串abcabc可匹配前三个字符
    \z默认匹配模式下,表示整个字符串的结束,如abc$匹配字符串结束处的abc,对于字符串abcabc可匹配后三个字符。
    $\Z\z类似,但是对回车换行符有一些兼容处理,如abc$也可匹配abcabc\r\n或abcabc\n的后三个abc字符。
    (注意:如需匹配abcabc\r\n的后5个字符,请使用abc\r\n$)
    \b表示单词边界,其中单词包括\w(a-zA-Z_0-9)和中文字符,如\bcat\b,可匹配cat,但不能匹配category

    注意:

    1. ^在字符组开头,表示排除字符组;在表达式开头,用于匹配字符串开始,如^[^abc],表示以一个不是a,b或c的字符开始。

     

    2) 环视边界匹配

    环视边界匹配是一种比边界符更为通用的边界匹配方式,要求边界左右必须满足指定的条件,主要分为如下四类:

    对同一个边界,可以写多个环视匹配,如(?=.*[A-Z])(?=.*[0-9])\w+要求该边界右边至少有一个大写字母和1个数字。

    注意:

    1. 环视边界匹配使用小括号(?),不过它不是分组,不占用分组编号。

    2. 环视边界匹配也被称为边界断言,断言的对象是边界,又因为其不占用字符,没有宽度,因此也被称为零宽度断言

     

     

    5. 特殊匹配模式

    1) 单行匹配模式(点号匹配模式)

    在默认匹配模式下,预定义字符组.匹配除\r\n外的任意一个字符,如a.f可匹配abf,acf等,但是不能匹配a\nf。

    可以通过(?s)前缀,指定点号匹配模式,该模式下,可额外匹配\r\n,即(?s)a.f可匹配a\nf

    注意:

    1. 注意文本换行的格式,(?s)a.f是不能匹配a\r\nf的,因为它是两个字符。

     

    2) 多行匹配模式(行匹配模式)

    在默认匹配模式下,边界符^$表示整个字符串的开始和结束,如^abc$可匹配abc、abc\r\n等,但是不能匹配abc\r\nabc。

    可以通过(?m)前缀,指定多行匹配模式,该模式下,^和$表示行的开始和结束,对于字符串abc\r\nabc,(?m)^abc$就会有两个匹配。

    注意:

    1. 多行匹配模式和单行匹配模式没有任何的关系。

    2. 单行模式影响的是字符组.的匹配规则,使其可以匹配换行符;

    3. 多行模式影响的是边界符^$的匹配规则,使它们可以匹配行的开始和结束。

     

    3) 大小写匹配模式

    在默认匹配模式下,匹配字符时是大小写敏感的,即the只能匹配the,不能匹配The等。

    可以通过(?i)前缀,指定大小写不敏感匹配模式,如(?i)the也可以匹配The等。

    注意:

    1. 上述一些匹配模式不是互斥的关系,可以一起使用,比如(?smi)表示开启点号匹配、多行匹配、大小写不敏感匹配。

     

    6. 正则语法补充

    1) 关于字符转义

    字符转义有两种,一种是将普通字符转义,使用具备特殊含义,如'\t', '\n', '\d', '\w', '\b', '\A'等;另一种是将元字符转义,使其变为普通字符,如'.', '*', '\?','(', '\'等。

    记住所有的元字符,并在需要的时候进行转义,这是比较困难的,有一个简单的办法,可以将所有元字符看做普通字符,就是在开始处加上\Q,在结束处加上\E,如:\Q(.*+)\E表示匹配(.*+)字符本身。

    注意:

    1. 正则语法中\是一个元字符,要匹配斜杠本身,需要转义写成:\\

    2. 而在Java字符串中,\也是一个特殊字符,因此通过字符串字面量写正则表达式时,匹配一个斜杠字符\需要写成:\\\\

     

    2) 运算符优先级

    正则表达式中的运算符具有优先级的概念,优先级高的先处理,相同优先级的从左到右处理,优先级列表从高到低如下:

    运算符描述
    \转义符
    ()(?:)(?=)[]圆括号,方括号
    *+?{n}{n,}{n,m}字符量词
    ^$\w等、a边界符,转义字符,普通字符
    |字符分组中的”或“操作,如ab|cd(m|f)ood

     

     

    7. Java正则API

    1) 正则API概述

    正则表达式编译和匹配相关的类位于java.util.regex包下,主要为PatternMatcher两个类。

    注意:

    1. 在Java中,没有什么特殊的语法能直接表示正则表达式,需要用字符串表示。

    2. 特殊的,反斜杠\在正则语法中和Java字符串中都是特殊字符,因此匹配\本身需要使用"\\"字符串。

    3. Pattern是线程安全的,为节省编译成本,推荐将正则表达式对象通过预编译的方式进行共享

     

    2) 字符串切分

    String类中有split方法可切分字符串,传入的是一个正则表达式:

    注意:

    1. 如果regex是一个普通字符(非元字符),优先选用String的split方法,它会采用更为简单高效的实现。

    2. 如果regex是多个字符或包含元字符,优先选用Pattern的split方法,并进行预编译节省编译成本。

     

    3) 字符串验证

    字符串验证就是检验输入文本是否完整匹配预定义的正则表达式,经常用于检验用户的输入是否合法。

     

    4) 字符串查找

    字符串查找就是在文本中寻找匹配正则表达式的子字符串

    上述示例中的Matcher正则匹配器,内部记录有一个位置,起始为0,find()方法从这个位置查找匹配正则表达式的子字符串,找到后,返回true,并更新这个内部位置,匹配到的子字符串信息可以通过如下方法获取:

    其中group()其实调用的是group(0),表示获取匹配的第0个分组的内容。我们在上节介绍过捕获分组的概念,分组0是一个特殊分组,表示匹配的整个子字符串。除了分组0,Matcher还有如下方法,获取分组的更多信息:

    示例代码如下:

     

    5) 字符串替换

    查找到子字符串后,一个常见的后续操作是替换。

    在replaceAll和replaceFirst中,参数replacement也不是被看做普通的字符串,可以使用美元符号加数字的形式,比如$1,引用捕获分组。

    如果需要替换为字符'$'本身,需要使用转义:

    如果替换字符串是用户提供的,为避免元字符的的干扰,可以使用Matcher的如下静态方法将其视为普通字符串:

    String的replaceAll和replaceFirst调用的其实是Pattern和Matcher中的方法,比如,replaceAll的代码为:

     

    6) 边查找边替换

    replaceAll和replaceFirst都定义在Matcher中,除了一次性的替换操作外,Matcher还定义了边查找、边替换的方法:

    这两个方法用于和find()一起使用,我们先看个例子:

    上述示例中,Matcher内部除了有一个查找位置,还有一个append位置,初始为0,当找到一个匹配的子字符串后,appendReplacement方法做了三件事情:

    appendTail将append位置之后所有的字符append到sb中。

     

    7) 模板引擎

    利用正则表达式,我们可以实现一个简单的模板引擎:

     

    8. Java正则示例

    对于同一个目的,正则表达式往往有多种写法,且大多没有唯一正确的写法,本节的写法主要是示例。

    此外,写一个正则表达式,匹配希望匹配的内容往往比较容易,但让它不匹配不希望匹配的内容,则往往比较困难,也就是说,保证精确性经常是很难的。

    不过,很多时候,我们也没有必要写完全精确的表达式,需要写到多精确与你需要处理的文本和需求有关,另外,正则表达式难以表达的,可以通过写程序进一步处理。

     

    1) 邮编

     

    2) 电话号码

     

    3) 日期

     

    4) 时间

     

    5) 身份证

    身份证有一代和二代之分,一代是15位数字,二代是18位,都不能以0开头,对于二代身份证,最后一位可能为x或X,其他是数字。

     

    6) IP地址

     

    7) URL

    URL的格式比较复杂,其规范定义在https://tools.ietf.org/html/rfc1738,我们只考虑http协议,其通用格式是:

    http://<host>:<port>/<path>?<searchpart>

    开始是http://,接着是主机名,主机名之后是可选的端口,再之后是可选的路径,路径后是可选的查询字符串,以?开头。

     

    8) Email

    完整的Email规范比较复杂,定义在https://tools.ietf.org/html/rfc822,我们先看一些实际中常用的。

     

    9) 中文字符