String对象创建的四种方式
在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。
8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有四种:
①直接使用双引号声明出来的String对象会直接存储在常量池中。
②如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中()
String类的内存原理:
这里我直接用了原文的代码,但是图我自己画了一下
1.第一段代码:
public static void main(String args[]){
String s1 = new String("1");
s1.intern();
String s2 = "1";
System.out.println(s1 == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
结果:
jdk1.6:false false
jdk1.7:false true
我们先看一下源码中intern方法:
public static String valueOf(double d) {
return Double.toString(d);
}
/**
* 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();
}
大意就是说,如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。
再通过图分别解析一下Jdk1.6和1.7为啥结果不一样
在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称 jdk8 已经直接取消了 Perm 区域,而新建立了一个元区域。应该是 jdk 开发者认为 Perm 区域已经不适合现在 JAVA 的发展了。
我们可以清晰地看到,str1指向的是堆中的str1对象,而str2指向的是堆中常量池中的1,所以str1==str2返回了false;
重点说一下s3,s4:(a,b,c三步)
a. String s3 = new String("1") + new String("1");
这句代码主要生成了 s3引用指向的对象。此时s3引用对象内容是"11",但是并没有在常量池中,
b. 接下来s3.intern();
这一句代码,是将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,按jdk6 的做法是在常量池中生成一个 "11" 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。(就是说把s3对象在堆中的位置存在了Stringpool中,反正都是“11”,没必要再去创建了)
c. String s4 = "11";
这句代码中"11"是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。
2.第二段代码:
public static void main(String[] args) {
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
}
结果:
jdk1.6:false false
jdk1.7:false false
重点讲一下jdk1.7中为啥变成false了,就是变更了一下intern的调用位置,结果怎么就不用了?
首先第一句String s3 = new String("1") + new String("1");
主要生成了 s3引用指向的对象。此时s3引用对象内容是"11",但是并没有在常量池中
然后执行String s4 = "11";声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。
jdk1.6和jdk1.7比较结论:
从上述的例子代码可以看出 jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括2点:
①将String常量池 从 Perm 区移动到了 Java Heap区
②String.intern ()方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。
String,StringBuffer,StringBuilder的异同 image.png
1.如果要操作少量的数据用String
2.单线程操作字符串缓冲区 下操作大量数据 用 StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据 用StringBuffer
面试常见
①
public static void main(String args[]){
String s1 = "123";
String s2 = "123";
System.out.println(s1 == s2);//true
}
这个就不说了吧,都指向常量池中同一个对象
②
public static void main(String args[]){
String s1 = new String("123");
String s2 = new String("123");
System.out.println(s1 == s2);//false
System.out.println(s1.intern() == s2.intern());//true
}
s1,s2在堆中开辟了不同的空间,所以是false,但是他们两个在stringpool中是同一个对象,所以调用intern时一样。
③
public static void main(String args[]){
String s1 = "123";
final String s2 = "12";
final String s3 = "3";
String s4 = s2 + s3;
System.out.println(s1 == s4);//true
}
因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”12”+”3”,而这种情况下,编译器会直接合并为s4=”123”,所以最终s1==s4。
④
public static void main(String args[]){
String s1 = "123";
String s2 = "12";
String s3 = "3";
String s4 = s2 + s3;
System.out.println(s1 == s4);//false
}
因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。
网友评论