问题简述

说白了,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根据指定分隔符分割字符串,忽略在引号里面的分隔符的更多相关文章

  1. Java中String的split()方法的一些需要注意的地方

    public String[] split(String regex, int limit) split函数是用于使用特定的切割符(regex)来分隔字符串成一个字符串数组,这里我就不讨论第二个参数( ...

  2. Java中String的split()方法的一些疑问和试验

    http://tjuking.iteye.com/blog/1507855 和我想的还是不大一样,因为不知道源码也不知道具体是怎么实现的,我的理解如下: 当字符串只包含分隔符时,返回数组没有元素:当字 ...

  3. Java中String和byte[]间的 转换

    数据库的字段中使用了blob类型时,在entity中此字段可以对应为byte[] 类型,保存到数据库中时需要把传入的参数转为byte[]类型,读取的时候再通过将byte[]类型转换为String类型. ...

  4. Java中String类的方法及说明

    String : 字符串类型 一.      String sc_sub = new String(c,3,2);    //      String sb_copy = new String(sb) ...

  5. 【转载】Java中String类的方法及说明

    转载自:http://www.cnblogs.com/YSO1983/archive/2009/12/07/1618564.html String : 字符串类型 一.      String sc_ ...

  6. 【转载】 Java中String类型的两种创建方式

    本文转载自 https://www.cnblogs.com/fguozhu/articles/2661055.html Java中String是一个特殊的包装类数据有两种创建形式: String s ...

  7. java成神之——java中string的用法

    java中String的用法 String基本用法 String分割 String拼接 String截取 String换行符和format格式化 String反转字符串和去除空白字符 String获取 ...

  8. Java中String类的特殊性

    java中特殊的String类型 Java中String是一个特殊的包装类数据有两种创建形式: String s = "abc"; String s = new String(&q ...

  9. java中string内存的相关知识点

    (一):区别java内存中堆和栈: 1.栈:数据可以共享,存放基本数据类型和对象的引用,其中对象存放在堆中,对象的引用存放在栈中: 当在一段代码块定义一个变量时,就在栈中 为这个变量分配内存空间,当该 ...

随机推荐

  1. Mesh R-CNN 论文翻译(原理部分)

    毕设做Mesh R-CNN的实现,在此翻译一下原论文.原论文https://arxiv.org/pdf/1906.02739.pdf. 摘要 二维感知的快速发展使得系统能够准确地检测真实世界图像中的物 ...

  2. CUDA 计算pi (π)

    通过简单的程序设计熟练CUDA的使用步骤 下面是cuda代码及相关注释 #include <stdio.h> #include <iostream> #include < ...

  3. JMeter入门 | 第一个并发测试

    JMeter入门 | 第一个并发测试 背景 近期我们组新来了一些新同事,之前从来没有用过JMeter做个并发测试,于是准备了一系列小教程去指引新同事,本章主要是新人入门体验教程,快速实现第一个接口并发 ...

  4. 文件系统(01):基于SpringBoot框架,管理Excel和PDF文件类型

    本文源码:GitHub·点这里 || GitEE·点这里 一.文档类型简介 1.Excel文档 Excel一款电子表格软件.直观的界面.出色的计算功能和图表工具,在系统开发中,经常用来把数据转存到Ex ...

  5. 快乐编程大本营【java语言训练班】第5课: java的数组编程

    快乐编程大本营[java语言训练班]第5课: java的数组编程 第1节. 声明数组变量 第2节. 创建数组对象 第3节. 访问数组元素 第4节. 修改数组元素 第5节. 多维数组 学习地址如下:ht ...

  6. Unreal Engine 4 蓝图完全学习教程(一)—— 简要介绍

    首先启动UE4: 新建项目类型为游戏: 选择空项目Blank: 项目设置选项: 点击创建项目: 打开后的窗口称为:“关卡编辑器”,由多个面板组成.在UE中,设计3D场景的空间称为“关卡”. 简单介绍一 ...

  7. Python小白入门题一——文件增删改

    题目描述:用python对文件进行增(创建一个文件).删(删除一个文件).改(重命名)操作. 说明:新建了一个文件夹files存放新增的两个文件,随后这两个文件被批量重命名成“数字.txt”,之后这两 ...

  8. illegal use of this type as an expression

    学习MCI时看别人样例手敲代码出现的一个很经典的错误. 在C语言中定义的变量没有放在函数的开头. #include <string.h> #include <windows.h> ...

  9. python环境安装及配置

    一.下载python,可选择python2.x或python 3.0 下载地址:[官网],选择系统 ---选择对应版本 注意自己电脑是32位(X86)还是64位(x86-64) 下载文件包,点击点击安 ...

  10. WeChall_ Training: Stegano I (Training, Stegano)

    This is the most basic image stegano I can think of. 解题: 一张小图片,文本方式打开.