Spads 出品字符串高效提取数值方法 Shane 末日圣诞奉献

博客介绍了 Spads 出品的 `parseNumber` 方法,该方法能高效地从字符串中提取数值,支持整数、浮点数、空格和直接小数点开头的数字。相比 Java 系统方法、正则表达式和其他第三方库,`parseNumber` 在性能上有显著优势。文章包含方法的详细实现、功能说明及性能测试结果。
摘要由CSDN通过智能技术生成
Spads 出品,作者 WangXP 与 Shane


目录
---------- ---------- ---------- ----------
1 【综述】
2 【程序】
3 【功能】
4 【性能】
5 【附录】



1 【综述】
---------- ---------- ---------- ----------
最近因为继续编写公式处理和 Json - Java 互转组件,所以专门制作、优化了字符串变成数值的工具方法,并在性能上取得了领先于阿帕奇工具和爪哇类库等方式的成就。本方法基本思路是逐字符扫描。目前关于将字符串转为数值,市面上常见的资料,我搜索总结了十几篇吧,大体是重复的,包括这么几种方法。我看过的资料网址收集在附录中。

1、通过 Java 系统提供的 Character#isDigit(char) 判断是否为数值,然后转换。
2、通过正则表达式判断是否为数值,然后转换。
3、直接转换,捕捉异常。
4、阿帕奇提供了 isNumeric(String) 和 isNumericSpace(String) 方法用以判断。如果是数值再转换。

目前我这边的需求是,需要支持整数、数字间的空格、空白头尾、小数以及直接小数点开头的小数。

JavaLib 的 isDigit(char) 只能判断数字,所以需要事先去除空格、首位负号和第一个小数点。
正则表达式在转换前需要去除空格
直接转换前也需要去除空格
阿帕奇工具方法能判断数字和空格,所以需要事先去除首位负号和第一个小数点。

Spads 编写程序是直接利用了字符特点,扫描字符串。也可以认为是一种结合了预处理与字符 ASCII 判断的方法。本方法会将能够解析成数值的字符串解析成数值,否则返回 null 。



2 【程序】
---------- ---------- ---------- ----------
Spads 直接编写了适用的程序,经过很长时间性能优化,达到了优于上述所有方法的效果。代码如下。
/**
 * <b>抽取字符串的数字值</b><br/>
 * 
 * 本方法用来将传入字符串的内容理解为十进制数值。如果传入字符串的内容无法理解为
 * 数值,则返回 <code>null</code> 。数值字符串是指包括正、负的整数、浮点数;其中
 * 允许出现分割用空格,允许直接使用小数点起头以表示 0.x 样的小数。<br/>
 * 方法使用示例如下。
 * <pre>
 * StringTool.fetchNumberValue("15") = 15 (java.lang.Integer)
 * StringTool.fetchNumberValue("300 1500 1010")
 * = 30015001010 (java.lang.Long)
 * StringTool.fetchNumberValue("	28 ") = 28 (java.lang.Integer)
 * StringTool.fetchNumberValue("3.8") = 3.8 (java.lang.Double)
 * StringTool.fetchNumberValue("-99") = -99 (java.lang.Integer)
 * StringTool.fetchNumberValue("+15") = null
 * StringTool.fetchNumberValue(".45") = 0.45 (java.lang.Double)
 * StringTool.fetchNumberValue("-.1") = -0.1 (java.lang.Double)
 * StringTool.fetchNumberValue("0x3AFF0") = null
 * StringTool.fetchNumberValue("042") = 42 (java.lang.Integer)
 * StringTool.fetchNumberValue("Spads") = null
 * StringTool.fetchNumberValue("") = null
 * StringTool.fetchNumberValue(null) = null
 * StringTool.fetchNumberValue("5 - 3") = null
 * StringTool.fetchNumberValue("3.1415926.5358") = null
 * StringTool.fetchNumberValue("13205972138597109830758190748937102794")
 * = 1.320597213859711E37 (java.lang.Double)
 * </pre>
 * 
 * 本方法不涉及正则表达式,同时未避免生成异常的巨大系统消耗,通过遍历字符串每
 * 一个字符来判断其是否符合十进制数值格式,格式不符则直接返回
 * <code>null</code> 。如果格式符合,浮点数按 {@link Double} 识别,整数按
 * {@link Long} 识别。如果数值范围符合 {@link Integer} ,则转换。
 * 
 * @see			StringTool#removeChar(String, char)
 * @param 		numStr	准备提取数值的字符串
 * @return		传入字符串按十进制理解的数值对象,其类型有可能为
 * <code>Long</code> 、 <code>Integer</code> 或者 <code>Double</code>。
 * @exception	NumberFormatException	当字符串是整数但超过了
 * {@link Long#MIN_VALUE} - {@link Long#MAX_VALUE} 范围同时其位数并不大于 20,
 * 或者字符串是浮点数 但超过了
 * {@link Double#MIN_VALUE} - {@link Double#MAX_VALUE} 范围。
 */
