java底层实现细节

  1. JDK6
    由char[], hash, offset, value组成。
  2. JDK8
private final char value[];// 该值用于字符存储。
private int hash; // 缓存字符串的哈希码,默认为0。

少了offset和count两个变量,占用内存少,同时String.subString方法也不再共享char[],从而解决了可能导致的内存泄漏问题。

String对象的创建

JVM创建对象简介:

java中对象的创建都是在堆中分配地址(基本类型除外),然后在栈内存的局部变量表中存放对象的引用。

但是String比较特殊,JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串常量池。

在JVM运行时区域的方法区中,有一块区域是运行时常量池,主要用来存储编译期生成的各种字面量符号引用

String对象创建两种方式:

硬编码

使用硬编码也就是在代码中显示的申明(比如String s = “java”;),JVM会在字符串常量池中先检查是否存在该字面量,如果存在,则返回该字面量在内存中的引用地址;如果不存在,则在字符串常量池中创建该字面量并返回引用。使用这种方式创建的好处是避免了相同值的字符串在内存中被重复创建,节约了内存,同时这种写法也会比较简单易读一些。

new String()

使用new String()对象时JVM同样会在字符串常量池中先检查是否存在该字面量:如果存在,则会在堆中另外创建一个String对象,然后在这个String对象的内部引用该字面量,最后返回该String对象在内存地址中的引用;如果不存在,则会先在字符串常量池中创建该字面量,然后再在堆中创建一个String对象,然后再在这个String对象的内部引用该字面量,最后返回该String对象的引用。

String对象的特性

不变性:String类使用了final修饰符,同时内部的char[]数组也使用了final修饰,所以该类既不能被继承同时在初始化之后也不能再被更改。如果需要修改的话可以参考下面的StringBuilder和StringBuffer,不变性主要是为了迎合字符串常量池的功能性和安全性。

StringBuilder与StringBuffer的区别与联系

StringBuffer是同步的,StringBuilder是非同步的,两者都是继承AbstractStringBuilder,StringBuffer在所有方法中都加了synchronized。

String中传入StringBuffer对象的时候,String也会对buffer对象加上同步锁:

public String(StringBuffer buffer) {
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

同时在String调用equals方法时也会判断是否为StringBuffer对象:

public boolean contentEquals(CharSequence cs) {
    // Argument is a StringBuffer, StringBuilder
    if (cs instanceof AbstractStringBuilder) {
        if (cs instanceof StringBuffer) {
            synchronized(cs) {
               return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        } else {
            return nonSyncContentEquals((AbstractStringBuilder)cs);
        }
    }
    // Argument is a String
    if (cs instanceof String) {
        return equals(cs);
    }
    // Argument is a generic CharSequence
    char v1[] = value;
    int n = v1.length;
    if (n != cs.length()) {
        return false;
    }
    for (int i = 0; i < n; i++) {
        if (v1[i] != cs.charAt(i)) {
            return false;
        }
    }
    return true;
}

StringBuffer为了性能优化添加了toStringCache

public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

substring引发的血案

String实际上是一个字符数组.在 JDK6中, String对象主要包含3个属性域:

private final char value[];

private final int offset;

private final int count;

他们用于存储实际的字符数组,数组的第一个索引,以及String的字符个数.
当调用 substring() 方法时,创建了一个新的String对象,但是string的value[] 属性域仍然指向堆内存中的原来的那个数组。区别就是 两个对象的 count 和 offset 这两个值不同了(当原字符串引用释放后,内存仍然会被substring()字串引用,就得不到释放,这样就会导致内存浪费)。在JDK 7 中这个问题得到改进, substring()方法真实地在堆内存中创建了另一个字符数组.