StringとStringBufferおよびStringBuilderの解析

この記事は、「新人クリエーションセレモニー」イベントに参加し、一緒にゴールドクリエーションの道を歩み始めました。

この記事はCSDNで最初に公開されたため、写真にもCSDN透かしがあります。記事はオリジナルであり、侵害はありません。

文字列文字列ビルダーパスワード文字列バッファ

文字列の解析

序文

文字列は参照型であり、基本型ではありません。最下層はchar配列で構成されています。文字列オブジェクトは不変であり、一度作成すると変更できないと言います。それでは、なぜ通常concat(String s)を使用して文字列を作成できるのでしょうか。 ?スプライシング?

実際、ソースコードでは、新しいStringオブジェクトが実際に作成され、それに値が割り当てられ、元のオブジェクトではなくなった新しいStringオブジェクトが返されることがわかります。

Stringのconcat()メソッドのソースコードは次のとおりです。

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);		//从这里我们可以看出这里是新new了一个对象
    }
复制代码

在这里插入图片描述

StringBufferとStringBuilderの場合、スプライシングメソッドを実行した後、基になるchar配列を展開できるため、アドレスは変更されません。これは元のアドレスのままです。(StringBufferとStringBuilderについては以下で詳しく説明します)

在这里插入图片描述

文字列オブジェクトの継承システム
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence 
复制代码
  • 文字列はファイナライズされます。つまり、継承できません。
  • シリアル化可能なインターフェイスを実装します。これは、シリアル化および逆シリアル化できることを意味します
  • Comparableインターフェースを実装します。これは、文字列オブジェクトを比較できることを意味します
  • これが文字列であることを示すCharSequenceインターフェイスを実装します
メンバー変数
private final char value[];		//被final修饰,表示只可以被赋值一次
private int hash; 				// Default to 0,哈希值,默认为0
复制代码
工法
//无参的构造方法
public String() {
    this.value = "".value;			//这里要注意:full并不是""
}
//传入一个String对象的构造方法,注意利用这个构造方法构造出来的对象和传入的对象并不指向同一块地址
public String(String original) {
        this.value = original.value;
        this.hash = original.hash;	//赋值哈希值
    }
//传入一个char数组构造String对象
public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
//传入一个byte数组构造String对象
public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }
//传入一个StringBuffer构造String对象
public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
//传入一个StringBuilder对象构造String对象
public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }
复制代码
メンバーメソッド
int length()

Stringオブジェクトの長さを返します

public int length() {
    return value.length;
}
复制代码
ブールisEmpty()

文字列が空の文字列かどうかを確認します

public boolean isEmpty() {
    return value.length == 0;
}
复制代码
char charAt(int index)

インデックスでchar文字を取得します

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {		//判断index是否非法
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}
复制代码
byte [] getBytes()

文字列オブジェクトをバイト配列に変換して返します

public byte[] getBytes() {
    return StringCoding.encode(value, 0, value.length);
}
复制代码
boolean equals(Object anObject)

着信パラメータがそれ自体と等しいかどうかを判断します(内容が等しい)

文字列はObjectのequalsを書き換え、equalsを内容が等しいかどうかを比較するためのメソッドに書き換えます(Objectでは、equalsはアドレスを比較するためのメソッドです)

public boolean equals(Object anObject) {
    if (this == anObject) {						//地址上相等,内容一定相等,直接返回true
        return true;
    }
    if (anObject instanceof String) {			//判断是不是String类型(或其父类)
        String anotherString = (String)anObject;	//强转为String类型
        int n = value.length;					//到这里就说明是String类型的了,得到字符串长度
        if (n == anotherString.value.length) {	//长度相等才可以继续比较,不相等直接中断返回false
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {					//一个一个比较
                if (v1[i] != v2[i])				
                    return false;				//不等返回false
                i++;
            }
            return true;						//可以执行到这里说明一定相等,返回true就好了
        }
    }
    return false;
}
复制代码
boolean equalsIgnoreCase(String anotherString)

忽略大小写判断是否相等(内容上是否相等)

public boolean equalsIgnoreCase(String anotherString) {
    return (this == anotherString) ? true
            : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}
复制代码
int compareTo(String anotherString)

比较两个字符串的大小(字典序比较)

长度不等则底层char数组一个一个从前往后比较,直到一个char数组中的字符和另一个char数组中的对应位置字符不等,

