写程序很难之去除字符串的空白字符

在做性能调优时,用JProfiler测试Web应用的性能,发现有个replaceBlank函数占用了10%的CPU时间,进去看了下,是个简单的用正则去除XML文档里空白字符串的功能。但是这个简单功能却消耗了10%的性能。

在Web应用里,去掉空白字符串,似乎是个简单的功能,但是真正写起来,却也有些麻烦事。总结下。

方式一:正则表达式

http://stackoverflow.com/questions/5455794/removing-whitespace-from-strings-in-java

有两种写法:

s.replaceAll("\\s+", "");
s.replaceAll("\\s", "");

至于具体哪一种比较好,和具体的场景有有关。有连续空白字符串的选择每一种,如果是空白字符串都只有一个的话,就选择第二种。个人倾向于第一种。

正则表达式是比较慢的,比下面的方法要慢3到4倍以上。

方式二:org.springframework.util.StringUtils.trimAllWhitespace

具体的实现代码如下:

	public static String trimAllWhitespace(String str) {
		if (!hasLength(str)) {
			return str;
		}
		StringBuilder sb = new StringBuilder(str);
		int index = 0;
		while (sb.length() > index) {
			if (Character.isWhitespace(sb.charAt(index))) {
				sb.deleteCharAt(index);
			}
			else {
				index++;
			}
		}
		return sb.toString();
	}

看起来,没有什么问题,但是程序员的直觉:deleteCharAt函数是怎么实现的?应该不会有什么高效的算法可以实现这样的。

果然,实现代码如下:

    public AbstractStringBuilder deleteCharAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        System.arraycopy(value, index+1, value, index, count-index-1);
        count--;
        return this;
    }

显然,过多地调用System.arraycopy会有性能问题。

方式三:改为调用StringBuilder.append 函数

	static public String myTrimAllWhitespace(String str) {
		if (str != null) {
			int len = str.length();
			if (len > 0) {
				StringBuilder sb = new StringBuilder(len);
				for (int i = 0; i < len; ++i) {
					char c = str.charAt(i);
					if (!Character.isWhitespace(c)) {
						sb.append(c);
					}
				}
				return sb.toString();
			}
		}
		return str;
	}

这个是最开始的思路。实际测试了下,发现大部分情况上,要比方式二效率高。

但是在某些情况,比如"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa",这种只有一个空白字符的,效率要慢。

方式四:结合二,三,只用System.arraycopy复制部分内存

第二种方式,在调用deleteAt时,要整个拷贝后面的所有字符串,显然在字符串很长的情况下,效率会降低。于是考虑只复制部分内存。

用两种pos来标记哪一部分是连续的非空白字符串。

	static public String myTrimAllWhitespace3(String str) {
		if (str != null) {
			int len = str.length();
			if (len > 0) {
				char[] src = str.toCharArray();
				char[] dest = new char[src.length];

				int destPos = 0;
				for (int pos1 = 0, pos2 = 0; pos2 < src.length;) {
					if (Character.isWhitespace(src[pos2])) {
						if (pos1 == pos2) {
							pos1++;
							pos2++;
						} else {
							System.arraycopy(src, pos1, dest, destPos, pos2
									- pos1);
							destPos += (pos2 - pos1);
							pos2++;
							pos1 = pos2;
						}
					} else {
						pos2++;
					}

					if (pos2 == src.length) {
						if (pos1 != pos2) {
							System.arraycopy(src, pos1, dest, destPos, pos2
									- pos1);
							destPos += (pos2 - pos1);
						}
						return new String(dest, 0, destPos);
					}
				}
			}
		}
		return str;
	}

方式五:去掉StringBuilder,直接操作char[]

在写完方式四,之后,测试发现效率在中间,和方式二,三相比,不好也不坏。似乎找到了一个平衡点。

但是忽然想到,既然在方式四中不直接操作char[]数组,为何不在方式二也这么做?于是有了:

	static public String myTrimAllWhitespace2(String str) {
		if (str != null) {
			int len = str.length();
			if (len > 0) {
				char[] dest = new char[len];
				int destPos = 0;
				for (int i = 0; i < len; ++i) {
					char c = str.charAt(i);
					if (!Character.isWhitespace(c)) {
						dest[destPos++] = c;
					}
				}
				return new String(dest, 0, destPos);
			}
		}
		return str;
	}

第六点:Unicode

上面的几种方式都只能处理大部分的情况,对于部分Unicode字符串,可能会有问题。

因为本人对这个比较敏感,最后写了个Unicode字符的处理:

	static public String myTrimAllWhitespace3(String str) {
		if (str != null) {
			int len = str.length();
			if (len > 0) {
				char[] src = str.toCharArray();
				char[] dest = new char[src.length];

				int destPos = 0;
				for (int pos1 = 0, pos2 = 0; pos2 < src.length;) {
					if (Character.isWhitespace(src[pos2])) {
						if (pos1 == pos2) {
							pos1++;
							pos2++;
						} else {
							System.arraycopy(src, pos1, dest, destPos, pos2
									- pos1);
							destPos += (pos2 - pos1);
							pos2++;
							pos1 = pos2;
						}
					} else {
						pos2++;
					}

					if (pos2 == src.length) {
						if (pos1 != pos2) {
							System.arraycopy(src, pos1, dest, destPos, pos2
									- pos1);
							destPos += (pos2 - pos1);
						}
						return new String(dest, 0, destPos);
					}
				}
			}
		}
		return str;
	}

这个处理Unicode的非常慢。。Java的String类并没有暴露足够多的函数来处理Unicode,所以处理起来很蛋疼。

总结:

测试代码在:

https://gist.github.com/hengyunabc/a4651e90db24cc5ed29a

我的电脑上测试最快的代码是方式五里的。

