JAVA封装字符串:String,StringBuffer,StringBuilder类

String基础

构造方法:

1)public String():空构造

2)public String(byte[] bytes):把字节数组转成字符串

3)public String(byte[] bytes,int offset,int length):把字节数组的一部分转成字符

4)public String(char[] value):把字符数组转成字符串

5)public String(char[] value,int offset,int count):把字符数组的一部分转成字符串

6)public String(String original):把字符串常量值转成字符串

常用方法:

(一)判断功能

boolean equals(Object obj):比较字符串的内容是否相同,区分大小写

boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写

boolean contains(String str):判断大字符串中是否包含小字符串

boolean startsWith(String str):判断字符串是否以某个指定的字符串开头

boolean endsWith(String str):判断字符串是否以某个指定的字符串结尾

boolean isEmpty():判断字符串是否为空。

(二)获取功能

int length():获取字符串的长度。

char charAt(int index):获取指定索引位置的字符

 int indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引:为什么这里是int类型,而不是char类型?原因是:'a'和97其实都可以代表'a'

 int indexOf(String str):返回指定字符串在此字符串中第一次出现处的索引。

 int indexOf(int ch,int fromIndex):返回指定字符在此字符串中从指定位置后第 一次出现处的索引。

 int indexOf(String str,int fromIndex):返回指定字符串在此字符串中从指定位置 后第一次出现处的索引。

 String substring(int start):从指定位置开始截取字符串,默认到末尾。

 String substring(int start,int end):从指定位置开始到指定位置结束截取字符 串。

(三)转换功能

byte[] getBytes():把字符串转换为字节数组。

char[] toCharArray():把字符串转换为字符数组

static String valueOf(char[] chs):把字符数组转成字符串。

static String valueOf(int i):把int类型的数据转成字符串。该方法的入参可以是任何基本数据类型

String toLowerCase():把字符串转成小写。

String toUpperCase():把字符串转成大写。

String concat(String str):把字符串拼接。

(四)替换功能

String replace(char old,char new)

String replace(String old,String new)

(五)去空格

String trim()

(六)按字典比较

int compareTo(String str)

int compareToIgnoreCase(String str)

(七)分割功能

String [] split(String,int)

String [] split(String)

String深入解析:

部分源码:

public final class String
    implements java.io.Serializable, Comparable, CharSequence {

    private final char value[];

    private int hash; // Default to 0

//下面省略。。。
}

String类是用final修饰的,这意味着String不能被继承,所有的成员方法都默认为final方法。

String类实现的接口:

java.io.Serializable:这个序列化接口仅用于标识序列化的语意。

Comparable:这个compareTo(T 0)接口用于对两个实例化对象比较大小。

CharSequence:这个接口是一个只读的字符序列。包括length(), charAt(int index), subSequence(int start, int end)这几个API接口,值得一提的是,StringBuffer和StringBuild也是实现了该接口。

String的成员变量:

value[] :char数组用于储存String的内容。

hash :String实例化的hashcode的一个缓存,String的哈希码被频繁使用,将其缓存起来,每次使用就没必要再次去计算,这也是一种性能优化的手段。这也是String被设计为不可变的原因之一,后面会讲到。

StringJVM层解析:

字符串常量池存在运行时常量池之中(在JDK7之前存在运行时常量池之中,在JDK7已经将其转移到堆中,也对intern 方法做了一些修改。因为字符串常量池和new的对象都存于Java堆中。字符串常量池的存在使JVM提高了性能和减少了内存开销

JDK7之前内存模型:

创建字符串的两种形式:

1)String s1=”1”;

2)String s2=new String(“1”);

JAVA封装字符串:String,StringBuffer,StringBuilder类_第1张图片

String s1=”1”;创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就将此字符串对象的地址赋值给引用s1(引用s1在Java栈中)。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,并将此字符串对象的地址赋值给引用s1

String s2=new String(”1”);创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s2,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s2。

“+”连接形式创建字符串:

1)String s1=”1”+”2”+”3”;

JAVA封装字符串:String,StringBuffer,StringBuilder类_第2张图片

2)String s2=”1”+”3”+new String(“1”)+”4”;

JAVA封装字符串:String,StringBuffer,StringBuilder类_第3张图片

当使用“+”连接字符串中含有变量时,也是在运行期才能确定的。首先连接操作最开始时如果都是字符串常量,编译后将尽可能多的字符串常量连接在一起,形成新的字符串常量参与后续的连接(可通过反编译工具jd-gui进行查看)。

 接下来的字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字符串为参数创建StringBuilder对象(可变字符串对象),然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方法转换成String对象(注意:中间的多个字符串常量不会自动拼接)。

实际上的实现过程为:String s2=new StringBuilder(“13”).append(new String(“1”)).append(“4”).toString();所以当使用+进行多个字符串连接时,实际上是产生了一个StringBuilder对象和一个String对象。

