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语言秋季作业3
本周作业头 这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 作业链接 我在这个课程的目标是 运用C语言编程解决一些简单的数学问题 这个作业在那个具体方面帮助我实现目标 学习if else ...
- SpringBoot消息篇Ⅲ --- 整合RabbitMQ
知识储备: 关于消息队列的基本概念我已经在上一篇文章介绍过了(传送门),本篇文章主要讲述的是SpringBoot与RabbitMQ的整合以及简单的使用. 一.安装RabbitMQ 1.在linux上 ...
- 关于Windows Server 服务器 安装tomcat部署Java Web 项母
抄至 http://blog.csdn.net/cx0330/article/details/68957914 我遇到的问题是:不知道怎么配置,感觉在服务器上部署一个web项目,应该是很高大上,步骤应 ...
- 一文教你一次性完成Helm 3迁移
2019年,Kubernetes软件包管理器--Helm发布了最新版本Helm 3,并且该版本已经stable.Helm 3中的一些关键特性我们在之前的文章中已经介绍过,其中一些功能吸引了许多开发人员 ...
- 西门子PLC在自动浇灌系统中的应用
西门子PLC在自动浇灌系统中的应用(鸿控整理) 2020-02-07 22:50:48 1 自动浇灌系统简介 系统采用自行研制的湿度传感器监测土壤的湿度情况,当土壤湿度低于所要求的值后,自动开启水泵电 ...
- struts.xml头部代码
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "- ...
- MSVC下快速Unicode I/O
http://blog.kingsamchen.com/archives/863 如果需要往console输出包含非ASCII字符的宽字符串,一个比较快速的方法是使用WriteConsoleW这个AP ...
- C语言中typedef用法
C语言中typedef用法 1. 基本解释 typedef为C语言的关键字,作用是为一种数据类型定义一个新名字.这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等) ...
- 开源项目SMSS发开指南(五)——SSL/TLS加密通信详解(下)
继上一篇介绍如何在多种语言之间使用SSL加密通信,今天我们关注Java端的证书创建以及支持SSL的NioSocket服务端开发.完整源码 一.创建keystore文件 网上大多数是通过jdk命令创建秘 ...
- 视觉slam十四讲第8章课后习题3+稀疏直接法程序注释
版权声明:本文为博主原创文章,转载请注明出处: http://www.cnblogs.com/newneul/p/8571653.html 3.题目回顾:在稀疏直接法中,假设单个像素周围小块的光度也不 ...