String类
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
private final byte coder;
private int hash;
......
}
从类的定义可以看出:
- String 类是 final 的,意味着它不能被子类继承
- String 类实现了 Serializable 接口,意味着它可以序列化
- String 类实现了 Comparable 接口,意味着最好不要用‘==’来比较两个字符串是否相等,而应该用
compareTo()
方法去比较 - StringBuffer、StringBuilder 和 String 一样,都实现了 CharSequence 接口,所以它们仨属于近亲。由于 String 是不可变的,所以遇到字符串拼接的时候就可以考虑一下 StringBuffer 和 StringBuilder,它俩是可变的
- Java 9 以前,String 是用 char 型数组实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码。在 Latin1 字符为主的程序里,可以把 String 占用的内存减少一半。当然,天下没有免费的午餐,这个改进在节省内存的同时引入了编码检测的开销。
- 每一个字符串都会有一个 hash 值,这个哈希值在很大概率是不会重复的,因此 String 很适合来作为 HashMap 的键值。
String具有不可变性
- String 类被 final 关键字修饰,所以它不会有子类,这就意味着没有子类可以重写它的方法,改变它的行为。
- String 类的数据存储在
byte[]
数组中,而这个数组也被 final 关键字修饰了,这就表示 String 对象是没法被修改的,只要初始化一次,值就确定了。
原因:
第一,可以保证 String 对象的安全性,避免被篡改,毕竟像密码这种隐私信息一般就是用字符串存储的。
第二,保证哈希值不会频繁变更。毕竟要经常作为哈希表的键值,经常变更的话,哈希表的性能就会很差劲。
第三,可以实现字符串常量池。
由于字符串的不可变性,String 类的一些方法实现最终都返回了新的字符串对象。例如replace、concat、substring方法
字符串常量池
由于字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一个字符串常量池。
java8之后,移除了永久代,字符串常量池移到了堆中。
示例:
String s = new String("ikun");
使用 new 关键字创建一个字符串对象时,Java 虚拟机会先在字符串常量池中查找有没有‘ikun’这个字符串对象,如果有,就不会在字符串常量池中创建‘ikun’这个对象了,直接在堆中创建一个‘ikun’的字符串对象,然后将堆中这个‘ikun’的对象地址返回赋值给变量 s。”
“如果没有,先在字符串常量池中创建一个‘ikun’的字符串对象,然后再在堆中创建一个‘ikun’的字符串对象,然后将堆中这个‘ikun’的字符串对象地址返回赋值给变量 s。
String.intern()方法
详细参看美团技术团队深入解析 String.intern()
。
在 JAVA 语言中有8中基本类型和一种比较特殊的类型String
。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。
8种基本类型的常量池都是系统协调的,String
类型的常量池比较特殊。它的主要使用方法有两种:
- 直接使用双引号声明出来的
String
对象会直接存储在常量池中。 - 如果不是用双引号声明的
String
对象,可以使用String
提供的intern
方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。但是在JDK7之后,字符串常量池迁到堆中,intern会将在常量池中存储对象的引用。
判断字符串相等
此问题也可以引申为.equals()
和 ‘==’ 操作符有什么区别
- “==”操作符用于比较两个对象的地址是否相等。
.equals()
方法用于比较两个对象的内容是否相等
但是equals方法一定是比较对象内容相等的吗?
并不是,例如java所有的类都继承Object这个超类。默认的equals比较的是两个对象的地址。String类对equals方法进行了重写,所以是进行比较内容。
String类Java源码如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
- 首先进行地址比较,如果地址相等,则直接返回。
- 判断是否是String类型对象
- 如果是String类,再根据采用的编码进行内容比较