String、StringBuilder、StringBuffer

String:字符串常量、线程安全
StringBuffer:字符串变量、线程安全
StringBuilder:字符串变量、线程不安全

CharSequence是字符序列,String,StringBuffer和StringBuilder都实现了CharSequence接口,本质上都是通过字符数组实现的。

String 类

String类实现了Serializable, Comparable, CharSequence接口,被final所修饰,也就是说String对象是不可变类,是线程安全的。

字符串可以通过两种方式进行初始化:字面常量和String对象。

字面常量:

String a = "java";
String b = "java";
String c = "ja" + "va";

a==b    // true;
a==c    // true;

变量a、b和c都指向常量池的 “java” 字符串,表达式 “ja” + “va” 在编译期间会把结果值”java”直接赋值给c。

String对象:

public class StringTest {
    public static void main(String[] args) {
        String a = "java";
        String c = new String("java");
        a==c    // false      
    }
}

对于 String c = new String("java") 这行代码,new 指令会在java堆上为String对象申请内存;然后尝试从常量池中获取”java”字符串,如果常量池中不存在,则在常量池中新建”java”字符串,并返回;最后,调用构造方法,初始化String对象。

其中String对象中使用char数组存储字符串,变量a指向常量池的”java”字符串,变量c指向Java堆的String对象,且该对象的char数组指向常量池的”java”字符串,所以很显然 a != c,如下图所示:

通过 “字面量 + String对象” 进行赋值会发生什么?

public class StringTest {
    public static void main(String[] args) {
        String a = "hello ";
        String b = "world";
        String c = a + b;
        String d = "hello world";

        c==d    // false  
    }
}

字符串变量的连接动作,在编译阶段会被转化成StringBuilder的append操作。

对于 String c = a + b 这行代码,先在Java堆上为StringBuilder对象申请内存;调用构造方法,初始化StringBuilder对象;调用append方法,添加a和b字符串;调用toString方法,生成String对象。变量c最终指向Java堆上新建String对象,变量d指向常量池的”hello world”字符串,所以 c != d。

特殊情况:当final修饰的变量发生连接动作时,虚拟机会进行优化,将表达式结果直接赋值给目标变量.

public class StringTest {
    public static void main(String[] args) {
        final String a = "hello ";
        final String b = "world";
        String c = a + b;
        String d = "hello world";
 
        c==d    // true
    }
}

String 类源码

1、成员变量

String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。

//用于存储字符串
private final char value[];
 
//缓存String的hash值
private int hash; // Default to 0
 
private static final long serialVersionUID = -6849794470754667710L;

2、构造函数

//不含参数的构造函数,一般没什么用,因为value是不可变量
public String() {
    this.value = new char[0];
}
 
//参数为String类型
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
 
//参数为char数组,使用java.utils包中的Arrays类复制
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
 
//从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
public String(byte bytes[], int offset, int length, String charsetName)
        throws UnsupportedEncodingException {
    if (charsetName == null)
        throw new NullPointerException("charsetName");
    checkBounds(bytes, offset, length);
    this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
 
//调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
public String(byte bytes[], String charsetName)
        throws UnsupportedEncodingException {
    this(bytes, 0, bytes.length, charsetName);
}

3、常用方法

String设计成不可变类的原因

(1)字符串常量池的需要。当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。

(2)HashCode 的需要。Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.

(3)String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

(4)因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

要实现一个不可变类:

  既然不可变类有这么多优势,那么我们借鉴String类的设计,自己实现一个不可变类。
  不可变类的设计通常要遵循以下几个原则:

  将类声明为final,所以它不能被继承。 将所有的成员声明为私有的,这样就不允许直接访问这些成员。 对变量不要提供setter方法。 将所有可变的成员声明为final,这样只能对它们赋值一次。 通过构造器初始化所有成员,进行深拷贝(deep copy)。 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。

对于下面的代码:
String s = "abcd";
s = s+1;
System.out.print(s); // result : abcd1
首先创建对象s,赋予一个abcd,然后再创建一个新的对象用来执行第二行代码,之前对象s并没有变化。

再比如:

String a = "a"; //假设a指向地址0x0001
a = "b"; //重新赋值后a指向地址0x0002,但0x0001地址中保存的"a"依旧存在,但已经不再是a所指向的。

但是,对于:
String str = “This is only a” + “ simple” + “test”;
就相当于 String str = “This is only a simple test”;

toCharArray()方法

String s = " a b  ";
char[] arr = s.toCharArray();
System.out.println(arr.length); // output: 6
System.out.println(Arrays.toString(arr)); //output: [ , a,  , b,  ,  ]

StringBuffer

StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 。

原文地址:https://www.cnblogs.com/hesier/p/5627206.html