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

传输控制协议

  1. 使用 TCP 协议前,须先建立 TCP 连接,形成传输数据通道。TCP 通信是一对一通信
  2. 传输前,采用 “三次握手” 方式,是可靠的
  3. TCP 协议进行通信的两个应用进程:客户端、服务端
  4. 在连接中可进行大数据量的传输。传输前,先确认要交流的数据量。那个数据量、数据窗口取较小方的数值。
  5. 发送方没有收到接收方的确认应答时,(在一定次数内)会再次发送数据包
  6. 传输完毕,需释放已建立的连接,效率低

UDP

用户数据协议

  1. 将 数据、源、目的 封装成数据包,不需要建立连接。可以同时向多个接收方发送
  2. 每个数据包大小限制在 64K 以内,不适合传输大量数据
  3. 因无需连接,所以是不可靠的
  4. 接收方无需发送确认应答
  5. 发送数据结束时无需释放资源(因为不是面向连接的),速度快

2 .InetAddress

相关方法

  • getLocalHost:获取本机 InetAddress 对象
  • getByName:根据指定主机名 / 域名获取 IP 地址对象
  • getHostName:获取 InetAddress 对象的主机名
  • getHostAddress:获取 InetAddress 对象的地址

3. Socket

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

3.1 TCP 网络通信编程

  1. 基于客户端——服务端的网络通信
  2. 底层使用的是 TCP / IP 协议
  3. 应用场景距离:客户端发送数据,服务端接收并显示
  4. 基于 Socket 的 TCP 编程

下面,示范一个 服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void server() throws IOException{
ServerSocket serverSocket = new ServerSocket(9000); //[1]
Socket clientSocket = serverSocket.accept(); //[2]
//下面是输入流,不解释了
InputStream inputStream = clientSocket.getInputStream();
System.out.println(clientSocket.getInetAddress());
int n;
byte[] b = new byte[1024];
byte[] B = new byte[0];
while ((n = inputStream.read(b, 0, 1024)) != -1) {
B = Arrays.copyOf(B, B.length + n);
for (int i = 0; i < n; i++) {
B[B.length - n + i] = b[i];
}
}
serverSocket.close(); //[3]
System.out.println(new String(B));
}
  1. ServerSocket serverSocket = new ServerSocket(9000);

    这个语句用以监听 9000 这个端口

    细节:这里要求该端口没有被其他服务占用。

  2. Socket clientSocket = serverSocket.accept();

    这个语句用以接收连接的 Socket。没有连接时,程序会阻滞在这里

    细节:此处 accept() 可以返回多个 Socket,即多并发

  3. serverSocket.close();

    结束后,务必关闭!

下面,示范一个客户端

1
2
3
4
5
6
7
8
9
public void client() throws IOException{
String serverIP = "192.168.3.16"; //[1]
Socket socket = new Socket(serverIP, 9000); //[2]
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello,Server!".getBytes(StandardCharsets.UTF_8));
socket.shutdownOutput(); //[3]
outputStream.close();
socket.close();
}
  1. 这个 IP 是我的本机地址。代表的是 服务端 地址

  2. Socket socket = new Socket(serverIP, 9000);

    表示访问指定 IP 的 9000 端口

  3. socket.shutdownOutput();

    这里是输出一个结束标记。若不如此做,socket 就不知道是否数据发送完成

    特别的,由 字节流 输出的场合,writer.newLine() 可以替代结束标记。但是这个场合,接收必须是 reader.readLine()

3.1.1 netstat 指令

  1. netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况

  2. netstat -an | more 可以分页显示

  3. netstat -anb 可以显示占用端口的应用

  4. 要求在 dos 控制台下执行

  5. Listening 表示某个端口在监听。

    如果有一个外部程序连接到该端口,就会显示一条连接信息 Established

19.3.1.2 TCP 连接秘密

当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的。这个端口由 TCP/IP 来分配,是不确定的,随机的。

