`
web001
  • 浏览: 98782 次
  • 性别: 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实现计算字符串表达式

    在Java编程语言中,计算字符串表达式是一项常见的任务,它涉及到解析、编译和执行包含数学运算符和操作数的字符串。这篇博客“Java实现计算字符串表达式”可能讲解了如何利用Java来处理这种问题,虽然具体的实现细节...

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

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

    AES.rar_AES 任意_AES任意字符串_字符串aes加密_字符串加密

    在实际编程中,开发者通常会利用现有的库来实现AES加密和解密,例如Java的JCE库、Python的pycryptodome库或者C#的Bouncy Castle库。这些库已经封装好了加密和解密的过程,只需调用相应的函数并提供密钥和待处理的...

    java执行字符串公式.docx

    3. **add2 方法**:这个方法扩展了加法功能,接受一个字符串数组作为参数,对数组中的所有元素进行累加。同样,每个元素在加入之前都会通过 `null2zero` 转换。 4. **sub 方法**:实现减法操作,与 `add` 方法类似...

    字符串转成算式

    实现字符串转算式的算法有很多种,例如递归下降解析(Recursive Descent Parsing)、LL(1)、LR(0)、LALR(1)等都是编译原理中的经典方法。对于简单的算式,可以使用更直观的方法,如Shunting Yard算法或直接使用栈来...

    基于字符串的高精度浮点计算实现

    2. 实现基本的字符串数学运算,如字符串加法、减法、乘法和除法,这可能涉及到复杂的位操作和边界条件处理。 3. 提供一个机制来设置精度,即决定保留多少位小数,这可以通过控制字符串截断或者四舍五入来实现。 4. ...

    计算器(输入字符串解析运算)

    在这个过程中,有两个关键步骤:**字符串解析**和**运算逻辑处理**。 **字符串解析**是将用户输入的文本转换为可执行的数学表达式的过程。这涉及到识别数字、操作符(如加号"+"、减号"-"、乘号"*"、除号"/"等)以及...

    Java实现任意进制的转换

    在Java中,我们可以使用`Integer`类的`toHexString`方法,将一个整数的二进制表示转换为十六进制字符串。例如: ```java int binaryNumber = 0b101010; // 二进制数 String hexString = Integer.toHexString(binary...

    编绎原理词法分析和字符串识别-java

    在Java中,词法分析器会识别诸如"int"(整型)、"+"(加法运算符)和"String"(字符串类型)这样的标识符。词法分析器的工作是基于预定义的规则,这些规则定义了源代码的字符序列如何映射到词法单元。例如,一个简单...

    字符串转公式,输入公式的字符串形式,可以输出结果。

    在IT行业中,字符串转公式是一项常见的任务,尤其是在计算和数据分析领域。这个过程涉及到将用户输入的字符串解析成可执行的数学公式,然后计算出结果。这通常需要编程语言中的表达式解析器或库来实现。以下是一些...

    计算器(字符串运算)

    在编程领域,实现一个计算器来处理字符串形式的运算表达式是一项常见的任务。这个“计算器(字符串运算)”项目旨在处理用户输入的复杂数学表达式,例如“-(1+(2-3)*4-(3+19)%3)/2”,并返回计算结果。这种...

    四则运算解析器(字符串)

    四则运算解析器是一种计算机程序,它能够接收包含加、减、乘、除等四则运算符的字符串表达式,并将其转化为可执行的计算过程。这个解析器通常用于解决基础的数学问题,对于编程初学者来说,理解并实现这样一个解析器...

    字符串计算引擎

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

    字符串计算器

    在提供的压缩包文件中,"字符串计算器.exe"是一个可执行文件,可能是用某种编程语言(如C++、Java、Python等)编译或打包后的程序,可以直接运行以使用这个字符串计算器。运行这个程序时,用户可以在命令行界面或...

    java-leetcode题解之第415题字符串相加.zip

    在本压缩包“java-leetcode题解之第415题字符串相加.zip”中,包含的是关于LeetCode第415题“字符串相加”(Add Strings)的Java解决方案。这道题目属于计算机编程领域,特别是Java语言的学习与算法实践。LeetCode是...

    JAVA_MAC地址校验和转换

    在Java中,我们通常使用字符串来表示MAC地址。 2. **MAC地址校验**: - **校验有效性**:可以使用正则表达式检查MAC地址的格式是否正确,例如`^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$`。 - **校验物理唯一性*...

    Java编程实现对十六进制字符串异或运算代码示例

    在这个示例中,使用 Java 语言实现了对十六进制字符串的异或运算,通过将十六进制字符串转换为二进制形式,然后进行异或运算,最后将结果转换回十六进制字符串。这个示例展示了异或运算在实际应用中的重要性和实用性...

    Java 程序将两个二进制字符串相加.docx

    在Java编程中,将两个二进制字符串相加是一个常见的任务,这通常涉及到将字符串转换为整数,进行加法运算,然后将结果转换回二进制字符串。下面我们将详细探讨两种实现方法。 方法一: 这种方法是通过首先将二进制...

    java高亮例子 简单易懂

    字符串在Java中是用双引号包围的一串字符,比如"example of string"。字符串常用于存储文本信息,比如用户输入、文件路径等。高亮字符串可以帮助快速定位和识别字符串,通常字符串会用一种醒目的颜色,比如紫色或者...

Global site tag (gtag.js) - Google Analytics