string、stringbuffer、stringbuilder源码解析

564 阅读6分钟

1.string源码

 private final char value[];

一个被final关键字修饰的char[]数组,所以实现细节上也是不允许改变,这就是String类的Immutable(不可变)属性。导致每次对String的操作都会生成新的String对象导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。

案例:

String string ="abc";

string+="def";

1、首先执行String string ="abc"在堆内存中开辟一块空间存储abc;

 2、执行string+="def"的时候需要先在对内存中开辟一块空间存储def,然后再在堆内存中开辟一块空间存储最终的abcdef,然后将string的引用指向该堆内存空间。可以发现执行这样的短短两行代码需要在堆内存中开辟三次内存空间,造成了对内存空间资源的极大浪费。 

String类能被继承吗?为什么?

Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。   

final类不能被继承,没有子类,final类中的方法默认是final的。   

final方法不能被子类的方法覆盖,但可以被继承。   

final成员变量表示常量,只能被赋值一次,赋值后值不再改变。   

final不能用于修饰构造方法。   

注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。

string源码里面有hash:

private int hash;
public int hashCode() {    
int h = hash;    
if (h == 0 && value.length > 0) {
        char val[] = value;    
    for (int i = 0; i < value.length; i++) { 
           h = 31 * h + val[i];       
 }      
  hash = h;   
 }  
  return h;
}

为什么 h == 0 时进行 hashCode()计算呢?
h 是一个 int 类型的值,默认值为 0,因此 0 可以表示可能未执行过 hash 计算,
但不能表示一定未执行过 hash 计算,原因是我们现在还不确定 hash 计算后是否会产生 0 值;

执行 hash 计算后,会不会产生值为 0 的 hash呢?
根据 hash 的计算逻辑,当 val[0] = 0 时,根据公式 h = 31 * h + val[i]
 进行计算, h 的值等于 0。

val[0] = 0 怎么解释呢?
查看 ASCII 表发现, null 的 ASCII 值为 0 。
显然 val[0]中永远不可能存放 null,因此 hash 计算后不会产生 0 值,h == 0 可以作为是否进行过 hash 计算的判定条件。value.length > 0:也就是说,如果字符串的长度为 0 ,不进行 hash 计算。


在String类中有个私有实例字段hash表示该串的哈希值。在第一次调用hashCode方法时,字符串的哈希值被计算并且赋值给hash字段,之后再调用hashCode方法便可以直接取hash字段返回。初始时hash为0。

String类的hash code的值为val[0]*31^(n-1) + val[1]*31^(n-2) + … + val[n-1],其中val是字符串对应的char数组。所以字符串内容一样的String对象,调用hashCode()返回的值也是一样的。但反过来,返回的hashCode()一样时,其字符串内容不一定一样,因为上面的方法计算hashCode时可能会发生位数溢出。

运行例子:

String name = "che";

value = {'c', 'h', 'e'};

hash = 0;

value.length = 3;

//执行逻辑:

val = value;

val[0] = "c";

val[1] = "h";

val[2] = "e";

h = 31 * 0 + c = c;

h = 31 * (31 * 0 + c) + h = 31 * c + h;

h = 31 * (31 * (31 * 0 + c) + h) + e = 31 * 31 * c + 31 * h + e;

为什么31的值??

主要是因为31是一个奇质数,所以31*i=32*i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。

素数又称质数:指在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数。

根据素数的特性,与素数相乘得到的结果比其他方式更容易产生唯一性,也就是说产生 hash 值重复的概率比较小。

31 的二进制为: 11111,占用 5 个二进制位,在进行乘法运算时,可以转换为 (x << 5) - x。 这里需要考虑乘法的因素,在 Java 中如果相乘的数字太大会导致内存溢出问题,从而导致数据丢失。 

 大数相乘会造成内存溢出,17 也是素数,那为什么不是 17 呢?

 我也蒙圈了。 关于 31 大致总结一下: 31 是一个素数,与素数相乘得到的结果比其他方式更容易产生唯一性。 Java 中如果相乘的数字太大会导致内存溢出问题,从而导致数据丢失。 基于以上两方面的考虑这个因子的值。 

关于选择31素数,具体研究可以参考:

blog.csdn.net/steveguosha…

blog.csdn.net/tayanxunhua…

stackoverflow.com/questions/2…

private static final ObjectStreamField[] serialPersistentFields =    new ObjectStreamField[0];

