常用类解析:StringBuilder与StringBuffer
前言
StringBuilder 和 StringBuffer 类似于 String 类,区别在于 String 类是不可改变的。
— 般来说,只要使用字符串的地方,都可以使用 StringBuilder / StringBuffer类。StringBuilder/StringBuffer类比String类更灵活。可以给一个 StringBuilder 或 StringBuffer 中添加、插入或追加新的内容,但是 String 对象一旦创建,它的值就确定了。
除了 StringBuffer 中修改缓冲区的方法是同步的,这意味着只有一个任务被允许执行方法之外,StringBuilder 类与 StringBuffer 类是很相似的。如果是多任务并发访问,就使用 StringBuffer, 因为这种情况下需要同步以防止 StringBuffer 崩溃。而如果是单任务访问,使用 StringBuilder 会更有效。StringBuffer 和 StringBuilder 中的构造方法和其他方法几乎是完全一样的。
本节介绍 StringBuilder。在本节的所有地方StringBuilder 都可以替换为 StringBuffer。程序可以不经任何修改进行编译和运行。StringBuilder 类有 3 个构造方法和 30多个用于管理构建器或修改构建器内字符串的方法。可以使用构造方法创建一个空的构建器或从一个字符串创建一个构建器,如图所示。
一、String类
1.回顾其特点
在解释StringBuilder与StringBuffer类之前,先回顾一下String类的特点,通过API的java.lang包下找String类:
public final class Stringextends Objectimplements Serializable, Comparable<String>, CharSequence
String 类代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。例如:
String str = "abc";
等效于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
所以说字符串本质上底层就是一个不可变长度且不可变内容的字符数组。所以可以规整字符串的特点:
字符串:
1.无论是字面量还是new出来的 一律都是字符串对象
2.字符串一旦创建,其长度不可改变(底层就是一个字符数组)
3.内容也不能改变(如果真的要改变内容,只能重新创建字符串)
4.字符串本质上底层就是一个不可变长度且不可变内容的字符数组!
在创建字符串时,会在堆里面存放一个特殊结构:字符串常量池(JVM虚拟机的特点),通过以下实例根据画图来理解String的特点:
public class StringDemo {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = "a" + "bc";
String s4 = new String("abc");
String s5 = new String("abc");
String s6 = new String("a");
String s7 = new String("bc");
String s8 = s6 + s7;
String s9 = "a" + s7;
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s1 == s4);
System.out.println(s1.equals(s4));
System.out.println(s1 == s5);
System.out.println(s4 == s5);
System.out.println(s1.equals(s5));
System.out.println(s1 == s8);
System.out.println(s4 == s8);
System.out.println(s5 == s8);
System.out.println(s4.equals(s8));
}
}
首先先给s1,s2和s3进行初始化,对比他们的物理地址和字符串的内容;
public class StringDemo {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = "a" + "bc";
//==两边比较的是真实物理地址的值
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
}
}
结果:通过内存图解图显示,由于s1和s2字符串的内容一致,常量池里面通过s1已经先创建出了abc,s2由于与s1一样的内容就直接调用的是常量池里面有的内容,所以s1和s2是指的一个东西,物理地址是一样的,也就是间接的说明只有物理地址是一样的,内容也就相等。s3也是一样的道理,想把新字符串“a”和“bc”创建在常量池里面,再因为s3字符串的内容与s1一致,所以也是调用的是常量池里面有的字符串“abc”。
接下来s4,s5,s6,s7,s8和s9都是new一个新对象
public class StringDemo {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = "a" + "bc";
String s4 = new String("abc");
String s5 = new String("abc");
String s6 = new String("a");
String s7 = new String("bc");
String s8 = s6 + s7;
String s9 = "a" + s7;
//==两边比较的是真实物理地址的值
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s1 == s4);
System.out.println(s1.equals(s4));
System.out.println(s1 == s5);
System.out.println(s4 == s5);
System.out.println(s1.equals(s5));
System.out.println(s1 == s8);
System.out.println(s4 == s8);
System.out.println(s5 == s8);
System.out.println(s4.equals(s8));
}
}
通过内存图解可以理解创建新对象后里面字符串内容的变化,s4new一个新对象,在堆里面创建出一个新对象,有新的物理地址的值,但是s4字符串的内容在常量池里面有,底层的value直接去找常量池里面已有的值,但是本身的物理地址的值不变,所以s4,s5,s6,s7都是同理,至于s8和s9,要注意,如果对象+对象的话,需要创建出一个新对象,但是如果是常量+常量的话,直接在常量池里面找,不用创建新对象,只要相加里面有对象,就需要创建新对象,新对象就有新物理地址:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
private final char value[];
结果:
2.intern() 方法
public String intern()
//返回字符串对象的规范化表示形式。
一个初始为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object)方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
返回: 一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池。
如以下实例:
public class StringDemo {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = "a" + "bc";
String s4 = new String("abc");
String s5 = new String("abc");
String s6 = new String("a");
String s7 = new String("bc");
String s8 = s6 + s7;
String s9 = "a" + s7;
System.out.println(s1 == s2);
System.out.println(s1.intern() == s2.intern());
System.out.println(s1 == s4);
//s1本身存放的是字符串常量在字符串常量池中的地址 所以intern()的结果和s1存储的地址是一样的
//s4本身存放的是字符串对象在堆内存中的地址,intern()返回的是字符串对象所指向的那个字符串常量在字符串常量池中的地址
System.out.println(s1.intern() == s4.intern());
System.out.println(s4 == s5);
System.out.println(s4.intern() == s5.intern());
}
}
结果:通过解释也知道intern()方法的意思,简而言之,就是注意,无论是对象还是常量,比较的是里面的内容的物理地址值,是在常量池里面找的,同一个内容说明内容地址相同说明 intern() 就是true。
二、StringBuilder类
StringBuilder与StringBuffer都是String类的缓冲字符串,特点就是可以改变其大小,也可进行增删改查操作的字符串
1.StringBuilder含义
StringBuilder继承自AbstractStringBuilder实现了CharSequence和Serializable
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{}
AbstractStringBuilder实现了Appendable(可扩展的)接口,要实现的方法append();
AbstractStringBuilder实现了CharSequence字符序列接口(同数据结构中List的定义)。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
看到StringBuilder的构造函数是 创建一个默认容量为16的字符数组,随着后续内容的增加,底层在进行动态的扩容。
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 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);
}
说白了StringBuilder就是一个可变长可修改内容的String,具体详细的StringBuilder方法 参考API就可以了。
public class StringBuilderDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abc");
//append什么都可以添加 都转换为字符串内容进行添加在尾部
//append执行之后 返回的是sb自己
sb.append(10);
sb.append(3.14);
sb.append(true);
sb.append(new Object());
sb.append(new Integer(1000));
sb.append("abc");
sb.append(sb);//在把之前所加的sb在加一遍
System.out.println(sb.toString());//打印sb
System.out.println(sb.capacity());//返回当前容量
System.out.println(sb.charAt(3));//角标3位置的数
System.out.println(sb.length());//有效长度
System.out.println(sb.reverse());//字符串反转,逆序
System.out.println(sb);//逆序后会改变原先sb
}
}
结果:
三、StringBuffer类
StringBuffer和StringBuilder基本完全一样;
1.StringBuffer类含义
查阅StringBuffer的API,线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
原来StringBuffer是个字符串的缓冲区,即就是它是一个容器,容器中可以装很多字符。并且能够对其中的字符进行各种操作。
- 是一个字符串缓冲区,其实就是一个容器。
- 长度是可变,任意类型都行。注意:是将任意数据都转成字符串进行存储。
- 容器对象提供很多对容器中数据的操作功能,比如:添加,删除,查找,修改。
- 所有的数据最终变成一个字符串。
四、StringBuilder类与StringBuffer类的区别
区别:
StringBuffer是线程安全的——多线程环境下和单线程环境下不容易出错;
StringBuilder是线程不安全——多线程环境下容易出错,单线程环境下不容易出错;
从代码的角度而言,StringBuffer里面的大部分成员函数都有synchronized关键字修饰里面的大部分成员函数都有synchronized关键字修饰
@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);
}
}
/**
* @since 1.5
*/
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
.............
例如:
StringBuilder就相当于一个人在家上厕所 一般不会反锁门 一般也不会判断卫生间里有没有人;StringBuffer就相当于你在火车上厕所 一般会反锁们 一般也会判断卫生间里有没有人。
在单线程情况下 StringBuilder比StringBuffer效率高一些 因为不需要判断锁这个问题;
在多线程情况下 StringBuffer比StringBuilder安全高一些 因为需要判断锁这个问题。
阅读StringBuilder的API说明发现,一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
原来StringBuilder是线程不安全的,即就是在对容器操作的时候,不用去判断同步锁的问题,那么效率就高。并且API告诉我们优先采用StringBuilder类.