深入解析String的intern方法
有关于String的intern方法
- 在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。
8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:
- 直接使用双引号声明出来的String对象会直接存储在常量池中。
- 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
intern方法
- Java源码
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
- String的intern方法中看到,这个方法是一个 native 的方法,但注释写的非常明了。“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。
代码测试与原理分析
Java测试代码
public class StringDemo {
public static void main(String[] args) {
String str = "abc";
String str1 = new String("abc");
String str2 = new String("abc");
String str3 = "a";
String str4 = "bc";
String str5 = str3 + str4;
System.out.println(str1 == str2);
System.out.println(str1.equals(str2));
System.out.println(str == str5);
System.out.println(str == str1.intern());
System.out.println(str1.intern() == str2.intern());
}
}
返回结果
结果分析
先说一下== 和 equals方法的却别
- == 对于基本类型和引用类型的作用效果是不同的,对于基本数据类型来说,==比较的是值,但是对于引用数据类型来说,==比较的是对象的内存地址。
- equals()方法存在于Object类中,而Object类是所有类的直接或间接父类:
public boolean equals(Object obj) {
return (this == obj);
}
equals() 方法存在两种使用情况:
类没有覆盖 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
类覆盖了 equals()方法 :一般我们都覆盖 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
- 所以第一个结果返回为false,由于String重写了equals()方法,所以第二个结果返回true
string重写过的equals()方法源码:
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
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;
}
字符串常量池
- JDK1.8运行时数据区域
JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
《深入理解Java虚拟机》中有提到
- 常量折叠
在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 常量折叠(Constant Folding) 的代码优化。《深入理解 Java 虚拟机》中是也有介绍到:
常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。 对于 String str3 = “a” + “bc”; 编译器会给你优化成 String str3 = “abc”; 。 并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:
基本数据类型(byte、boolean、short、char、int、float、long、double)以及字符串常量
final 修饰的基本数据类型和字符串变量
字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )
因此,str1 、 str2 、 str3 都属于字符串常量池中的对象。
引用的值在程序编译期是无法确定的,编译器无法对其进行优化。 对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
String str5 = new StringBuilder().append(str3).append(str4).toString();
因此,str5 并不是字符串常量池中存在的对象,属于堆上的新对象。
- 由上面分析可以得出第三个结果为false的原因
intern方法
-
intern方法的作用:如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回
-
由于jdk1.8 的时候字符串常量池被移动到了堆中,那么再来解释为什么会有结果4和结果5的打印结果
-
如上图所示,当调用intern()方法他们都指向字符串常量池中的唯一地址,所以结果4和结果5都为true