1. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的
简单的来说: String 类中使用 final 关键字字符数组保存字符串, private
final char value[],所以 String 对象是不可变的。而 StringBuilder 与
StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中
也是使用字符数组保存字符串 char[]value 但是没有用 final 关键字修饰,所以
这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是
AbstractStringBuilder 实现的,大家可以自行查阅源码。
AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了
一些字符串的基本操作,如 expandCapacity、 append、 insert、 indexOf 等公
共方法。 StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以
是线程安全的。 StringBuilder 并没有对方法进行加同步锁,所以是非线程安全
的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将
指针指向新的 String 对象。 StringBuffer 每次都会对 StringBuffer 对象本身
进行操作,而不是生成新的对象并改变对象引用。相同情况下使用
StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,
但却要冒多线程不安全的风险。
对于三者使用的总结:
1. 操作少量的数据 = String
2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer
String 的equals方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
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;
}
从代码段中可以看到,equals方法比较的是字符串是否相等,首先比较指针是否相等,相等直接返回true,最后取出他们的final数组,进行每个字符的比较,记住字符是基本数据类型,可以直接比较。
下面是== 和 equals的区别
package com.example.base.string;
public class MyStringTest {
public static void main(String args[]) {
testString();
}
private static void testString() {
String hello = "Hello";
String hello2 = new String("Hello");
String lo = "lo";
//false. ==比较的是引用.hello是一个字符串常量表达式,并实现为调用interned,以共享不同的示例(string pool)
//hello2是一个对象,指向的是堆上的一个地址
System.out.println(hello == hello2); //false
//true. equals比较的是两个对象的值
System.out.println(hello.equals(hello2)); //true
//true. intern()会检查当前字符串池是否包含这个字符串。包含则返回池中的字符串,否则将字符串对象添加到池中,并返回该字符串对象的引用
//(最终结果就是返回一个指向常量池字符串的引用)
System.out.println(hello == hello2.intern()); //true
//true. 两个都是常量表达式(编译时确定的),都是指向字符串池的引用
System.out.println(hello == "Hello"); //true
//true. 不同类下的两个常量表达式,依然是指向同一个字符串池的引用
System.out.println(Other.hello == hello); //true
//true. 不同包下的两个常量表达式,依然是指向同一个字符串池的引用
//System.out.println(com.example.base.string.other.Other.hello == hello); //true
//true. 两个都是常量表达式,都是指向字符串池的引用
System.out.println(hello == ("Hel" + "lo")); //true
//false. 后者不是常量表达式,是运行时通过串联计算的字符串(lo是一个对象,不是常亮"xxx"),会新建对象
System.out.println(hello == ("Hel" + lo)); //false
//true. 将新对象intern()会返回指向字符串池的引用
System.out.println(hello == ("Hel" + lo).intern()); //true
}
}
class Other {
static String hello = "Hello";
}
StringBuffer 中toStringCache的作用
StringBuffer 调用了 String(char[] value, boolean share) 来构造一个 String 对象,它传入了一个字符数组,也就是缓存 toStringCache。Java 中定义,一个 String 对象是常量,是不能被改变的,因此 StringBuffer 的 toString() 返回的对象也应该是不能被改变。也就意味着传入的 toStringCache 数组的元素的值也不能被改变,否则由它构造的 String 对象就会改变。
如下,我们通过反射调用 String(char[] value, boolean share) 构造处一个字符串对象,然后修改 value 数组的值,会发现字符串对象发生了改变。
class ToStringCacheTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<String> constructor = String.class.getDeclaredConstructor(char[].class, boolean.class);
constructor.setAccessible(true);
char[] value = new char[]{'R', 'o', 'b', 'o', 't', 'h', 'y'};
String str = constructor.newInstance(value, true);
System.out.println(str); // 输出:Robothy
value[0] = 'A'; // 修改 str 绑定的字符数组 value
System.out.println(str); // 输出:Aobothy
}
}
StringBuffer 中的 toStringCache 是字符数组 value 复制的一个副本,每当 value 发生改变时,toStringCache 都会被置为空。这就保证了每次只要 StringBuffer 对象发生改变,再调用 toString() 方法就必然产生一个新的 toStringCache 数组,从而保证了引用了旧的 toStringCache 的字符串对象不会发生改变。即使多个线程同时访问 StringBuffer 对象,某一时刻也只有一个线程能够进入修改 toStringCache 和 value 的代码块,这通过修饰 StringBuffer 方法的 synchroinzed 关键字来保证。
StringBuffer 调用了 String(char[] value, boolean share) 来构造一个 String 对象,它传入了一个字符数组,也就是缓存 c。Java 中定义,一个 String 对象是常量,是不能被改变的,因此 StringBuffer 的 toString() 返回的对象也应该是不能被改变。也就意味着传入的 toStringCache 数组的元素的值也不能被改变,否则由它构造的 String 对象就会改变。
如下,我们通过反射调用 String(char[] value, boolean share) 构造处一个字符串对象,然后修改 value 数组的值,会发现字符串对象发生了改变。
class ToStringCacheTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<String> constructor = String.class.getDeclaredConstructor(char[].class, boolean.class);
constructor.setAccessible(true);
char[] value = new char[]{'R', 'o', 'b', 'o', 't', 'h', 'y'};
String str = constructor.newInstance(value, true);
System.out.println(str); // 输出:Robothy
value[0] = 'A'; // 修改 str 绑定的字符数组 value
System.out.println(str); // 输出:Aobothy
}
}
StringBuffer 中的 toStringCache 是字符数组 value 复制的一个副本,每当 value 发生改变时,toStringCache 都会被置为空。这就保证了每次只要 StringBuffer 对象发生改变,再调用 toString() 方法就必然产生一个新的 toStringCache 数组,从而保证了引用了旧的 toStringCache 的字符串对象不会发生改变。即使多个线程同时访问 StringBuffer 对象,某一时刻也只有一个线程能够进入修改 toStringCache 和 value 的代码块,这通过修饰 StringBuffer 方法的 synchroinzed 关键字来保证。
引用:https://www.cnblogs.com/robothy/p/13895241.html