如果直到一个char数组为空前面的对应位置的字符都相等则比较字符串长度

public int compareTo(String anotherString) {
    int len1 = value.length;					//获取自身字符串的长度
    int len2 = anotherString.value.length;		//获取传入字符串的长度
    int lim = Math.min(len1, len2);				//得到最小的长度
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    ////先比较从起始长度到较短字符串的结尾,如果可以得出结果就返回结果,得不出结果就比较长度
    while (k < lim) {							
        char c1 = v1[k];		
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;						 	//前面都相等进行长度比较
}
复制代码
compareToIgnoreCase(String str)

忽略大小写比较两个String对象的大小

public int compareToIgnoreCase(String str) {
    return CASE_INSENSITIVE_ORDER.compare(this, str);
}
复制代码
String substring(int beginIndex, int endIndex)

截取String对象(从下标为beginIndex到下标为endIndex - 1处,这里不包括endIndex处的字符)

从下面的源码中我们也可以看出所谓的截取字符串也是创建了一个新的String对象,这也再次印证了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);		//从这里可以看出截取返回的String是新new的
    }
复制代码
int hashcode()

因为String没有继承类,所以其父类就是Object,Object中的hashcode()方法返回的是地址,而String重写了hashcode()返回的是内容的hash,也就是说,如果两个String对象内容一样,则使用hashcode()返回的值也一定相等

这里的hashcode()使用的是内容作为哈希,我觉得应该是为了之后的键值对映射,为了使内容相等的String键可以映射到一块相同的索引处,如果不重写hashcode()则内容相同的String不会映射到同一块地址。在选取基数时选取质数为基数哈希冲突更小,而至于为什么选取31作为基数呢?我觉得是为了哈希冲突的概率更小吧。总之,一切为了减小哈希冲突的概率

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;
}
复制代码
常量池

在java中==比较的是地址

当我们使用双引号直接创建字符串时,其实会先创建字符串,再把字符串放入常量池中,当接下来如果再使用双引号创建相同的字符串时,会直接从常量池中取出,而不会重新创建,节省了空间。如下的结果也说明了常量池的存在

在这里插入图片描述

而如果使用new创建的字符串(即使创建的是常量池中的字符串,也不会从常量池中取),自己会在堆中新创建一个

那么尽然是new的,肯定和原来内容相等的字符串通过==比较出的结果肯定不一致,如下

在这里插入图片描述

最后,我们说说String中利用+号拼接字符串的原理

对于两个常量的拼接就是使用双引号创建的字符串

//字符串常量拼接
public class Main {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        String s3 = "abc" + "edf";						//两个常量使用+号拼接
        System.out.println(s1 == s2);
    }
}
//反编译之后
public class Main {
    public Main() {
    }

    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        String s3 = "abcedf";							//这里可以看出本质是使用双引号创建的
        System.out.println(s1 == s2);
    }
}
复制代码

对于两个变量的拼接,本质是new了一个StringBuilder对象

//源代码
public class Main {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        String s3 = s1 + s2;									//两个变量使用+号拼接
    }
}
//反编译
public class Main {
    public Main() {
    }

    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        (new StringBuilder()).append(s1).append(s2).toString();//本质是new了一个StringBuilder(可变字符串)进行拼接
    }
}
复制代码

因为StringBuffer与StringBuilder的源码极其类似,我们把这两个类放在一起讲

StringBuffer与StringBuilder解析

前言

我们说StringBuffer和StringBuilder是可变字符串,其底层是一个char数组,那么我们是如何来实现这样一个可变字符串类呢?我们一起来看一看吧!

继承体系
//这是StringBuffer的继承体系
public final class StringBuffer
   extends AbstractStringBuilder
   implements java.io.Serializable, CharSequence
   
//这是StringBuilder的继承体系
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
复制代码
  • StringBuffer和StringBuilder被final修饰,意味着它不可以被继承
  • 继承了AbstractStringBuilder父类,具有了一些成员变量,也具有append(),insert(),delete()等方法
  • 实现了Serializable接口,表明可以序列化和反序列化
  • 实现了CharSequence,表明这是一个字符串
成员变量

在StringBuffer与StringBuilder只有一个记录版本的成员变量,对于我们理解这两个类没有太大关系,那么我们到它的父类看看吧!

下面是它的父类AbstractStringBuilder的成员变量

