一、String
- String 对象是不可变。
- 一个 String 字符串实际上是一个char 数组。
1.1 属性
private final char[] value;
private int hash;
1.2 构造器
1.3 equals
- 相比较
==
,相等直接返回 true。 - 使用
instanceof
判断是否为字符串类型,不是则直接返回 false。 - 转换为字符串类型,并判断两字符串的
length
是否相等,不相等直接返回 false。 - length 相等,依次比较每一个 char 类型。
public boolean equals(Object var1) {
if (this == var1) {
return true;
} else {
if (var1 instanceof String) {
String var2 = (String)var1;
int var3 = this.value.length;
if (var3 == var2.value.length) {
char[] var4 = this.value;
char[] var5 = var2.value;
for(int var6 = 0; var3-- != 0; ++var6) {
if (var4[var6] != var5[var6]) {
return false;
}
}
return true;
}
}
return false;
}
}
1.3 hashCode
s[0] * 31^(n-1) + s[1] * 31^(n-2) + ... + s[n-1]
public int hashCode() {
int var1 = this.hash;
if (var1 == 0 && this.value.length > 0) {
char[] var2 = this.value;
for(int var3 = 0; var3 < this.value.length; ++var3) {
var1 = 31 * var1 + var2[var3];
}
this.hash = var1;
}
return var1;
}
为什么选择 31
作为优选乘子
?
- 31 是一个 不大不小的质数,可被 JVM 优化,
31 * i = (i << 5) - i
。 - 如果选择的乘子过小,例如 2。假如 n = 6,则 2^5 = 32。说明当字符串长度很小时,计算出来的哈希值也会很小。导致哈希值分布在一个
较小的区间,分布性不佳,冲突率上升
。 - 如果选择的乘子过大,例如 101。101^5 = 10 510 100 501,值太大
超出了 int 类型的范围,结果溢出,导致信息丢失
。
1.4 常量池
- JDK1.6 及之前,常量池放在
方法区
中;JDK1.7 及以后,常量池放在堆
中。 - 字面量会存入字符串常量池中。
- 字符串变量拼接的原理是
StringBuilder
。 - 字符串常量拼接的原理是
编译器优化
。 intern()
方法,可以主动将串池中还没有的字符串对象放入常量池。
// 创建 字符串对象"a"后,会在字符串常量池中查找是否已存在。
// 如果串池中不存在,就将 对象"a"存入串池Hashtable中;若已经存在,就会直接使用串池中的对象。
String s1 = "a";
String s2 = "b";
String s3 = "ab";
// new StringBuilder().append("a").append("b").toString();
// 即 s4 = new String("ab"); 存放在堆中
String s4 = s1 + s2;
// javac 在编译期的优化,结果已经在编译期间确定 s5 = "ab"
String s5 = "a" + "b";
// 串池["a", "b"]
// 堆 new String("a"); new String("b"); new String("ab");
String s1 = new String("a") + new String("b");
// 将这个字符串放入串池。有则不放入(s1 存堆中),无则放入且返回给s1(s1 串池中)。【注意】
// 串池["a", "b", "ab"]
String s2 = s1.intern();
System.out.println(s2 == "ab"); // true
System.out.println(s1 == "ab"); // true, 【注意】s1 也为 true。若intern方法不放入,才为 false
1.5 String 并非不可变
public final class String implements Serializable, Comparable<String>, CharSequence {
private final char[] value;
- String 类被 final 所修饰,可以被认为是不可变的对象。
- value 被 final 所修饰,只能保证引用值不被改变,但是 value 所指向的堆中的数组,才是真正的数据。
- 只要能够操作堆中的数组,就能改变数据。
- 可通过反射来改变,但是几乎不会使用反射来操作 String 字符串。所以认为 String 类型是不可变的。
String str = "hello";
System.out.println(str); //hello
// 反射 获取 字段 value
Field field = String.class.getDeclaredField("value");
// value 为 private,改变访问权限
field.setAccessible(true);
// 获取 str 对象上 value属性 的值
char[] value = (char[]) field.get(str);
// 修改
value[0] = 'H';
System.out.println(str); // Hello
String 类设计成不可变的原因:
- 引发安全问题。例如数据库中的用户名、密码都是以String形式传入来获得数据库的连接,它们的值是不可变的,可以防止黑客的改变而造成的安全漏洞。
- 保证线程安全。在并发场景下,多个线程同时读写资源,由于字符串不可变,不会引发线程安全的问题而保证了线程。
二、StringBuilder
- StringBuilder 对象是可变的,可通过 append()、insert() 等方法来改变这个字符串对象的字符序列。
- StringBuilder 对象可以调用 toString() 方法将其转换为一个 String 对象。
2.1、父类
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count; // 实际字符的个数
AbstractStringBuilder() {}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
2.2、构造器
默认 数组长度为 16
public StringBuilder() {
super(16);
}
public StringBuilder(int var1) {
super(var1);
}
public StringBuilder(String var1) {
super(var1.length() + 16);
this.append(var1);
}
public StringBuilder(CharSequence var1) {
this(var1.length() + 16);
this.append(var1);
}
2.3 append 方法
(1)父类中的 append
public AbstractStringBuilder append(StringBuffer sb) {
if (sb == null)
return appendNull();
int len = sb.length();
ensureCapacityInternal(count + len); // 保证容量
sb.getChars(0, len, value, count);
count += len;
return this;
}
(2)扩容
扩容为 原容量*2 + 2
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
三、StringBuffer
StringBuffer与 StringBuilder 功能相似,只是大部分方法都被synchronized
所修饰,所以StringBuffer 是线程安全的。