对于开发人员来说,字符串分割很常见。那么我们就来掰一下他的由来和应用场景,在前后端交互场景中,用户批量操作,
比如批量审批退款,批量删除数据等等,这对于程序来说就是一种批量操作,这种问题有两种解决方案:
1)前端循环调后端接口
2)前端调用后端批量操作接口
显然第一种是不可取的,在大体量系统和网站中完全是不可行的,因为循环调用会增加前后端交互次数,网络环境不好时丢包、调用失败的概率成倍增加,并且增加服务器压力。
我们一般会采用第二种方式来实现批量操作,但是调用批量接口根据不同的业务场景也有两种常用的实现方式:
I 前端将完整的数据信息传给后端
II前端将关键标识传给后端
当然,第一种方式在批量新增数据的场景用的比较多,有一个缺点是依赖带宽和后端程序能够接收的数据大小阈值;第二种在查询更新和删除数据的场景下用的比较多,比如我根据会员id列表查询会员信息,批量审批退款以及批量删除无用数据,这时我们只需要将关键信息(比如id)用特殊字符隔开形成字符串传给后端,同样也可以使用I方式,将完整数据信息传送给后端,但是这种场景下完全没必要,并且服务端还需要反序列化成原来的数据样式,性能当然没有原生的plaint text好。
那么接下来,我们将对于业务场景中的前端传送给后端的关键信息形成的字符串分割成我们能够应用的关键字列表做详细分析。
场景引入
对于逆向交易的审批退款操作,有时候需要批量审批退款,这时候前端调用后端批量审批接口并将退款编号用逗号隔开传输参数,服务端的做法是先将字符串分割解析成退款编号列表,然后再调用真实的退款接口。
问题引出
对于将前端传来的字符串(格式"123,456,789......")解析成程序能够识别的退款编号列表,有不同的实现方式,那么解析性能的好坏将直接影响到用户体验,那么我们如何解析才能保证性能呢?
解决方案
根据个人编码经验以及参阅资料,解析字符串至少有一下四种方式:
1)使用String自带split方法分割字符串并解析
2)使用StringTokenizer分割并解析
3)使用Pattern正则表达式分割解析
4)使用substring分割解析
那么哪一种方式性能更好?接下来将一一实现和分析对比
方式一: String的split解析
使用String中的split方法将字符串分割成数组然后转换成列表
/**
* 使用string.split分解字符串并转换成list
*
* @author Typhoon
* @date 2018-05-18 22:14 Friday
* @param str
* @param delimiter
* @return
*/
private static List<Long> splitStr2List(String str,String delimiter) {
if(null == str || str.isEmpty()) {
return null;
}
String[] arr = str.split(delimiter);
List<Long> list = new ArrayList<>(arr.length);
int i = 0;
String temp;
do{
temp = arr[i];
if(!isNumber(temp)) {
continue;
}
list.add(Long.valueOf(temp));
} while( i < arr.length);
return list;
}
方式二: StringTokenizer解析
实现代码:
/**
* 使用StringTokenizer分割字符串并转换成list
*
* @author Typhoon
* @date 2018-05-18 22:07 Friday
* @param str
* @param delimiter
* @return
*/
private static List<Long> transferStr2List(String str,String delimiter) {
if(null == str || str.isEmpty()) {
return null;
}
StringTokenizer tokenizer = new StringTokenizer(str, DEFAULT_DELIMITER);
List<Long> list = new ArrayList<>(tokenizer.countTokens());
String temp;
while(tokenizer.hasMoreTokens()) {
temp = tokenizer.nextToken();
if(!isNumber(temp)) {
continue;
}
list.add(Long.valueOf(temp));
}
return list;
}
方式三: Pattern解析
Pattern正则表达式将字符串按照传入表达式分割:
/**
* 使用Pattern分割字符串并转换成list
*
* @author Typhoon
* @date 2018-05-18 22:19 Friday
* @param str
* @param delimiter
* @return
*/
private static List<Long> patternStr2List(String str,String delimiter) {
if(null == str || str.isEmpty()) {
return null;
}
Pattern pattern = Pattern.compile(delimiter);
String[] arr = pattern.split(str);
List<Long> list = new ArrayList<>(arr.length);
int i = 0;
String temp;
do{
temp = arr[i];
if(!isNumber(temp)) {
continue;
}
list.add(Long.valueOf(temp));
} while( i < arr.length);
return list;
}
方式四: substring解析
使用String自带的substring方法,将字符串分割成子字符串的方式分割:
/**
* 使用substring方式分割字符串并转换成list
*
* @author Typhoon
* @date 2018-05-18 22:45 Friday
* @param str
* @param delimiter
* @return
*/
private static List<Long> substring2List(String str,String delimiter) {
//12,33,45
if(null == str || str.isEmpty()) {
return null;
}
assert null != delimiter && str.length()>= delimiter.length();
int idx = 0;
String temp;
int deliLength = delimiter.length();
List<Long> list = new ArrayList<>();
for(int i = 0,length = str.length();i < length; i ) {
String del = str.substring(i, i deliLength);
if(!del.equals(delimiter)) {
continue;
}
temp = str.substring(idx,i);
if(!isNumber(temp)) {
continue;
}
list.add(Long.valueOf(temp));
idx = temp.length() deliLength;
}
return list;
}
对比性能
上边我们已经用四种方式实现了将字符串分割解析城Long类型列表,那么每种方式的具体性能如何,我们编写测试程序可以对比一下:
基础指标,我们将字符串包含的Long数据个数设置为10,100,1000,10000,100000,1000000,5000000(太大的话程序运行导致堆内存溢出并且没有业务场景)
1)string的split方式:
长度10
长度100
长度1000
长度10000
长度100000
长度1000000
长度5000000
2)StringTokenizer方式
长度10
长度100
长度1000
长度10000
长度100000
长度1000000
长度5000000
3)Pattern方式
长度10
长度100
长度1000
长度10000
长度100000
长度1000000
长度5000000
4)substring方式
长度10
长度100
长度1000
长度10000
长度100000
长度1000000
长度5000000
上面我们把每一种方式按照给定指标都跑了测试,下图是我们整理出来的性能测试结果:
注意:时间单位 毫秒
可以看出在数据量不是很大的情况下,StringTokenizer分割字符串性能应该是最好的,其他的性能差别也都不太大。
但是话有说回来,后端服务器会接收前端传来长度百万级别以上的数据吗?有前端会像后端发送长度百万级别以上的数据吗?
写此篇文章的目的,是对几种常见的分割字符串的方式做一下讲解和性能对比,具体应用在什么样的场景适用怎么样的方式还是取决于开发人员。
对于四种分割字符串的实现方式和性能对比做了一番描述,但是有没有人发现上述几种实现方式代码层面的返回结果都写死成List<Long>类型,那么如果我想解析成Integer是不是还要重写一份代码?当然作为一个有经验的程序员,你写代码的时候一定会考虑代码复用性和封装等等,那么此处我们想把字符串解析成通用的数据类型列表,显然要引入泛型,看一下实现方案:
/**
* 将字符串解析成指定的数据类型列表
*
* @author Typhoon
* @date 2018-05-25 23:01 Friday
* @param str
* @param delimiter
* @param clazz
* @return
*/
public static <T> List<T> transfer2ListV2(String str,String delimiter,Class<T> clazz) {
if(null == str || str.isEmpty()) {
return null;
}
String[] arr = str.split(delimiter);
List<T> list = new ArrayList<>(arr.length);
for(int i = 0,length = arr.length; i < length ; i ) {
String temp = arr[i];
if(clazz.isAssignableFrom(Long.class)) {//long类型
Long element = Long.valueOf(temp);
list.add((T)element);
} else if(clazz.isAssignableFrom(Integer.class)) {
Integer ele = Integer.valueOf(temp);
list.add((T)ele);
} else if(clazz.isAssignableFrom(String.class)) {
list.add((T)temp);
} else {
throw new IllegalArgumentException("转换类型不支持");
}
}
return list;
}
这里边有一个点需要解释一下,为什么对泛型T类型的判断只有Long、Integer和String呢?因为,首先我们分隔出来的具体每一个子项是有具体的业务含义的,比如会员id,退款编号等等,这些业务数据类型有一个很明显的特点,标识性特别强,基本不会重复,那么我们常用的数据类型那些可以描述标识性比较强的数据呢,显然Long,Integer和String类型,Boolean类型识别性太差,传过来true和false有什么意义呢。
接下来使用三种类型的数据简单跑一下测试:
1)Long类型
2)Integer类型
3)String类型
我们使用错误的转换类型跑一下
总结
经过上述的描述和代码验证,我们对几种常见的字符串分割解析方式做了详细的介绍的性能对比,以及最后对解析方式做了泛化来提高代码的复用性,希望给大家在日常开发中带来帮助!