java.lang.String【核心机制】

java.lang.String【核心机制】

Java8大基础类型:boolean/char/byte/short/int/long/float/double
基础类型对象存储的是实际数据。

  • StringJava中不是基础类型,而是引用类型
  • 也就是说String对象中存储的是实际数据的内存地址,而不是实际数据。

(一)不变性(immutable

  • String只读的,任何对字符串的操作都是创建一个新对象,然后将引用指向新对象
  • String线程安全

1.String底层是final类型的char数组

private final char value[];

2.String类本身是final类型的

public final class String{...}不允许被继承,提高了系统的安全性。


(二)常量池优化

String对象创建后可以(但不是一定,通过构造函数创建的就不会)缓存在字符串常量池中,下次创建同样对象会返回缓存中的引用,而不是新建。

实例化String的两种方式

  1. 直接赋值(对象存储在字符串常量池中):String str = "Hello";

    注意:JDK8之后字符串常量池也移到了堆内存中!

  2. 构造函数(对象存储在堆内存中):String str = new String("Hello");

1
2
3
4
5
6
7
8
9
10
11
12
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); //true:返回的是同一个字符串常量池对象

String s3 = new String("Hello");
String s4 = new String("Hello");
System.out.println(s3 == s4); //false:两个不同的堆内存对象
System.out.println(s1 == s3); //false:字符串常量池对象和堆内存对象,内存地址注定不等

System.out.println(s1.equals(s3)); //true:对象内存地址不同,但是值相等

System.out.println(s1 == s3.intern()); //true:通过intern()返回的是s1的常量池对象

(三)核心方法

1.public boolean equals(Object anObject)

  • String#equals()重载了Object#equals(),比较的是两个字符串的值是否相等,而不是内存地址是否相等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//存储字符串的实际数组
private final char value[];

public boolean equals(Object anObject){
//内存地址相同是同一个对象,值一定相等,不需要比较
if (this == anObject) return true;
if (anObject instanceof String) {
String anotherString = (String)anObject; //强转String
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;
}

2.public native String intern()

  • intern()是本地方法
  • 调用某个字符串的intern()会去字符串常量池中寻找,如果已经存在一个值相等的字符串对象,则直接返回该对象的引用;如果不存在,则在字符串常量池中创建该对象并返回引用

3.public int hashCode()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//存储实际元素的字符数组,由于是final的所以String不可变
private final char value[];

//用于缓存hash值,默认是0
private int hash;

//@return: 字符串对应的hashCode值
public int hashCode() {
int h = hash; //中间存储:hash值
//hash为0,即尚未计算过hash值
//value.length > 0,即字符串非空
if(h == 0 && value.length > 0) {
char val[] = value; //中间存储:实际数组

for(int i = 0; i < value.length; i++) {
//计算hashCode
//h * 31会被JVM优化成:(h << 5) - h
h = h * 31 + val[i];
//等价于:
//s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2)
//+ ... + s[n - 1]
//s[] 即 val[]
}
hash = h; //缓存最新的hash值
}
return h;
}

为什么选择31作为乘子?

  1. 31是一个奇质数,同时它的大小正合适

    如果选择一个偶数会在乘法运算中产生溢出,导致数值信息丢失,因为乘2相当于移位运算
    如果使用较大或较小的质数,都会导致哈希冲突增加

  2. 31相关的乘法可以被JVM优化成位运算31 * i = (i << 5) - i

    参考:左移运算的应用——乘法

-------------本文结束感谢您的阅读-------------

本文标题:java.lang.String【核心机制】

文章作者:DragonBaby308

发布时间:2019年11月20日 - 23:00

最后更新:2020年01月25日 - 13:04

原始链接:http://www.dragonbaby308.com/java-lang-String/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

急事可以使用右下角的DaoVoice,我绑定了微信会立即回复,否则还是推荐Valine留言喔( ఠൠఠ )ノ
0%