3.2 UDP 网络通信编程

  1. DatagramSocketDatagramPacket 实现了基于 UDP 协议网络程序
  2. 没有明确的服务端和客户端,演变成数据的发送端和接收端
  3. UDP 数据报通过数据报套接字 DatagramSocket 发送和接收。系统不保证 UDP 数据报一定能安全送到目的地,也不能确定什么时候能抵达
  4. DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地址和端口号以及接收端的 IP 地址和端口号
  5. 接收到 DtagramPacket 对象时,需要进行拆包,取出数据
  6. DatagramSocket 可以指定在哪个端口接收数据
  7. UDP 协议中每个数据报都给出了完整的地址信息,因此无需发送方和接收方的连接

下面,示范一个接收端

1
2
3
4
5
6
7
8
9
DatagramSocket ds = new DatagramSocket(9000);					//[1]
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length); //[2]
System.out.println("萝茵 聆听中……");
ds.receive(dp); //[3]
int len = dp.getLength();
bytes = dp.getData();
System.out.println("萝茵听到了如下内容:\n" + new String(bytes, 0, len));
ds.close(); //[4]
  1. DatagramSocket ds = new DatagramSocket(9000);

    以 9000 这个端口作为监听端口

  2. DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

    构建 DatagramPacket 对象,准备接收数据

  3. ds.receive(dp);

    监听信息,放到刚刚创建的 DatagramPacket 对象

  4. ds.close()

    要记得关闭呦 ★ ~

下面,示范一个发送端

1
2
3
4
5
6
7
8
9
System.out.println("萝茵,大声喊道:你好,世界!");
DatagramSocket ds = new DatagramSocket(8000); //[1]
InetAddress ia = InetAddress.getByName(serverIP);
byte[] bytes = "你好,世界".getBytes(StandardCharsets.UTF_8);
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, ia, 9001);
//[2]
ds.send(dp); //[3]
System.out.println("声音在虚无中回荡着……");
ds.close(); //[4]
  1. DatagramSocket ds = new DatagramSocket(8000);

    以 8000 这个端口作为发送端口

  2. DatagramPacket dp = new DatagramPacket(bytes, bytes.length, ia, 9001);

    把要发送的数据、数据长度、对象地址、对象端口 放到包里

  3. ds.send(dp);

    走你 ★ ~

  4. ds.close();

    鸟尽弓藏

附录

项目开发流程

1 需求分析

需求分析师(懂技术 + 懂行业)

  1. 需求分析报告
    • 项目功能
    • 客户要求

2 设计阶段

架构师 / 项目经理

  1. 设计工作
    • UML 类图
    • 流程图
    • 模块设计
    • 数据库设计
    • 架构
  2. 原型开发
  3. 组建团队

3 实现阶段

程序员 / 码农

  1. 完成架构师的模块功能
  2. 测试自己的模块

4 测试阶段

测试工程师

  1. 单元测试
  2. 测试用例
  3. 白盒测试
  4. 黑盒测试
  5. 集成测试

5 实施阶段

实施工程师(开发能力 / 环境配置部署能力)

  1. 把项目正确地部署到客户的平台,并保证运行正常
  2. 身体好

6 维护阶段

  1. 发现 bug 并解决
  2. 项目升级

2 反射

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

ocp 原则(开闭原则):不修改源码来扩展功能

计算机的三个阶段

  1. 代码阶段 / 编译阶段

    编写代码 ——(Javac 编译)——> .class 字节码文件

  2. Class 类阶段 / 加载阶段

    字节码文件 ——(ClassLoader 类加载器)——> Class 类对象(堆中)· 字节码二进制数据 / 元数据(方法区)

    Class 类对象包含:成员变量 Field[] fields、构造器 Constructor[] cons、成员方法 Methord[] ms

  3. Runtime 运行阶段

    创建对象,该对象知道其属于哪个 Class 对象

反射机制可以完成

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

1. 反射相关的常用类

  1. java.lang.Class:代表一个类。Class 对象表示某个类加载后在堆中的对象

    1
    2
    Class cls = Class.forName(classFullPath);			//[1]
    Object o = cls.newInstance(); //[2]
    1. 通过完整类名得到一个类的 Class 对象
    2. 通过该 Class 对象创建一个该类的 对象实例
  2. java.lang.reflect.Method:代表类的方法。Method 对象表示某个类的某个方法

    1
    2
    Method method = cls.getMethod(methodName);			//[1]
    method.invoke(o); //[2]
    1. 通过该 Class 对象得到一个 方法对象
    2. 方法对象.invoke:调用该方法
  3. java.lang.reflect.Field:代表类的成员变量

    1
    2
    Field field = cls.getField(fieldName);				//[1]

    1. 该方法只能得到非私有对象
  4. java.lang.reflect.Constructor:代表类的构造方法

    1
    2
    3
    Constructor constructor = cls.getConstructor();		//[1]
    Constructor constructor2 = cls.getConstructor(String.class)
    //[2]
    1. 得到一个无参构造器
    2. 得到一个形参是 (String str) 的构造器