可能在某些特殊情况下,方式四中用System.arraycopy来复制标记两段内存会快点,但这个算法太复杂了,得不偿失。

本人倾向于符合直觉,而且效率线性的算法。

给Spring提了个path,一开始是方式三的代码,但是在某些情况下效率不高,导致周末心神不宁。。于是就有了后面的几种方式。

一个简单的功能,直正实现起来却也不容易,所以我尽量避免写Util类和方式,因为保证代码的质量,性能,不是一件容易的事。

https://github.com/spring-projects/spring-framework/pull/562

时间: 2024-09-06 10:07:00

写程序很难之去除字符串的空白字符的相关文章

为神马说写程序是很艰难的

我曾经认为编程很容易, 但多年之后我慢慢意识到我错了: 一份程序员的工作和我理解的"写程序"是不同的.起初我觉得编程无非就是命令计算机工作, 而这相对来说并不算难. 在工作了二十多年之后,我愈发觉得这实在是非常容易的事情. https://yqfile.alicdn.com/bad0b6274763399f8a35eda9e3548bf32aeb47f4.png" > 文章转载自 开源中国社区 [http://www.oschina.net]

写js正则表达式,去除字符串的首尾的逗号,怎么写?

问题描述 写js正则表达式,去除字符串的首尾的逗号,怎么写? 写js正则表达式,去除字符串的首尾的逗号,怎么写???????????????????? 解决方案 <script language="JavaScript"> <!-- String.prototype.Trim=function(){ return this.replace(/^,*|,*$/g,'') } alert(",234324,".Trim()) //--> <

算法-一个很难的程序难题,关于m选n

问题描述 一个很难的程序难题,关于m选n 求一个m选n的算法,比如4选2,输出12 13 14 23 24 34 m选n的m和n要自由输入,怎么实现 解决方案 http://bbs.csdn.net/topics/390550326 解决方案二: 这个就是数学里面的组合问题,百度下实现算法呗. 解决方案三: m选n的算法 解决方案四: 经典算法题,你去搜索下能找到 解决方案五: 如何编写从M中选N的组合数程序 解决方案六: void print_subset(int cur,int m ,int

三星很难提供有吸引力的应用程序

网易科技讯 5月24日消息,据国外媒体报道,三星电子日前表示,公司将从7月1日起,关闭自己为移动设备提供的的音乐与电子书服务.此举再次显示出,三星很难提供有吸引力的应用程序. 公司并未解释关停这两项服务的原因,只是说公司"努力实现差异化的服务并增加顾客价值".公司发言人在邮件中表示,三星将通过自家的以及合作的服务,向用户提供多种内容选择.此举不影响公司近期在美国推出的"Milk"流媒体音乐服务. 这两项服务的关停,让公司的多媒体内容商店即"SamsungH

php去除字符串首尾中英文空格程序

例1.trim函数删除空格 trim()函数用于去除字符串开始位置以及结束位置的空格,并返回去掉空格后的字符串.语法如下: string trim(string str[,string charlist]); ltrim()函数用于去除字符串左边的空格或指定字符串.语法如下: string ltrim(string str[,string charlist]); rtrim()函数用于去除字符串右边的空格和特殊字符.语法如下: string rtrim(string str[,string ch

jQuery Trim去除字符串首尾空字符的实现方法说明

 本篇文章主要是对jQuery Trim去除字符串首尾空字符的实现方法进行了详细的介绍,需要的朋友可以过来参考下,希望对大家有所帮助 假如您的项目正在使用jQuery框架,要去除字符串首尾的空字符您当然会选择:jQuery.trim(string).如果没有用到jQuery如何简单的实现呢.之前本人曾发过一个代码小片段:Javascript去除字符串左右的空格-trim(),这个写的比较粗糙并要用到递规操作.所以很多人很不满意代码的质量,包括我在内. 偶尔看了下jQuery的代码,发现非常值的我

为什么我们程序员难晋升

今天看到微博上 发 了一个帖子:"内部晋升越来越困难,但是外部来的大P越来越多,所以很多人都选择跳槽",之后我从三个方面简要的进行了回答:"外面来的总是有包装的,内 部的都是肉身PK,此一输:外面来的总是小股人马,内部的一批批的,升谁都伤感情,此二输:外面来的通常都是大佬推荐的,没有特别重大机会,人不会来,内 部的就不解释了,成果都被大佬吸收,难有机会,此三输".之后讨论不断,我也余兴未了,继续写来. 这个世界上有一类人特别苦逼,苦逼到什么程度呢?他们省吃俭用攒钱买

函数式编程很难,这正是你要学习它的原因

很奇怪不是,很少有人每天都使用函数式编程语言.如果你用Scala,Haskell,Erlang,F#或某个Lisp方言来编程,很可能没有公司会花钱聘你.这个行业里的绝大部分人都是使用像Python,Ruby,Java或C#等面向对象的编程语言--它们用起来很顺手.不错,你也许会偶然用到一两个"函数式语言特征",例如"block",但人们不会去做函数式编程. 然而,很多年来,我们一直被教导说函数式编程语言很好很棒.我仍然记得当我第一次阅读ESR的著名的关于学习Lisp

难题求解答-c语言程序设计(很难的一道题)

问题描述 c语言程序设计(很难的一道题) 一.将分数变小数:写出一个程序,接受一个以N/D的形式输入的分数,其中N为分子,D为分母,输出它的小数形式.如果它的小数形式存在循环节,要将其用括号括起来.例如:1/3=.00000...表示为.(3),又如41/333=.123123123...表示为.(123). 一些转化的例子: 1/3=.(3) 22/5=4.4 1/7=.(142857) 3/8=.375 45/46=.803(571428) ??用上面的分数和13/79来测试你的程序.求高手