String,StringBuffer和StringBuilde


String类

String类的源码声明如下



public final class String//父类是Object
    implements java.io.Serializable, Comparable, CharSequence 

①String对象保存的是一个字符串,也就是一个字符序列。

②双引号括起来的字符序列叫字符常量

③字符串使用Unicode编码,一个char占两个字节(不管char保存的是英文还是中文)

④String有很多构造器,常用的如下

      String s = new String();
        String s1 = new String(String original);
        String s2 = new String(char[] a);
        String s3 = new String(char[] a,int startIndex,int count);
        String s4 = new String(byte b);

⑤serializable表示可串行化,即String类可以进行网络传输

⑥注意String是final类

⑦String源码有这样一个数组

 private final char value[];

这个是用来存放字符串的每一个字符,所以本质上来讲,字符串保存形式其实就是字符数组

并且这个数组名也是final,即不能让这个数组名指向另一个地址

String的创建剖析

String创建有两个方式

方式一

String s = "123";

该String创建时,会先在常量池查找有无"123"这个字符串,有的话就直接返回地址给栈中的s,没有的话就创建一个"123",然后返回地址给s

方式二

String s = new String("123");

该方式,首先会在堆中创建一个String对象(注意这是在堆中的String对象和常量池中的String对象不完全相同),其中含有value这个字符数组,它会在常量池找有没有"123",有的话value就指向这个字符串,没有的话就创建一个"123"的String对象,然后value指向这个字符串,最后String对象的堆中地址返回给s。(即s指向一个堆中String对象,这对象中包含的数组value指向常量池的字符串)。

String对象的特性

①String是final的,所以一个String对象保存的字符串是不变的(注意,不是指栈中的String对象名,而是指字符串,即String对象),比如下面的代码

public static void main(String[] args) {
       String s = "123";
       s = "456";
    }

这段代码一共是在常量池创建了两个String对象,整个步骤这样的,第一句,s先在常量池找有没有字符串"123",有的话就指向它,没有就创建一个字符串"123",返回地址给s,然后第二句执行的时候,因为String类的final修饰的,所以并不会直接把原来的"123"这个字符串改变成"456",而是现在常量池找有没有字符串"456",有就直接指向,没有就创建一个字符串"456",并返回地址给s。

②编译器会对字符串的相加有一个重要规则:常量相加,在池中进行,变量相加,在堆中进行,如下

 String s = "1" + "2";

该代码,因为两个字符串是确定的,编译器会做优化,直接把"1" + "2"优化为"12",也就是只在常量池创建一个String对象

但是要注意,如果是变量那就不会做优化(因为编译器不确定你的变量会不会变),如下

 public static void main(String[] args) {
       String s = "1";
       String s1 = "2";
       String s2 = "12";
       String s3 = s + s1;
       //如果编译器对s3做了优化,那么s2==s3
        System.out.println(s2 == s3);
    }

运行结果
false

为什么会这样呢,我们来调试下,看看s3这行语句,到底是搞了什么东东

调试s3时,发生了如下步骤

(1)创建了一个StringBuffer对象

 

(2)执行append("1")

(3)执行appen("2")

(4)执行toString(),在堆中创建了一个新对象,返回给s3

(5)s3就执行完毕了

此时jvm里内存布局如下

如果我们要对String进行大量修改,不要使用String类

String类的value是final的不能指向新的数组,同时也是private权限,并且String类也没有相应的方法来访问value,所以每次String类的字符串更新都是新创建一个对象,这也意味着会新创建了一个字符数组,更新后的字符串存在这个新字符数组里,并把地址返回给相应的String类引用,这样效率非常慢,而且还会导致常量池残留了大量无用的字符串。

String常用方法

 

★StringBuffer类

java.lang.StringBuffer代表可变的字符序列,可以字符串进行增减,它有很多方法与String类相同,但StringBuffer的长度是可变的,并且StringBuffer是一个容器,还是一个final类。

StringBuffer类的继承结构

StringVSStringBuffer

①和String不同,StringBuffer类的字符数组value,是存在与AbstractStringBuilder中的,并且不是final修饰

②String类的value是final的不能指向新的数组,同时也是private权限,并且String类也没有相应的方法来访问value,所以每次String类的字符串更新都是新创建一个对象,这也意味着会新创建了一个字符数组,更新后的字符串存在这个新字符数组里,并把地址返回给相应的String类引用,这样效率非常慢。

StringBuffer类的value不是final,default访问权限,并且StringBuffer的初始化只能是new,所以value必定存在于堆中,该类的数组value初始化时会被分配一片空间,当字符串长度发生增加时,如果value剩下的空闲空间可以放得下新加的字符,那就value指向的地址不变,如果空间不足,则在堆中另开一个字符数组,把原来的字符和新增的字符一起放进来,并将该数组的地址返回给value。

也就是StringBuffer只有一些情况是需要重开数组,重新赋值新地址给value的,其余都是在原本数组上加东西,所以总体上,效率比String高。

String和StringBuffer之间的转换

        //String--->StringBuffer
        //方式一,用StringBuffer的构造器
        String str = "123";
        //此方法是返回一个StringBuffer对象,不会对str造成影响
        StringBuffer stringBuffer = new StringBuffer(str);

        //方式二,append()
        stringBuffer = new StringBuffer().append(str);
        //同样会返回一个StringBuffer对象,不会对str造成影响


        //StringBuffer---->String
        //方式一,用String的构造器,不会对StringBuffer造成影响
        String s = new String(stringBuffer);

        //方式二,用StringBuffer的toString,不会对StringBuffer造成影响
        String s1 = stringBuffer.toString();    

StringBuffer类常用方法

StringBuilderl类

StringBuilder类也是一个可变的字符序列,它提供了一个与StringBuffer兼容的API,继承结构与StringBuffer完全一样,所以数组value也是用的AbstractStringBuilder里面的,是在堆中的,是一个final类

①该类被作为StringBuffer的一个简易替换,但由于它的没有synchronize关键字,所以用在字符串缓冲区被单个线程使用时,而StringBuffer的方法有synchronize关键字,所以多线程还是使用StringBuffer

②StringBuilder类主要用的是append和insert方法

String,StringBuffer和StringBuilder的比较

①StringBuffer和StringBuilder非常类似,都是可变的字符序列,而且由于继承结构一样,拥有的方法一样

②String:不变的字符序列,效率低,但因为每次新String产生时都会现在池子里找有没有相同的,所以复用性高。

StringBuffer:可变的字符序列,效率较高(体现在增删时),能保证线程安全

StringBuilder:可变的字符序列,效率最高(体现在增删时),单线程不安全

我们可以总结出这三个类的使用原则

(1)如果字符串存在大量的修改操作,并且是单线程情况,使用StringBuilder

(2)如果字符串存在大量的修改操作,并且是多线程情况,使用StringBuffer

(3)如果一个字符串不需要大量修改,并且被多个对象引用,使用String,比如配置信息等等。