【JKD源码】String 三兄弟

一、String

  • String 对象是不可变。
  • 一个 String 字符串实际上是一个char 数组。

1.1 属性

private final char[] value;
private int hash;

1.2 构造器

1.3 equals

  1. 相比较 == ,相等直接返回 true。
  2. 使用 instanceof 判断是否为字符串类型,不是则直接返回 false。
  3. 转换为字符串类型,并判断两字符串的length是否相等,不相等直接返回 false。
  4. 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 类设计成不可变的原因:

  1. 引发安全问题。例如数据库中的用户名、密码都是以String形式传入来获得数据库的连接,它们的值是不可变的,可以防止黑客的改变而造成的安全漏洞。
  2. 保证线程安全。在并发场景下,多个线程同时读写资源,由于字符串不可变,不会引发线程安全的问题而保证了线程。

二、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 codeint 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 是线程安全的。

【JKD源码】String 三兄弟

一、String

  • String 对象是不可变。
  • 一个 String 字符串实际上是一个char 数组。

1.1 属性

private final char[] value;
private int hash;

1.2 构造器

1.3 equals

  1. 相比较 == ,相等直接返回 true。
  2. 使用 instanceof 判断是否为字符串类型,不是则直接返回 false。
  3. 转换为字符串类型,并判断两字符串的length是否相等,不相等直接返回 false。
  4. 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 类设计成不可变的原因:

  1. 引发安全问题。例如数据库中的用户名、密码都是以String形式传入来获得数据库的连接,它们的值是不可变的,可以防止黑客的改变而造成的安全漏洞。
  2. 保证线程安全。在并发场景下,多个线程同时读写资源,由于字符串不可变,不会引发线程安全的问题而保证了线程。

二、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 codeint 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 是线程安全的。