StringBuffer和StringBuilder和String
StringBuffer
知识点解读(StringBuffer源码分析)
- StringBuffer 的直接父类是 AbstractStringBuilder
- StringBuffer 实现了 Serializable 即StringBuffer的对象可以串行
- 在父类中有一个属性 char[] value,不是final常量,该value存放字符串内容(存放在堆中的)
- StringBuffer是一个final类,不能被继承
- 因为StringBuffer 字符内容是存在 char[] value,所以在变化的时候(例如:新增/删除),不用每次都更换地址,即:创建新对象(即:不是每次都创建新对象),所以效率高于String
String 和 StringBuffer
- String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是改地址,效率最低
- StringBuffer保存的是字符串变量,里面的值可以修改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率更高。
// char[] value放在堆
String 和 StringBuffer相互转换
public static void main(String[] args) {
// TODO Auto-generated method stub
// 构造器的使用
// 1.创建1个大小为16的char[]数组,用于存放16字符的数据内容
StringBuffer stringBuffer = new StringBuffer();
// 2.通过构造器指定 char[] 的大小
StringBuffer stringBuffer2 = new StringBuffer(100);
// 3.通过给一个String 创建 StringBuffer:当前字符串长度(str.length) + 16
StringBuffer stringBuffer3 = new StringBuffer("Hello");
// 4.String 和 StringBuffer 的转换
// 方式1: 使用构造器
// 注意:返回的才是StringBuffer对象,对str本身没有影响
String str = "Hello world";
StringBuffer stringBuffer4 = new StringBuffer(str);
System.out.println("str: " + str);
System.out.println("stringBuffer4: " + stringBuffer4);
// 方式2 :使用append方法
StringBuffer stringBuffer5 = new StringBuffer();
stringBuffer5.append(str);
System.out.println("stringBuffer5: " + stringBuffer5);
// StringBuffer → String
// 1.用toString()方法转化
StringBuffer stringBuffer6 = new StringBuffer("你好");
System.out.println("stringBuffer6.toString(): " + stringBuffer6.toString());
// 2.使用构造器
StringBuffer stringBuffer7 = new StringBuffer("转换");
String s = new String(stringBuffer7);
System.out.println("s:" +s);
// 常用方法
StringBuffer stringBuffer8 = new StringBuffer("hello");
// 增加 append
stringBuffer8.append(",");
stringBuffer8.append("张三丰");
stringBuffer8.append("18").append(true).append(100);
System.out.println("stringBuffer8 append:" + stringBuffer8); // 输出:hello,张三丰18true100
// 删除 delete(start,end) 删除start——end的字符,但是不包含end位置的字符
stringBuffer8.delete(0, 3);
System.out.println("stringBuffer8 delete:" + stringBuffer8); // 输出:lo,张三丰18true100
// 更改 replace(start,end,String)
stringBuffer8.replace(0, 2,"01");
System.out.println("stringBuffer8 replace:" + stringBuffer8); // 输出:01,张三丰18true100
// 查询 indexOf 查找指定字符串第一次出现的索引,找不到返回-1
System.out.println("stringBuffer8 indexOf:" + stringBuffer8.indexOf("张三丰")); // 输出:3
// 插入 insert 指定一个位置插入字符串,原先位置自动后移
stringBuffer8.insert(3, "我是");
System.out.println("stringBuffer8 insert:" + stringBuffer8); // 输出:01,我是张三丰18true100
// 获取长度 length
System.out.println("stringBuffer8 length:" + stringBuffer8.length()); // 输出:17
};
length()与capacity()
package com.StringBuffer;
import org.junit.Test;
/**
* @author wty
* @date 2022/11/17 16:37
*/
public class StringBufferEx05 {
@Test
public void test(){
StringBuffer stringBuffer = new StringBuffer(10);
stringBuffer.append("12345");
System.out.println(stringBuffer.length());
System.out.println(stringBuffer.capacity());
/**
* 输出结果
* 5
* 10
*/
stringBuffer.append("123456");
System.out.println(stringBuffer.length());
System.out.println(stringBuffer.capacity());
/**
* 输出结果
* 5
* 10
* 11
* 22 扩容了一倍
*
* 扩容源码
* public AbstractStringBuilder append(String var1) {
* if (var1 == null) {
* return this.appendNull();
* } else {
* // var1 = 11这里进行扩容
* int var2 = var1.length();
* // 这里进行扩容
* this.ensureCapacityInternal(this.count + var2);
* var1.getChars(0, var2, this.value, this.count);
* this.count += var2;
* return this;
* }
* }
*/
}
}
练习题:
@Test
public void testStringBuffer(){
StringBuffer stringBuffer2 = new StringBuffer();
String c = null;
stringBuffer2.append(c);
System.out.println(stringBuffer2.length()); // 这里会输出什么
System.out.println(stringBuffer2);
/**源码解读:
* // public AbstractStringBuilder append(String var1) {
* if (var1 == null) {
*return this.appendNull();
*}
*
* private AbstractStringBuilder appendNull() {
* int var1 = this.count; // 0
* this.ensureCapacityInternal(var1 + 4); // 4
* char[] var2 = this.value; // ,,,,,,,,
* var2[var1++] = 'n'; //var2[0] = n
* var2[var1++] = 'u'; //var2[1] = u
* var2[var1++] = 'l'; //var2[2] = l
* var2[var1++] = 'l'; //var2[3] = l
* this.count = var1; // var1 = 4
* return this; // 返回4
}
*
*/
StringBuffer stringBuffer3 = new StringBuffer(c); // java.lang.NullPointerException
/**
* 源码解读:
*
* public StringBuffer(String var1) {
* super(var1.length() + 16); // 这里抛出空指针异常
* this.append(var1);
* }
*
*/
System.out.println(stringBuffer3);
}
课后练习:小数点前,每3位1个逗号
package com.StringBuffer;
import org.junit.Test;
import java.util.Scanner;
/**
* @author wty
* @date 2022/10/14 0:16
*/
public class HomeWork03 {
@Test
public void getStringBuffer() {
System.out.println("请输入商品名称");
Scanner scanner = new Scanner(System.in);
String a = scanner.nextLine();
System.out.println("请输入商品价格");
Scanner scanner2 = new Scanner(System.in);
String b = scanner2.nextLine();
// 先找到小数点
StringBuffer stringBuffer = new StringBuffer(b);
if (stringBuffer.indexOf(".") > 0) {
for (int i = stringBuffer.lastIndexOf(".") - 3; i > 0; i -= 3) {
if (i < 0) {
break;
}
stringBuffer.insert(i, ",");
}
} else {
for (int i = stringBuffer.length() - 3; i > 0; i -= 3) {
if (i < 0) {
break;
}
stringBuffer.insert(i, ",");
}
}
System.out.println(a + " " + stringBuffer);
}
}
StringBuilder
基本介绍
- 一个可变的字符序列,此类提供了一个与StringBuffer兼容的API,但不能保证同步(StringBuilder 不是线程安全的),该类被设计用作StringBuffer的一个简易替换,**用在字符串缓冲区被单个线程使用的时候。**如果可以,单线程建议优先使用StringBuilder,它比StringBuffer快。
- 在StringBuilder上的主要操作是append和insert方法,可以重载这些方法,可以接受任意类型的数据。
StringBuilder源码阅读
public static void main(String[] args) {
// TODO Auto-generated method stub
// 1.StringBuilder继承了 AbstractStringBuilder类型
// 2.实现了Serializable(可串行的),说明StringBuilder对象是可以串行化的(对象可以网络传输,可以保存到文件)
// 3.StringBuilder 是final类,不能被继承
// 4.StringBuilder对象的字符序列,仍然存放在其父类AbstractStringBuilder的 char[] value
// 因此,因此字符序列存放在堆中
// 5.stringBuilder的所有方法没有做互斥的处理,即没有synchronized(同步)关键字处理,因此在单线程的情况下使用stringBuilder
StringBuilder stringBuilder = new StringBuilder();
}
为什么说StringBuffer是线程安全而StringBuilder不是
StringBuffer
可以看到相关操作被synchronized修饰
StringBuilder
没有被synchronized修饰
String、StringBuffer和StringBuilder的比较
- StringBuffer和StringBuilder非常类似,均代表可变的字符序列,而且方法也一样。
- String:不可变的字符序列,效率低,但是复用率高
- StringBuffer:可变字符序列、效率较高(增删)、线程安全。
- StringBuilder:可变字符序列、效率最高,线程不安全
- String的注意事项 如果对字符串做大量修改,并且放在循环中,多线程用StringBuffer,单线程用StringBuilder,而不要使用String
名称 | 是否可变 | 效率 | 线程安全 |
---|---|---|---|
String | 不可变的字符序列 | 效率低3 | |
StringBuffer | 可变的字符序列 | 效率较高2 | 线程安全 |
StringBuilder | 可变的字符序列 | 效率最高1 | 线程不安全 |
效率
StringBuilder > StringBuffer > String
package com.StringBuffer;
import org.junit.Test;
/**
* @author wty
* @date 2022/11/17 16:48
*/
public class StringBuilderEx {
@Test
public void test(){
long startTime = 0L;
long endTime = 0L;
StringBuffer buffer = new StringBuffer("");
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//StringBuffer 拼接 20000 次
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer 的执行时间:" + (endTime - startTime));
StringBuilder builder = new StringBuilder("");
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//StringBuilder 拼接 20000 次
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder 的执行时间:" + (endTime - startTime));
String text = "";
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//String 拼接 20000
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String 的执行时间:" + (endTime - startTime));
}
}
执行结果
StringBuffer 的执行时间:19
StringBuilder 的执行时间:8
String 的执行时间:5766
使用原则
- 如果字符串存在大量的修改操作,一般用StringBuilder或StringBuffer
- 如果字符串存在大量的修改操作,并在单线程的情况下,那么使用 StringBuilder
- 如果字符串存在大量的修改操作,并在多线程的情况下,那么使用 StringBuffer
- 如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等