static public Number parseNumber(String numStr)
{
	if (numStr == null || (numStr = numStr.trim()).length() == 0)
		return null;
	final StringBuilder numBuilder = new StringBuilder();
	boolean doubleFlag = false;
	final int length = numStr.length();
	char numChar;
	for (int index = 0; index != length; index++)
	{
		numChar = numStr.charAt(index);
		if (numChar > 57) return null;
		switch (StringTool.charType[numChar])
		{
			// 在之前没有小数点的情况下出现小数点,记录,同时认为此字符串是浮点数
			case 3:
				if (doubleFlag || index == length - 1) return null;
				doubleFlag = true;
				// 此处通过

			// 如果是数字,认为此字符串还是数值,检查下一个字符
			case 4: numBuilder.append(numChar);
				// 此处通过

			// 如果是空格,直接检查下一个字符
			case 1: continue;

			// 如果是负号,又不是首位,则按非数值字符串进行返回
			case 2:
				if (index == 0)
				{
					numBuilder.append(numChar);
					continue;
				}
				// 此处通过

			// 如果字符不符合数值格式,方法按非数值字符串进行返回
			case 0: return null;
		}
	}

	if (doubleFlag || length > 20)
		return Double.valueOf(numBuilder.toString());

	// 非浮点数情况,如果数值在整数范围内,则按整数返回。否则按长整数返回。
	final long num = Long.parseLong(numBuilder.toString());
	final int finalNum = (int) num;
	if (finalNum != num) return num;
	return finalNum;
}

/**
 * <b>字符类型数组</b><br/>
 * 为字符串抽取数值含义提供的字符类型获取器。通过将字符本身作为此数组下标获取对应
 * 类型。类型用整数表示,含义如下。<br/>
 * 1 为空白间隔,对应 <code>' '</code> 和 <code>'\t'</code><br/>
 * 2 为负号,对应 <code>'-'</code><br/>
 * 3 为小数点,对应 <code>'.'</code><br/>
 * 4 为数字,对应 <code>'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'</code>
 */
static private int[] charType = {
	0,0,0,0,0, 0,0,0,0,1,
	0,0,0,0,0, 0,0,0,0,0,
	0,0,0,0,0, 0,0,0,0,0,
	0,0,1,0,0, 0,0,0,0,0,
	0,0,0,0,0, 2,3,0,4,4,
	4,4,4,4,4, 4,4,4
};
经过测试,发现本方法可以完美地达到需要的处理效果。以下是测试报告。
Function test...
---------- ---------- ---------- ----------
String: 15
In StringTool way, get: 15 (Integer)
---------- ---------- ---------- ----------
String: 	28 
In StringTool way, get: 28 (Integer)
---------- ---------- ---------- ----------
String: 3.8
In StringTool way, get: 3.8 (Double)
---------- ---------- ---------- ----------
String: -99
In StringTool way, get: -99 (Integer)
---------- ---------- ---------- ----------
String: .45
In StringTool way, get: 0.45 (Double)
---------- ---------- ---------- ----------
String: -.1
In StringTool way, get: -0.1 (Double)
---------- ---------- ---------- ----------
String: null
In StringTool way, get: null (null)
---------- ---------- ---------- ----------
String: 0x3AFF0
In StringTool way, get: null (null)
---------- ---------- ---------- ----------
String: 042
In StringTool way, get: 42 (Integer)
---------- ---------- ---------- ----------
String: Spads
In StringTool way, get: null (null)
---------- ---------- ---------- ----------
String: 
In StringTool way, get: null (null)
---------- ---------- ---------- ----------
String:       
In StringTool way, get: null (null)
---------- ---------- ---------- ----------
String: 5 - 3
In StringTool way, get: null (null)
---------- ---------- ---------- ----------
String: 三十八
In StringTool way, get: null (null)
---------- ---------- ---------- ----------
String: 3.1415926.5358
In StringTool way, get: null (null)
---------- ---------- ---------- ----------
String: 39501.50
In StringTool way, get: 39501.5 (Double)
---------- ---------- ---------- ----------
String: 0.000385427
In StringTool way, get: 3.85427E-4 (Double)
---------- ---------- ---------- ----------
String: .33796678
In StringTool way, get: 0.33796678 (Double)
---------- ---------- ---------- ----------
String: 300011110000
In StringTool way, get: 300011110000 (Long)
---------- ---------- ---------- ----------
String: 300 1500 1010
In StringTool way, get: 30015001010 (Long)
---------- ---------- ---------- ----------
String: -570 0015 6726
In StringTool way, get: -57000156726 (Long)
---------- ---------- ---------- ----------
String: -2378957832975
In StringTool way, get: -2378957832975 (Long)
---------- ---------- ---------- ----------
String: 1370 0000
In StringTool way, get: 13700000 (Integer)
---------- ---------- ---------- ----------
String: 2015773
In StringTool way, get: 2015773 (Integer)
---------- ---------- ---------- ----------
String: 3850
In StringTool way, get: 3850 (Integer)
---------- ---------- ---------- ----------
String: -80058
In StringTool way, get: -80058 (Integer)
---------- ---------- ---------- ----------
String: -1 2853 2781
In StringTool way, get: -128532781 (Integer)
---------- ---------- ---------- ----------
String: 3
In StringTool way, get: 3 (Integer)
---------- ---------- ---------- ----------
String: 0
In StringTool way, get: 0 (Integer)
---------- ---------- ---------- ----------
String: -597213859710830758190748937102794
In StringTool way, get: -5.972138597108308E32 (Double)
---------- ---------- ---------- ----------
String: 78 9254 3253 2452 9572 8532
In StringTool way, get: 7.892543253245296E21 (Double)
---------- ---------- ---------- ----------
String: 13205972138597109830758190748937102794
In StringTool way, get: 1.320597213859711E37 (Double)
---------- ---------- ---------- ----------



