Java基础之String、StringBuffer、StringBuilder的区别?
问题:String、StringBuffer和StringBuilder?
首先,从可变性来说,String类中使⽤ final 关键字修饰字符数组来保存字符串,所以String对象是不可变的。⽽ StringBuilder 与 StringBuffer都继承⾃ AbstractStringBuilder类,在 AbstractStringBuilder中也是使⽤字符数组保存字符串,但是没有⽤ final 关键字修饰,所以这两种对象都是可变的。
其次,从线程安全性来说,String中的对象是不可变的,也就可以理解为常量,因此是线程安全的。AbstractStringBuilder 是StringBuilder与StringBuffer 的公共⽗类,定义了⼀些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共⽅法。StringBuffer对⽅法加了同步锁或者对调⽤的⽅法加了同步锁,所以是线程安全的。而StringBuilder并没有对⽅法进⾏加同步锁,是⾮线程安全的。
最后,从性能来说,每次对 String类型进⾏改变的时候,都会⽣成⼀个新的 String对象,然后将指针指向新的String对象。StringBuffer每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象引⽤。相同情况下使⽤ StringBuilder相⽐使⽤ StringBuffer仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的⻛险。
总结的来说,我们在操作少量数据的时候推荐使用 String,单线程操作字符串缓冲区下操作⼤量数据的时候推荐使用 StringBuilder,多线程操作字符串缓冲区下在操作⼤量数据的时候推荐使用 StringBuffer。
1.String
1.1 String内存存储
存储方式:在字符串的内部使用char数组来保存字符串的内容,数据中的每一位存放一个字符,char数组的长度也就是字符串长度,例如:“hello world”的存储方式为:
在源码中的表示为:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];//因为有“final”修饰符,所以可以知道string对象是不可变的。
/** Cache the hash code for the string */
private int hash; // Default to 0
}
String内存位置的说明:显式的(在编译的时候可以确定的字符串)是存放在字符串常量池中,而new出来的String对象是存放在堆中的,字符串对象的引用是存放在栈中的,即使用了new关键字或者是编译的时候不确定的值则不会使用字符串池。
◣注意:在JDK1.7之前,字符串常量池是在方法区中,在JDK1.7之后字符串常量池被移到堆中了。
(1)显式的String常量
String s1="hello";
String s3="hello";
System.out.println(s1==s3);//true
说明:第一句代码执行后就在常量池中创建了一个值为hello的String对象,并将该内存地址赋值给s1;第二句执行时,因为常量池中存在hello所以就不再创建新的String对象了,直接将s1的内存地址返回给s3。此时该字符串的引用s1和s3在虚拟机栈里面,但是”hello”在常量池中,所以他们用“==”比较的结果为true。
(2)String对象
String s2=new String("hello");
String s4=new String("hello");
System.out.println(s2==s4);//false
说明:Class被加载时就在常量池中创建了一个值为hello的String对象,第一句执行时会在堆里创建new String(“hello”)对象,因为通过new方法创建对象是直接在堆内存中分配空间用来创建字符串的;第二句执行时,因为常量池中存在hello所以就不再创建新的String对象了,直接在堆里创建new String(“hello”)对象;所以s2和s4用“==”比较的时候默认比较的是堆中的地址,所以返回false。
◣注意:两个字符串比较的时候使用equals比较才会比较字符串的内容(其他的引用类型同理)。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
//instanceof 是 Java 中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回 true;否则,返回 false。
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
1.2 String类中常用方法
返回类型 | 方法名称 | 作用 |
---|---|---|
int | length() | 获取字符串的长度 |
char | charAt(int i) | 返回指定索引处的 char 值,即获取字符串中的某一个字符 |
int | indexOf(String s) | 返回传入字符串/字符在原来字符串中第一次出现的位置,如果没有找到则返回-1 |
int | indexOf(String s,int from index) | 返回在此字符串/字符中第一次出现指定字符处的索引,从指定的索引开始搜索 |
int | lastIndexOf(String s) | 返回传入字符串在原来字符串中最后一次出现的位置,如果没有找到返回-1 |
boolean | startWith(String s) | 判断字符串是否是传入的字符串开头 |
boolean | endWith(String s) | 判断字符串是否是传入字符串结尾 |
String | toLowerCase() | 将字符串转换成小写字符串 |
String | toUpperCase() | 将字符串转换成大写字符串 |
String | subString(int start) | 截取字符串,从传入的位置截取到末尾 |
String | subString(int start,int end) | 截取字符串,从传入的参数1的位置开始,到参数2的位置结束。 |
String | trim() | 去掉字符串两边的空格(中间的去不掉) |
String[] | split(String s) | 将源字符串按照传入的参数分割成字符串数组 |
string | replace(new char,old char) | 返回一个新的字符串,它是通过用 new char 替换此字符串中出现的所有 old char 得到的 |
2.StringBuffer
String类是一个常量类,如果想创建一个内容可变的字符串对象使用String类就不行了,所以java提供了一个StringBuffer类。
public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
}
StringBuffer类是一个长度可变的字符串,它包括三大操作字符串的方法:
(1)构建字符串:可以使用append方法向末尾加入字符串,也可以使用insert方法向字符串中插入字符串,同时我们也可以在创建StringBuffer对象时设置一个初始字符串。例如:StringBuffer sbu=new StringBuffer(“hello”);
(2)获取字符串:可以使用subString获取当前字符串的一部分,也可以使用toString方法获得所有的字符串。
(3)修改字符串:可以使用replace方法替换字符串中的部分内容,也可以使用delete方法来删除字符串中的某些字符。
◣注意:具体的方法可以查看java API
public String(StringBuffer buffer) {
synchronized(buffer) {//StringBuffer是线程安全的
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
3.StringBuilder
StringBulider是从JDK1.5之后加入进来的,这个类与StringBuffer的使用方式一样,不过他们的不同之处就在于StringBuffer类中的大部分方法都是线程安全的,而StringBulider不是线程安全的。
◣ 注意:对于这两个操作字符串来说,使用哪个可以自己去决定,不过在处理多线程的时候使用StringBuffer比较安全。
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
4.String/StringBuffer/StringBuilder的比较
(1)可变与不可变
String类中使用字符数组保存字符串,因为有“final”修饰符,所以string对象是不可变的。
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,但是这两种对象都是可变的。
(2)多线程安全
String中的对象是不可变的,也就可以理解为常量,显然线程安全。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
(3)StringBuilder与StringBuffer共同点
StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。
抽象类与接口的其中一个区别是:抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的申明和常量的定义。
(4)字符串连接
当进行大量字符串连接的时候,一般使用StringBuffer或StringBulider,因为他提供了一个缓冲区的功能,在字符串拼接的时候不会去频繁的操作内存,而使用加号进行字符串拼接的时候会频繁的操作内存,这样导致程序性能降低。
例如:
public class Test{
/**
* StringBuffer:一个类似于 String 的字符串缓冲区,线程安全的,重量级的,大量进行字符串拼接的操作效率高于String
* StringBulider:JDK1.5之后出现的,非线程安全的,轻量级的
* 大量进行字符串拼接的效率:StringBulider > StringBuffer > String
* 建议:如果要进行大量的字符串拼接,要使用StringBulider
*/
public static void main(String[] args) {
Season sea=Season.SPRING;
Season1 sea1=Season1.SPRING;
//Season sea=new Season("春天");
String s="";
long start=System.currentTimeMillis();
for(int i=0;i<100000;i++){
s=s+i;
}
long end=System.currentTimeMillis();
//System.out.println(s);
//System.out.println("String使用的时间为"+(end-start));
StringBuffer sbu=new StringBuffer();
long start1=System.currentTimeMillis();
for(int i=0;i<100000;i++){
//s=s+i;
sbu.append(i);//进行字符串的拼接的方法
}
long end1=System.currentTimeMillis();
//System.out.println(sbu.toString());
System.out.println("StringBuffer使用的时间为"+(end1-start1));
StringBuilder sbu1=new StringBuilder();
long start2=System.currentTimeMillis();
for(int i=0;i<100000;i++){
//s=s+i;
sbu1.append(i);//进行字符串的拼接的方法
}
long end2=System.currentTimeMillis();
//System.out.println(sbu.toString());
System.out.println("StringBuilder使用的时间为"+(end2-start2));
}
}
运行结果:
从以上的运行结果可以看出:
大量进行字符串拼接的效率:StringBulider > StringBuffer > String