浅谈java两个数的异或交换以及Integer的缓存机制


今天看数据结构的时候看到一个通过异或(位运算)交换数组内的数的方法。

import java.lang.reflect.Field;
public class test {

    public static void main(String[] args) {
       int arr[]={1,2,3,4};
        System.out.println("1:"+arr[0]+"|2:"+arr[3]);
        swap(arr, 0, 3);
        System.out.println("1:"+arr[0]+"|2:"+arr[3]);
    }
   
   public static void swap(int []arr, int i, int j) {
        //i=2 j=3
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
        /*
        流程分析如何实现交换
        0101 = 0001 ^ 0100 = 6
        0001 = 0101 ^ 0100 = 1
        0100 = 0101 ^ 0001 = 5
        **/
    }

}

在尝试之后,联想到了java没有指针,java做不到真正的引用传递(对一块内存地址操作,而非对拷贝的地址),便想到用反射交换两个数


import java.lang.reflect.Field;

public class test {

    public static void main(String[] args) {
        Integer a =2, b=3;
        System.out.println("a="+a+" | b="+b);
        try {
            swap2(a, b);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println("a="+a+" | b="+b);
    }
    public static void swap2(Integer a, Integer b) throws NoSuchFieldException, IllegalAccessException {
        int temp = a;
        Field field = a.getClass().getDeclaredField("value");
        field.setAccessible(true);
        field.setInt(a, b);
        field.setInt(b, temp);
    }
}


运行截图

而如果将int temp = a;改为Integer temp = a;呢

结果如下

这是什么原因呢?

这就涉及到了java中Integer 的缓存机制

Integer的缓存机制: Integer是对小数据(-128~127)是有缓存的,再jvm初始化的时候,数据-128~127之间的数字便被缓存到了本地内存中,如果初始化-128~127之间的数字,会直接从内存中取出,不需要新建一个对象.

我们添加一个变量debug一下

下一步

wow,很有意思,temp的值’貌似‘也随着a的变化而变化了,但事实是这样吗?下一行的temp2怎么也变化了?它只是做了一个简单的赋值,它怎么也变了?

个人理解:当缓存中存在某个值a时,如果存在多个Integer对象的值都等于(或指向)这个缓存值a,那么当a改变了值的时候,原本等于(指向)a的Integer对象的值也会随着改变,由于Integer是对小数据(-128~127)是有缓存的,上述代码a和temp2都等于2 符合条件,因此temp2也会随着改变

那么当我们把a和temp的值改为128时

下一步

如我们所料,这次temp2的值并没有改变。

很好,我们来看一下Integer的源码就能更好地理解了

public final class Integer extends Number implements Comparable {
  
  //...省略...
  
  /*
  初始化一个Integer数组——cache,最小值 -128,最大值默认为 127,但可以从属性
  "java.lang.Integer.IntegerCache.high"中获取设置的值从而修改最大值
  */
  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() {}
  }
  
  //...省略...
}

那么如何修改这个初始化的最大值呢?我们以IDEA为例

找到VM options并添加命令

-Djava.lang.Integer.IntegerCache.high={你想要设置的上限}

测试

ok,Integer的缓存的最大值已经被改变了。

那好,我们回到开始,如何用Integer定义的变量实现交换而不是随着a的变化而变化呢?

改成下面这样就可以了

Integer temp = new Integer(a.intValue());

那为什么这次的temp不会随着a的值变化而变化呢?因为new和直接赋值并不一样

直接赋值的Integer变量指向的是 java 常量池中的对象(也就是在缓存中的数据)

而new Integer() 的变量指向堆中新建的对象,两者在内存中的地址不同。

所以你明白了吧。

一开始也没想到简单的swap能扯到这么多东西,不过感觉到了知识之间的联系,o( ̄▽ ̄)d

如果看到哪里有问题或者描述不准确的话,请多多指教啊。