字符串在JVM中如何存放 及常量池技术

字符串对象在JVM中可能有两个存放的位置:字符串常量池或堆内存

  • 使用常量字符串初始化的字符串对象,它的值存放在字符串常量池中;

  • 使用字符串构造方法创建的字符串对象,它的值存放在堆内存中;

String提供了一个API, java.lang.String.intern(),这个API可以手动将一个字符串对象的值转移到字符串常量池中。

在1.7之前,字符串常量池是在PermGen区域,这个区域的大小是固定的——不能在运行时根据需要扩大,也不能被垃圾收集器回收,因此如果程序中有太多的字符串调用了intern方法的话,就可能造成OOM。

在1.7以后,字符串常量池移到了堆内存中,并且可以被垃圾收集器回收,这个改动降低了字符串常量池OOM的风险。

案例分析

1. String对象的两种创建方式:

           String s1="javaadu";

           String s2="javaadu";

           String s3=new String("javaadu");

           System.out.println(s1==s2);          //true     

           System.out.println(s1==s3);          //false

           

           String s4=s3.intern();

           System.out.println(s1==s4);          //true

           System.out.println(s3==s4);          //false

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。

  • 如果不是用双引号声明的String对象,可以使用String提供的intern方法。String.intern() 是一个Native方法,它的作用是:如果常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。

2. String字符串拼接

           String str1 = "str";

           String str2 = "ing";

           String str3 ="str"+"ing";  //常量池中的对象

           String str4 = str1 + str2;  //在堆上创建的新对象       

           String str5 = "string";        

           System.out.println(str3 == str4);          //flase

           System.out.println(str3 == str5);          //true

           System.out.println(str4 == str5);          //false

String s1 = new String("abc");这句话创建了几个对象?

创建了两个对象。或者一个

如果如果常量池没有,就是两个;如果常量池已经有,就是一个。

           String str1=new String("abc");

           String str2="abc";

           System.out.println(str1==str2);      //false

解释:

先有字符串"abc"放入常量池,然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池,而 Java 堆上的"abc"是在运行期初始化阶段才确定),然后 Java 栈的 str1 指向Java堆上的"abc"。

3. 8种基本类型的包装类和常量池

  • Java 基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。

  • 两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。

           Integer aInteger=1;

           Integer bInteger=2;

           

           Integer cInteger=3;

           Integer dInteger=3;

           

           Integer eInteger=321;

           Integer fInteger=321;

           

           Long gLong=3L;

           Long hLong=2L;

           

           System.out.println(cInteger==dInteger);         //true

           System.out.println(eInteger==fInteger);         //false

           System.out.println(cInteger==(aInteger+bInteger));        //true

           System.out.println(cInteger.equals(aInteger+bInteger)); //true

           System.out.println(gLong==(aInteger+bInteger));           //true

           System.out.println(gLong.equals(aInteger+bInteger)); //false

           System.out.println(gLong.equals(aInteger+hLong));         //true

使用==:

①如果比较Integer变量,默认比较的是地址值。

②Java的Integer维护了从-128~127的缓存池。

③如果比较的某一边有操作表达式(如a+b),那么比较的是具体数值。

使用equals():

①无论Integer还是Long中的equals默认比较的是数值。

②Long的equals()方法,JDK的默认实现:会判断是否是Long类型。

           int a=59;       //基本类型,存储在栈中。

           Integer bInteger=59; //会调用Integer的valueOf方法

           /*

            * valueOf这个方法就是返回一个Integer对象,返回之前,需要判断当前值是否在[-128,127]区间。

            * 如果在此区间,先看IntegerCache中是否存在此对象,若存在,直接返回引用,否则创建一个新的对象。

            * 若不在此区间,return new Integer(i);

            */

           Integer cInteger=Integer.valueOf(59); //因为IntegerCache中存在此对象,所以直接返回引用。

           Integer dInteger=new Integer(59);    //直接创建一个新的对象。

           

           System.out.println(a==bInteger);           //true    Integer会自动拆箱成int,然后进行值比较

           System.out.println(bInteger==cInteger);         //true

           System.out.println(cInteger==dInteger);         //false

           System.out.println(a==dInteger);           //true  dInteger会自动拆箱,进行值比较

注意:

==比较的是地址。

但当为基本类型时,比较的是值。

如果两边有包装类型,则先将包装类型转换为基本类型再比较值是否相等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章