3 【功能】
---------- ---------- ---------- ----------
Spads 推出的 parseNumber(String) 方法能够完整地实现解读数值字符串的功能。那么其它几种方法表现如何呢?

其它方法的程序代码经过 Shane 认真处理,依然有 200 多行。我就不每行都贴出来了。这里简述一下各种方法所使用的方式。

A、简单爪哇类库方法 SimpleJavaLib
B
、爪哇类库方法 JavaLib
核心判断方法
private boolean isNumericByJavaLib(CharSequence numSequence)
{
	for (int i = numSequence.length(); --i >= 0; )
		if (!Character.isDigit(numSequence.charAt(i)))
			 return false;
	return true;
}
SimpleJavaLib 将字符串直接传入此检测方法进行判断,如果检验通过则直接按 Long 类型转换。
JavaLib 方法首先给字符串去除空格、首位负号和第一个小数点,然后再传入此检测方法进行判断;如果检验通过则根据有无小数点决定用 Long 还是 Double ;如果是 Long ,得到结果之后会试图转换为 int ,已决定是否可以用整形。

C、简单正则表达式 SimpleRegex
用 \d* 来判断,然后按长整数转换。

D、完整正则表达式 WholeRegex
用 ^[-]?(((\s|\d)*\d(\s|\d)*(\.(\s|\d)*\d(\s|\d)*)?)|(\.(\s|\d)*[1-9](\s|\d)*)) 来判断。如果检验通过则根据有无小数点决定用 Long 还是 Double ;如果是 Long ,得到结果之后会试图转换为 int ,已决定是否可以用整形。

E、直接转换捕捉异常 Direct
根据字符串是否含有 '.' 字符,决定用 Double 转换还是用 Long 转换。如果转换发生异常,则返回 null 。如果是 Long ,得到结果之后会试图转换为 int ,已决定是否可以用整形。

F、直接判断字符 CharAscii
private Number fetchNumberByCharAscii(String numStr)
{
	for (int i = numStr.length(); --i >= 0; )
	{
		int chr = numStr.charAt(i);
		if (chr < 48 || chr > 57) return null;
	}
	return Long.valueOf(numStr);
}
G、简单阿帕奇工具方法 SimpleApacheUtils
核心判断方法为 org.apache.commons.lang3.StringUtils.isNumeric(CharSequence)
其余部分与 A 类似

H、完整阿帕奇工具方法 WholeApacheUtils
核心判断方法为 org.apache.commons.lang3.StringUtils.isNumericSpace(CharSequence)
首先给字符串去除首位负号和第一个小数点,然后再传入此检测方法进行判断;如果检验通过则先消除空格,然后根据有无小数点决定用 Long 还是 Double ;如果是 Long ,得到结果之后会试图转换为 int ,已决定是否可以用整形。

我设计了这些测试用字符串
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值