BUGFIX 09 - 记一次Java中String的split正则表达式匹配 - 引发`OutOfMemoryError: Java heap space`的oom异常 排查及解决 -Java根据指定分隔符分割字符串,忽略在引号里面的分隔符
问题简述
说白了,Java根据指定分隔符分割字符串,忽略在引号(单引号和双引号)里面的分隔符; oom压测的时候,正则匹配"(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)(?=(?:[^']*'[^']*')*[^']*$)" 挂掉了,栈溢出了.
压测使用了200k的sql字符串,也就是200*1024Byte的字符串,单层时间复杂度就有2*10^5,不说时间的问题,正则匹配的迭代量太大,往往2*10^5中首次就可以匹配到上千个分隔符,上千1个再向后迭代,云云.
本地复现,debug一遍找到漏洞点
使用正则,200K的字符串扛不住;量小的话,运算时间也挺长的
/**
* 根据指定分隔符分割字符串---忽略在引号里面的分隔符
* @param str
* @param delimter
* @Deprecated Reason : 针对200K大小的sql任务,会存在OOM的问题
* @return
*/
public static String[] splitIgnoreQuota(String str, String delimter){
String splitPatternStr = delimter + "(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)(?=(?:[^']*'[^']*')*[^']*$)";
return str.split(splitPatternStr);
}
不使用正则, 完全通过使用单层for循环完全重写String的split方法, 废弃正则表达式, OOM的问题得到解决,秒出结果!
/**
* 使用非正则表达式的方法来实现 `根据指定分隔符分割字符串---忽略在引号里面的分隔符`
* @param str
* @param delimiter 分隔符
* @return
*/
public static String[] splitIgnoreQuotaNotUsingRegex(String str, String delimiter) {
// trim
str = str.trim();
// 遍历出成对的双引号的位置区间,排除转义的双引号
List<Pair<Integer, Integer>> doubleQuotas = getQuotaIndexPairs(str, '\"');
// 遍历出成对的单引号的位置区间,排除转义的单引号
List<Pair<Integer, Integer>> singleQuotas = getQuotaIndexPairs(str, '\'');
// 遍历出所有的delimiter的位置,排除掉在上述两个区间中的,排除掉转义的,按该delimiter位置拆分字符串
List<String> splitList = new ArrayList<>(128);
// index 表示目前搜索指针下标
// beforeIndex 表示目前已经成功匹配到的指针下标
int index = 0, beforeIndex = -1;
while ((index = str.indexOf(delimiter, Math.max(beforeIndex + 1, index))) != -1) {
// 排除转义
if (index == 0 || str.charAt(index - 1) != '\\') {
boolean flag = false;
// 排除双引号内的
for (Pair<Integer, Integer> p : doubleQuotas) {
if (p.getKey() <= index && p.getValue() >= index) {
flag = true;
break;
}
}
// 排除单引号内的
for (int i = 0; !flag && i < singleQuotas.size(); i++) {
Pair<Integer, Integer> p = singleQuotas.get(i);
if (p.getKey() <= index && p.getValue() >= index) {
flag = true;
break;
}
}
// flag = true, 表示该字符串在匹配的成对引号,跳过
if(flag){
index++;
continue;
}
// 这里的substring只取到分隔符的前一位,分隔符不加进来
splitList.add(str.substring(beforeIndex + 1, index));
beforeIndex = index;
} else {
index++;
}
}
// 收尾串
if (beforeIndex != str.length()) {
splitList.add(str.substring(beforeIndex + 1, str.length()));
}
return splitList.toArray(new String[0]);
}
/**
* 遍历出成对的双/单引号的位置区间,排除转义的双引号
* @param str
* @param quotaChar
* @return
*/
private static List<Pair<Integer, Integer>> getQuotaIndexPairs(String str, char quotaChar) {
List<Pair<Integer, Integer>> quotaPairs = new ArrayList<>(64);
List<Integer> posList = new ArrayList<>(128);
for (int idx = 0; idx < str.length(); idx++) {
if (str.charAt(idx) == quotaChar) {
if (idx == 0 || str.charAt(idx - 1) != '\\') {
posList.add(idx);
}
}
}
// 每两个装进Pair中,总数为奇数的话最后一个舍掉
for (int idx = 0; idx <= posList.size() - 2; idx += 2) {
quotaPairs.add(new Pair<>(posList.get(idx), posList.get(idx + 1)));
}
return quotaPairs;
}
样例输入 简单单测
@Test
public void test02() throws Exception {
String builder = "create table if not exists exam_ads_sales_all_d (\n" +
" stat_date string comment '统计日期'\n" +
" ,ord_quantity bigint comment '订单数量'\n" +
" ,ord_amount double comment '订单金额'\n" +
" ,pay_quantity bigint comment '付款数量'\n" +
" ,pay_amount double comment '付款金额'\n" +
" ,shop_cnt bigint comment '有交易的店铺数量'\n" +
")comment '测试;订单交易总表'\n" +
"PARTITIONED BY (ds string) lifecycle 7;select * from exam_ads_sales_all_d";
String[] splits = MyFormatter.splitIgnoreQuota(builder.toString(), ";");
System.out.println("================splitIgnoreQuota分割后行数: " + splits.length);
for (int i = 0; i < splits.length; i++) {
System.out.println(splits[i]+"\n");
}
String[] splits2 = DtStringUtil.splitIgnoreQuotaNotUsingRegex(builder.toString(), ";");
System.out.println("================splitIgnoreQuotaNotUsingRegex分割后行数: " + splits2.length);
for (int i = 0; i < splits2.length; i++) {
System.out.println(splits2[i]+"\n");
}
Assert.assertEquals(splits.length, splits2.length);
}
样例输出
================splitIgnoreQuotaNotUsingRegex分割后行数: 2
create table if not exists exam_ads_sales_all_d (
stat_date string comment '统计日期'
,ord_quantity bigint comment '订单数量'
,ord_amount double comment '订单金额'
,pay_quantity bigint comment '付款数量'
,pay_amount double comment '付款金额'
,shop_cnt bigint comment '有交易的店铺数量'
)comment '测试;订单交易总表'
PARTITIONED BY (ds string) lifecycle 7
select * from exam_ads_sales_all_d
- 为了后续的业务需求,算法中去掉了分隔符(有注释);更多问题,欢迎指正!
- class Pair 引用自package org.apache.commons.math3.util; 自行添加maven依赖
码字不易啊~~

