A String object is immutable: Its content cannot be changed once the string is created.
String 是个不可变对象。
String a = "hello";
String b = new String("hello");////创建了两个对象。第一个是创建在字符串常量池中,一个是堆中。
String c = new String("hello" + "world");//按理应该是创建了4个对象,前提是字符串字面量"hello","world"之前没有出现过
System.out.println(a == b);//false
System.out.println(a == b.intern());//true.b引用指向了字符串常量池中的字面量
String d = "2";
String mm = "hello2";
System.out.println(a + d == mm);//false
System.out.println("hello" + "2" == mm);//true。字符串拼接,全是字面量常量(字符串,数字2),直接拼接放入常量池中。
//有一个不是字面量常量,JVM内部做了优化,会创建一个StringBuilder进行拼接,最后得到的是一个String object.
System.out.println("hello" + 2 == mm);//true
final String aa = "hello";
final String dd= "2";
System.out.println(aa + dd == mm);//true,加上final,变量成为了编译期的常量,相当于c#中的宏定义。使用该变量的时候直接替换值。
final String aaa = getHello();
System.out.println(aaa + dd == mm);//false 只有在编译期间确定final的值,才会有常量的效果。
-
public String intern()
Returns a canonical representation for the string object.A pool of strings, initially empty, is maintained privately by the class
String
.When the intern method is invoked, if the pool already contains a string equal to this
String
object as determined by theequals(Object)
method, then the string from the pool is returned. Otherwise, thisString
object is added to the pool and a reference to thisString
object is returned.It follows that for any two strings
s
andt
,s.intern() == t.intern()
istrue
if and only ifs.equals(t)
istrue
.
intern方法被触发后,检测JVM的运行时常量池中是否有和当前字符串相同内容的字符串(equal方法为true),如果有的话,返回常量池中的字符串(外部的字符串不会加入池中);
如果池中没有这样的字符串,当前字符串对象就会加入到常量池中,并且返回该字符串对象的引用。
这样做的好处就是能够节约空间
ps:如果两个字符串的equal方法比较后是true(
s.equals(t)
is
true)
,那么
s.intern() == t.intern()
is
true
Java语言规范中定义了字符串文字以及更一般的常量表达式的值的字符串是被内部化的,以便它们共享同一个实例。我们试验一下下面代码:
String s1="你好,Java"; String s2="你好,"+"Java"; System.out.println(s1==s2); System.out.println(s1.intern()==s2.intern());
打印结果为两个true,说明字符串s1,s2是共享同一个实例的。
我们再来看看这个例子
package com.zhb.jvm; /** * * @author zhb * */ public class RuntimeConstantPoolOOM { public static void main(String[] args){ //String str1 = new StringBuilder("计算机") String str1 = "abc";//str1.intern == str1 为true System.out.println(str1.intern() == str1); String str2 = new String("java");//false System.out.println(str2.intern() == str2); String str3 =new StringBuilder("math").append("analyze").toString(); System.out.println(str3.intern() == str3);//true String str4 =new StringBuilder("computer").append("software").toString(); System.out.println(str4.intern() == str4);//true String str5 =new StringBuilder("jav").append("a").toString(); System.out.println(str5.intern() == str5);//false String str6 = new StringBuilder("math").toString(); System.out.println(str6.intern() == str6);//false } }
str1.intern == str1 为true 这个比较好理解.
str2.intern() == str2 为false 这个也比较好理解。new String("java")创建了两个对象,str2.intern() 返回的是已经存在string pool里面的字符串的引用。
str3,str4中都是为true,因为执行append后,string pool中没有"mathanalyze",
符合首次出现原则。在jdk1.7以后,intern实现不会再复制实例,只是在常量池中记录首次出现的实例的引用,所以str3和str4都是为true.而在之前的jdk1.6的时候,intern()方法会把首次遇到的字符串实例复制到永久代(方法区)中,返回的也是永久代中这个字符串实例的引用。
如果使用jdk1.6运行的话,str3和str4都是false。
str5为false,是不是感觉有点奇怪,其实执行append方法之后,产生的新字符串java,该字符串在append方法执行之前,在string pool中已经存在该字符串了。所以不符合首次出现原则,返回为false.
str6为false,其实是和str3是一样的,string pool 存在math这个字符串了。
了解这个处理机制也可以让我们在用到字符串常量的时候了解如何节省这些字符串所占用的内存。
例1:
package com.zhb.string.intern; import javax.swing.JOptionPane; /** * * @author Administrator * intern的作用 */ public class StringIntern1 { public static void main(String[] args){ String s1,s2,s3,s4,output; s1=new String("hello"); s2=new String("hello"); if(s1==s2) output="s1 and s2 are the same object in memory"; else output="s1 and s2 are not the same object in memory"; if(s1.equals(s2)) output+="\ns1 and s2 are equal" ; else output+="\ns1 and s2 are not equal" ; s3=s1.intern(); s4=s2.intern(); if(s3==s4) output+="\ns3 and s4 are the same object in memory" ; else output+="\ns3 and s4 are not the same object in memory" ; if(s1==s3) output+="\ns1 and s3 are the same object in memory"; else output+="\ns1 and s3 are not the same object in memory"; if(s2==s4) output+="\ns2 and s4 are the same object in memory"; else output+="\ns2 and s4 are not the same object in memory"; if(s1==s4) output+="\ns1 and s4 are the same object in memory"; else output+="\ns1 and s4 are not the same object in memory"; System.out.println(output); } }
输出结果是:
s1 and s2 are not the same object in memory s1 and s2 are equal s3 and s4 are the same object in memory s1 and s3 are not the same object in memory s2 and s4 are not the same object in memory s1 and s4 are not the same object in memory
例2:
package com.zhb.string.intern; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import com.zhb.util.DBHandler; /** * * @author Administrator * intern作用2 */ public class StringIntern2 { static String get() { return "123456789123456789123456789"; } public static void main(String[] args) throws Exception{ //test1();//测试结果:The busy memory is: 3260176 //test2();//测试结果:The busy memory is: 2156624 test3();//测试结果:The busy memory is: 2152072 /* * 从上面可以发现 * 1.test2使用intern后比test1占用的内存少了很多 * 2.test3比test2少了一点,因为test3中将rs.getString(1)换成了get() * get()中返回的是一个和数据记录相同的字符串,但是这个字符串是存在类的静态方法中 * 所以该字符串不是放在方法区中的运行时常量池中,而放在虚拟机栈中的局部变量表中 * 所以test3比test2少的正是字符串在常量池中的那点内存。 */ long total = Runtime.getRuntime().totalMemory(); long free = Runtime.getRuntime().freeMemory(); System.out.println("The busy memory is: " + (total - free)); } public static void test1() throws Exception{ List<Pojo> list = new ArrayList<Pojo>(); Connection conn = DBHandler.getServerConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select name from intern"); while(rs.next()){ String s = rs.getString(1);; Pojo p = new Pojo(); p.setName(s); list.add(p); s = null; p = null; } rs.close(); stmt.close(); conn.close(); System.gc(); } public static void test2() throws Exception{ List<Pojo> list = new ArrayList<Pojo>(); Connection conn = DBHandler.getServerConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select name from intern"); while(rs.next()){ String s = rs.getString(1); Pojo p = new Pojo(); p.setName(s.intern()); list.add(p); s = null; p = null; } rs.close(); stmt.close(); conn.close(); System.gc(); } public static void test3() throws Exception{ List<Pojo> list = new ArrayList<Pojo>(); Connection conn = DBHandler.getServerConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select name from intern"); while(rs.next()){ String s = get(); Pojo p = new Pojo(); p.setName(s); list.add(p); s = null; p = null; } rs.close(); stmt.close(); conn.close(); System.gc(); } }
/* * 从上面可以发现 * 1.test2使用intern后比test1占用的内存少了很多 * 2.test3比test2少了一点,因为test3中将rs.getString(1)换成了get() * get()中返回的是一个和数据记录相同的字符串,但是这个字符串是存在类的静态方法中 * 所以该字符串不是放在方法区中的运行时常量池中,而放在虚拟机栈中的局部变量表中 * 所以test3比test2少的正是字符串在常量池中的那点内存。 */
总结:
Intern方法可以节约内存占用,建议从数据库中查询出来的数据可以使用intern方法。