//注意:这里未被final修饰,表名可以被修改
char[] value;						//底层的char数组,是动态的,当容量不足时,会对它进行扩容
int count;							//实际存储的字符数量
复制代码
构造方法
StringBuffer的构造方法
//无参构造,字符数组长度为16
public StringBuffer() {
    super(16);
}
//传入一个容量参数,指定字符数组的长度
public StringBuffer(int capacity) {
     super(capacity);
}
//传入一个String对象,开辟的数组长度是传入的String对象的长度+16
public StringBuffer(String str) {
    super(str.length() + 16);
    append(str);	//这里使用了StringBuilder重写的append方法,但加上了synchronized其本质也是调用了父类的append方法
}
复制代码
StringBuilder的构造方法
//无参构造,字符数组长度为16
public StringBuilder() {
    super(16);
}
//传入一个容量参数,指定字符数组的长度
public StringBuilder(int capacity) {
    super(capacity);
}
//传入一个String对象,开辟的数组长度是传入的String对象的长度+16    
public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);	//这里使用了StringBuilder重写的append方法,其本质也是调用了父类的append方法
}
复制代码

既然StringBuffer与StringBuilder的构造方法基本上都是调用父类AbstractStringBuilder的构造方法,那我们来看一下父类AbstractStringBuilder的构造方法吧!

AbstractStringBuilder的构造方法
//无参构造
AbstractStringBuilder() {
}

//字符数组的长度为指定长度
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}
复制代码
成员方法
append()

StringBuffer的append(方法)

进行拼接(可以拼接String,StringBuffer,StringBuilder,boolean,int,float),返回类型都是StringBuffer

我们可以看到这些拼接方法其实都是调用父类AbstractStringBuilder的拼接方法,但是在StringBuffer中这些方法都使用synchronized修饰,表明是线程安全的。

//拼接Object
@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }
//拼接String
@Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
//拼接StringBuffer,这个拼接是子类自己定义的,但是本质还是调用父类的拼接方法
public synchronized StringBuffer append(StringBuffer sb) {
        toStringCache = null;
        super.append(sb);
        return this;
    }
//拼接AbstractStringBuilder(可以接收StringBuilder)
@Override
    synchronized StringBuffer append(AbstractStringBuilder asb) {
        toStringCache = null;
        super.append(asb);
        return this;
    }
//拼接字符数组
@Override
    public synchronized StringBuffer append(char[] str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
//拼接boolean类型的
@Override
    public synchronized StringBuffer append(boolean b) {
        toStringCache = null;
        super.append(b);
        return this;
    }
//拼接字符类型的
@Override
    public synchronized StringBuffer append(char c) {
        toStringCache = null;
        super.append(c);
        return this;
    }
复制代码

StringBuilder的append()方法

与StringBuffer不同的是,这些方法没有使用synchronized修饰,表明这些方法不是线程同步的,但是其内部还是调用的是父类AbstractStringBuilder的拼接方法。

//拼接Object
@Override
public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }
//拼接String
@Override    
public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
//拼接StringBuffer
@Override
public StringBuilder append(StringBuffer sb) {
        super.append(sb);
        return this;
    }
//拼接字符数组
@Override
public StringBuilder append(char[] str) {
        super.append(str);
        return this;
    }
//拼接boolean
@Override
public StringBuilder append(boolean b) {
        super.append(b);
        return this;
    }
//拼接字符
@Override
public StringBuilder append(char c) {
        super.append(c);
        return this;
    }

复制代码

既然StringBuffer与StringBuilder的拼接其实都是调用父类AbstractStringBuilder的拼接方法,那我们来看看父类的拼接方法吧!

AbstractStringBuilder的append()方法

append()方法内部基本都是确保容量(确保容量方法里面有扩容方法),然后追加。

public AbstractStringBuilder append(Object obj) {
    return append(String.valueOf(obj));
}
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;
    }
public AbstractStringBuilder append(StringBuffer sb) {
        if (sb == null)
            return appendNull();
        int len = sb.length();
        ensureCapacityInternal(count + len);		//确保容量
        sb.getChars(0, len, value, count);
        count += len;
        return this;
    }
AbstractStringBuilder append(AbstractStringBuilder asb) {
        if (asb == null)
            return appendNull();
        int len = asb.length();
        ensureCapacityInternal(count + len);		//确保容量
        asb.getChars(0, len, value, count);
        count += len;
        return this;
    }
