zoukankan      html  css  js  c++  java
  • 解析Java中的String、StringBuilder、StringBuffer类(一)

    引言

    String 类及其相关的StringBuilder、StringBuffer 类在 Java 中的使用相当的多,在各个公司的面试中也是必不可少的。因此,在本周,我打算花费一些时间来认真的研读一下 String、StringBuilder、StringBuffer类 的相关代码。

    String的不可变性

    这个特性是 String 相当重要的一个特性,为了深入理解,我直接贴上其源代码

    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);
        }
    public String replace(char oldChar, char newChar) {
            if (oldChar != newChar) {
                int len = value.length;
                int i = -1;
                char[] val = value; /* avoid getfield opcode */
    
                while (++i < len) {
                    if (val[i] == oldChar) {
                        break;
                    }
                }
                if (i < len) {
                    char buf[] = new char[len];
                    for (int j = 0; j < i; j++) {
                        buf[j] = val[j];
                    }
                    while (i < len) {
                        char c = val[i];
                        buf[i] = (c == oldChar) ? newChar : c;
                        i++;
                    }
                    return new String(buf, true);
                }
            }
            return this;
        }
    public String substring(int beginIndex, int endIndex) {
            if (beginIndex < 0) {
                throw new StringIndexOutOfBoundsException(beginIndex);
            }
            if (endIndex > value.length) {
                throw new StringIndexOutOfBoundsException(endIndex);
            }
            int subLen = endIndex - beginIndex;
            if (subLen < 0) {
                throw new StringIndexOutOfBoundsException(subLen);
            }
            return ((beginIndex == 0) && (endIndex == value.length)) ? this
                    : new String(value, beginIndex, subLen);
        }
        .....
    

    通过以上几个方法的代码,我们可以得出以下的结论:

    1. String 对象时不可变的。所谓不可变的意思是说我们使用的很多方法来对字符串进行修改,如以下所示:
    public static void main(String[] args) {
            String str = "test";
            str = str + "a";
            System.out.println(str);//testa
            str += "b";
            System.out.println(str);//testab
        }
    

    诸如上面的 + 号和 concat, replace 等看起来会改变 String 值的方法,其最终都是创建了一个全新的 String 对象,用来包含修改后的字符串内容。str 最先指向的对象 "test" 一直呆在原物理位置上。各个方法操作的其实是复制的一份引用,返回的是一个新的对象,以上例子的原 "test" 还在原始处。

    一些误区:String 的不可变性并不是因为下面的语句

     private final char value[];
    

    final 在引用类型中,只是确保了不能指向其它引用,而不能确保引用的更改。value 是 private 的, 虽然 String 没有提供更改value的方法,但通过反射可将其更改。

    String的 + 与 += 符号

    众所周知, C++ 是可以重载操作符的, 但 Java 并不允许程序员对操作符进行重载,而String的 + 与 += 符号却违反了这个规则。我们都知道,Java 中的这两个操作符都是对字符串进行拼接,看一下以下的代码:

    class Test{
    	public static void main(String[] args){
    		String str = "a";
    		str +="hello" + "world" + "!";
    		System.out.println(str);//ahelloworld!
    		
    		String pStr = "a" + str + "b";
    		System.out.println(pStr);//aahelloworld!b
    	}
    }
    

    如符按照我们理解的 String 的不可变性,那么在多次进行 + 操作时,应该会在最终的结果之前生成多个中间的文件,那么事实真的是这样子吗?如果是这样子想一下就知道效率和性能有多糟糕了。我们对代码进行反编译。

    javap -c Test
    

    其产生了如下的 JVM 字节码:
    字节码
    我们可以看到,在以上的代码中, 编译器为我们自动的引入了 java.lang.StringBuilder 类, 并使用了该类的 append(String str) 这个方法, 最终使用 toString() 产生 String 字符串并进行了赋值。

    「循环」中拼接字符串不要使用 String

    那么,我们是不是可以愉快的使用 + 和 += 这两个操作符了呢? NO! 编译器所能做的也是有限的。

    public class Test {
        public static void main(String[] args) {
            String s = "";
            long start = System.currentTimeMillis();
            for (int i = 0; i <100000 ; i++) {
                s += "a";
            }
            long end = System.currentTimeMillis();
            System.out.println("String time:"+ (end - start));
    
            StringBuilder sb = new StringBuilder("");
            long start2 = System.currentTimeMillis();
            for (int i = 0; i <100000 ; i++) {
               sb.append("a");
            }
            long end2 = System.currentTimeMillis();
            System.out.println("StringBuilder time:"+ (end2 - start2));
        }
    }
    

    在以上的代码中, 输出是这样子的, 我还去掉了中间装载类所花费的时间:

    String time:2573
    StringBuilder time:5
    

    为什么差距这么大呢?

    同样的,我们对这个文件进行反编译,第一个循环的字节码如下:
    第一个循环字节码
    可以看出 10 到 40 就是我们的循环体了,在该循环体中,有一个 new 的操作,这意味着每次进行循环时,都会创建一个 StringBuilder 的对象,每次都使用一次 toString() 方法。而这些过程都是相当的影响性能的。

    而第二个循环的字节码如下:
    这里写图片描述
    从以上我们可以看出,循环是从 95 到 113 行,而这个过程中,始终只有一个 StringBuilder 的对象, 也就是说它没有产生新的对象, 可想而知两个之间的性能是怎么产生差异的了。

    因此,「循环」中拼接字符串不要使用 String, 这个在写 toString() 方法时可能会遇到。

    好,今天我们就分析到这里,下篇文章再会。

    另,转载请注明出处。

  • 相关阅读:
    restframework 生成接口文档
    django simpleRoute 简化路由
    django 视图GenericView
    备份问题-原生问题
    django 中如何使用celery 和redis
    主外键定义与主从表关系
    django restframework 钩子函数:全局钩子和局部钩子
    QuerySet Django 几个必会方法
    django restframework -模型序列化高级用法终极篇
    django User.objects.get()报错 raise self.model.DoesNotExist手动处理解决办法
  • 原文地址:https://www.cnblogs.com/homejim/p/8526096.html
Copyright © 2011-2022 走看看