BUGFIX 09 - 记一次Java中String的split正则表达式匹配 - 引发`OutOfMemoryError: Java heap space`的oom异常 排查及解决 -Java根据指定分隔符分割字符串,忽略在引号里面的分隔符的更多相关文章
- Java中String的split()方法的一些需要注意的地方
public String[] split(String regex, int limit) split函数是用于使用特定的切割符(regex)来分隔字符串成一个字符串数组,这里我就不讨论第二个参数( ...
- Java中String的split()方法的一些疑问和试验
http://tjuking.iteye.com/blog/1507855 和我想的还是不大一样,因为不知道源码也不知道具体是怎么实现的,我的理解如下: 当字符串只包含分隔符时,返回数组没有元素:当字 ...
- Java中String和byte[]间的 转换
数据库的字段中使用了blob类型时,在entity中此字段可以对应为byte[] 类型,保存到数据库中时需要把传入的参数转为byte[]类型,读取的时候再通过将byte[]类型转换为String类型. ...
- Java中String类的方法及说明
String : 字符串类型 一. String sc_sub = new String(c,3,2); // String sb_copy = new String(sb) ...
- 【转载】Java中String类的方法及说明
转载自:http://www.cnblogs.com/YSO1983/archive/2009/12/07/1618564.html String : 字符串类型 一. String sc_ ...
- 【转载】 Java中String类型的两种创建方式
本文转载自 https://www.cnblogs.com/fguozhu/articles/2661055.html Java中String是一个特殊的包装类数据有两种创建形式: String s ...
- java成神之——java中string的用法
java中String的用法 String基本用法 String分割 String拼接 String截取 String换行符和format格式化 String反转字符串和去除空白字符 String获取 ...
- Java中String类的特殊性
java中特殊的String类型 Java中String是一个特殊的包装类数据有两种创建形式: String s = "abc"; String s = new String(&q ...
- java中string内存的相关知识点
(一):区别java内存中堆和栈: 1.栈:数据可以共享,存放基本数据类型和对象的引用,其中对象存放在堆中,对象的引用存放在栈中: 当在一段代码块定义一个变量时,就在栈中 为这个变量分配内存空间,当该 ...
随机推荐
- C语言作业12—学期总结
一.我学到的内容 二.我的作业及收获 我的作业: 第一次作业 第二次作业 第三次作业 第四次作业 第五次作业 第六次作业 第七次作业 第八次作业 第九次作业 第十次作业 第十一次作业 我的收获: 转眼 ...
- windows 安装mongodb2
安装文件:mongodb-win32-x86_64-2008plus-ssl-3.2.6-signed.msi 电脑配置:win7 64位 MongoDB的安装很简单,设置好安装路径后,一直Next直 ...
- 命令行下使用RAR和7-Zip压缩数据
3.6.1 RAR Winrar的命令行模式程序在安装目录下的 rar.exe (打包压缩程序),unrar.exe(解压缩程序) WinRAR的常用参数如下: -a 添加文件到压缩文件 -k 锁定压 ...
- python安装BeautifulSoup4
今天学习使用python爬虫,书上说让安装一个BeautifulSoup的插件,网上找了,说是下载压缩文件,直接解压然后,通过两行代码就实现了,我下载了两个,还是无法使用,后来就使用在在Pycharm ...
- Java使用反射实现根据字符串类名及参数创建对象
要根据字符串创建对象,可以使用 Class.forName(String) 方法: 而要新建一个可以指定初始值参数的对象,就必须得使用 getConstructor(Class<T>... ...
- [win]更改win终端编码
更改cmd的编码格式 chcp: 显示当前的编码格式 chcp 65001: 更改当前编码格式为UTF-8 字体选择`Lucida Console` 更改PowerShell编码格式(from zhi ...
- kendo-ui 几个有用的数据操作
在工作中发现几个有用的api: 一,grid1.获得grid var grid = $("#proList").data("kendoGrid");2.获得da ...
- 【题解】P1559 运动员最佳匹配问题
[题目](https://www.luogu.com.cn/problem/P1559) 题目描述 羽毛球队有男女运动员各n人.给定2 个n×n矩阵P和Q.P[i][j]是男运动员i和女运动员j配对组 ...
- MongoDB疑难解析:为什么升级之后负载升高了?
本文是"我和MongoDB的故事"征文比赛的二等奖得主李鹏冲的文章.下面我们一起来欣赏下. 问题 近期线上一个三分片集群从 3.2 版本升级到 4.0 版本以后,集群节点的 CPU ...
- Andriod you must restart adb and eclipse
今天看着视频 学习着 andriod ,启动 的时候 竟然报错 我试了N种google来的方法,都失效,现在把我的解决方法告诉大家,希望能帮到大家. 首先,我先罗列下我搜到的方法,大家也可以尝试. 1 ...