序列化要包含的域,在序列化源码时在详细介绍,序列化作用。应该清楚transient是用于指定哪个字段不被默认序列化,对于不需要序列化的属性直接用transient修饰即可。而serialPersistentFields用于指定哪些字段需要被默认序列化

  • 字符串和字符

1

1``. public String(``char``[] value); // 构造方法

将全部字符数组变为字符串;

1

2``. public String(``char``[] value, int offset, int count);``// 构造方法

将部分字符数组变为字符串;

1

3``. public char charAt(``int index); // 普通方法

获取指定位置的索引字符;

1

4``. public char``[] toCharArray(); // 普通方法

将字符串中数据按字符数组返回;

  • 字符串和字节

主要目的:进行二进制的数据传输,或者是进行编码转换。

1

1``. public String(``byte``[] bytes); // 构造方法

将全部字节数组变为字符串;

1

2``. public String(``byte``[] bytes, int offset, int length);``// 构造方法

将部分字节数组变为字符串;

1

3``. public byte``[] getBytes(); // 普通方法

将字符串转化为字节数组;

1

4``. public byte``[] getBytes(String charsetName) throws UnsupportedEncodingException; // 普通方法

在字符串判断上:

public boolean equals(String anObject); // 普通方法

if (this == anObject) {  //比较是否是同一个引用,是否都是指向同一个内存中的事例

if (anObject instanceof String) {  //这里就用到了 instanceof  就是判断 穿进来的数据是不是 String类型的 对象 
 这里注意了 虽然 穿进来的是object类型的 但是只能是  String 类 或者是String类的父类才可以 为true
if (n == anotherString.value.length) {  //判断 长度是否一致 为了加快判断

//最后就是判断两个相同字符串的每一位 是否相同

重点函数regionMatches:用来比较两个字符串中指定区域的子串

 public boolean regionMatches(int toffset, String other, int ooffset, int len) { 
   char ta[] = value;
    int to = toffset;
    char pa[] = other.value;
    int po = ooffset;    // Note: toffset, ooffset, or len might be near -1>>>1.
    if ((ooffset < 0) || (toffset < 0)|| (toffset > (long)value.length - len)||
         (ooffset > (long)other.value.length - len)) { //判断偏移量符合定义和字符串比较长度在范围
        return false;    
}
    while (len-- > 0) {
        if (ta[to++] != pa[po++]) {   //循环比较单个字符的
            return false;
        }
    }
    return true;
}

toffset – 此字符串toffset区域的起始偏移量。other – 字符串参数。ooffset – 字符串参数ooffset区域的起始偏移量。len – 要比较的字符数。


public boolean regionMatches(boolean ignoreCase, int toffset,String other, int ooffset,
 int len) {//ignoreCase 忽略大小写
  • 字符串查找

从一个完整的字符串之中查找子字符串的存在

1

1``. public boolean contains(String s); // 普通方法

判断子字符串是否存在;

1

2``. public int indexOf(String str); // 普通方法

从头查找字符串,找不到返回 -1;

1

3``. public int indexOf(String str, int fromIndex); // 普通方法

从指定索引位置查找子字符串;

1

4``. public int lastIndexOf(String str); // 普通方法

由后向前查找子字符串;

1

5``. public int lastIndexOf(String str, int fromIndex); // 普通方法

从指定位置由后向前查找子字符串;

1

6``. public boolean startsWith(String prefix); // 普通方法

判断是否以指定字符串开头;

1

7``. public boolean startsWith(String prefix, int toffset); // 普通方法

从指定位置判断是否以指定字符串开头;

1

8``. public boolean endsWith(String suffix); // 普通方法

判断是否以指定字符串结尾;

  • 字符串替换

1

1``. public String replaceAll(String regex, String replacement); // 普通方法

全部替换;

1

2``. public String replaceFirst(String regex, String replacement); // 普通方法

 替换首个;

  • 字符串拆分

拆分完成的数据将以字符串数组的形式返回。

1

1``. public String[] split(String regex); // 普通方法

按照指定的字符串全部拆分;

1

2``. public String[] split(String regex, int limit); // 普通方法

按照指定的字符串拆分为指定个数;

字符串截取

1

1``. public String substring(``int beginIndex); // 普通方法

从指定索引截取到字符串结束;

1

2``. public String substring(``int beginIndex, int endIndex); // 普通方法

截取指定索引范围的字符串;

其他方法

1

1``. public String concat(String str);

字符串连接;

1

2``. public String intern();

字符串入池;

1

3``. public boolean isEmpty();

判断字符串是否为空;

1

4``. public int length();

字符串长度;

1

5``. public String trim();

去除字符串左右空格;

1

6``. public String toUpperCase();

转大写;

1

7``. public String toLowerCase();

转小写。

这里这些基础方法就不一一介绍了,看代码基本都可以看懂。

2. stringbuffer源码

stringbuffer继承的是AbstractStringBuilder 类。

public final class StringBuffer   extends AbstractStringBuilder  
implements java.io.Serializable, CharSequence

private transient char[] toStringCache;  //不需要序列化


public synchronized StringBuffer append(String str) {  //string类没有append方法
    toStringCache = null;
    super.append(str);
    return this;
}public synchronized StringBuffer insert(int offset, String str) {   
    toStringCache = null;
    super.insert(offset, str);
    return this;
}

transient:java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

synchronized:保证线程安全

有append、delete、replace、insert等等。

重点介绍AbstractStringBuilder:

扩容方法

private void ensureCapacityInternal(int minimumCapacity) {

// overflow-conscious code

if (minimumCapacity - value.length > 0)

value = Arrays.copyOf(value, newCapacity(minimumCapacity));

}

private void newCapacity(int minimumCapacity) {

int newCapacity = value.length * 2 + 2; // 扩容为原来的 2 倍加 2

if (newCapacity - minimumCapacity < 0) // 如果扩容后的长度仍然小于最小扩容长度,则新长度赋值为最小扩容长度

newCapacity = minimumCapacity;

if (newCapacity < 0) { // 新长度为负数,超过 int 的最大值,变为了负数

if (minimumCapacity < 0) // overflow // 最小的扩容长度为负数,也是超过了 int 最大值

throw new OutOfMemoryError();

newCapacity = Integer.MAX_VALUE; // 如果最小扩容长度没有超过 int 最大值,但是原长度翻倍加2后超过了,则把新长度赋值为 int 最大值

}

value = Arrays.copyOf(value, newCapacity); // 调用 Arrays.copyOf 生成新长度的数组

}

private int newCapacity(int minCapacity) { // overflow-conscious code //二倍加二的扩容方式

int newCapacity = (value.length << 1) + 2;   //按位计算速度更快,

if (newCapacity - minCapacity < 0) {

newCapacity = minCapacity;

}

return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; }

private int hugeCapacity(int minCapacity) {
    if (Integer.MAX_VALUE - minCapacity < 0) { // overflow        
throw new OutOfMemoryError();  
  }  
  return (minCapacity > MAX_ARRAY_SIZE)  ? minCapacity : MAX_ARRAY_SIZE;}

在append方法时,有添加null,boolean、char、char【】等等。

delete:

public AbstractStringBuilder delete(int start, int end) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        end = count;
    if (start > end)
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    if (len > 0) { 
        System.arraycopy(value, start+len, value, start, count-end);  //start + len 位置开始的元素向前移动 len 个距离
        count -= len;
    }
    return this;
}

insert:

public AbstractStringBuilder insert(int offset, String str) {
    if ((offset < 0) || (offset > length())) 
       throw new StringIndexOutOfBoundsException(offset);
    if (str == null)
        str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    System.arraycopy(value, offset, value, offset + len, count - offset);
    str.getChars(value, offset);
    count += len;
    return this;
}

reverse:

public AbstractStringBuilder reverse() {
    boolean hasSurrogates = false;
    int n = count - 1;
    for (int j = (n-1) >> 1; j >= 0; j--) {
        int k = n - j;
        char cj = value[j];
        char ck = value[k];
        value[j] = ck;
        value[k] = cj;
        if (Character.isSurrogate(cj) || Character.isSurrogate(ck)) {
            hasSurrogates = true;
        }
    }
    if (hasSurrogates) {
        reverseAllValidSurrogatePairs();
    }
    return this;
}

stringbuilder源码

StringBuilder类表示一个可变的字符序列。StringBuilder的API与StringBuffer互相兼容,但是StringBuilder是非线程安全的。在可能的情况下,建议优先使用StringBuilder,因为在大多数实现中它比StringBuffer更快。

大部分代码与stringbuffer是相同的,主要是没有synchronized。

执行速度上:

stringbuilder > stringbuffer > string 

线程安全上: 

StringBuffer是线程安全的,stringbuilder 是非线程安全; 

使用场景: 

string适合少量字符串操作情况; 

stringbuilder适合单线程下载 字符缓冲区进行大量操作;

 stringbuffer 适合多线程下字符缓冲区进行大量操作;