女朋友最近要用到电力系统分析相关的一些数据, 但是用到的BPA软件生成的文件有好几个, 八万多行的文件都有. 每次处理起来都是自己手动输入, 很是麻烦. 我今天一看, 不就是基本的文件I/O加上正则表达嘛, 这又什么难得, Python走起!
一. 需求分析
多个类似于:
# File name: 33bpa.lis
Wall clock : Sun Sep 08 09:52:36 2019
<MULTI_NODE_EQUAL_RESULT>
.# ??? ?????? ?????? ?????? ??????
.# G0+jB0(p.u.) G1+jB1(p.u.) G2+jB2(p.u.) Im(kA) Ia(deg)
.#---------------- ------------------ ------------------ ------------------ ------------------
B "BUS-30 " 100.0 0.0000 0.0000 3.4355 -51.9441 3.2023 -52.7944 30.0554 -86.2160
B "BUS-31 " 100.0 0.0000 0.0000 3.9110 -30.8303 3.5087 -33.5190 17.9425 -82.7702
B "BUS-32 " 100.0 0.0000 0.0000 3.6146 -36.5561 3.2913 -38.7305 21.2086 -84.3531
........................
SWING CASE: 39测试 POWERFLOW CASE: 039bpa * * * 输 出 信 息 * * * Date: 2019-09-06
* 发电机"BUS-30 100.0 "输出数据列表
功角 (度) 最大值 = -51.3867/ 74.0 最小值 = -51.3869/ 186.0 参考发电机 BUS-31 100.0
0.0 -51.3868 0.0 -51.3868 1.0 -51.3868 2.0 -51.3868 3.0 -51.3868 4.0 -51.3867 5.0 -51.3867 6.0 -51.3867
7.0 -51.3867 8.0 -51.3867 9.0 -51.3867 10.0 -51.3867 11.0 -51.3868 12.0 -51.3868 13.0 -51.3868 14.0 -51.3868
15.0 -51.3868 16.0 -51.3868 17.0 -51.3868 18.0 -51.3868 19.0 -51.3868 20.0 -51.3868 21.0 -51.3868 22.0 -51.3868
23.0 -51.3868 24.0 -51.3868 25.0 -51.3868 26.0 -51.3868 27.0 -51.3868 28.0 -51.3868 29.0 -51.3868 30.0 -51.3868
31.0 -51.3868 32.0 -51.3868 33.0 -51.3868 34.0 -51.3868 35.0 -51.3868 36.0 -51.3868 37.0 -51.3868 38.0 -51.3868
39.0 -51.3868 40.0 -51.3868 41.0 -51.3868 42.0 -51.3868 43.0 -51.3868 44.0 -51.3868 45.0 -51.3868 46.0 -51.3868
47.0 -51.3868 48.0 -51.3868 49.0 -51.3868 50.0 -51.3868 51.0 -51.3868 52.0 -51.3868 53.0 -51.3868 54.0 -51.3868
55.0 -51.3868 56.0 -51.3868 57.0 -51.3868 58.0 -51.3868 59.0 -51.3868 60.0 -51.3868 61.0 -51.3868 62.0 -51.3868
......................
这样的文件中提取某些参数, 并构建对应的矩阵, 包括对称矩阵等.
二. 实现分析
1. 构建配置文件:
首先创建config.json配置文件, 今后从此文件中读取配置, 而不用在源码上进行修改!
{
"data_folder": "E:\\BPA_generator",
"out_file": "ieee90.OUT",
"swi_file": "IEEE90.SWI",
"lis_file": "ieee90.lis",
"motor_number": [1,2,3],
"fault_time": 1,
"save_to": ".\\generate.txt"
}
在python中读取配置:
# 读入配置文件
with open("config.json") as f:
config = json.load(f)
data_folder = config["data_folder"]
out_file = config["out_file"]
swi_file = config["swi_file"]
lis_file = config["lis_file"]
motor_number = config["motor_number"]
fault_time = float(config["fault_time"])
motor_dict = dict()
for i in range(len(motor_number)):
motor_dict[motor_number[i]] = i
save_to = config["save_to"]
其中motor_dict用于构建将给定的发电机序号与数组下标进行一一对应的反向索引! 这样可以在寻找到对应的发电机数据后, 写入对应的结果数组!
2. 分析
1): *.OUT文件
SWING CASE: 39测试 POWERFLOW CASE: 039bpa * * * 输 出 信 息 * * * Date: 2019-09-06
* 发电机"BUS-30 100.0 "输出数据列表
功角 (度) 最大值 = -51.3867/ 74.0 最小值 = -51.3869/ 186.0 参考发电机 BUS-31 100.0
0.0 -51.3868 0.0 -51.3868 1.0 -51.3868 2.0 -51.3868 3.0 -51.3868 4.0 -51.3867 5.0 -51.3867 6.0 -51.3867
7.0 -51.3867 8.0 -51.3867 9.0 -51.3867 10.0 -51.3867 11.0 -51.3868 12.0 -51.3868 13.0 -51.3868 14.0 -51.3868
15.0 -51.3868 16.0 -51.3868 17.0 -51.3868 18.0 -51.3868 19.0 -51.3868 20.0 -51.3868 21.0 -51.3868 22.0 -51.3868
23.0 -51.3868 24.0 -51.3868 25.0 -51.3868 26.0 -51.3868 27.0 -51.3868 28.0 -51.3868 29.0 -51.3868 30.0 -51.3868
31.0 -51.3868 32.0 -51.3868 33.0 -51.3868 34.0 -51.3868 35.0 -51.3868 36.0 -51.3868 37.0 -51.3868 38.0 -51.3868
39.0 -51.3868 40.0 -51.3868 41.0 -51.3868 42.0 -51.3868 43.0 -51.3868 44.0 -51.3868 45.0 -51.3868 46.0 -51.3868
47.0 -51.3868 48.0 -51.3868 49.0 -51.3868 50.0 -51.3868 51.0 -51.3868 52.0 -51.3868 53.0 -51.3868 54.0 -51.3868
55.0 -51.3868 56.0 -51.3868 57.0 -51.3868 58.0 -51.3868 59.0 -51.3868 60.0 -51.3868 61.0 -51.3868 62.0 -51.3868
速度偏差 (Hz) 最大值 = 14.8872/ 295.0 最小值 = 0.0000/ 0.0
0.0 0.0000 0.0 0.0000 1.0 0.1273 2.0 0.2546 3.0 0.3820 4.0 0.5094 5.0 0.6368 6.0 0.7643
7.0 0.8919 8.0 1.0196 9.0 1.1474 10.0 1.2755 10.0 1.2755 11.0 1.2734 12.0 1.2599 13.0 1.2395
14.0 1.2154 15.0 1.1896 16.0 1.1639 17.0 1.1392 18.0 1.1166 19.0 1.0967 20.0 1.0802 21.0 1.0675
22.0 1.0590 23.0 1.0548 24.0 1.0553 25.0 1.0606 26.0 1.0708 27.0 1.0860 28.0 1.1062 29.0 1.1314
30.0 1.1617 31.0 1.1971 32.0 1.2377 33.0 1.2838 34.0 1.3355 35.0 1.3932 36.0 1.4575 37.0 1.5291
38.0 1.6088 39.0 1.6979 40.0 1.7977 41.0 1.9098 42.0 2.0361 43.0 2.1784 44.0 2.3388 45.0 2.5186
46.0 2.7182 47.0 2.9362 48.0 3.1684 49.0 3.4068 50.0 3.6398 51.0 3.8528 52.0 4.0308 53.0 4.1620
......................
*.OUT类型文件如上所示, 要求找出功角数据下面的与配置文件中给定的初始故障数据相同的数据, 如: 1: -51.3868.
分析: 由于有多台发电机都有这样的数据, 且有可能出现功角, 转速等多个条目, 但是功角一定是首个出现的条目. 所以可以先通过正则表达式寻找到: 发电机”BUS-30 100.0 “输出数据列表 字段, 然后再按行找到对应的时间, 加入结果集即可.(这里用到了正则分隔.)
代码:
############# 1. 故障时各台电机功角 #################
fault_power_angle = list()
# 读取发电机功角行向量
with open(data_folder + "\\" + out_file, 'r', encoding='gbk') as f:
find_flag = False
for line in f:
line = line.strip()
if line.find('发电机') >= 0 and line.find('输出数据列表') >= 0:
motor_index = int(re.findall(r"\d+\.?\d*", line)[0])
if (motor_number.__contains__(motor_index)):
find_flag = True
continue
if find_flag:
line_data = [x.strip() for x in re.split(r" +", line)]
if len(line_data) < 8 or not is_all_float(line_data):
continue
line_data = [float(x) for x in line_data]
# 寻找故障停止时间的功角
for i in range(0, len(line_data), 2):
time = line_data[i]
power_angle = line_data[i + 1]
if time == fault_time:
fault_power_angle.append(power_angle)
find_flag = False
break
# print(fault_power_angle)
按行读入文件(注: 在win下生成的文件为
gbk
编码, 所以需要添加encoding), 并判断是否包含相应的字符串使用str.find()
方法;一旦发现了数据标题标志, 将标题中的电机序号提出:
# 原字符串为: *发电机"BUS-30 100.0 "输出数据列表* motor_index = int(re.findall(r"\d+\.?\d*", line)[0])
使用[\d+.?\d*]可以提取类似于: [数字.数字****]的效果(其中小数点: .可要可不要!)
如果提取出的电机序号在给定的电机需求之内
list.\__contains__(e)
, 则将find_flag
置为True
, 此时表示, 要在接下来的数据中寻找所需的数据!当
find_flag
被置为True
后, 表示接下来的数据可以在下文中找到!分析每一行的数据, 将数据按照多个空格为间隔拆分为多个数据.
如果处理过的数据长度小于8(正常的数据应当每行为8个), 或者不全为float类型, 则这一行数据不合法! 判断数据是否为浮点数的函数:
# 字符串小数判断 def is_float(str): if str.count('.') == 1: #小数有且仅有一个小数点 left = str.split('.')[0] #小数点左边(整数位,可为正或负) right = str.split('.')[1] #小数点右边(小数位,一定为正) lright = '' #取整数位的绝对值(排除掉负号) if str.count('-') == 1 and str[0] == '-': #如果整数位为负,则第一个元素一定是负号 lright = left.split('-')[1] elif str.count('-') == 0: lright = left else: return False if right.isdigit() and lright.isdigit(): #判断整数位的绝对值和小数位是否全部为数字 return True else: return False else: return False # 判断一整行的数据是否都为浮点数 def is_all_float(arr): for s in arr: if not is_float(s): return False return True
如果这一簇数据符合条件, 则将数据全部浮点数化:
line_data = [float(x) for x in line_data]
之后寻找故障停止时间的功角数据, 由于数据为两个一组, 所以构建(0, len, 2)步长为2的index索引, 并寻找
一旦找到, 就将数据结果加入结果集中, 并将find_flag标志位清空.
2): *.lis文件
# File name: 33bpa.lis
Wall clock : Sun Sep 08 09:52:36 2019
<MULTI_NODE_EQUAL_RESULT>
.# ??? ?????? ?????? ?????? ??????
.# G0+jB0(p.u.) G1+jB1(p.u.) G2+jB2(p.u.) Im(kA) Ia(deg)
.#---------------- ------------------ ------------------ ------------------ ------------------
B "BUS-30 " 100.0 0.0000 0.0000 3.4355 -51.9441 3.2023 -52.7944 30.0554 -86.2160
B "BUS-31 " 100.0 0.0000 0.0000 3.9110 -30.8303 3.5087 -33.5190 17.9425 -82.7702
B "BUS-32 " 100.0 0.0000 0.0000 3.6146 -36.5561 3.2913 -38.7305 21.2086 -84.3531
........................
*.lis
文件格式如上所示, 要求提取指定行的R
与jX
的数据. 并构建对称矩阵.
############# 2. 正序阻抗 R与X #################
row = len(motor_number)
col = len(motor_number)
r_resistance_matrix = [[0] * row for c in range(col)]
x_resistance_matrix = [[0] * row for c in range(col)]
with open(data_folder + "\\" + lis_file, 'r', encoding='gbk') as f:
for line in f:
line = line.strip().replace("\"", "")
line_data = [x.strip() for x in re.split(r" +", line)]
if line_data[0] != "L":
continue
data_from = motor_dict[int(re.sub(r"\D", "", line_data[1]))]
data_to = motor_dict[int(re.sub(r"\D", "", line_data[3]))]
r = float(line_data[-4])
x = float(line_data[-3])
r_resistance_matrix[data_from][data_to] = r
x_resistance_matrix[data_from][data_to] = x
for i in range(row):
for j in range(col):
r_resistance_matrix[j][i] = r_resistance_matrix[i][j]
x_resistance_matrix[j][i] = x_resistance_matrix[i][j]
# print(r_resistance_matrix)
# print(x_resistance_matrix)
代码如上所示.
首先构建输出的矩阵NxN矩阵
r_resistance_matrix = [[0] * row for c in range(col)] x_resistance_matrix = [[0] * row for c in range(col)]
按行读入文件, 并进行数据预处理: 删去前后空格, 替换多余符号, 拆分数据等
判断分隔的行首个所需字符为:
"L"
当满足条件时, 可以通过正则表达式提取数字:通过将非数字字符替换为空字符!
data_from = motor_dict[int(re.sub(r"\D", "", line_data[1]))] data_to = motor_dict[int(re.sub(r"\D", "", line_data[3]))]
其中:
\D
表示非数字字符最终提取所需数据, 并通过反向索引字典, 找到对应的结果index位置, 加入!
最后, 根据上三角矩阵,
生成对角矩阵
for i in range(row): for j in range(col): r_resistance_matrix[j][i] = r_resistance_matrix[i][j] x_resistance_matrix[j][i] = x_resistance_matrix[i][j]
3): *.swi文件
有了以上处理方法, 处理最后一个文件显得很是简单!
############# 3. 发电机动能/平衡交流节点电压 #################
node_voltage = list()
motivate_energy = list()
with open(data_folder + "\\" + swi_file, 'r', encoding='gbk') as f:
for line in f:
line = line.strip().replace("\"", "")
line_data = [x.strip() for x in re.split(r" +", line)]
if line_data[0] != "MF":
continue
node_voltage.append(float(line_data[2]))
motivate_energy.append(float(line_data[3]))
# print(node_voltage)
# print(motivate_energy)
4): 保存文件
将文件按照配置路径保存为*.txt
格式即可!
def format_matrix(s):
return s.replace("], [", ";")
############# 4. 保存 #################
with open(save_to, 'w') as f:
f.write("############# 1. 故障时各台电机功角 #################\n")
f.write(str(fault_power_angle) + "\n\n")
f.write("############# 2. 正序阻抗 R与X #################\n")
f.write("# 1. 电阻\n")
f.write(format_matrix(str(r_resistance_matrix))[1:-1] + "\n\n")
f.write("# 2. 电抗\n")
f.write(format_matrix(str(x_resistance_matrix))[1:-1] + "\n\n")
f.write("############# 3. 发电机动能/平衡交流节点电压 #################\n")
f.write("# 1. 节点电压\n")
f.write(str(node_voltage) + "\n\n")
f.write("# 2. 动能\n")
f.write(str(motivate_energy) + "\n")
print("生成成功!")
此处由于保存时, 需要保存为MATLAB中的矩阵输入格式, 所以, 可以通过将Python中的数组字符串之间的], [
替换为;
并删除前后多余的[
和]
即可!
三. 正则表达式总结
1. 基本语法
1): 元字符
代码 | 说明 | 举例 |
---|---|---|
. | 匹配除换行符外任意一个字符 | |
[abc] | 字符集合,只能表示一个字符位置。匹配所包含的任意一个字符 | |
[^abc] | 字符集合,只能表示一个字符位置。匹配除去集合内字符的任意一个字符 | |
[a-z] | 字符范围,一个集合,表示一个字符位置匹配所包含的任意一个字符 | |
[^a-z] | 字符范围,一个集合,表示一个字符位置匹配除去集合内字符的任意一个字符 | |
\b | 匹配单词的边界 | |
\B | 不匹配单词的边界 | |
\d | 等同[0-9] 匹配一位数字 | |
\D | 等同[^0-9] 匹配一位非数字 |
|
\s | 匹配1位空白字符,包括换行符、制表符、空格等同[\f\r\n\t\v] | |
\S | 匹配1位非空白字符 | |
\w | 等同[a-zA-Z0-9_] 包含中文 | |
\W | 匹配\w之外的字符 |
2): 转义
- 凡是在正则表达式中有特殊意义的符号,转义时使用\
3): 重复
代码 | 说明 | 举例 |
---|---|---|
* | 前面的正则表达式重复0次或多次 | |
+ | 前面的正则表达式重复至少一次 | |
? | 前面的正则表达式重复0次或1次 | |
{n} | 重复n次 | |
{n,} | 重复n次以上 | |
{n,m} | 重复n到m次 |
4): 分组(捕获)断言
代码 | 说明 | 举例 | |||
---|---|---|---|---|---|
x | y | 匹配x或y | |||
(pattern) | 分组(捕获)后会自动分配组号从1开始可以改变优先级 \数字 匹配对应的分组(指的是前一个匹配上的分组的内容) | ||||
(?:pattern) | 只改变优先级不分组 | ||||
(? |
分组捕获 给组命名Python句法为(?P |
||||
(?=exp) | 零宽度正预测先行断言断言exp一定在匹配的右边出现 | ||||
(?<=exp) | 零宽度正回顾后发断言断言exp一定出现在匹配的左边出现 | ||||
(?!exp) | 零宽度负预测先行断言断言exp一定不会出现在右侧 | ||||
(?<!exp) | 零宽度负回顾后发断言断言exp一定不会出现在左侧 | ||||
(?#comment) | 注释 |
断言不会占用分组号
5): 贪婪与非贪婪
- 默认贪婪模式,尽可能多的匹配字符串
代码 | 说明 | 举例 |
---|---|---|
*? | 匹配任意次,尽可能少重复 | |
+? | 匹配至少一次,尽可能少重复 | |
?? | 匹配0或1次,尽可能少重复 | |
{n}? | 匹配至少n次,尽可能少重复 | |
{n,m}? | 匹配至少n次,至多m次,尽可能少重复 |
6): 引擎选项
代码 | 说明 | 举例 |
---|---|---|
IgnoreCase | 匹配时忽略大小写 | re.Ire.IGNORECASE |
Singleline | 单行模式,可穿透/n | re.Sre.DOTALL |
Multiline | 多行模式 | re.Mre.MULTILINE |
IgnorePatternWhitespace | 忽略表达式中空白字符,若要使用空白字符 | re.Xre.VERBOSE |
代码 说明 举例
IgnoreCase
匹配时忽略大小写 re.Ire.IGNORECASE
Singleline
单行模式,可穿透/n
re.Sre.DOTALL
Multiline
多行模式 re.Mre.MULTILINE
IgnorePatternWhitespace
忽略表达式中空白字符,若要使用空白字符 re.Xre.VERBOSE
2. Java中使用正则表达式
Java 提供了功能强大的正则表达式API,在java.util.regex
包下.
1): 正则表达式实例
下面是一个简单的Java正则表达式的例子,用于在文本中搜索 http://
String text = "This is the text to be searched " +
"for occurrences of the http:// pattern.";
String pattern = ".*http://.*";
boolean matches = Pattern.matches(pattern, text);
System.out.println("matches = " + matches);ches = " + matches);
示例代码实际上没有检测找到的 http://
是否是一个合法超链接的一部分,如包含域名和后缀(.com
,.net
等等)。代码只是简单的查找字符串 http://
是否出现。
2): Java中关于正则表达式的API
Pattern: java.util.regex.Pattern
类 java.util.regex.Pattern
简称 Pattern
, 是Java正则表达式API中的主要入口,无论何时,需要使用正则表达式,从Pattern 类开始!
- Pattern.matches()
检查一个正则表达式的模式是否匹配一段文本的最直接方法是调用静态方法Pattern.matches()
,示例如下:
String text =
"This is the text to be searched for occurrences of the pattern.";
String pattern = ".*is.*";
boolean matches = Pattern.matches(pattern, text);
System.out.println("matches = " + matches);
// true
上面代码在变量 text 中查找单词 “is” 是否出现,允许”is” 前后包含 0或多个字符(由 .* 指定).
Pattern.matches() 方法适用于检查一个模式在一个文本中出现一次的情况,或适用于Pattern类的默认设置。
如果需要匹配多次出现,甚至输出不同的匹配文本,或者只是需要非默认设置.需要通过Pattern.compile() 方法得到一个Pattern 实例。
- Pattern.compile()
如果需要匹配一个正则表达式在文本中多次出现,需要通过Pattern.compile()
方法创建一个Pattern对象。示例如下
String text =
"This is the text to be searched " +
"for occurrences of the http:// pattern.";
String patternString = ".*http://.*";
Pattern pattern = Pattern.compile(patternString);
可以在Compile 方法中,指定一个特殊标志:Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
Pattern 类包含多个标志(int 类型),这些标志可以控制Pattern 匹配模式的方式
。上面代码中的标志使模式匹配是忽略大小写.
- Pattern.matcher()
一旦获得了Pattern对象,接着可以获得Matcher对象。Matcher 示例用于匹配文本中的模式.示例如下Matcher matcher = pattern.matcher(text);
Matcher类有一个matches()方法,可以检查文本是否匹配模式。以下是关于Matcher的一个完整例子.
String text =
"This is the text to be searched " +
"for occurrences of the http:// pattern.";
String patternString = ".*http://.*";
Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(text);
boolean matches = matcher.matches();
System.out.println("matches = " + matches);
- Pattern.split()
Pattern 类的 split()方法,可以用正则表达式作为分隔符,把文本分割为String类型的数组。示例:
String text = "A sep Text sep With sep Many sep Separators";
String patternString = "sep";
Pattern pattern = Pattern.compile(patternString);
String[] split = pattern.split(text);
System.out.println("split.length = " + split.length);
for(String element : split){
System.out.println("element = " + element);
}
上例中把text 文本分割为一个包含5个字符串的数组。
- Pattern.pattern()
Pattern 类的 pattern 返回用于创建Pattern 对象的正则表达式,示例:
String patternString = "sep";
Pattern pattern = Pattern.compile(patternString);
String pattern2 = pattern.pattern();
上面代码中 pattern2 值为sep ,与patternString 变量相同。
Matcher: java.util.regex.Matcher
java.util.regex.Matcher
类用于匹配一段文本中多次出现一个正则表达式,Matcher
也适用于多文本中匹配同一个正则表达式。
Matcher
有很多有用的方法,详细请参考官方JavaDoc
。这里只介绍核心方法。
以下代码演示如何使用Matcher
String text = "This is the text to be searched for occurrences of the http:// pattern.";
String patternString = ".*http://.*";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(text);
boolean matches = matcher.matches();
首先创建一个Pattern,然后得到Matcher ,调用matches() 方法,返回true 表示模式匹配,返回false表示不匹配。
可以用Matcher 做更多的事。
- 创建Matcher
通过Pattern 的matcher() 方法创建一个Matcher。
String text =
"This is the text to be searched " +
"for occurrences of the http:// pattern.";
String patternString = ".*http://.*";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(text);
- matches()
Matcher 类的 matches() 方法用于在文本中匹配正则表达式
boolean matches = matcher.matches();
如果文本匹配正则表达式,matches() 方法返回true。否则返回false。
matches() 方法不能用于查找正则表达式多次出现。如果需要,请使用find(), start() 和 end() 方法。
- lookingAt()
lookingAt()
与matches()
方法类似,最大的不同是,lookingAt()
方法对文本的开头匹配正则表达式;
而matches()
对整个文本匹配正则表达式。换句话说,如果正则表达式匹配文本开头而不匹配整个文本,lookingAt()
返回true
,而matches()
返回false
。 示例:
String text =
"This is the text to be searched " +
"for occurrences of the http:// pattern.";
String patternString = "This is the";
Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(text);
System.out.println("lookingAt = " + matcher.lookingAt());
System.out.println("matches = " + matcher.matches());
上面的例子分别对文本开头和整个文本匹配正则表达式 “this is the”
. 匹配文本开头的方法lookingAt()
返回true。
对整个文本匹配正则表达式的方法 matches()
返回false
,因为整个文本包含多余的字符,而 正则表达式要求文本精确匹配"this is the"
,前后又不能有额外字符。
- find() + start() + end()
find()
方法用于在文本中查找出现的正则表达式,文本是创建Matcher
时,通过 Pattern.matcher(text)
方法传入的。如果在文本中多次匹配,find()
方法返回第一个,之后每次调用 find()
都会返回下一个。
start()
和 end()
返回每次匹配的字串在整个文本中的开始和结束位置。实际上, end()
返回的是字符串末尾的后一位,这样,可以在把 start()
和 end()
的返回值直接用在String.substring()
里。
String text =
"This is the text which is to be searched " +
"for occurrences of the word 'is'.";
String patternString = "is";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(text);
int count = 0;
while(matcher.find()) {
count++;
System.out.println("found: " + count + " : " + matcher.start() + " - " + matcher.end());
}
这个例子在文本中找到模式 “is” 4次,输出如下:
found: 1 : 2 - 4
found: 2 : 5 - 7
found: 3 : 23 - 25
found: 4 : 70 - 72
- reset()
reset()
方法会重置Matcher
内部的 匹配状态。当find()
方法开始匹配时,Matcher 内部会记录截至当前查找的距离。调用 reset()
会重新从文本开头查找。
也可以调用 reset(CharSequence)
方法. 这个方法重置Matcher
,同时把一个新的字符串作为参数传入,用于代替创建 Matcher 的原始字符串。
- group()
假设想在一个文本中查找URL链接
,并且想把找到的链接提取出来。当然可以通过 start()
和 end()
方法完成。但是用group()
方法更容易些。
分组在正则表达式中用括号表示,例如: (John)
此正则表达式匹配John, 括号不属于要匹配的文本。括号定义了一个分组。当正则表达式匹配到文本后,可以访问分组内的部分。
使用group(int groupNo)
方法访问一个分组。一个正则表达式可以有多个分组。每个分组由一对括号标记。想要访问正则表达式中某分组匹配的文本,可以把分组编号传入 group(int groupNo)
方法。
group(0)
表示整个正则表达式,要获得一个有括号标记的分组,分组编号应该从1开始计算。
String text = "John writes about this, and John writes about that," +
" and John writes about everything. " ;
String patternString1 = "(John)";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
System.out.println("found: " + matcher.group(1));
}
以上代码在文本中搜索单词John从每个匹配文本中,提取分组1,就是由括号标记的部分。输出如下
found: John
found: John
found: John
- 多分组
上面提到,一个正则表达式可以有多个分组,例如:(John) (.+?)
这个表达式匹配文本John 后跟一个空格,然后跟1个或多个字符,最后跟一个空格
。你可能看不到最后的空格。
这个表达式包括一些字符有特别意义。字符点 .
表示任意字符。 字符 +
表示出现一个或多个,和. 在一起表示 任何字符,出现一次或多次
。字符?
表示 匹配尽可能短的文本。
完整代码如下
String text =
"John writes about this, and John Doe writes about that," +
" and John Wayne writes about everything."
;
String patternString1 = "(John) (.+?) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
System.out.println("found: " + matcher.group(1) +
" " + matcher.group(2));
}
注意代码中引用分组的方式。代码输出如下
found: John writes
found: John Doe
found: John Wayne
- 嵌套分组
在正则表达式中分组可以嵌套分组,例如((John) (.+?))
这是之前的例子,现在放在一个大分组里. (表达式末尾有一个空格)。
当遇到嵌套分组时, 分组编号是由左括号的顺序确定的。上例中,分组1 是那个大分组。分组2 是包括John
的分组,分组3 是包括 .+?
的分组。当需要通过groups(int groupNo)
引用分组时,了解这些非常重要。
以下代码演示如何使用嵌套分组
String text =
"John writes about this, and John Doe writes about that," +
" and John Wayne writes about everything."
;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
System.out.println("found: ");
}
输出如下
found:
found:
found:
- replaceAll() + replaceFirst()
replaceAll()
和 replaceFirst()
方法可以用于替换Matcher搜索字符串中的一部分
。replaceAll()
方法替换全部匹配的正则表达式,replaceFirst()
只替换第一个匹配的。
在处理之前,Matcher 会先重置。所以这里的匹配表达式从文本开头开始计算。
示例如下
String text =
"John writes about this, and John Doe writes about that," +
" and John Wayne writes about everything."
;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
String replaceAll = matcher.replaceAll("Joe Blocks ");
System.out.println("replaceAll = " + replaceAll);
String replaceFirst = matcher.replaceFirst("Joe Blocks ");
System.out.println("replaceFirst = " + replaceFirst);
输出如下
replaceAll = Joe Blocks about this, and Joe Blocks writes about that,
and Joe Blocks writes about everything.
replaceFirst = Joe Blocks about this, and John Doe writes about that,
and John Wayne writes about everything.
输出中的换行和缩进是为了可读而增加的。
注意第1个字符串中所有出现 John 后跟一个单词 的地方,都被替换为 Joe Blocks 。第2个字符串中,只有第一个出现的被替换。
- appendReplacement() + appendTail()
appendReplacement()
和 appendTail()
方法用于替换输入文本中的字符串短语,同时把替换后的字符串附加到一个 StringBuffer
中。
当find()
方法找到一个匹配项时,可以调用 appendReplacement()
方法,这会导致输入字符串被增加到StringBuffer 中,而且匹配文本被替换。 从上一个匹配文本结尾处开始,直到本次匹配文本会被拷贝。
appendReplacement()
会记录拷贝StringBuffer
中的内容,可以持续调用find()
,直到没有匹配项。
直到最后一个匹配项目,输入文本中剩余一部分没有拷贝到 StringBuffer
. 这部分文本是从最后一个匹配项结尾,到文本末尾部分。通过调用 appendTail()
方法,可以把这部分内容拷贝到 StringBuffer
中.
String text =
"John writes about this, and John Doe writes about that," +
" and John Wayne writes about everything."
;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
StringBuffer stringBuffer = new StringBuffer();
while(matcher.find()){
matcher.appendReplacement(stringBuffer, "Joe Blocks ");
System.out.println(stringBuffer.toString());
}
matcher.appendTail(stringBuffer);
System.out.println(stringBuffer.toString());
注意我们在while循环中调用appendReplacement() 方法
。在循环完毕后调用appendTail()
。
代码输出如下:
Joe Blocks
Joe Blocks about this, and Joe Blocks
Joe Blocks about this, and Joe Blocks writes about that, and Joe Blocks
Joe Blocks about this, and Joe Blocks writes about that, and Joe Blocks
writes about everything.
四. 附录
正则表达式在线测试: https://c.runoob.com/front-end/854