String与字符串常量池

119 阅读3分钟

String的不可变性

原因

​ String类底层采用 final 来定义,String类本身不提供修改底层byte[]数组的方法,而且String类也不可以被继承,所以String不可变。

优势

  1. HashMap等集合可以通过利用String的不可变性来进行hash(),String内部通过String的内容来进行哈希,而不是对象的地址。
  2. JVM虚拟机可以节省空间,通过将String字面量存储在字符串常量池中。
  3. String不可变带来了线程安全,不必要去处理其线程同步等问题。
  4. 在Java中,安全参数例如密码等经常使用String来定义,同时当JVM类加载器加载类时也大量使用。

code

String s1="java";

这个例子足够简单吧,创建了一个Java字符串,s1指向它,接下来让我们看下面的代码

s1=s1.contact(" code");
System.out.println(s1);

输出结果会是什么呢

java code

结果可能出乎有些人的预料,不是说String是不可变的吗?为什么s1指向的字符串变成了java code呢?

其实这里不是字符串改变了,而是s1的指向改变了,java字符串并没有改变,在字符串常量池中新创建了两个字符串常量,分别是codejava code,而s1指向了java code

创建方法

1.使用“”

String s1="FrankLig";

上述代码首先在字符串常量池搜索是否存在FrankLig ,如果存在则s1指向它,如果不存在则会在字符串常量池创建FrankLig,再将s1指向它。

2.使用new

String s2=new String("FrankLig");

上述代码首先在堆中创建一个String对象,如果字符串常量池中不存在FrankLig,则再在字符串常量池中创建一个FrankLig,一共创建两个String对象。字符串常量池中存在FrankLig的话则只在堆中创建一个对象。s2指向的是堆中的对象。

创建了多少个字符串

这是我们经常遇到的问题,看接下来用代码就可以把这个问题弄清楚。

public class StringExample{
	public static void main(String[] args){
		String s1 = new String("Java");
		
		String s2 = s1;
		System.out.println(s2==s1);	//true
		
		String s3 = "code";
		
		System.out.println(s3==s1); //true
		System.out.println(s3==s2); //true
		
		String s4 = "Frank";
		
		String s5 = s4;
		String s6 = new String("Frank");
		
		System.out.println(s5==s4); //true
		System.out.println(s6==s4); //false

		s5.contact("blog");
	}
}

上述代码一共创建了多少个字符串呢?

答案是6个。

来看看它们的内存分布

String内存布局.jpg

String.intern()

intern()方法返回一个指向字符串常量池中字符串的应用。

​ 如果字符串常量池中有一个字符串与使用String.intern()方法的字符串相等,则直接返回对此字符串的应用。

​ 如果没有则在字符串常量池中创建一个此字符串,返回对它的应用。

public class InternExample{
	public static void main(String[] args){
		String s1 = "Java";
		
		String s2 = s1.intern();
		System.out.println(s1==s2);	//true
		
		String s3 = "Rust";
		String s4 = new String("Rust");
		
		s5 = s3.intern();
		s6 = s4.intern();
		System.out.println(s5==s3);	//true
		System.out.println(s6==s3); //true
		
		System.out.println(s6==s4); //false
	}
}

一共创建了三个字符串,一个在堆中,两个在字符串常量池。

String_内存布局2.jpg