IntegerCache、longCache与String.intern()方法总结

一道经典面试题:

public class Main {
	private Integer a = 123;
	private Integer b = 128;
	private Long c = 123L;
	private Long d = 1000L;
	String str = "abc";
	public static void main(String[] args) {
		Main m = new Main();
		System.out.println(Integer.valueOf(123)==m.a);
		System.out.println(Integer.valueOf(128)==m.b);
		System.out.println(Long.valueOf(123)==m.c);
		System.out.println(Long.valueOf(1000)==m.d);
		System.out.println(new String("abc").intern()==m.str);
		System.out.println(new String("ab").intern()==m.str);
	}
}

如上的结果中,程序的输出结果依次是什么?

结果:

true
false
true
false
true
false

通常,我们知道这是因为缓存的问题,那么为什么会有缓存,为什么缓存的大小是这样的,缓存的大小可不可以人为设置呢?

可更改的IntegerCache

我们先来看Integer,查看Integer类中的valueOf方法:

/**
      *返回表示指定的表示int值的Integer实例。如果没有新的Integer实例
      *要求,一般应在构造函数Integer(int)之前优先使用此方法,因为这
      *种方法会缓存经常请求的值来提高显着更好的空间和时间性能
      *
      *
      *此方法将始终缓存-128到127范围内的值,
      *包含,并可以缓存此范围之外的其他值。
      *
      * @param我是一个{@code int}值。
      * @return表示{@code i}的{@code Integer}实例。
      * @since 1.5
     */
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
}

如上说明,如果定义的是Integer类型的变量默认情况下只能缓存-128到127之间的值,但是如注释所说,它也可以缓存除此范围之外的值。也就是说实际上这个范围并不是固定的。而IntegerCache是Integer的内部类,它的定义如下:

/**
      *缓存以支持自动装箱之间的值的对象标识语义
      * JLS要求的-128和127(含)。
     *
      *首次使用时初始化缓存。 缓存的大小
      *可能由{@code -XX:AutoBoxCacheMax = <size>}选项控制。
      *在VM初始化期间,java.lang.Integer.IntegerCache.high属性
      *可以设置并保存在私有系统属性中
      * sun.misc.VM类。
     */    
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

通过源码我们了解到,默认范围为[-128,127],但是IntegerCache可以通过获取jvm中已保存的属性java.lang.Integer.IntegerCache.high来设置缓存的大小,设我们的设定值为m,则Integer的缓存大小为[-128,m],m最大不应超过Integer.MAX_VALUE+low(负数),我们可以使用jvm参数“-XX:AutoBoxCacheMax = <size>”控制自动装箱时的缓存大小。

举个栗子,如下代码:

代码1:

public class Main {
	public static void main(String[] args) {
		Integer a = 128;
		System.out.println(Integer.valueOf(128)==a);
	}
}

默认情况下上述代码的结果是false,但是我们如果手动修改自动装箱的缓存大小(-128~200)如下:

此时我们再运行,结果就变为true了。

另外我们还有可能会把下面代码的输出结果弄错。。。

代码2: 

public class Main {
	public static void main(String[] args) {
		int a = 128;
		System.out.println(Integer.valueOf(128)==a);
	}
}

输出结果会是什么呢?请不要犯了关心思维的毛病,就直觉的认为这个输出的也是false;但是这个程序正确的输出结果是true。我们可以通过反编译生成两者的字节码进行分析:

代码1的:

         0: sipush        128
         3: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         6: astore_1
         7: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;
        10: sipush        128
        13: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        16: aload_1
        17: if_acmpne     24
        20: iconst_1
        21: goto          25
        24: iconst_0
        25: invokevirtual #28                 // Method java/io/PrintStream.println:(Z)V
        28: return

可以看到代码1中,Integer a=128这时候先调用了Integer.valueOf方法,然而由于此时的值不在缓存范围内,所以只好调用构造函数生成一个对象实例,之后再次调用Integer.valueOf()又生成了一个对象实例,所以这两个都不在缓存中,比较的是对象实例,所以不相等。

代码2的:

         0: sipush        128
         3: istore_1
         4: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
         7: sipush        128
        10: invokestatic  #22                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: invokevirtual #28                 // Method java/lang/Integer.intValue:()I
        16: iload_1
        17: if_icmpne     24
        20: iconst_1
        21: goto          25
        24: iconst_0
        25: invokevirtual #32                 // Method java/io/PrintStream.println:(Z)V

 可以看到这里没有调用valueOf方法,而是直接将常量加载到操作数栈后,将其存储到局部变量表中。然后在生成Integer对象后调用intValue方法进行值的比较,即该等式相当于Integer.valueOf(128).intValue()==128,所以返回的结果是true。

LongCache可更改吗?

正如我们之前修改Integer自动装箱的缓存大小一样,我们自然会问:LongCache的大小能更改吗?

别急,我们下来看下源码:

    private static class LongCache {
        private LongCache(){}

        static final Long cache[] = new Long[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Long(i - 128);
        }
    }

    /**
     * 返回指定的表示long数值的实例,如果不需要新的实例
     * 则将此方法优先于构造函数使用,因为这种方法会
     * 通过缓存显着改善空间和时间性能
     
     * 请注意,与Integer中的valueOf(int)方法不同
     * ,此方法不能够指定缓存特定内容中的取值范围。
     *
     * @param  l a long value.
     * @return a {@code Long} instance representing {@code l}.
     * @since  1.5
     */
    public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
    }

可以看到,LongCache的实现是通过静态数组实现的,它的大小是固定不能改变的,所以只能是-128~127之间,同样的Short类型的也是如此。

迷人的intern方法

javaAPI中对string中的intern方法解释如下:

public String intern()

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

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

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

它遵循以下规则:对于任意两个字符串 st,当且仅当 s.equals(t)true 时,s.intern() == t.intern() 才为 true

所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。

所以当我们输入以下代码时:

public class Main {
	public static void main(String[] args) {
		String s = "abc";
		System.out.println(new String("abc").intern()==new String("abc"));
		System.out.println(new String("abc").intern()=="abc");
		System.out.println(new String("abc").intern()==s.intern());
	}
}

第一个由于常量池中的引用与对象的hashcode不同,所以导致不相等。而后两个常量自身的比较,所以他们的结果是相等的。正确结果为false,true,true

猜你喜欢

转载自blog.csdn.net/yinweicheng/article/details/81191812
今日推荐