Java 语言设计中的部分共享策略

Java 语言的设计者认为共享带来的效率远远高于提取、拼接字符串所带来的低效率。    ——Core Java

 

  在之前的学习和使用过程中,遇到过字符串常量池的概念,对于整形,在其源码中也有缓存数组的概念。其实这些类似概念,都是JVM对获取常用的字符串、整形对象这一操作所做的优化。

下面我们来分析下创建字符串的两种方式 

 1         String s1 = "hello";
 2         String s2 = "hello";
 3         String s3 = new String("hello");
 4      // equals比较字符串字符值是否相等,均为 true 
 5         System.out.println(s1.equals(s2));
 6         System.out.println(s2.equals(s3));
 7         System.out.println(s1.equals(s3));
 8      
 9         System.out.println(s1==s2); // true
10         System.out.println(s1==s3); // false
11         System.out.println(s2==s3); // false 

对于第一种方式

  用字面值的方式创建一个字符串时,JVM首先会去字符串常量池中查找是否存在"hello"这个对象,如果不存在,则在字符串常量池中创建"hello"这个对象,然后将池中"hello"这个对象的引用地址返回给"hello"对象的引用s1,这样s1会指向字符串常量池中"hello"这个字符串对象;如果存在,则不创建任何对象,直接将池中"hello"这个对象的地址返回,赋给引用s2。因为s1、s2都是指向同一个字符串池中的"hello"对象,所以“==”比较结果true。

对于第二种方式

  采用new关键字新建一个字符串对象时,JVM会首先在字符串池中查找有没有"hello"这个字符串对象,如果有,则不在池中再去创建"hello"这个对象了,直接在堆中创建一个"hello"字符串对象,然后将堆中的这个"hello"对象的地址返回赋给引用s3,这样,s3就指向了堆中创建的这个"hello"字符串对象;如果没有,则首先在字符串池中创建一个"hello"字符串对象,然后再在堆中创建一个"hello"字符串对象,然后将堆中这个"hello"字符串对象的地址返回赋给s3引用,这样,s3指向了堆中创建的这个"hello"字符串对象。所以在进行“==”比较的时候,s1 和 s2 都是指向常量池中的字符串对象。s3 是指向堆中字符串对象。结果肯定为false。

考虑下面的整形恒等判断的代码

1         Integer a = Integer.valueOf(3);
2         Integer b = Integer.valueOf(3);
3 
4         Integer c = Integer.valueOf(323);
5         Integer d = Integer.valueOf(323);
6 
7         System.out.println(a==b);
8         System.out.println(c==d);

输出结果为

1 ```
2 true
3 false
4 ```
为什么会有这样的输出结果呢?
点击查看Integer.valueOf()方法的源码,发现:
    @HotSpotIntrinsicCandidate
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
  其方法注释的意思是:返回表示指定 Integer 的实例。如果不需要新的 integer 实例,则通常应优先使用此方法,而不是使用构造函数 integer(int),因为通过缓存频繁请求值,此方法可能会显著提高空间和时间性能。此方法将始终缓存范围为-128到127(含)的值,并可能缓存此范围之外的其他值。
 
  再去看看IntegerCache类的源码,发现在这个静态内部类声明了一个Integer cache[],对于-128~127的整形,也就是占用1个字节的整形来说,如果用Integer.valueOf()方法进行初始化,就会从这个缓存数组中取值,因为JVM对这种操作进行了优化,,所以可以用一个缓存数组进行速度上的提升。
再看看下面一段代码:
1         Integer e  = 42;
2         Integer f = 42;
3 
4         Integer g  = 423;
5         Integer h = 423;
6         
7         System.out.println(e==f); // true
8         System.out.println(g==h); // false
 
让我们来分析一下产生如此结果之原因,对于 e, f 他们的代码等同于
        Integer e  = 42; // Integer d = Integer.valueOf(42);
        Integer f = 42;  // Integer d = Integer.valueOf(42);
所以 e==f 返回true;对于 g 和 h
1         Integer g  = 423; 
2         Integer h = 423;    
由于423超过了-128~127(含)的范围,所以不会再去使用缓存数组中的整形值,而是使用了:
1         Integer g = new Integer(423);
2         Integer h = new Integer(423); 

  都是使用了 new 关键字进行整形的创建,g 和 h指向的是堆中两个不同的地址,他们进行恒等判断结果当然就是false。IntegerCache 类的 cache[] 数组和数组字符串常量池有异曲同工之妙。

 

 

 

 

posted @ 2019-07-08 22:28  LimLee  阅读(266)  评论(0编辑  收藏  举报