`
web001
  • 浏览: 96270 次
  • 性别: Icon_minigender_1
  • 来自: 天津
社区版块
存档分类
最新评论

「译」在java中,字符串的加法是如何实现的?

    博客分类:
  • Java
阅读更多

 

原文:How is + implemented in Java?

译文:在java中,字符串的加法是如何实现的?

 

当我查看String类的concat函数的源码时,发现字符串连接是这么实现的:

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

那么,字符串的连接符(+)的实现和这个有什么区别呢?如果有区别的话,那它是如何实现的呢?

此外,这两者分别在什么场合使用,有没有性能上的差异。

 

为了回答这个问题,我们可以做一个测试。

首先,我们连接两个字符串

String s1 = "foo";
String s2 = "bar";
String s3 = s1 + s2;

下面我们将这个代码编译成class文件,然后再反编译(可以用JAD),我们得到反编译后的代码是:

String s = "foo";
String s1 = "bar";
String s2 = (new StringBuilder()).append(s).append(s1).toString();

所以,+ 和 concat 肯定是有区别的。

在性能上,从 concat() 源码可以看出,StringBuilder创建了更多的对象,而concat却没有,它使用的String类的内部实现。

 

综上,当我们需要连接两个字符串的时候,我们应当优先考虑使用 concat() 函数,当我们需要连接字符串和其它类型的变量时,再考虑使用+运算符

 

译者注:用 javap -c 查看java生成的字节码:

java.lang.String cat(java.lang.String, java.lang.String);
  Code:
   0:   new     #2; //class java/lang/StringBuilder
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   7:   aload_1
   8:   invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  aload_2
   12:  invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   15:  invokevirtual   #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/    String;
   18:  astore_1
   19:  aload_1
   20:  areturn

可以看出 a += b 其实等价于

a = new StringBuilder()
    .append(a)
    .append(b)
    .toString();
7
12
分享到:
评论
14 楼 runfriends 2013-01-02  
kidneyball 写道
runfriends 写道
楼主你用的是谁家的编译器啊?
我用jdk6、jdk7、eclipse jdt的编译器编译后再反编译回来的都还是+。


不排除这是你用的反编译器干的。这种事情还是直接看bytecode靠谱。
确实是我的反编译器的问题

runfriends 写道

在用+连接字符串时,每遇到一个+都会创建一个StringBuilder对象,调用两次append和一次toString()


很久很久以前做过测试,每个字符串连加的表达式只会创建一个StringBuilder。所以只有当字符串连接被分散在不同的表达式里,或者表达式在循环或者递归里,StringBuilder和加号连接才会有显著差异。

我又测试了一下,你是对的,我错了。之前是我测试不严格。
之前我测试的时候是在循环里面连接字符串。

runfriends 写道

在这个过程中如果调用n次append,最少创建一个append,最多创建n+1次


啥叫“创建一个append”呀?

我按错了键,应该是最少创建一个char[],最多创建n+1个
13 楼 kidneyball 2013-01-02  
runfriends 写道
楼主你用的是谁家的编译器啊?
我用jdk6、jdk7、eclipse jdt的编译器编译后再反编译回来的都还是+。


不排除这是你用的反编译器干的。这种事情还是直接看bytecode靠谱。

runfriends 写道

在用+连接字符串时,每遇到一个+都会创建一个StringBuilder对象,调用两次append和一次toString()


很久很久以前做过测试,每个字符串连加的表达式只会创建一个StringBuilder。所以只有当字符串连接被分散在不同的表达式里,或者表达式在循环或者递归里,StringBuilder和加号连接才会有显著差异。

runfriends 写道

在这个过程中如果调用n次append,最少创建一个append,最多创建n+1次


啥叫“创建一个append”呀?
12 楼 runfriends 2012-12-31  
so the original article is incorrect!!!!
11 楼 runfriends 2012-12-31  
所以前面我说的那一些对jdk7也适用
10 楼 runfriends 2012-12-31  
楼主你用的是谁家的编译器啊?
我用jdk6、jdk7、eclipse jdt的编译器编译后再反编译回来的都还是+。
9 楼 runfriends 2012-12-31  
为什么我用+的时候反编译结果跟楼主的不一样呢?
8 楼 runfriends 2012-12-31  
讲运行期效率,对java来说当然是编译后的。
以下内容都是基于jdk6的。jdk7的没有试过。
另外实际上编译器并没有把+编译成StringBuilder,反编译以后还是+。
因此,有理由相信把+作为StringBuilder处理是jvm的行为。

单步调试就会发现下述情况。
在用+连接字符串时,每遇到一个+都会创建一个StringBuilder对象,调用两次append和一次toString()。
在这个过程中,假设连接一个字符串一共用到n个+,就创建了n个StringBuilder对象,调用了2n次append和n次toString。其中这n个StringBuilder内维持的char[]尺寸是默认的16个字符,在一次+操作中如果第一个append的参数长度超过16个字符,jvm还要重新创建一个新的char[]对象,然后把append实参转化成字符串的字符串逐字符复制到这个char[]对象内。调用第二个append时会重复上述过程。最后调用toString。
所以整个字符串连接完毕,最少会创建n个char[],最多3n个。

如果显式使用StringBuilder连接,StringBuilder对象的数量由程序员控制,所以一般连接一次字符串只创建一个StringBuilder对象,如果规划的好,甚至连接数个字符串都可以只用一个StringBuilder对象。最后创建一个String对象。在这个过程中如果调用n次append,最少创建一个append,最多创建n+1次。

如果使用concat,每次调用,都会创建一个char[]和一个String,调用n次就会创建n个char[]和n个String。

这个3n和n+1的来历解释如下。
n个+会连接n+1个片断。显式使用StringBuilder,调用n次append会连接n个片断。
每次调用一个+都会调用StringBuilder无参构造器,这时会创建一个16个字符的char[],
+左边的片断是一次append,这时如果char[]长度不够,又会创建一个char[],
+右边的片断也是一次append,这时如果char[]长度还不够,又会创建一个char[]。
一次+最多创建三个char[],n次+最多创建3n个。
显示使用StringBuilder,创建StringBuilder对象时创建一个char[],以后每次调用append如果char[]长度不够就创建一个新的。所以调用n次append,最多创建n+1个char[]。
因此在最坏的情况下,使用+,创建了3n个char[],才构造一个想要的字符串,中间还会产生n个作为中间结果的字符串;而显式使用StringBuilder,构造相同的字符串最多只创建了n+1个char[],而且没有作为中间结果的字符串。

StringBuilder内char[]尺寸扩容算法如下:
//jdk7
void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }

//jdk6
void expandCapacity(int minimumCapacity) {
int newCapacity = (value.length + 1) * 2;
        if (newCapacity < 0) {
            newCapacity = Integer.MAX_VALUE;
        } else if (minimumCapacity > newCapacity) {
    newCapacity = minimumCapacity;
}
        value = Arrays.copyOf(value, newCapacity);
    }

足见两个版本的实现有显著差异,jdk6的实现有一个逻辑上的错误,就是判断newCapacity<0,这个判断永远也不会为true。
jdk7的实现相对更合理,不过也有问题因为miniCapacity是在执行append的时候如果原char[]对象不足以容纳append后的结果时才会调用expandCapacity,而mimumCapacity就是expandCapacity的实参,这个时候字符串长度如果超过了int最大值就会抛出OutOfMemoryError,而在这种情况下,只能说字符串太过于长了,而不一定会超出cpu寻址范围,也就不一定会耗尽jvm堆;因此在这里抛出OOM个人认为并不合适。

7 楼 Shen.Yiyang 2012-12-31  
人家都说了,现在编译器把+都编译成StringBuilder了,你还纠结什么 + 。。。
6 楼 runfriends 2012-12-31  
原文和楼主还有各位楼上的其实说的都不对。
我实际测试过StringBuilder与+的性能对比相差1000多倍。
2010年测试的,现在硬件性能更强,但是肯定仍然有明显差异,所以多个变量连接字符串还是应该使用StringBuilder,因为它只创建了一个StringBuilder对象,而且StringBuilder对它维护的char[]还有类似ArrayList实现的预置空间。

StringBuilder在连接过程中会创建一个StringBuilder、若干char[],创建多少次char[]对象由调用append的次数和每次传入append的参数尺寸决定。最后如果还调用了toString()还会创建一个String对象

String的concat会在每次调用时都会创建一个char[]对象和一个String对象。
每出现一次+都会创建一个StringBuilder对象和一个String对象,StringBuilder对象里面还会创建一个char[]对象。

综上,我们可以得出以下结论。
如果只是连接两个字符串片段还是cancat性能最高,StringBuilder和+性能一样。
如果连接三个以上的字符串版本StringBuilder性能最高,concat次之,+最差。
5 楼 java_user 2012-12-31  
其实现在硬件性能很强大,如果不是习惯根本没有必要这样咬文嚼字
4 楼 shichuang2393 2012-12-31  
路过....
3 楼 Shen.Yiyang 2012-12-31  
contact也会创建new String啊,如果你的+运算有很多个字符串,那么也只会创建一个StringBuilder去append,contact则不然
2 楼 cuisuqiang 2012-12-31  
你很细心,不过开发者不必纠结这个,顶你!
1 楼 justjavac 2012-12-31  
concat() 函数中有这么一句:return new String(buf, true);

相关推荐

    输入字符串实现加减乘除四则运算(java)

    将近250行的算法 实现了通过字符串进行加减乘除四则运算 纯通过处理字符串和数组实现 希望能帮助大家: 例如:String input &quot;33+26 77+70&quot;; String result &quot;&quot;; try { result Account...

    字符串计算引擎

    功能:给出一个字符串表达式(可以是任意复杂的字符串表达式),计算字符串表达式的值. 特性: 1:用户可以添加其它运算符号 ,也就是说用户可以制定新的运算符,引擎中不存在的运算符号,当然具体的运算类...

    Java实现任意进制的转换

    java实现任意进制的转换,包括2进制转16进制,10进制转16进制,10进制转任意进制,36进制加法,可自行手动设置转换进制和需要转换的数字.

    在123456789中插入+-*/使表达式等于100

    在123456789中任意位置插入任意个+-*/使表达式等于100,用go语言实现,Count函数借用他人,主要功能是计算表达式的值,本文没有提供。

    Java用String实现大整数算法

    集成了大整数的、加法、减法(仅限被减数大于减数)、乘法、乘以2、除以2、减去1、判断奇偶、判断是否为1等方法 乘法和加法经过多次测试和修改,基本上不存在什么问题,除法因为没用到所以没有写。 可以用到大数算法...

    java_calculator实现

    * 把一个字符串表达式,拆分成两个集合,一个集合包含计算中的数,一个集合包含计算中的符号 * 1.把一个表达式中的运算子提取出来 * 2.把一个表达式中的数提取出来 * 3.遍历运算子集合,优先运算乘法和除法(遇到乘法...

    String中三种加法的区别

    通常成为一个优秀的JAVA程序员需要较长时间的经验积累,包括从程序的tuning中,或从其他有经验的程序员口中,才知道一定功能需要怎样实现,在程序中需要避免那些问题.但这往往是比较片面的,知其然而不知其所以然.

    Java实验答案

    Java实验上机答案,内附详细解释说明,答题规范有效。

    详解Python核心对象类型字符串

    Python与C语言,Java语言都不一样,没有单个字符,只有一个有一个字符的字符串。 字符串对象不可修改,属于不可变类型 字符串和列表,元组都从属于序列这个对象类别。所以序列支持的操作,字符串也支持。 用单...

    图灵机Java程序源码

    本资源给出了Eclipse下图灵机的完整Java程序源码工程包,开发说明文档中给出了图灵机概要设计,功能实现程序结构剖析,明了易懂,源码导入Eclipse即可使用,无需编译,实用性极强。 功能简述:Java GUI(AWT)生成...

    125集专攻JAVA基础 JAVA零基础入门学习视频教程 动力节点JAVA视频教程.txt

    北京动力节点-Java编程零基础教程-091-Java基本语法-控制语句-switch语句-字符串作为比较对象.avi 北京动力节点-Java编程零基础教程-092-Java基本语法-控制语句-switch语句-举例1.avi 北京动力节点-Java编程零...

    Visual C++ 2005入门经典--源代码及课后练习答案

    6.7.2 从字符串中删除空格 268 6.7.3 计算表达式的值 268 6.7.4 获得项值 271 6.7.5 分析数 272 6.7.6 整合程序 274 6.7.7 扩展程序 276 6.7.8 提取子字符串 277 6.7.9 运行修改过的程序 280 6.8 ...

    java范例开发大全源代码

     实例42 字符串索引越界异常(StringIndexOutBounds) 60  实例43 操作错误(UnsupportedOperationException) 60  4.2 运行时异常 61  实例44 找不到指定类时发生的异常(ClassNotFoundException) 62 ...

    java范例开发大全

    实例42 字符串索引越界异常(StringIndexOutBounds) 60 实例43 操作错误(UnsupportedOperationException) 60 4.2 运行时异常 61 实例44 找不到指定类时发生的异常(ClassNotFoundException) 62 实例45 请求的...

    Java范例开发大全 (源程序)

     实例42 字符串索引越界异常(StringIndexOutBounds) 60  实例43 操作错误(UnsupportedOperationException) 60  4.2 运行时异常 61  实例44 找不到指定类时发生的异常(ClassNotFoundException) 62  ...

    StringCalculator:用定界符输入字符串数字并产生数字加法

    字符串计算器用定界符输入字符串数字并产生数字加法

    Java范例开发大全(全书源程序)

    实例42 字符串索引越界异常(StringIndexOutBounds) 60 实例43 操作错误(UnsupportedOperationException) 60 4.2 运行时异常 61 实例44 找不到指定类时发生的异常(ClassNotFoundException) 62 实例45 请求...

    java范例开发大全(pdf&源码)

    实例42 字符串索引越界异常(StringIndexOutBounds) 60 实例43 操作错误(UnsupportedOperationException) 60 4.2 运行时异常 61 实例44 找不到指定类时发生的异常(ClassNotFoundException) 62 实例45 请求的...

    Ciphyor:我自己的加密算法的Java实现,受到Vernam One的启发

    您输入两个字符串:一个键和一个消息 该消息使用base32编码(使用Guava) 密钥被哈希化为SHA-3(515位)哈希,我们将其称为h1(这是一个整数) 根据一个赋予属性的小型算法,生成一个称为salt的伪随机数:将其添加...

    java实验2实验报告.doc

    实验指导书 实验名称:包、接口与异常处理 学时安排:2 实验类别:综合设计型实验 实验要求:1人1组  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 一、实验目的 1、掌握java字符串处理...

Global site tag (gtag.js) - Google Analytics