Swap in JAVA, 并不简单

前不久,一个同事一大早发了一个题目给我,让我实现,今天突然想起来,闲来无聊,做一下笔记

入题

请用Java完成swap函数,交换两个整数类型的值。

我用的Java版本是1.8u152

@Test
public void swap() throws Exception {
    Integer a = 1, b = 2;
    swap(a, b);
    System.out.println("a=" + a + ", b=" + b);
}

public void swap(Integer a, Integer b){
   // 需要实现的部分

}

很多人会写出下面这样的代码

public void swap(Integer a, Integer b) throws Exception {
    Integer temp= a;
    a = b;
    b = temp;
}

上面的运行的结果显示a和b两个值肯定不会交换。 那么让我们来看一下上述程序运行时,Java对象在内存中的分配方式:

24211833_o7tV.jpg

由此可以看到,在两个方法的局部变量表中分别持有的是对a、b两个对象实际数据地址的引用。上面实现的swap函数,仅仅交换了swap函数里局部变量a和局部变量b的引用,并没有交换JVM堆中的实际数据。所以main函数中的a、b引用的数据没有发生交换,所以main函数中局部变量的a、b并不会发生变化。

那么要交换main函数中的数据要如何操作呢?
仔细想了一下,只能去内存中去修改a和b的数值,然后第一时间就是想到反射去修改,于是去查了一下integer的成员属性,发现有一个int 型的value属性。所以我们使用反射来修改该值,代码如下:

/**
 * 1、java 存在传参和传引用,不像C/C++中使用指针改变一下指向的地址就解决了,
 * 于是我们使用反射来实现,需要注意的是,要避免拆箱装箱
 * 2、如何避免使用缓存空间:
 * a) 使用new 关键字来强制生成对象,避免自动拆箱装箱
 * b) 使用Field 类的 setInt 来避免使用缓存,避免拆箱装箱
 *
 * @param var1
 * @param var2
 * @throws IllegalAccessException
 * @throws NoSuchFieldException
 */
private void swap(Integer var1, Integer var2) throws IllegalAccessException, NoSuchFieldException {
    int temp = var1;
    Field value = Integer.class.getDeclaredField("value");
    value.setAccessible(true);
    value.set(var1, var2);
    System.out.println("反射机制修改var1后的Field实例的值:" + value.get(var1));
    System.out.println("-------------separate------------");
    value.setInt(var2, temp);
    System.out.println("反射机制修改var2后的Field实例的值:" + value.get(var2));
}

运行结果完全符合预期。

上面的程序运行成后,如果我在声明一个 Integer c = 1, d = 2;会有什么结果

@Test
public void swap() throws Exception {
    Integer a = 1, b = 2;
    swap(a, b);
    System.out.println("a=" + a + ", b=" + b);
    Integer c = 1, d = 2;
    System.out.println("c=" + c + ", d=" + d);
}

输出结果如下:

a=2; b=1

c=2; d=1

惊不惊喜,意不意外!!!

深入

在Java对原始类型int自动装箱到Integer类型的过程中使用了 Integer.valueOf(int)这个方法了。肯定是这个方法在内部封装了一些操作,使得我们修改了 Integer.value后,产生了全局影响。

然后去看了一下integer的源码,发现了事情的真相

public class 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);
    }
    
    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() {}
    }
    
}

如上所示Integer内部有一个私有静态类IntegerCache,该类静态初始化了一个包含了Integer.IntegerCache.lowjava.lang.Integer.IntegerCache.high的Integer数组。
其中java.lang.Integer.IntegerCache.high的取值范围在[127~Integer.MAX_VALUE - (-low) -1]之间。
在该区间内所有的Integer.valueOf(int)函数返回的对象,是根据int值计算的偏移量,从数组Integer.IntegerCache.cache中获取,对象是同一个,不会新建对象。

所以当我们修改了 Integer.valueOf(1)value后,所有Integer.IntegerCache.cache[ 1 - IntegerCache.low ]的返回值都会变更。

好了,那么不在  [  IntegerCache.lowIntegerCache.high  ]的部分呢?
很显然,它们是幸运的,没有被IntegerCache缓存,每次它们的到来,都会new一个新的对象,在JVM上分配一块内存。

后记

XCache
是否有Cache最小值最大值
Boolean----
ByteByteCache-128127(固定)
ShortShortCache-128127(固定)
CharacterCharacterCache0127(固定)
IntegerIntegerCache-128java.lang.Integer.IntegerCache.high
LongLongCache-128127(固定)
Float----
Double----
java.lang.Integer.IntegerCache.high

看了IntegerCache类获取high的方法 sun.misc.VM.getSavedProperty,可能大家会有以下疑问,我们不拖沓,采用一个问题一解答的方式。

1. 这个值如何如何传递到JVM中?

和系统属性一样在JVM启动时,通过设置 -Djava.lang.Integer.IntegerCache.high=xxx传递进来,在IDEA中可以这样设置。

211313_ugHK_3017023.png

2. 这个方法和System.getProperty有什么区别?

为了将JVM系统所需要的参数和用户使用的参数区别开,
java.lang.System.initializeSystemClass在启动时,会将启动参数保存在两个地方:
2.1 sun.misc.VM.savedProps中保存全部JVM接收的系统参数。
JVM会在启动时,调用java.lang.System.initializeSystemClass方法,初始化该属性。
同时也会调用sun.misc.VM.saveAndRemoveProperties方法,从java.lang.System.props中删除以下属性:

  • sun.nio.MaxDirectMemorySize
  • sun.nio.PageAlignDirectMemory
  • sun.lang.ClassLoader.allowArraySyntax
  • java.lang.Integer.IntegerCache.high
  • sun.zip.disableMemoryMapping
  • sun.java.launcher.diag
    以上罗列的属性都是JVM启动需要设置的系统参数,所以为了安全考虑和隔离角度考虑,将其从用户可访问的System.props分开。

2.2 java.lang.System.props中保存除了以下JVM启动需要的参数外的其他参数。

  • sun.nio.MaxDirectMemorySize
  • sun.nio.PageAlignDirectMemory
  • sun.lang.ClassLoader.allowArraySyntax
  • java.lang.Integer.IntegerCache.high
  • sun.zip.disableMemoryMapping
  • sun.java.launcher.diag

 

转载于:https://my.oschina.net/GinkGo/blog/1581220

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值