Java中的String和Integer

来源:互联网 发布:中国是否允许持枪知乎 编辑:程序博客网 时间:2024/05/16 18:59

    • 一字符串常量池String Constant Pool
      • intern
    • Java 的Integerint与new Integer到底怎么回事
      • 源码解析

一、字符串常量池(String Constant Pool)

字符串在java程序中被大量使用,为了避免每次都创建相同的字符串对象及内存分配,JVM内部对字符串对象的创建做了一定的优化,在Permanent Generation中专门有一块区域用来存储字符串常量池(一组指针指向Heap中的String对象的内存地址)。

创建字符串对象的几种形式:

(1)通过new方式如String s = new String(“iByteCode”)及string.intern()方法

(2)通过字面量的形式如String s = “aaaaa”

(3)字面量+字面量如String s = “bbbb” + “ccccc”

(4)字面量+变量如String s1 = “dddd”;String s = “eeeee”+s1

假设刚开始字符串常量池为空,那么对于第一种创建方式,JVM内部是怎么处理的,这里也有一个面试题就是一共创建了几个对象,在这里答案是两个,为什么说是两个呢?一个是字符串字面量本身(可以通过string.intern()方法来取得,下图中常量池所指向的字符串对象),一个是单独的字符串对象,Heap视图如下所示:
这里写图片描述

两种方式都能创建字符串对象,但方式二要比方式一更优。
因为字符串是保存在常量池中的,而通过new创建的对象会存放在堆内存中。
一:常量池中已经有字符串常量”aaa”

通过方式一创建对象,程序运行时会在常量池中查找”aaa”字符串,将找到的”aaa”字符串的地址赋给a。
通过方式二创建对象,无论常量池中有没有”aaa”字符串,程序都会在堆内存中开辟一片新空间存放新对象。
一:常量池中没有字符串常量”aaa”

通过方式一创建对象,程序运行时会将”aaa”字符串放进常量池,再将其地址赋给a。
通过方式二创建对象,程序会在堆内存中开辟一片新空间存放新对象,同时会将”aaa”字符串放入常量池,相当于创建了两个对象。

看下面的代码:

