博客
关于我
Java中String和StringBuilder
阅读量:124 次
发布时间:2019-02-26

本文共 6854 字,大约阅读时间需要 22 分钟。

做了好几天的面试题,发现 String 类的题目一直是个大头,看似简单,实则不然,牵扯到很多底层的东西。接下来我就跟着源码和题目来分析一下把

一、String的对象不可变

public final class String    implements java.io.Serializable, Comparable
, CharSequence { /** The value is used for character storage. */ private final char value[];

可以看到,String 类是被 final 修饰的,即 String 类不能被继承。其中有一个很重要的数组,char 数组,用来保存字符串的,既然是用 final 关键字来修饰的,那就代表 String 是不可变的

举以下两个方法为例,通过源代码可以看到,substring,replace 最后都是通过 new String(xxx) 来产生了一个新的 String 对象,最原始的字符串并没有改变

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);}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 void stringTest3(){       String a = "nihaoshije";    String b = a.substring(0, 4);    System.out.println(b);    System.out.println(a);}

最终的输出为:niha nihaoshije。可见如我们所预料的,原来的 String 的值并没有改变

二、StringBuilder 中的字符可变

我们可以通过 StringBuilder 的源码分析下

我们假设使用 append 方法,该方法返回的依然是 StringBuffer 对象本身,说明他确实是值改变了

public StringBuilder append(String str) {       super.append(str);    return this;}

该方法实际调用的是 StringBuilder 的父方法。该父方法,会先检测容量够不够,不够的话会进行扩容,然后调用 String 的 getChars 方法。注意,最后返回的依旧是 StringBuffer 对象

public AbstractStringBuilder append(String str) {       if (str == null)        return appendNull();    int len = str.length();    ensureCapacityInternal(count + len);    str.getChars(0, len, value, count);    count += len;    return this;}

getChars 方法具有 4 个参数

  • srcBegin – 字符串中要复制的第一个字符的索引。
  • srcEnd – 字符串中要复制的最后一个字符之后的索引。
  • dst – 目标数组。
  • dstBegin – 目标数组中的起始偏移量。

其中的 dst 对应 StringBuilder 的父类里的数组 char[] value,该数组是专门用来存放字符的。append(String str) 里的字符串就是以字符格式一个个加到 char 数组里的

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {       if (srcBegin < 0) {           throw new StringIndexOutOfBoundsException(srcBegin);    }    if (srcEnd > value.length) {           throw new StringIndexOutOfBoundsException(srcEnd);    }    if (srcBegin > srcEnd) {           throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);    }    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);}

我们可以举一个例子来看一下 StringBuffer 的对象是否真正改变了

public void stringBuilderTest() {       StringBuilder stringBuilder = new StringBuilder("hello");    System.out.println(stringBuilder);    stringBuilder.append("world");    System.out.println(stringBuilder);}

第一输出为 hello,第二次再次输出同一个对象,变为 helloworld,结果一目了然

三、关于字符串相加

这里我讨论的是使用“+”对两个字符串相加的过程,看似很简单的相加,其实蕴藏着很多的密码

《Java编程思想》提到,String 对象的操作符“+”其实被赋予了特殊的含义,该操作符是 Java 中仅有的两个重载过的操作符。而通过反编译可以看到,String 对象在进行“+”操作的时候,其实是调用了 StringBuilder 对象的 append() 方法来加以构造

按照惯例,我们先来看两个字符串相加的例子

public void stringTest4(){       String a1 = "helloworld";    String a = "hello" + "world";    String b = "hello";