string+和stringbuffer的速度比较
public class Main{ public static void main(String[] args){
/* 1 */
String string = "a" + "b" + "c";
/* 2 */
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
string = stringBuffer.toString();
} }
当时大部分的新手猿友都表示,stringbuffer快于string+。唯有群里一位有工作经验的猿友说,是string+的速度快。这让LZ意识到,工作经验确实不是白积累的,一个小问题就看出来了。
这里确实string+的写法要比stringbuffer快,是因为在编译这段程序的时候,编译器会进行常量优化,它会将a、b、c直接合成一个常量abc保存在对应的class文件当中。LZ当时在群里贴出了编译后的class文件的反编译代码,如下。
public class Main
{
public static void main(String[] args)
{
String string = "abc"; StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
string = stringBuffer.toString();
}
}
可以看出,在编译这个java文件时,编译器已经直接进行了+运算,这是因为a、b、c这三个字符串都是常量,是可以在编译期由编译器完成这个运算的。假设我们换一种写法。
public class Main{ public static void main(String[] args){
/* 1 */
String a = "a";
String b = "b";
String c = "c";
String string = a + b + c;
/* 2 */
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(a);
stringBuffer.append(b);
stringBuffer.append(c);
string = stringBuffer.toString();
} }
此处的答案貌似应该是stringbuffer更快,因为此时a、b、c都是对象,编译器已经无法在编译期进行提前的运算优化了。
但是,事实真的是这样的吗?
其实答案依然是第一种写法更快,也就是string+的写法更快,这一点可能会有猿友比较疑惑。这个原因是因为string+其实是由stringbuilder完成的,而一般情况下stringbuilder要快于stringbuffer,这是因为stringbuilder线程不安全,少了很多线程锁的时间开销,因此这里依然是string+的写法速度更快。
尽管LZ已经解释了原因,不过可能还是有猿友依然不太相信,那么下面我们来写一个测试程序。
public class Main
{
public static void main(String[] args)
{
String a = "a";
String b = "b";
String c = "c";
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
String string = a + b + c;
if (string.equals("abc")) {}
}
System.out.println("string+ cost time:" + (System.currentTimeMillis() - start) + "ms");
start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(a);
stringBuffer.append(b);
stringBuffer.append(c);
String string = stringBuffer.toString();
if (string.equals("abc")) {}
}
System.out.println("stringbuffer cost time:" + (System.currentTimeMillis() - start) + "ms");
}
}
我们每个进行了1亿次,我们会看到string+竟然真的快于stringbuffer,是不是瞬间被毁了三观,我们来看下结果。
答案已经很显然,string+竟然真的比stringbuffer要快。这里其实还是编译器捣的鬼,string+事实上是由stringbuilder完成的。我们来看一下这个程序的class文件内容就可以看出来了。
由于文件太长,所以LZ是分开截的图。可以看到,里面有两次stringbuilder的append方法调用,三次stringbuffer的append方法调用。stringbuilder只有两次append方法的调用,是因为在创建stringbuilder对象的时候,第一个字符串也就是a对象已经被当做构造函数的参数传入了进去,因此就少了一次append方法。
不过请各位猿友不要误会,这里stringbuilder之所以比stringbuffer快,是因为少了锁同步的开销,而不是因为少了一次append方法,原因看下面这段stringbuilder类的源码就知道了。
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
可以看到,实际上带有string参数的构造方法,依然是使用的append方法,因此stringbuilder其实也进行了三次append方法的调用。
看到这里,估计有的猿友就该奇怪了,这么看的话,似乎string+的速度比stringbuffer更快,难道以前的认识都错误了?
答案当然是否定的,我们来看下面这个小程序,你就看出来差别有多大了。
public class Main
{
public static void main(String[] args)
{
String a = "a";
long start = System.currentTimeMillis();
String string = a;
for (int i = 0; i < 100000; i++) {
string += a;
}
if (string.equals("abc")) {}
System.out.println("string+ cost time:" + (System.currentTimeMillis() - start) + "ms");
start = System.currentTimeMillis();
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 100000; i++) {
stringBuffer.append(a);
}
if (stringBuffer.toString().equals("abc")) {}
System.out.println("stringbuffer cost time:" + (System.currentTimeMillis() - start) + "ms");
}
}
这个程序与刚才的程序有着细微的差别,但是结果却会让你大跌眼镜。我们来看结果输出。
看到这个结果是不是直接给跪了,效率差了这么多?这还是LZ将循环次数降到了10万,而不是1亿,因为1亿次LZ跑了很久也没跑完,LZ等不急了,0.0。
造成这种情况的原因,我们看两个程序的区别就看出来了。第一个循环1亿次的程序,不管是string+还是stringbuffer都是在循环体里构造的字符串,最重要的是string+是由一个语句构造而成的,因此此时string+其实和stringbuffer实际运行的方式是一样的,只不过string+是使用的stringbuilder而已。
而对于上面这个10万次循环的程序,stringbuffer就不用说了,实际运行的方式很明显。而对于string+,它将会创造10万个stringbuilder对象,每一次循环体的发生,都相当于我们新建了一个stringbuilder对象,将string对象作为构造函数的参数,并进行一次append方法和一次toString方法。
由上面几个小程序我们可以看出,在string+写成一个表达式的时候(更准确的说,是写成一个赋值语句的时候),效率其实比stringbuffer更快,但如果不是这样的话,则效率会明显低于stringbuffer。我们来再写一个程序证实这一点。
为了不会导致编译失败,我们将循环次数减为1万次,否则会超出文件的最大长度,我们先来看看刚才的程序改为1万次循环的结果。
可以看到,在1万次的循环下,依然可以看到效率上的明显差异,这个差距已经足够我们观察了。现在我们就改一种写法,它会让string+的效率提高到stringbuffer的速度,甚至更快。
这里我们是将1万次字符串的拼接直接写成了一个表达式,那个a+a+...表达式一共是1万个(是LZ使用循环打印出来贴到代码处的),可以看到,此时string+的速度已经超过了stringbuffer。
因此LZ给各位猿友一个建议,如果是有限个string+的操作,可以直接写成一个表达式的情况下,那么速度其实与stringbuffer是一样的,甚至更快,因此有时候没必要就几个字符串操作也要建个stringbuffer(如果中途拼接操作的字符串是线程间共享的,那么也建议使用stringbuffer,因为它是线程安全的)。但是如果把string+的操作拆分成语句去进行的话,那么速度将会指数倍下降。
总之,我们大部分时候的宗旨是,如果是string+操作,我们应该尽量在一个语句中完成。如果是无法做到,并且拼接动作很多,比如数百上千成万次,则必须使用stringbuffer,不能用string+,否则速度会很慢。
Java的方法参数传递方式
这个问题的引入是当时LZ在群里问了这样一个问题,就是Java的方法参数传递是值传递还是引用传递?对于基本类型和对象来说,都会发生什么情况?
这道题大部分猿友还是说的不错的,包括群里的新手猿友。答案是Java只有值传递,因为Java只有值传递,因此在改变形参的值的时候,实参是不会因此而改变的。这一点从下面这个小程序可以很明显的看出来。
public class Main
{
public static void main(String[] args)
{
int a = 2;
Object object = new Object();
System.out.println(a + ":" + object);
change(a, object);
System.out.println(a + ":" + object);
} public static void change(int a,Object object){
a = 1;
object = new Object();
}
}
我们在方法当中改变形参的值,之后再次输出两个实参的值,会发现它们无任何变化。
这就足以说明Java只有值传递了,无论是对象还是基本类型,改变形参的值不会反应到实参上面去,这也正是值传递的奥义所在。
对于基本类型来说,这一点比较明显,不过对于对象来讲,很多猿友会有误解。认为我们在方法里改变形参对象属性的值,是会反映到实参上面去的,因此部分猿友认为这就是引用传递。
首先LZ要强调的是,上面也说了,我们只是改变形参对象属性的值,反映到实参上面去的,而不是真的改变了实参的值,也就是说实参引用的对象依然是原来的对象,只不过对象里的属性值改变了而已。
针对上面这一点,我们使用下面这个程序来说明。
public class Main
{
public static void main(String[] args)
{
int a = 2;
Entity entity = new Entity();
entity.a = 100;
System.out.println(a + ":" + entity);
System.out.println(entity.a);
change(a, entity);
System.out.println(a + ":" + entity);
System.out.println(entity.a);
} public static void change(int a,Entity entity){
a = 1;
entity.a = 200;
}
} class Entity{
int a;
}
我们在方法里改变了entity对象的属性值为200,我们来看一下结果。
可以看到,实参对象的值依然没有改变,只是属性值变了而已,因此这依旧是值传递的范围。为了说明这个区别,我们来看下真正的引用传递。由于Java当中不存在引用传递,因此LZ借用C/C++来让各位看下真正的引用传递是什么效果。
1 #include <stdio.h>
2
3 class Entity{
4 public:
5 int a;
6 Entity(){};
7 };
8
9 void change(int &a,Entity *&entity);
10
11 int main(){
12 int a = 2;
13 Entity *entity = new Entity();
14 printf("%d:%p\n",a,entity);
15 change(a, entity);
16 printf("%d:%p\n",a,entity);
17 }
18
19 void change(int &a,Entity *&entity){
20 a = 1;
21 entity = new Entity();
22 }
LZ尽量保持和Java的第一个程序是一样的结构,只不过C/C++中没有现成的Object对象,因此这里使用Entity对象代替,这样便于各位猿友理解。我们来看下结果,结果会发现引用传递的时候,在方法里改变形参的值会直接反应到实参上面去。
可以看到,在引用传递的时候,无论是基本类型,还是对象类型,实参的值都发生了变化,这里才是真正的引用传递。当然了,LZ对C/C++的理解非常有限,不过毋庸置疑的是,真正的引用传递应该是类似上述的现象,也就是说实参会因形参的改变而改变的现象,而这显然不是我们Java程序当中的现象。
因此,结论就是Java当中只有值传递,但是这并不影响我们在方法中改变对象参数的属性值。
文章小结
我们平时多了解一些语言的特性确实是有很多好处的,这会潜移默化的影响我们编码的质量。希望各位猿友在遇到这种问题的时候也自己多写写代码,看看自己的理解对不对,在这样的过程中进步会很快,尤其是在初次接触一个编程语言的时候。
string+和stringbuffer的速度比较的更多相关文章
- 新手容易混乱的String+和StringBuffer,以及Java的方法参数传递方式。
之前在交流群里和猿友们讨论string+和stringbuffer哪个速度快以及Java的方法参数传递的问题,引起了群里猿友的小讨论.最终LZ得出的结果是string+没有stringbuffer快, ...
- String、StringBuffer与StringBuilder之间区别
关于这三个类在字符串处理中的位置不言而喻,那么他们到底有什么优缺点,到底什么时候该用谁呢?下面我们从以下几点说明一下 1.三者在执行速度方面的比较:StringBuilder > String ...
- (转)String、StringBuffer与StringBuilder之间区别
原文地址: http://www.cnblogs.com/A_ming/archive/2010/04/13/1711395.html 关于这三个类在字符串处理中的位置不言而喻,那么他们到底有什么优缺 ...
- String、Stringbuffer、StringBuilder的区别(转载)
最近学习到StringBuffer,心中有好些疑问,搜索了一些关于String,StringBuffer,StringBuilder的东西,现在整理一下. 关于这三个类在字符串处理中的位置不言而喻,那 ...
- String、StringBuffer、StringBuilder的一些小经验……
一说String.StringBuffer和StringBuilder,想必大家都很熟悉,这三者经常在我们的面试题中出现,我也是看到了关于这三个的经典面试题,才触动了我之前工作中的一些经历,故而根据我 ...
- String、StringBuffer与StringBuilder之间区别[全屏看文]
String.StringBuffer与StringBuilder之间区别[全屏看文] 最近学习到StringBuffer,心中有好些疑问,搜索了一些关于String,StringBuffer,S ...
- String、StringBuffer、StringBuilder的不同使用场景
String 字符串常量StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主要性能 ...
- Android课程---String、StringBuffer 、StringBuilder 的区别(转)
String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主 ...
- String与StringBuffer的区别
首先,String和StringBuffer主要有2个区别: (1)String类对象为不可变对象,一旦你修改了String对象的值,隐性重新创建了一个新的对象,释放原String对象,StringB ...
随机推荐
- 封装jQuery下载文件组件
使用jQuery导出文档文件 jQuery添加download组件 jQuery.download = function(url, data, method){ if( url && ...
- Hibernate学习笔记3.3(Hibernate组建映射2)
多对多 相当于一个老师可以教多个学生,一个学生也可以有多个老师 数据表中都是再设计一个表寸相关的id 1.多对多单向 1annotation Student.java package com.bjsx ...
- 什么是 BULK INSERT
MSDN的解释: 在 SQL Server 中以用户指定的格式将数据文件导入到数据库表或视图中. 语法: BULK INSERT [ database_name . [ schema_name ] . ...
- PowerDesigner 逆向工程
原文地址:https://www.cnblogs.com/feilong3540717/archive/2011/11/18/2254040.html PowserDesigner 的打开路径:Fil ...
- JXS In Depth
[JXS In Depth] 1.Spread Attributes If you already have props as an object, and you want to pass it i ...
- 安装FP
一.安装Oracle 11.2 64-bit数据库 1.安装数据库软件并将SEINESCM数据库还原到服务器上, 2.配置监听和TNS信息 二.安装数据库32位客户端(为SSIS配套使用).安装ORA ...
- django models返回数据根据某字段倒序排列
例如有一个models表叫做report,report表中有一个endtime,想将结果按照endtime倒序排列 正序排列的方法:[models对象.objects.order_by(“字段名& ...
- Android Studio 插件-Android Styler 的使用 (转)
作用:把 xml文件 转为 style 截图保留 使用方法 使用方法:选中xml代码 按下 Ctrl+Shift+D 转自:http://blog.csdn.net/zxwd2015/article/ ...
- 能判断是电脑端还是手机端的javascript
<script language="javascript"> //平台.设备和操作系统 var system ={ win : false, mac : false, ...
- 跳台阶(python)
题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级.求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果). # -*- coding:utf-8 -*- class Soluti ...