反射的优点和缺点

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

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

  1. MethodFieldConstructor 对象都有 setAccessible() 方法

  2. setAccessible() 作用是启动和禁用访问安全检查的开关

  3. 参数值为 true,表示反射对象在使用时取消访问检查,这样能提高反射效率。

    为 false 表示执行访问检查

1.2 Class

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

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
2
package com.melody.note;
class Test {}
  1. (编译阶段)已知一个类的全类名,且该类在类路径下:

    1
    Class cls1 = Class.forName("com.melody.note.Test");

    应用场景:配置文件,读取类全路径,加载类。

    可能抛出 ClassNotFoundExcption

  2. (加载阶段)已知具体的类:

    1
    Class cls2 = Test.class;

    应用场景:参数传递。

    该方法最为安全

  3. (运行阶段)已知某个类的实例:

    1
    Class cls3 = new Test().getClass();

    应用场景:通过创建好的对象获取 Class 对象

  4. 通过类加载器:

    1
    2
    ClassLoader cll = new Test().getClass().getClassLoader();
    Class cls4 = cll.loadClass("com.melody.note.Test");
  5. 基本数据类型:

    1
    2
    Class clsB1 = int.class;
    Class<Boolean> clsB2 = boolean.class;
  6. 基本数据类型包装类:

    1
    2
    Class clsB3 = Character.TYPE;
    Class<Long> clsB4 = Long.TYPE;

1.2.3 哪些类有 Class 对象

  1. 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  2. 接口(interface)
  3. 数组
  4. 枚举(enum)
  5. 注解
  6. 基本数据类型
  7. void

1.3 类的加载

基本说明

反射机制是 Java 实现动态语言的关键,也就是通过反射实现类动态加载

  1. 静态加载:编译时加载相关的类,如果没有则报错。依赖性强
  2. 静态加载:运行时加载需要的类,如果运行时不用该类,则不报错。降低了依赖性

类加载时机

  1. 创建对象时(new) [静态加载]
  2. 子类被加载时,父类也被加载 [静态加载]
  3. 调用类中的静态成员 [静态加载]
  4. 通过反射 [动态加载]
  • 加载(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
    3
    public 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 通过反射创建对象

  1. 调用类中的 public 修饰的无参构造器

    1
    Object obj1 = cls.newInstance();
  2. 调用类中指定的构造器

    1
    2
    Constructer cons = cls.getConstructer(int.class, String.class, ...);
    Object obj2 = cons.newInstance(1, "nnn", ...);
  3. setAccessible(true):爆破(暴力破解)。使用反射可以访问 private 构造器

    1
    2
    3
    Constructer 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]
  1. o 表示一个类的实例

    如果该属性是静态属性,则对象 o 可以是 null

1
2
3
Method method = cls.getDeclaredMethod("m1");
method.setAccessible(true);
Object returnObj = method.invoke(o, 'c', ...); //[1]
  1. o 表示一个类的实例,后面是实参列表

    同理,静态方法的场合,对象 o 可以是 null

3. 正则表达式

正则表达式:对字符串执行模式匹配的技术。一个正则表达式,就是用某种模式去匹配字符串的一个公式。除 Java 外,还有许多语言支持正则表达式。

1
2
3
4
5
6
7
String content = "HeruinKCoin";				//对象文本
String regular = "[A-Z]"; //[1] 创建规则
Pattern pattern = Pattern.compile(regular); //[2] 创建模式对象
Matcher matcher = pattern.matcher(content); //[3] 创建匹配器
while (matcher.find()){ //[4] find() 是否找到下一个
System.out.println(matcher.group(0)); //[5] group(0) 输出找到的当前对象
}

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
    3
    String 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):分割内容