Java字符串对象池的作用是什么?
在 Java 中,字符串池(String Pool)是字符串常量的存储区域,它位于堆区域中。字符串池的作用是提高字符串的重用性,减少内存消耗。
字符串池的位置:
-
在堆中: 字符串池是堆区的一部分。当创建字符串时,Java 首先检查字符串池中是否已经存在相同内容的字符串,如果存在,则直接返回池中的引用,不会重复创建。如果字符串池中没有相同内容的字符串,则将新字符串添加到池中。
-
String 类中的 intern() 方法:
String
类中有一个intern()
方法,该方法可以手动将字符串对象添加到字符串池中。如果字符串池中已经存在相同内容的字符串,intern()
方法返回池中的引用;否则,将当前字符串对象添加到池中并返回。
与堆中 new
出来的字符串的区别:
-
重用性: 字符串池中的字符串具有重用性。如果在字符串池中已经存在相同内容的字符串,不会创建新的对象,而是直接返回现有对象的引用。而使用
new
关键字创建的字符串对象,无论内容是否相同,都会在堆中创建一个新的对象。 -
比较: 字符串池中的字符串可以使用
==
进行比较,因为它们是相同对象的引用。而在堆中通过new
创建的字符串,应该使用equals()
方法进行内容比较,因为它们可能是不同的对象。
示例代码:
String str1 = "Hello"; // 字符串池中创建
String str2 = "Hello"; // 直接使用字符串池中的引用
String str3 = new String("Hello"); // 在堆中创建新的字符串对象
System.out.println(str1 == str2); // true,因为它们引用相同的对象
System.out.println(str1 == str3); // false,因为它们引用不同的对象
System.out.println(str1.equals(str3)); // true,因为它们内容相同
总体来说,使用字符串池可以减少内存占用,提高字符串的比较效率。在大部分情况下,推荐使用字符串池来创建字符串对象。
在 Java 中,可以使用 System.identityHashCode()
方法来获取对象的哈希码,从而间接获取对象的地址。以下是一个例子,演示了使用 new
关键字创建字符串和使用字符串池创建字符串的地址输出:
public static void main(String[] args) {
String str1 = "Hello"; // 字符串池中创建
String str2 = "Hello"; // 直接使用字符串池中的引用
String str3 = new String("Hello"); // 在堆中创建新的字符串对象
String str4 = "Hello"; // 直接使用字符串池中的引用
System.out.println(str1 == str2); // true,因为它们引用相同的对象
System.out.println(str1 == str3); // false,因为它们引用不同的对象
System.out.println(str1.equals(str3)); // true,因为它们内容相同
System.out.println("Address of str1 (str1): " + System.identityHashCode(str1));// Address of str1 (str1): 2065951873
System.out.println("Address of str2 (str2): " + System.identityHashCode(str2));// Address of str2 (str2): 2065951873
System.out.println("Address of str3 (str3): " + System.identityHashCode(str3));// Address of str3 (str3): 1922154895
System.out.println("Address of str4 (str4): " + System.identityHashCode(str4));// Address of str4 (str4): 2065951873
}
通过 System.identityHashCode()
输出它们的地址信息。请注意,具体的地址值可能会因为不同的 JVM 实现而有所不同。
true
false
true
Address of str1 (str1): 2065951873
Address of str2 (str2): 2065951873
Address of str3 (str3): 1922154895
Address of str4 (str4): 2065951873
Java中的字符串
Java中没有原生的字符串类型,但是提供了String
、StringBuffer
和StringBuilder
来表示字符串,在它们的代码实现中都是通过char[]
来存储字符串中的字符的。下图是它们的继承关系:
String
、StringBuffer
和 StringBuilder
是 Java 中用于处理字符串的三个主要类,它们在设计和使用上有一些关键的区别。
String 类:
-
不可变性:
String
对象是不可变的,一旦创建,其内容不能被修改。任何对String
类的操作都会创建一个新的字符串对象。 -
线程安全性: 由于不可变性,
String
对象是线程安全的,可以在多个线程中共享。 -
使用场景: 适用于字符串内容不经常变化的场景,例如字符串常量、配置信息等。
public class Main{
public static void main(String[] args) {
String str = "Hello";
System.out.println("Address of str (str): " + System.identityHashCode(str));//
str = str + " World"; // 创建了一个新的字符串对象
System.out.println("Address of str (str): " + System.identityHashCode(str));//
System.out.println("Address of str (str): " + System.identityHashCode("Hello"));//
System.out.println("Address of str (str): " + System.identityHashCode("World"));//
}
}
结果
Address of str (str): 2065951873
Address of str (str): 883049899
Address of str (str): 2065951873
Address of str (str): 2093176254
StringBuffer 类:
-
可变性:
StringBuffer
是可变的,可以通过其方法对字符串进行修改,而不创建新的对象。 -
线程安全性:
StringBuffer
是线程安全的,所有的方法都是同步的,适合在多线程环境下使用。 -
使用场景: 适用于频繁修改字符串的场景,例如拼接大量字符串。
public class Main{
public static void main(String[] args) {
StringBuffer buffer = new StringBuffer("Hello");
System.out.println("Address of str (buffer): " + System.identityHashCode(buffer));//
buffer.append(" World"); // 在原有对象上进行修改
System.out.println("Address of str (buffer): " + System.identityHashCode(buffer));//
System.out.println("Address of str (Hello): " + System.identityHashCode("Hello"));//
System.out.println("Address of str ( World): " + System.identityHashCode(" World"));//
}
}
结果
Address of str (buffer): 2065951873
Address of str (buffer): 2065951873
Address of str (Hello): 1922154895
Address of str ( World): 883049899
StringBuilder 类:
-
可变性:
StringBuilder
也是可变的,类似于StringBuffer
,但不同的是它的方法不是同步的,因此在单线程环境下使用更高效。 -
线程安全性:
StringBuilder
不是线程安全的,不适合在多线程环境下使用。 -
使用场景: 适用于单线程环境下频繁修改字符串的场景,性能比
StringBuffer
更好。
public class EventOverlapChecker {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder("Hello");
System.out.println("Address of str (builder): " + System.identityHashCode(builder));//
builder.append(" World"); // 在原有对象上进行修改
System.out.println("Address of str (builder): " + System.identityHashCode(builder));//
System.out.println("Address of str (Hello): " + System.identityHashCode("Hello"));//
System.out.println("Address of str ( World): " + System.identityHashCode(" World"));//
}
}
结果
Address of str (builder): 2065951873
Address of str (builder): 2065951873
Address of str (Hello): 1922154895
Address of str ( World): 883049899
总结比较:
- 如果字符串内容基本不变,且需要频繁操作,使用
StringBuffer
或StringBuilder
。 - 如果字符串内容基本不变,不需要频繁操作,使用
String
。 - 如果字符串内容可能会改变,且在多线程环境下使用,使用
StringBuffer
。 - 如果字符串内容可能会改变,且在单线程环境下使用,使用
StringBuilder
。
字符串常量池
由于字符串的不可变性以及字符串使用的频繁性,JVM在堆中通过哈希表实现了一个字符串常量池,用于避免字符串的重复创建。在使用字符串字面量实例化字符串对象时,如果字符串常量池中没有该字符串,就会将该字符串实例化并将该字符串的引用加入字符串常量池;如果字符串常量池中有该字符串引用,那么就会直接返回该引用。
public static void main(String[] args) {
String str1 = "helloWorld";
String str2 = "helloWorld";
System.out.println(str1 == str2);//true
}
String str1=new String("helloWorld");
String str2=new String(new char[]{'h','e','l','l','o','W','o','r','l','d'});
再看str2,就只有一个new出来的对象:
String str=new String("helloWorld");
System.out.println(str.intern()==str);//false
String str=new String("hello")+new String("World");
System.out.println(str.intern()==str);//true
在 Java 中,字符串池中实际上保存的是字符串对象的引用,而不是在堆中存储字符串对象的地址。每个字符串池中的引用指向在堆中实际存储的字符串对象。
字符串是不可变的,当我们创建一个字符串时,如果字符串池中已经存在相同内容的字符串,则直接返回池中的引用。如果字符串池中没有相同内容的字符串,会创建一个新的字符串对象,并将其引用加入到字符串池中。
这种机制有助于提高字符串的重用性,减少内存占用。因为相同的字符串在池中只保存一份,多个字符串变量可以共享相同的引用,而不需要重复创建相同的字符串对象。