出于方便的考虑,我们在进行字符串的内容处理的时候往往会出现以下的代码:
String result="";
result+="ok"; |
这段代码看上去好像没有什么问题,但是需要指出的是其性能很低,原因是java中的String类不可变的(immutable),这段代码实际的工作过程会是如何的呢?通过使用javap工具我们可以知道其实上面的代码在编译成字节码的时候等同的源代码是:
String result="";
StringBuffer temp=new StringBuffer();
temp.append(result);
temp.append("ok");
result=temp.toString(); |
短短的两个语句怎么呢变成这么多呢?问题的原因就在String类的不可变性上,而java程序为了方便简单的字符串使用方式对+操作符进行了重载,而这个重载的处理可能因此误导很多对java中String的使用。
下面给出一个完整的代码:
public class Perf {
public static String detab1(String s) {
if (s.indexOf('\t') == -1)
return s;
String res = "";
int len = s.length();
int pos = 0;
int i = 0;
for (; i < len && s.charAt(i) == '\t'; i++)
{
res += " ";
pos += 8;
}
for (; i < len; i++)
{
char c = s.charAt(i);
if (c == '\t') {
do {
res += " ";
pos++;
} while (pos % 8 != 0);
}
else {
res += c;
pos++;
}
}
return res;
}
public static String detab2(String s)
{
if (s.indexOf('\t') == -1)
return s;
StringBuffer sb = new StringBuffer();
int len = s.length();
int pos = 0;
int i = 0;
for (; i < len && s.charAt(i) == '\t'; i++)
{
sb.append(" ");
pos += 8;
}
for (; i < len; i++) {
char c = s.charAt(i);
if (c == '\t') {
do {
sb.append(' ');
pos++;
}
while (pos % 8 != 0);
}
else {
sb.append(c);
pos++;
}
}
return sb.toString();
}
public static String testlist[] ={
"",
"\t",
"\t\t\tabc",
"abc\tdef",
"1234567\t8",
"12345678\t9",
"123456789\t"
};
public static void main(String args[])
{
for (int i = 0; i < testlist.length; i++) {
String tc = testlist[i];
if (!detab1(tc).equals(detab2(tc)))
System.err.println(tc);
}
String test_string =
"\t\tthis is a test\tof detabbing performance";
int N = 5000;
int i = 0;
long ct = System.currentTimeMillis();
for (i = 1; i <= N; i++)
detab1(test_string);
long elapsed = System.currentTimeMillis() - ct;
System.out.println("String time = " + elapsed);
ct = System.currentTimeMillis();
for (i = 1; i <= N; i++)
detab2(test_string);
elapsed = System.currentTimeMillis() - ct;
System.out.println("StringBuffer time = " + elapsed);
}
} |
执行以上代码的结果可以看到使用StringBuffer的版本的方法比使用String版本的一般都快十倍以上(本人使用的是JDK1.4.0),你可以执行一下看看结果到底如何。
因此得到的结论是:如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用StringBuffer的toString()方法好了!也许这就是你的程序的性能瓶颈!
网友评论
评论人:kare 这在《Java编程思想》里有讲,应该讲这部分和另一篇《我对的理解》结合再讲一下,因为如果用户用String验证《我对的理解》这篇文章讲的内容就会出现不一致,这个不一致就是因为String是个特例,他是不可变的(immutable)
评论人:cherami 正是因为String的不可变性才没有用它做例子啊,而你提到的那篇文章不是初学者应该看的,如果他连String的不可变性都不知道的话。
评论人:jeffyan77 呵呵,这里也有一个思辨题目供大家娱乐:
假设有
String tableName = "customers";
String customerId = "ABCDEFG0012345678901234567890"; |
那么代码(1)
{
String sql = "SELECT * FROM " +
tableName + " WHERE customer_id
= '" + customerId + "'";
return sql;
} |
与代码(2)
{
StringBuffer sql = new StringBuffer();
sql.append(
"SELECT * FROM ").append(
tableName ).append(
" WHERE customer_id = '").append(
customerId ).append("'");
return sql.toString();
} |
有什么性能区别吗?
评论人:gui_jq 这么短应该没有吧!?呵呵!不过作者这编文章不错,加分。我虽然用了挺久java但是缺不知道这个,惭愧!
评论人:heroinit 文章很好,不过在实际应用中,觉得跟多时候用String,比如SQL.因为一串Stringbuffer 的append让你看不清,别人读不懂。倒是在循环里用Stringbuffer有优势
评论人:kert to JeffYan:我认为(1)和(2)性能大致相等。在编译时,java好像会将连加的String变成StringBuffer.append()。
评论人:xmpp 其实关于这个问题的讨论,早就有了。chinajavaworld,sun的讨论组上都有这个问题。
评论人:cherami 其实这个问题我们不必过于在意,就像文章最后提到的,当这个问题是处于一个非常频繁调用的循环里面的时候我们才去考虑这个问题。
本身性能优化的出发点就是优化性能瓶颈部分,如果这样的操作仅仅是偶尔调用一两词,那么我们还是使用我们最习惯的方式就可以了,不必太在意这个问题,而且这样的操作可能和编译器有关,据说现在的JDK已经可以对上述的问题进行优化了,但是由于编译器不可能预知操作的对象长度,因此连接的字符串的长度是动态的时候编译器的优化效果就不是很好了,这时就需要开发者自己优化了。这个说来就话长了。
评论人:cherami 以后有时间我会再多写一点关于这方面的文章的,我现在正在看一本书,是有关java性能优化的,有什么心得会和大家分享的。
评论人:admin 测试Cherami提交的Bug
评论人:jeffyan77 呵呵,支持cherami未来的文章。现在我把代码(2)改一改成为代码(3),
代码(3):
{
StringBuffer sql = new StringBuffer(100);
sql.append(
"SELECT * FROM ").append(
tableName ).append(
" WHERE customer_id = '").append(
customerId ).append("'");
return sql.toString();
} |
现在有(1)、(2)、(3),大家看看,哪一个performance会好一些?
评论人:glistar 现在我把代码(3)改一改成为代码(4),
代码(4):
{
StringBuffer sql = new StringBuffer(128);
sql.append(
"SELECT * FROM ").append(
tableName ).append(
" WHERE customer_id = '").append(
customerId ).append("'");
return sql.toString();
} |
现在有(1)、(2)、(3)、(4),大家看看,哪一个performance会好一些?
评论人:kaiyuwu In fact , when we use String or StringBuffer, we need to consider only when it is passed as argument or return value. Because String is passed by value, StringBuffer is passed by reference.
If you know C or C++, it's not difficult to understand at all, in c or c++, we usually pass pointer instead of instance. It's the same idea when we use String or StringBuffer.
评论人:albert_qhd 编译器进行优化的时候,就把这种情况改为StringBuffer
评论人:omygod 问题的关键不是用String或者StringBuffer,如果只要用StringBuffer就可以使得速度快很多,那么大家都去用StringBuffer好了,没这么简单。StringBuffer的一个好处是它可以让你初始一个字串的长度,这样你在需要大量进行字串添加的地方估计字串总长度,然后初始一个该长度的StringBuffer,然后对它进行操作,这样就会加快速度。加快速度的原因是因为系统不需随时来更改数组长度了,这是减慢速度的致命原因。
|