我们打开源码
public final class Stringimplements java.io.Serializable, Comparable, CharSequence {/** The value is used for character storage. */private final char value[];
String对象里面的字符串其实就是存放在一个char[]类型的数组中,该数组是由final修饰的,并且没有提供任何的set方法共给用户使用,也就是说String类是不可变的。这样做有什么好处呢?
我们经常使用String类型的字符串作为Hashmap的key,此时的hash值使用的就是String类中的存储hash的成员变量里面的值,而String里面的hash属性是通过第一次调用hashCode()方法计算出来存到属性中的。
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
因为Sting不可变,自然不用担心线程安全的问题
String Pool也叫字符串常量池,其本质上是维护在堆上的一张hash表。
如下:
public static void main(String[] args) {String s1 = "hello";String s2 = "hello";String s3 = new String("hello");String s4 = new String("hello");System.out.println(s1 == s2); // trueSystem.out.println(s1 == s3); // falseSystem.out.println(s3 == s4); // false
}
如果我们调用new String()方法是直接创建了一个新的String对象,所以两次创建的都是不同的对象,
但是如果我们直接String s1 = "hello"这样创建字符串,本质上是在字符串常量池中创建了一个“hello”这么一个String对象在String Pool中,再使s3指向常量池中的内容。所以s4和s3的内容其实都是String Pool中“hello”的引用。所以相等。
如果我们调用
String str1 = "hello";
str1+=" world";
其实在字符串常量池中是,创建了“ world”对象,并且重新创建了一个新的String对象,内容是“hello world”,然后改变了str1的指向,从而改变了str1中的值,原来的“hello”还在String Pool中,此时的StringPool中一共有三个对象“hello”“ world”“hello world”。后续无用的会被JVM回收
注意:
我们在开发中,应该避免出现下列代码
String str = "hello" ;
for(int x = 0; x < 1000; x++) {str += x ;
}
System.out.println(str);
因为会多次在String Pool创建中创建对象,影响效率。
String str1 = new String("hello").intern() ;
String str2 = "hello" ;
System.out.println(str1 == str2);
// 执行结果
true
调用intern的本质就是把该字符串放入到String Pool中。
JDK版本 | 是否有永久代,字符串常量池放在哪里? | 方法区逻辑上规范,由哪些实际的部分实现的? |
jdk1.6及之前 | 有永久代,运行时常量池(包括字符串常量池),静态变量存放在永久代上 | 这个时期方法区在HotSpot中是由永久代来实现的,以至于这个时期说方法区就是指永久代 |
jdk1.7 | 有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中; | 这个时期方法区在HotSpot中由永久代(类型信息、字段、方法、常量)和堆(字符串常量池、静态变量)共同实现 |
jdk1.8及之后 | 取消永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆中 | 这个时期方法区在HotSpot中由本地内存的元空间(类型信息、字段、方法、常量)和堆(字符串常量池、静态变量)共同实现 |