public AbstractStringBuilder append(boolean b) {
        if (b) {
            ensureCapacityInternal(count + 4);		//确保容量
            value[count++] = 't';
            value[count++] = 'r';
            value[count++] = 'u';
            value[count++] = 'e';
        } else {
            ensureCapacityInternal(count + 5);		//确保容量
            value[count++] = 'f';
            value[count++] = 'a';
            value[count++] = 'l';
            value[count++] = 's';
            value[count++] = 'e';
        }
        return this;
    }
复制代码

AbstractStringBuilder中的扩容方法

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {			//容量不够,进行扩容
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;		//扩容为原来的两倍加2
        if (newCapacity - minCapacity < 0) {			//扩容之后还是不够,以最小需要的容量为准
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)//扩容之后的容量小于等于0或大于最大容量
            ? hugeCapacity(minCapacity)									//执行hugeCapacity方法
            : 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;							//最小需要的容量小于最大容量返回最大容量
    }
复制代码
delete(int start, int end)方法

StringBuffer的delete方法

移除起始位置从下标start开始,end终止(不包括end)的字符

@Override
    public synchronized StringBuffer delete(int start, int end) {
        toStringCache = null;
        super.delete(start, end);
        return this;
    }
复制代码

StringBuilder的delete方法

移除起始位置从下标start开始,end终止(不包括end)的字符

@Override
public StringBuilder delete(int start, int end) {
    super.delete(start, end);
    return this;
}
复制代码

本质都是调用了父类AbstractBuilder的delete方法

AbstractBuilder的delete方法

public AbstractStringBuilder delete(int start, int end) {
    if (start < 0)						//其实索引小于0,抛异常
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)					//删除的终止处大于数组长度,将end赋值为数组长度
        end = count;
    if (start > end)					//起始索引大于终止索引,抛异常
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    if (len > 0) {
        System.arraycopy(value, start+len, value, start, count-end);	//调用arraycopy进行拷贝完成删除操作
        count -= len;
    }
    return this;
}
复制代码
replace(int start, int end, String str)方法

StringBuffer的replace方法

@Override
public synchronized StringBuffer replace(int start, int end, String str) {
    toStringCache = null;
    super.replace(start, end, str);
    return this;
}
复制代码

StringBuilder的replace方法

@Override
public StringBuilder replace(int start, int end, String str) {
    super.replace(start, end, str);
    return this;
}
复制代码

都是调用了父类AbstractBuilder的replace方法

AbstractBuilder的replace(int start, int end, String str)

public AbstractStringBuilder replace(int start, int end, String str) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (start > count)
        throw new StringIndexOutOfBoundsException("start > length()");
    if (start > end)
        throw new StringIndexOutOfBoundsException("start > end");

    if (end > count)
        end = count;
    int len = str.length();
    int newCount = count + len - (end - start);
    ensureCapacityInternal(newCount);							//确保容量

    System.arraycopy(value, end, value, start + len, count - end);		//进行赋值
    str.getChars(value, start);
    count = newCount;
    return this;
}
复制代码
toString方法

StringBuffer的toString方法

将StringBuffer对象转化为String对象

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}
复制代码

StringBuilder的toString方法

将StringBuilder转化为String对象

@Override
public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}
复制代码

总结

  • String是不可变字符串,底层char数组使用final修饰。String的优点,使用线程池,可以节省空间。

  • StringBuffer与StringBuilder是可变字符串(底层char数组长度可变),两者的主要区别是StringBuffer的方法都加上了synchronized修饰,表明StringBuffer是线程安全的,而StringBuilder是线程不安全的。

  • StringBuff与StringBu内部的许多方法其实都是调用父类AbstractStringBuilder的方法,在进行频繁的字符串拼接时,建议使用StringBuilder与StringBuilder会比String更快些。

  • 转化

    在使用构造方法时转化,其中一个可以传入另外两种类对象的的任意一个(当然也可以穿入同属于同一种类的对象)

    在创建完之后转化,StringBuffer和StringBuilder转化为String——>使用toString()方法

    ​ 虽然String不可以转化为StringBu或StringBuilder,但是String可以使用+号拼接StringBuffer和StringBuider

    ​ StringBuffer可以使用append方法拼接String或StringBuilder

    ​ StringBuilder可以使用append方法拼接String或StringBuffer

おすすめ

転載: juejin.im/post/7083618834128568350