String.intern()解析:

 String.intern()是一个Native方法,底层调用C++的 StringTable::intern 方法,当调用 intern 方法时,如果常量池中已经存在该字符串,则返回池中字符串;否则直接存储堆中的引用,也就是字符串常量池中存储的是指向堆里的对象。所以结果为true。

 

JDK7之后的内存模型:

1)String s3=new String(“1”)+new String(“1”);

JAVA封装字符串:String,StringBuffer,StringBuilder类_第4张图片

equals和== :

(1)对于==,如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。

(2)equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。在Object类中,equals方法是用来比较两个对象的引用是否相等。

(3)对于equals方法,注意:equals方法不能作用于基本数据类型的变量。如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;而String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。


StringBuffer

内部可变数组,存在初始化StringBuffer对象中字符数组容量为16,存在扩容

StringBuffer类继承AbstractStringBuilder抽象类,其中StringBuffer的大部分方法都是直接调用的父类的实现。

部分源码:

    //构造方法
    public StringBuffer() {
        super(16);
    }

    public StringBuffer(int capacity) {
        super(capacity);
    }

    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

    //方法    
     @Override
    public synchronized int length() {
        return count;
    }

    @Override
    public synchronized int capacity() {
        return value.length;
    }


    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
            expandCapacity(minimumCapacity);
        }
    }
    //...省略部分

由上图可以看出StringBuffer大部分方法使用了synchronized关键字,来实现多线程下的线程安全,synchronized代表方法加锁,例如线程A,要运行到声明了synchronized 方法时,先检查有没有其他线程B,C正在使用这synchronized 方法,若有就先等B,C线程运行完这个synchronized 方法后,在运行A,若无直接运行。StringBuilder其继承关系、方法与StringBuffer是一样的,区别在于StringBuilder类所有方法没有加synchronized关键字,所以StringBuffer与StringBuilder的区别在于StringBuffer线程是安全的,StringBuilder线程不是安全的,其本质区别是方法有没有使用synchronized关键字

append方法

获得要追加的字符串的长度,判断当前字符数组是否能够存储追加的字符串,如果容量不够则确定新的字符数组的容量,申请新的字符数组,将以前字符数组的内容复制到新字符数组。最后将要追加的字符串,复制到新字符数组中

源码:

abstract class AbstractStringBuilder implements Appendable, CharSequence {

    char[] value;
    int count;

    AbstractStringBuilder() {
    }

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];//StringBuffer 的构造函数本质是调用了父类 AbstractStringBuilder 类的构造函数,该构造函数初始化了一个默认大小为16的字符数组。
    }

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);//将追加的字符串str的0-len位置的字符复制到 字符数组 value 的起始位置 count 处。
        count += len;
        return this;
    }

    //这是一个私有的方法。首先确保容量足够,其次我们看到所谓的 null 本质就是追加了'null'这样的四        个字符到字符数组中。
 
    private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

    private void ensureCapacityInternal(int minimumCapacity) {
        // minimumCapacity指的就是当前字符串的最小容量,如果这个容量比当前字符数组的容量要大,则需要重新申请新的字符数组,并将以前字符数组的内容复制到新的字符数组中。
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }


    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;//申请原有容量2倍+2的容量
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }

toString方法

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

    private transient char[] toStringCache;//这里比StringBuilder多了一个参数,作用就是去缓存toString的

     @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }
//下面省略

}

 StringBuffer的toString方法,如果StringBuffer对象此时存在toStringCache,在多次调用其toString方法时,其new出来的String对象是会共享同一个char[] 内存的,达到共享的目的。但是StringBuffer只要做了修改,其toStringCache属性值都会置null处理。这也是StringBuffer和StringBuilder的一个区别点。

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    
    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
//下面省略
}

总结:

String 类不可变,内部维护的char[] 数组长度不可变,为final修饰,String类也是final修饰,不存在扩容。字符串拼接,截取,都会生成一个新的对象。频繁操作字符串效率低下,因为每次都会生成新的对象。

StringBuilder 类内部维护可变长度char[] , 初始化数组容量为16,存在扩容, 其append拼接字符串方法内部调用System的native方法,进行数组的拷贝,不会重新生成新的StringBuilder对象。非线程安全的字符串操作类, 其每次调用 toString方法而重新生成的String对象,不会共享StringBuilder对象内部的char[],会进行一次char[]的copy操作。

StringBuffer 类内部维护可变长度char[], 基本上与StringBuilder一致,但其为线程安全的字符串操作类,大部分方法都采用了Synchronized关键字修饰,以此来实现在多线程下的操作字符串的安全性。其toString方法而重新生成的String对象,会共享StringBuffer对象中的toStringCache属性(char[]),但是每次的StringBuffer对象修改,都会置null该属性值。

 

你可能感兴趣的:(Java)