最近在研究String,对String用“+"拼接的过程很好奇,于是有了下面的描述。。。

本文先对两个字符串拼接进行了研究,然后再对两个字符串变量拼接进行,最后小结了一下,一起来看吧!!!

片段1:两个字符串拼接

public class Test {
    public static void main(String args[]) {
        String str = "hello" + "world";
    }
}

用javap查看class文件结构

从文件结构中我们可以看出,"hello" + "world"直接被编译成了"helloworld",并且将这个常量加载到操作数栈,再存储到局部变量表,最后return结束。

public class Test {
    public static void main(String args[]) {
        String str1 = "hello" + "world";
        String str2 = "helloworld;
     System.out.print(str.equals(str));
    }
}

所以,上述代码执行equals的结果是true,String str = "hello" + "world" 和 String str = "helloworld"的效果是一样的。

 

延伸:上面讲述的是直接把字符串赋值给变量,那new String()是一个怎样的过程呢?

public class Test {
    public static void main(String args[]) {
        String str = new String("helloworld");
    }
}

用javap查看class文件结构

从文件结构中我们可以看出,首先不管如何会先new一个String,然后复制栈顶值且将该值压入栈顶,初始化String,再存储到局部变量表,最后return结束。

 

片段2:两个变量拼接

public class Test {
    public static void main(String args[]) {
        String str = "hello";
        str = str + "world";
    }
} 

以上这段代码会发生什么呢?

我们先来先看一下class文件

首先将变量str存储局部变量表里,接着new StringBuilder(),并初始化,然后调用StringBuilder.append()方法拼接字符串,再通过StringBuilder.toString()调用new String()得到一个新的字符串变量,再将这个变量存储到局部变量表,最后return结束。

所以代码片段2的过程其实为new StringBuilder().append(str).append("world").toString()。

既然String用加号的拼接过程用到了StringBuilder的append和toString方法,不如看看这两个方法的过程吧。

// StringBuilder的append方法
public StringBuilder append(String str) {
    // 调用父类AbstractStringBuilder的append方法
    super.append(str);
    return this; 
}    

public AbstractStringBuilder append(String str) {
     // 若字符串为空,返回"null"
     if (str == null)
        return appendNull();
     int len = str.length();
     // 获取数组的大小,若是新的字符串的size大于char[]的length则new一个新的char[],并把原来的char赋值到新的char[]上
     ensureCapacityInternal(count + len);
     // 把str放入char[],底层调用本地方法System.arraycopy
     str.getChars(0, len, value, count);
     count += len;
     return this;
}

// 获取容器大小
private int newCapacity(int minCapacity) {
    // 若新字符串 + 已存在的字符串的大小大于char[]的大小,则新容器的大小翻倍+2
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
          newCapacity = minCapacity;
     }
     // 若newCapacity<Integer.MAX_VALUE - 8则char[]大小取newCapacity;若Integer.MAX_VALUE- 8<newCapacity<Integer.MAX_VALUE,取则char[]大小取Integer.MAX_VALUE;若newCapacity>Integer.MAX_VALUE,抛错。
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)? hugeCapacity(minCapacity): newCapacity;
}    

 

// 而StringBuilder的toString()调用的是new String()
public String toString() {
     return new String(value, 0, count);
}}    

public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
     }
     if (count <= 0) {
        if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
         }
        if (offset <= value.length) {
            this.value = "".value;
            return;
            }
        }
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        // 根据拼接后的字符串长度new一个新的char[],并把原来的char[]数组的值复制到新的char[]上,节省内存
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
}

 

小结

1、两个字符串直接拼接时,编译器对其进行了优化,此时String str = "hello" + "world"和String str = "helloworld"是一样的;

2、由上述的分析可以两个字符串的拼接的底层是通过StringBuilder的append方法先将字符串放在char[]数组中,再通过toString()调用new String()新建一个合适长度的char[],再把旧的char[]复制到新的char[]上完成的,而一个小小的”+“拼接的过程经过了3次System.arraycopy,过程复杂,每次“+”都会new StringBuilder又要toString,若是在for循环中使用这种方式,可谓是呜呼哀哉,并且每次循环一次就会new StringBuilder(),若循环的次数非常多会new非常多的StringBuilder。

 

最后,欢迎大家留言。。。

posted on 2020-04-12 22:40  呵呵静  阅读(3937)  评论(0编辑  收藏  举报