public class StringConstantPoolTester {    //private String s1 = new String("iByteCode");    public static void main(String[] args) throws Exception {        String s1 = new String("iByteCode");        System.out.println(s1);        CyclicBarrier barrier = new CyclicBarrier(2);        barrier.await();    }}

那么怎么来验证上面的结论的正确性呢?我们可以通过JVisualVM的Heap dump功能来实现,通过OQL语言来查询Heap内值为iByteCode的字符串对象的个数就可以确定上面的代码到底创建了几个对象。执行结果如下图所示:
这里写图片描述

这里有一点要注意,对于通过new方式创建的String对象,每次都会在Heap上创建一个新的实例,但是对于字符串字面量的形式,只有当字符串常量池中不存在相同对象时才会创建。

也就是说

String a = new String("abc");String b = "abc";

a==b 返回false
a.equles(b) 返回true

第二种方式不用说,相当于第一种方式中的字面量部分。

第三种和第四种方式会怎样创建字符串对象,可以通过javap和JVisualVM来验证,下面通过一段代码来验证:

public class StringConstantPoolTester {    //private String s1 = new String("iByteCode");    public static void main(String[] args) throws Exception {        String s1 = new String("iByteCode");        String s2 = "bbbb" + "ccccc";        String s3 = "dddd" + s2;        System.out.println(s3);        CyclicBarrier barrier = new CyclicBarrier(2);        barrier.await();    }}

这里写图片描述
对于第三种形式String s2 = “bbbb” + “ccccc”,在main方法字节码的第10-12可以看到在JVM里直接通过ldc指令将指向bbbbccccc字符串字面量的引用的值放入到Operand Stack顶,然后存入到Local variable Array的第二个slot位。同时可以通过JVisualVM验证结论的正确性,由于篇幅问题这里省略。

对于第四种形式String s3 = “dddd” + s2,在main方法字节码的13-32可以看到在JVM里面创建了两个字符串字面量dddd和ddddbbbbccccc,并且调用StringBuilder对字符串进行连接。

intern

public String intern()

返回字符串对象的规范化表示形式。

一个初始时为空的字符串池,它由类 String 私有地维护。

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

所有字面值字符串和字符串赋值常量表达式都是内部的。

返回:

一个字符串,内容与此字符串相同,但它保证来自字符串池中。

String str1 = "a";String str2 = "b";String str3 = "ab";String str4 = str1 + str2;String str5 = new String("ab");System.out.println(str5.equals(str3));System.out.println(str5 == str3);System.out.println(str5.intern() == str3);System.out.println(str5.intern() == str4);

得到的结果:

true

false

true

false
为什么会得到这样的一个结果呢?我们一步一步的分析。

第一、str5.equals(str3)这个结果为true,不用太多的解释,因为字符串的值的内容相同。

第二、str5 == str3对比的是引用的地址是否相同,由于str5采用new String方式定义的,所以地址引用一定不相等。所以结果为false。

第三、当str5调用intern的时候,会检查字符串池中是否含有该字符串。由于之前定义的str3已经进入字符串池中,所以会得到相同的引用。

第四,当str4 = str1 + str2后,str4的值也为”ab”,但是为什么这个结果会是false呢?先看下面代码:

String a = new String("ab");String b = new String("ab");String c = "ab";String d = "a" + "b";String e = "b";String f = "a" + e;System.out.println(b.intern() == a);System.out.println(b.intern() == c);System.out.println(b.intern() == d);System.out.println(b.intern() == f);System.out.println(b.intern() == a.intern());

运行结果:

false

true

true

false

true

由运行结果可以看出来,b.intern() == a和b.intern() == c可知,采用new 创建的字符串对象不进入字符串池,并且通过b.intern() == d和b.intern() == f可知,字符串相加的时候,都是静态字符串的结果会添加到字符串池,如果其中含有变量(如f中的e)则不会进入字符串池中。但是字符串一旦进入字符串池中,就会先查找池中有无此对象。如果有此对象,则让对象引用指向此对象。如果无此对象,则先创建此对象,再让对象引用指向此对象。

当研究到这个地方的时候,突然想起来经常遇到的一个比较经典的Java问题,就是对比equal和==的区别,当时记得老师只是说“==”判断的是“地址”,但是并没说清楚什么时候会有地址相等的情况。现在看来,在定义变量的时候赋值,如果赋值的是静态的字符串,就会执行进入字符串池的操作,如果池中含有该字符串,则返回引用。

执行下面的代码:

String a = "abc";String b = "abc";String c = "a" + "b" + "c";String d = "a" + "bc";String e = "ab" + "c";System.out.println(a == b);System.out.println(a == c);System.out.println(a == d);System.out.println(a == e);System.out.println(c == d);System.out.println(c == e);

返回结果全部为true

Java 的Integer、int与new Integer到底怎么回事?

  1. int 和Integer在进行比较的时候,Integer会进行拆箱,转为int值与int进行比较。
  2. Integer与Integer比较的时候,由于直接赋值的时候会进行自动的装箱,那么这里就需要注意两个问题,一个是-128<= x<=127的整数,将会直接缓存在IntegerCache中,那么当赋值在这个区间的时候,不会创建新的Integer对象,而是从缓存中获取已经创建好的Integer对象。二:当大于这个范围的时候,直接new Integer来创建Integer对象。
  3. new Integer(1) 和Integer a = 1不同,前者会创建对象,存储在堆中,而后者因为在-128到127的范围内,不会创建新的对象,而是从IntegerCache中获取的。那么Integer a = 128, 大于该范围的话才会直接通过new Integer(128)创建对象,进行装箱。
public class Test {      public static void main(String[] args) {          Integer i = new Integer(128);          Integer i2 = 128;          System.out.println(i == i2);          Integer i3 = new Integer(127);          Integer i4 = 127;          System.out.println(i3 == i4);          Integer i5 = 128;          Integer i6 = 128;          System.out.println(i5 == i6);          Integer i7 = 127;          Integer i8 = 127;          System.out.println(i7 == i8);      }  }  

输出结果为
false
false
false
true

源码解析

这里写图片描述

当不用new Integer的时候,构造方法会通过这个方法来构造对象并返回。那么你看if中的代码就知道了,i>=IntegerCache.low&&i<=IntegerCache.high的时候,从IntegerCache中获取的实例,否则,直接new Integer(i);

下面就是IntegerCache的代码,其中low就是-128,high是默认的127,不过可以进行设置。
通过静态代码块直接创建了256个Integer的对象,从-128到127之间。
所以在第四种情况中,取出来的时候,都是经过了上面的return IntegerCache.cache[i + (-IntegerCache.low)]得到的最终的对象,因为在IntegerCache中是创建好的,不会从新new了,所以地址值是一样的,也就是同一个对象,所以为true。
这里写图片描述

原创粉丝点击