Java网络编程
1 网络编程
网络通信:两台设备之间通过网络实现数据传输。
java.net
包下提供了一系列类或接口,供程序员使用,完成网络通信
1.1 网络的相关概念
网络
网络:两台或多台设备通过一定物理设备连接起来构成了网络
根据网络覆盖范围的不同,对网络进行分类:
- 局域网:覆盖范围最小,仅覆盖一个教室·机房
- 城域网:覆盖范围较大,可覆盖一个城市
- 广域网:覆盖范围最大,可以覆盖全国,甚至全球。万维网 是广域网的代表
IP 地址
IP 地址:用于唯一标识网络中的每台计算机 / 主机
查看 IP 地址:ipconfig
IPv4 是 4 个字节(32位)表示。每个字节范围是 [0,255]
IP 地址的表示形式:点分十进制(xx.xx.xx.xx),每个十进制数范围是 [0,255]
IP 地址的组成 = 网络地址 + 主机地址
- A类:0 + 7 位网络号 + 24 位主机号(0.0.0.0 ~ 127.255.255.255)
- B类:1 + 0 + 14 位网络号 + 16 位主机号(128.0.0.0 ~ 191.255.255.255)
- C类:1 + 1 + 0 + 1 位网络号 + 8 位主机号(192.0.0.0 ~ 223.255.255.255)
- D类:1 + 1 + 1 + 0 + 28 位多播组号(224.0.0.0 ~ 239.255.255.255)
- E类:1 + 1 + 1 + 1 + 0 + 27 位(留待后用)(240.0.0.0 ~ 247.255.255.255)
IPv6 是互联网工程任务组设计的用于替代 IPv4 的下一代 IP 协议。其地址数量可以为全世界每一粒沙子编上一个地址
IPv4 最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6 的使用,不仅能解决网络地址资源数量的问题,也解决了多种接入设备接入互联网的障碍
IPv6 使用 16 个字节(128 位)表示地址。
表示形式有:
-
冒分十六进制表示法: (X:X:X:X:X:X:X:X)
:
之间的部分,出现 0 开头的场合,那些 0 可以省略 -
0 位压缩表示法:把连续的
0
压缩为::
,这个压缩只能出现一次(X::X:X) -
内嵌 IPv4 地址表示法:前 96位 用冒分十六进制表示,后面 32位 用 IPv4 的点分十进制(X:X:X:X:X:XX:d.d.d.d)
子网掩码
只用一个 IP 地址,无法分辨网络部分与主机部分的分界线。因此,使用子网掩码来表示分界线。
这个场合,对应的网络部分的子网掩码的二进制数字设为 1
此外,还能把子网掩码与 IP 地址组合
- 在 IP 地址后加斜线及网络部分二进制数字数(IPV4):192.168.15.1/16
- IPv6:X:X:X:X:X:X:X:X/64
通过更改子网掩码,可以细分网络为多个子网。
保留地址
IP 还定义了一套特殊的地址格式,称为保留地址,这些保留地址不分配给任何主机。
网络号 | 主机号 | 地址类型 | 举例 | 用途 |
---|---|---|---|---|
全 0 | 全 0 | 本机地址 | 0.0.0.0 | 启动时使用 |
任意 | 全 0 | 网络地址 | 61.0.0.0 | 标识一个网络 |
任意 | 全 1 | 直接广播地址 | 129.21.255.255 | 在特定网络上广播 |
全 1 | 全 1 | 有线广播地址 | 255.255.255.255 | 在本网段上广播 |
第一段为 127 | 任意 | 回送地址 | 127.0.0.1 | 测试 |
私有地址
私有地址:与 IP 地址(全局地址)相比,在不同的网络中可以重复的地址。
私有地址是以下范围中的地址。这些地址不能作为全局地址使用:
- 10.0.0.0 ~ 10.255.255.255
- 172.16.0.0 ~ 172.31.255.255
- 192.168.0.0 ~ 192.168.255.255
将私有地址连接到全局地址的方法:
- NAT:一种私有地址与全局地址一一对应的机制
- NAPT:一种用一个全局地址连接多个计算机的机制
域名
示例:http://bbs.tianya.cn/post-house-252774-1.shtml
为了方便记忆,解决记忆 IP 的困难
IP 地址根据 HTTP 协议 映射成域名
通过 DNS(Domain Name System)服务将域名转化为 IP 地址
端口号
用于标识计算机上某个特定的网络程序
表示形式:以整数形式,范围 [0,65535]
0 ~ 1024 已经被占用,不要使用。比如 ssh 22、ftp 21、smtp 25、http 80
常见的网络程序端口号:
- tomcat:8080
- mysql:3306
- oracle:1521
- sqlserver:1433
网络通信协议
协议(TCP/IP)
TCP/IP:传输控制协议 / 因特网互联协议(Transmission Control Protocol / Internet Protocol),又叫 网络通讯协议。这个协议是 Internet 最基本的协议、Internet 国际互联网络的基础。简单来讲,就是由 网络层的 IP 协议 和传输层的 TCP 协议 组成
OSI 模型(理论) | TCP/IP 模型(实际使用) | TCP/IP 模型各层对应协议 |
---|---|---|
应用层 | 应用层 | HTTP、ftp、telent、DNS…… |
表示层 | 应用层 | 同上 |
会话层 | 应用层 | 同上 |
传输层 | 传输层(TCP) | TCP、UDP…… |
网络层 | 网络层(IP) | IP、ICMP、ARP…… |
数据链路层 | 物理 + 数据链路层 | Link |
物理层 | 物理 + 数据链路层 | 同上 |
1.1.1 TCP 和 UDP
TCP
传输控制协议
- 使用 TCP 协议前,须先建立 TCP 连接,形成传输数据通道。TCP 通信是一对一通信
- 传输前,采用 “三次握手” 方式,是可靠的
- TCP 协议进行通信的两个应用进程:客户端、服务端
- 在连接中可进行大数据量的传输。传输前,先确认要交流的数据量。那个数据量、数据窗口取较小方的数值。
- 发送方没有收到接收方的确认应答时,(在一定次数内)会再次发送数据包
- 传输完毕,需释放已建立的连接,效率低
UDP
用户数据协议
- 将 数据、源、目的 封装成数据包,不需要建立连接。可以同时向多个接收方发送
- 每个数据包大小限制在 64K 以内,不适合传输大量数据
- 因无需连接,所以是不可靠的
- 接收方无需发送确认应答
- 发送数据结束时无需释放资源(因为不是面向连接的),速度快
2 .InetAddress
类
相关方法
getLocalHost
:获取本机InetAddress
对象getByName
:根据指定主机名 / 域名获取 IP 地址对象getHostName
:获取InetAddress
对象的主机名getHostAddress
:获取InetAddress
对象的地址
3. Socket
- 套接字(Socket)开发网络应用程序被广泛采用,以至于成为了事实上的标准
- 通信的两端都要有 Socket,是两台机器间通信的端点
- 网络通信其实就是 Socket 间的通信
- Socket 允许程序把网络连接当成一个流,数据在两个 Socket 间通过 IO 传输
- 一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端
3.1 TCP 网络通信编程
- 基于客户端——服务端的网络通信
- 底层使用的是 TCP / IP 协议
- 应用场景距离:客户端发送数据,服务端接收并显示
- 基于 Socket 的 TCP 编程
下面,示范一个 服务端
1 | public void server() throws IOException{ |
-
ServerSocket serverSocket = new ServerSocket(9000);
这个语句用以监听 9000 这个端口
细节:这里要求该端口没有被其他服务占用。
-
Socket clientSocket = serverSocket.accept();
这个语句用以接收连接的
Socket
。没有连接时,程序会阻滞在这里细节:此处
accept()
可以返回多个Socket
,即多并发 -
serverSocket.close();
结束后,务必关闭!
下面,示范一个客户端
1 | public void client() throws IOException{ |
-
这个 IP 是我的本机地址。代表的是 服务端 地址
-
Socket socket = new Socket(serverIP, 9000);
表示访问指定 IP 的 9000 端口
-
socket.shutdownOutput();
这里是输出一个结束标记。若不如此做,socket 就不知道是否数据发送完成
特别的,由 字节流 输出的场合,
writer.newLine()
可以替代结束标记。但是这个场合,接收必须是reader.readLine()
3.1.1 netstat 指令
-
netstat -an
可以查看当前主机网络情况,包括端口监听情况和网络连接情况 -
netstat -an | more
可以分页显示 -
netstat -anb
可以显示占用端口的应用 -
要求在 dos 控制台下执行
-
Listening 表示某个端口在监听。
如果有一个外部程序连接到该端口,就会显示一条连接信息 Established
19.3.1.2 TCP 连接秘密
当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的。这个端口由 TCP/IP 来分配,是不确定的,随机的。
3.2 UDP 网络通信编程
- 类
DatagramSocket
和DatagramPacket
实现了基于 UDP 协议网络程序 - 没有明确的服务端和客户端,演变成数据的发送端和接收端
- UDP 数据报通过数据报套接字
DatagramSocket
发送和接收。系统不保证 UDP 数据报一定能安全送到目的地,也不能确定什么时候能抵达 DatagramPacket
对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地址和端口号以及接收端的 IP 地址和端口号- 接收到
DtagramPacket
对象时,需要进行拆包,取出数据 DatagramSocket
可以指定在哪个端口接收数据- UDP 协议中每个数据报都给出了完整的地址信息,因此无需发送方和接收方的连接
下面,示范一个接收端
1 | DatagramSocket ds = new DatagramSocket(9000); //[1] |
-
DatagramSocket ds = new DatagramSocket(9000);
以 9000 这个端口作为监听端口
-
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
构建
DatagramPacket
对象,准备接收数据 -
ds.receive(dp);
监听信息,放到刚刚创建的
DatagramPacket
对象 -
ds.close()
要记得关闭呦 ★ ~
下面,示范一个发送端
1 | System.out.println("萝茵,大声喊道:你好,世界!"); |
-
DatagramSocket ds = new DatagramSocket(8000);
以 8000 这个端口作为发送端口
-
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, ia, 9001);
把要发送的数据、数据长度、对象地址、对象端口 放到包里
-
ds.send(dp);
走你 ★ ~
-
ds.close();
鸟尽弓藏
附录
项目开发流程
1 需求分析
需求分析师(懂技术 + 懂行业)
- 需求分析报告
- 项目功能
- 客户要求
2 设计阶段
架构师 / 项目经理
- 设计工作
- UML 类图
- 流程图
- 模块设计
- 数据库设计
- 架构
- 原型开发
- 组建团队
3 实现阶段
程序员 / 码农
- 完成架构师的模块功能
- 测试自己的模块
4 测试阶段
测试工程师
- 单元测试
- 测试用例
- 白盒测试
- 黑盒测试
- 集成测试
5 实施阶段
实施工程师(开发能力 / 环境配置部署能力)
- 把项目正确地部署到客户的平台,并保证运行正常
- 身体好
6 维护阶段
- 发现 bug 并解决
- 项目升级
2 反射
- 反射机制(Reflection)允许程序在执行期借助于 Reflection API 取得任何类的内部信息(如成员变量、成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
- 加载完类之后,在堆中就产生了一个
Class
类型的对象(一个类只有一个Class
对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。所以,形象地称之为:反射ocp 原则(开闭原则):不修改源码来扩展功能
计算机的三个阶段
-
代码阶段 / 编译阶段
编写代码 ——(Javac 编译)——> .class 字节码文件
-
Class 类阶段 / 加载阶段
字节码文件 ——(ClassLoader 类加载器)——>
Class
类对象(堆中)· 字节码二进制数据 / 元数据(方法区)Class
类对象包含:成员变量Field[] fields
、构造器Constructor[] cons
、成员方法Methord[] ms
-
Runtime 运行阶段
创建对象,该对象知道其属于哪个
Class
对象
反射机制可以完成
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
1. 反射相关的常用类
-
java.lang.Class
:代表一个类。Class
对象表示某个类加载后在堆中的对象1
2Class cls = Class.forName(classFullPath); //[1]
Object o = cls.newInstance(); //[2]- 通过完整类名得到一个类的 Class 对象
- 通过该 Class 对象创建一个该类的 对象实例
-
java.lang.reflect.Method
:代表类的方法。Method
对象表示某个类的某个方法1
2Method method = cls.getMethod(methodName); //[1]
method.invoke(o); //[2]- 通过该 Class 对象得到一个 方法对象
- 方法对象.invoke:调用该方法
-
java.lang.reflect.Field
:代表类的成员变量1
2Field field = cls.getField(fieldName); //[1]
- 该方法只能得到非私有对象
-
java.lang.reflect.Constructor
:代表类的构造方法1
2
3Constructor constructor = cls.getConstructor(); //[1]
Constructor constructor2 = cls.getConstructor(String.class)
//[2]- 得到一个无参构造器
- 得到一个形参是
(String str)
的构造器
反射的优点和缺点
- 优点:可以动态地创建和使用对象(也是框架底层核心),使用灵活。没有反射机制,框架技术就失去底层支撑
- 缺点:使用反射基本是解释执行。这对执行速度有影响。
反射调用优化 - 关闭访问检查
-
Method
和Field
、Constructor
对象都有setAccessible()
方法 -
setAccessible()
作用是启动和禁用访问安全检查的开关 -
参数值为 true,表示反射对象在使用时取消访问检查,这样能提高反射效率。
为 false 表示执行访问检查
1.2 Class
类
Class
也是类,因此也继承Object
类Class
类不是 new 出来的,而是系统创建的- 对于某个类的
Class
类对象,在内存中只有一份,因为类只加载一次 - 每个类的实例都会记得自己是由哪个
Class
实例生成 - 通过
Class
可以完整地得到一个类的完整结构,通过一系列 API Class
对象是存放在堆的- 类的字节码二进制数据,是放在方法区的。有的地方称为类的元数据(包括 方法代码、变量名、方法名、访问权限 等)
1.2.1 Class
类的常用方法
-
Class.forName(String)
:返回指定类名的Class
对象 -
newInstance()
:返回一个无参构造器创建的实例 -
getName()
:返回该Class
对象表示的实体的全类名 -
getClass()
:返回该Class
对象的运行类型java.lang.Class
-
getPackage()
:返回该Class
对象所在的包 -
getSuperClass()
:返回该Class
对象的父类Class
对象 -
getInterface()
:返回该Class
对象的接口(数组) -
getAnnotations()
:返回注解信息(Annotation[]
) -
getClassLoader()
:返回该Class
对象的加载器(ClassLoader
类型) -
getSuperclass()
:返回该Class
对象实体的超类的Class
-
getConstructors()
:返回本类所有包含public
修饰的构造器的Constructor
对象数组该方法返回的构造器不含父类构造器!
-
getDeclaredConstructer()
:返回本类所有构造器的Constructor
对象数组 -
getFileds()
:返回一个包含public
修饰的属性的Field
对象的数组getFiled(String name)
:返回指定的Field
-
getDeclaredFields()
:获取本类中所有属性 -
field.get(instance)
:返回指定实例的指定属性 -
field.set(instance, ..)
:给指定实例的指定属性赋值 -
getMethod()
:获得所有public
修饰的方法的Method
对象 -
getMethod(String name, Class paramTypes, ...)
:返回一个Method
对象,其形参类型为 paramType -
getDeclaredMethod()
:获取本类中所有方法
1.2.2 获取 Class
对象
1 | package com.melody.note; |
-
(编译阶段)已知一个类的全类名,且该类在类路径下:
1
Class cls1 = Class.forName("com.melody.note.Test");
应用场景:配置文件,读取类全路径,加载类。
可能抛出
ClassNotFoundExcption
-
(加载阶段)已知具体的类:
1
Class cls2 = Test.class;
应用场景:参数传递。
该方法最为安全
-
(运行阶段)已知某个类的实例:
1
Class cls3 = new Test().getClass();
应用场景:通过创建好的对象获取
Class
对象 -
通过类加载器:
1
2ClassLoader cll = new Test().getClass().getClassLoader();
Class cls4 = cll.loadClass("com.melody.note.Test"); -
基本数据类型:
1
2Class clsB1 = int.class;
Class<Boolean> clsB2 = boolean.class; -
基本数据类型包装类:
1
2Class clsB3 = Character.TYPE;
Class<Long> clsB4 = Long.TYPE;
1.2.3 哪些类有 Class
对象
- 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
- 接口(interface)
- 数组
- 枚举(enum)
- 注解
- 基本数据类型
- void
1.3 类的加载
基本说明
反射机制是 Java 实现动态语言的关键,也就是通过反射实现类动态加载
- 静态加载:编译时加载相关的类,如果没有则报错。依赖性强
- 静态加载:运行时加载需要的类,如果运行时不用该类,则不报错。降低了依赖性
类加载时机
- 创建对象时(new) [静态加载]
- 子类被加载时,父类也被加载 [静态加载]
- 调用类中的静态成员 [静态加载]
- 通过反射 [动态加载]
-
加载(Loading):
将类的 .class 文件读入内存,并为之创建一个 java.lang.Class 对象。此过程由类加载器完成
-
连接(Linking):
将类的二进制数据合并进 JRE 中
-
初始化(initialization):
JVM 负责对类进行初始化。这里主要是静态成员
1.3.1 类加载的五个阶段
-
加载阶段
JVM 在该阶段的主要目的是将字节码从不同数据源(.class 文件、jar 包、网络等)转化为二进制字节流加载到内存中,并生成一个代表该类的
java.lang.Class
对象 -
连接阶段 - 验证
目的是确保
Class
文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。包括:文件格式验证(是否以魔数 0xcafebabe 开头)、元数据验证、字节码验证、符号引用验证
可以考虑使用
-Xverify:none
参数来关闭大部分的类验证措施,以缩短虚拟机加载的时间 -
连接阶段 - 准备
JVM 会在该阶段对 静态变量 分配内存并执行默认初始化。这些变量使用的内存都将在方法区中进行分配
1
2
3public int n1 = 1; //实例属性,非静态变量,此阶段不分配内存
public static int n2 = 2; //静态变量,默认初始化为 0
public static final int n3 = 3; //static final 常量,静态初始化为 3 -
连接阶段 - 解析
JVM 将常量池内符号引用替换为直接引用的过程
-
初始化
到初始化阶段,才真正开始执行类中定义的 Java 程序代码。此阶段是执行
<clinit>()
方法的过程<clinit>()
方法是由编译器按语句在文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并JVM 会保证一个类的
<clinit>()
方法在多线程环境中被正确地加锁、同步。如果多个线程去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()
方法完毕
1.4 通过反射获取类的结构信息
java.lang.Class
类(与前面的的重复)
getSuperClass()
:返回该Class
对象的父类Class
对象
getInterface()
:返回该Class
对象的接口(数组)
getAnnotations()
:返回注解信息(Annotation[]
)
getClassLoader()
:返回该Class
对象的加载器(ClassLoader
类型)
getSuperclass()
:返回该Class
对象实体的超类的Class
getConstructors()
:返回本类所有包含public
修饰的构造器的Constructor
对象数组该方法返回的构造器不含父类构造器!
getDeclaredConstructer()
:返回本类所有构造器的Constructor
对象数组
getFileds()
:返回一个包含public
修饰的属性的Field
对象的数组
getFiled(String name)
:返回指定的Field
getDeclaredFields()
:获取本类中所有属性
field.get(instance)
:返回指定实例的指定属性
field.set(instance, ..)
:给指定实例的指定属性赋值
getMethod()
:获得所有public
修饰的方法的Method
对象
getMethod(String name, Class paramTypes, ...)
:返回一个Method
对象,其形参类型为 paramType
getDeclaredMethod()
:获取本类中所有方法
java.lang.reflect.Field
类
getModifiers()
:以 int 形式返回修饰符默认修饰符 [0]、public [1]、private [2]、protected [4]、static [8]、final [16]
示例:
1 public static final int n = 0;这个变量的修饰符的 int 表示 = 1 + 8 + 16 = 25
getType()
:以Class
形式返回类型上例变量的
getType()
等同于Integer.getClass()
getName()
:返回属性名
java.lang.reflect.Method
类
getModifiers()
:以 int 形式返回修饰符(同上)getName()
:返回方法名getReturnType()
:以Class
形式返回返回类型getParameterTypes()
:以Class[]
形式返回形参类型数组
java.lang.reflect.Constructer
类
getModifiers()
:以 int 形式返回修饰符getName()
:返回构造器名(和全类名相等)getParameterTypes()
:以Class[]
形式返回形参类型数组
1.5 通过反射创建对象
-
调用类中的 public 修饰的无参构造器
1
Object obj1 = cls.newInstance();
-
调用类中指定的构造器
1
2Constructer cons = cls.getConstructer(int.class, String.class, ...);
Object obj2 = cons.newInstance(1, "nnn", ...); -
setAccessible(true)
:爆破(暴力破解)。使用反射可以访问 private 构造器1
2
3Constructer cons2 = cls.getDeclaredConstructer(boolean.class ...);
cons2.setAccessible(true);
Object obj3 = cons.newInstance(false, ...);
1.6 通过反射访问成员
1
2
3 Field field = cla.getDeclaredField("name");
field.setAccessible(true);
field.set(o, "111"); //[1]
o 表示一个类的实例
如果该属性是静态属性,则对象 o 可以是 null
1
2
3 Method method = cls.getDeclaredMethod("m1");
method.setAccessible(true);
Object returnObj = method.invoke(o, 'c', ...); //[1]
o 表示一个类的实例,后面是实参列表
同理,静态方法的场合,对象 o 可以是 null
3. 正则表达式
正则表达式:对字符串执行模式匹配的技术。一个正则表达式,就是用某种模式去匹配字符串的一个公式。除 Java 外,还有许多语言支持正则表达式。
1 | String content = "HeruinKCoin"; //对象文本 |
Matcher
底层维护了一个group[]
数组。如果 [4] 在文本里匹配到对象,会在group[0]
记载该起始位置 n1,在group[1]
记录该结束位置的下一位 n2。即 [n1,n2) 为匹配的字符串,n2 位置是下次匹配的起始位置。当 [1] 创建的规则包含分组(如
String regular = "(\\d\\d)(\\d\\d)";
),则第一组的起止位置记录在group[2]
、group[3]
,第二组在group[4]
、group[5]
。以此类推。这时,[5] 的group(0)
代表输出全部,group[1]
代表输出第一组,以此类推。
3.1 语法
元字符
- 限定符
- 选择匹配符
- 分组组合和反向引用符
- 特殊字符
- 字符匹配符
- 定位符
3.1.1 转义符号 \
使用正则表达式去检索某些特殊字符时,需要加上转义符号(如:(
需要写成 \(
)
在 Java 的正则表达式中,\\
代表一个 \
。——见 [[1.8 Java 转义字符 ]](https://i-melody.github.io/2021/11/21/Java/入门阶段/1 基础知识/#1-8-Java-转义字符)
需要用到转义符号的字符有:.
、+
、(
、)
、$
、/
、\
、?
、[
、]
、^
、{
、}
3.1.2 字符匹配符
符号 | 含义 | 示例 | 解释 |
---|---|---|---|
[ ] |
可接收的字符列表 | [abcd] |
abcd 中的任一字符 |
[^] |
不接收的字符列表 | [^abcd] |
非 abcd 的任意字符 |
- |
连字符 | [a-z] |
a - z 中的任意字符 |
. |
匹配除 \n 外的任意字符 |
a..b |
a 开头,b结尾,中间含 2 字符 |
\d |
匹配单个数字字符 | \d{3} |
包含 3 个数字 |
\D |
匹配单个非数字字符 | \D(\d)* |
单个非数字字符开头,后接任意个数字字符 |
\w |
匹配单个数字、大小写字母字符 | \w{2}\d{3} |
2 个数字字母字符开头,后接 3 个数字字符 |
\W |
匹配单个非数字、非大小写字母字符 | \W+\d{2} |
以至少 1 个非数字字母字符开头,后接 2 个数字字符 |
\s |
匹配空白字符(空格、制表位等) | ||
\S |
匹配非空白字符 |
-
关于
.
:特别地,出现[.]
的场合,那个小圆点依然表示小圆点。[?]
同理,表示问号 -
正则表达式默认区分大小写。要不区分大小写,就加上
(?i)
-
(?i)abc
:即 abc 都不区分大小写 -
a(?i)bc
:即仅 bc 不区分大小写 -
a((?i)b)c
:即仅 b 不区分大小写 -
创建模式对象时,若如此做:
1
Pattern pattern = Pattern.compile(regular, Pattern.CASE_INSENSITIVE);
这个场合,也能不区分大小写。
-
3.1.3 选择匹配符 |
……我的感想是,和 Java 的逻辑或 |
一样!
3.1.4 限定符
符号 | 含义 | 示例 | 解释 |
---|---|---|---|
* |
指定字符重复任意次(可以为 0 次) | (abc)* |
仅包含任意个 abc 字符串的字符串 |
+ |
指定字符重复至少一次 | m+(abc)* |
以任意个 m 开头,后面可以有 abc 字符串的字符串 |
? |
指定字符重复最多一次(可以为 0 次) | m+abc? |
以任意个 m 开头,后面可以有最多一个 abc 字符串的字符串 |
{n} |
n 个匹配 | [abc]{3} |
长度为 3 的 abc 中的任意字符的组合 |
{n,} |
至少 n 个匹配 | [abc]{3,} |
长度不小于 3 的 abc 中的任意字符的组合 |
{n,m} |
n 到 m 个匹配 | [abc]{3,5} |
长度介于 3 到 5 之间的 abc 中的任意字符的组合 |
-
Java 的匹配模式默认是贪婪匹配。即:
aaaaa
匹配a{3,5}
的场合,会匹配到aaaaa
希望实现非贪婪匹配,可以添加额外的
?
。如:*?
、+?
、??
代表各自规则的非贪婪匹配
3.1.5 定位符
符号 | 含义 | 示例 | 解释 |
---|---|---|---|
^ |
指定起始字符 | ^[0-9]+[a-z]* |
至少一个数字开头,后接任意小写字母字符串 |
& |
指定结束字符 | ^[0-9][a]$ |
一个数字开头,一个 a 结尾 |
\b |
匹配目标字符串的边界 | K\.C\b |
匹配边界的 K.C |
\B |
匹配目标字符串的非边界 | K\.C\B |
匹配非边界的 K.C |
- 边界即字符串的末尾,或字符串中空格间隔的子串的末尾。
3.1.6 分组
符号 | 含义 |
---|---|
(pattern) |
非命名捕获。捕获匹配的字符串。 |
(?<name>pattern) 、(?'name'pattern) |
命名捕获。用于 name 的字符串不能包含标点符号,也不能以数字开头 |
- 编号为 0 的第一个捕获是由整个正则表达式匹配的文本。其他捕获结果根据左括号的顺序从 1 开始自动编号。
3.1.7 非捕获分组
符号 | 含义 | 示例 | 解释 |
---|---|---|---|
(?:pattern) |
匹配 pattern 但不捕获该匹配的子表达式。 | `industr(?:y | ies)` |
(?=pattern) |
匹配处于 pattern 前的搜索字符串。非捕获分组。 | `Windows(?=7 | 10 |
(?!pattern) |
匹配不处于 pattern 前的搜索字符串。非捕获分组。 | `Windows(?!7 | 10 |
3.2 常用类
-
Pattern
类:Pattern
对象是一个正则表达式对象,该类没有公共构造方法。用
Pattern.compile(reg)
获取一个Pattern
对象。 -
Matcher
类:Matcher
对象是输入字符串进行解释和匹配的引擎,也没有公共构造方法。用
Pattern
对象的matcher(content)
方法获得一个Matcher
对象。 -
PatternSyntaxExcption
类:PatternSyntaxExcption
是一个非强制异常类,表示一个正则表达式中的语法错误。
3.2.1 Pattern
类常用方法
-
Pattern.matches(reg, content)
:整体匹配,输入的字符串是否符合表达式。返回布尔值。matcher.matches()
:整体匹配,字符串是否符合表达式。返回布尔值。前面的方法实际上就是这个方法。 -
Pattern.compile(reg)
:返回一个指定表达式的Pattern
对象 -
pattern.matcher(content)
:返回一个字串的Matcher
对象 -
matcher.pattern()
:返回该Matcher
对象的表达式pattern.pattern()
:返回该Pattern
对象的表达式 -
matcher.find()
:尝试查找下一个匹配的序列,返回布尔值matcher.find(int)
:重置该匹配器,从指定索引位置开始重新查找 -
matcher.start()
:返回本次匹配的字符起始位置的索引matcher.end()
:返回本次匹配的字符结束位置 + 1 的索引这个场合,
content.substring(matcher.start(), matcher.end())
就是匹配的字符串 -
matcher.start(int)
:返回本次匹配的字符的该组内容的起始位置的索引matcher.end(int)
:返回本次匹配的字符的该组内容的结束位置 + 1 的索引 -
matcher.replaceAll(str)
:替换匹配到的全部内容matcher.replaceFirst(str)
:替换第一次匹配到的内容这些场合,返回的字符串才是替换后的字符串。原字符串不变。
3.3 分组、捕获、反向引用
-
分组(子表达式)
-
捕获:把正则表达式中,子表达式(分组)的内容保存到内存中以数字编号或显式命名的组里,方便后面引用。以分组的左括号为标志,第一组组号为 1,第二组为 2,以 0 代表整个正则表达式。
-
反向引用:分组的内容被捕获后,可以在这个括号后使用。这种引用既可以是在正则表达式内部,也可以在外部。内部反向引用
\分组号
、外部反向引用$分组号
1
2
3String regular = "(\\w)\\1+"; //即,重复的字母或数字
Matcher matcher = Pattern.compile(regular).mathcer(content);
content = matcher.replaceAll("$1"); //这样,就完成了去重
3.4 在 String
中使用正则表达式
str.matches(reg)
:整体匹配str.replaceAll(reg, reg)
:替换匹配到的全部内容str.split(reg)
:分割内容