Integer相关方法的源码解析

本文深入解析了Java Integer类中关于位翻转的方法,包括reverseBytes和reverse的实现原理,详细阐述了位翻转的过程。同时介绍了循环移位rotateLeft和rotateRight的实现,讲解了移位操作的细节。还探讨了valueOf方法,揭示了Integer缓存机制以优化对象创建。
摘要由CSDN通过智能技术生成

来源:Java编程的逻辑
包装类的总体解析

7.1 位翻转

Integer有两个静态方法,可以按位进行翻转:

public static int reverse(int i)//会补齐32位后进行翻转,得到的结果会忽略掉首部的0
public static int reverseBytes(int i)

位翻转就是将int当做二进制,左边的位与右边的位进行互换,reverse是按位进行互换,reverseBytes是按byte进行互换。

reverseBytes(int i)代码

public static int reverseBytes(int i) {
    return ((i >>> 24)           ) | 
		   ((i >>   8) &   0xFF00) | 
		   ((i <<   8) & 0xFF0000) |
           ((i << 24)            );
}

以参数i等于0x12345678(00010010001101000101011001111000)为例,我们来分析执行过程:

i>>>24 无符号右移,最高字节挪到最低位:
结果是 0x00000012。

(i>>8) & 0xFF00(00000000000000001111111100000000),保留左边第二个字节:
i>>8是 0x00123456,再进行 & 0xFF00,结果是0x00003400。

(i << 8) & 0xFF0000(00000000111111110000000000000000),保留右边第二个字节
i<<8是0x34567800,再进行 & 0xFF0000,结果是0x00560000。

i<<24,最右字节挪到最左边。
结果是0x78000000

这四个结果再进行或操作|,结果就是0x78563412,这样,通过左移、右移、与和或操作,就达到了字节翻转的目的。

reverse

我们再来看reverse的代码:

public static int reverse(int i) {
    // HD, Figure 7-1,代码的来源地
    i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
    i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
    i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
    i = (i << 24) | ((i & 0xff00) << 8) | ((i >>> 8) & 0xff00) | (i >>> 24);
    return i;
}

高效实现位翻转的基本思路,首先交换相邻的单一位,然后以两位为一组,再交换相邻的位,接着是四位一组交换、然后是八位、十六位,十六位之后就完成了。这个思路不仅适用于二进制,十进制也是适用的,为便于理解,我们看个十进制的例子,比如对数字12345678进行翻转,

第一轮,相邻单一数字进行互换,结果为:

21 43 65 87

第二轮,以两个数字为一组交换相邻的,结果为:

43 21 87 65

第三轮,以四个数字为一组交换相邻的,结果为:

8765 4321

翻转完成。

对十进制而言,这个效率并不高,但对于二进制,却是高效的,因为二进制可以在一条指令中交换多个相邻位。

这行代码就是对相邻单一位进行互换:

x = (x & 0x55555555) <<  1 | (x & 0xAAAAAAAA) >>>  1;

5的二进制是0101,0x55555555的二进制表示是:01010101010101010101010101010101;
x & 0x55555555就是取x的奇数位。

A的二进制是1010,0xAAAAAAAA的二进制表示是:10101010101010101010101010101010;
x & 0xAAAAAAAA就是取x的偶数位。

即:

(x & 0x55555555) <<  1 | (x & 0xAAAAAAAA) >>>  1;

表示的就是x的奇数位向左移,偶数位向右移,然后通过|合并,达到相邻位互换的目的。
这段代码可以有个小的优化:只使用一个常量0x55555555,后半部分先移位再进行与操作,变为:

//先将偶数位移位到奇数位,再与代表奇数的0x55555555相与即可
(i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;

同理,如下代码就是以两位为一组,对相邻位进行互换:

i = (i & 0x33333333) << 2 | (i & 0xCCCCCCCC)>>>2;

3的二进制是0011,0x33333333的二进制表示是:00110011001100110011001100110011
x & 0x33333333就是取x以两位为一组的低半部分。

C的二进制是1100,0xCCCCCCCC的二进制表示是:11001100110011001100110011001100
x & 0xCCCCCCCC就是取x以两位为一组的高半部分。

即:

(i & 0x33333333) << 2 | (i & 0xCCCCCCCC)>>>2;

表示的就是x以两位为一组,低半部分向高位移,高半部分向低位移,然后通过|合并,达到交换的目的。
同样,可以去掉常量0xCCCCCCCC,代码可以优化为:

(i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;

同理,下面代码就是以四位为一组,进行交换。

i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;

到以八位为单位交换时,就是字节翻转了,可以写为如下更直接的形式,代码和reverseBytes基本完全一样。

i = (i << 24) | ((i & 0xff00) << 8) |
    ((i >>> 8) & 0xff00) | (i >>> 24);

要实现翻转,一种常见的思路是,第一个和最后一个交换,第二个和倒数第二个交换,直到中间两个交换完成。如果数据不是二进制位,这个思路是好的,但对于二进制位,这个效率比较低。

CPU指令并不能高效的操作单个位,它操作的最小数据单位一般是32位(32位机器),另外,CPU可以高效的实现移位和逻辑运算,但加减乘除则比较慢。
reverse是在充分利用CPU的这些特性,并行高效的进行相邻位的交换,也可以通过其他更容易理解的方式实现相同功能,但很难比这个代码更高效。

7.2 循环移位

Integer有两个静态方法可以进行循环移位:

public static int rotateLeft(int i, int distance)
public static int rotateRight(int i, int distance) 

rotateLeft是循环左移,rotateRight是循环右移,distance是移动的位数,所谓循环移位,是相对于普通的移位而言的,普通移位,比如左移2位,原来的最高两位就没有了,右边会补0,而如果是循环左移两位,则原来的最高两位会移到最右边,就像一个左右相接的环一样。

实现代码

这两个函数的实现代码为:

public static int rotateLeft(int i, int distance) {
    return (i << distance) | (i >>> -distance);
}
public static int rotateRight(int i, int distance) {
    return (i >>> distance) | (i << -distance);
}

关于i >>> -distance:其实,实际的移位个数不是后面的直接数字,而是直接数字的最低5位的值,或者说是直接数字 & 0x1f的结果。之所以这样,是因为5位最大表示31,移位超过31位对int整数是无效的。

比如说,-8的二进制表示是:

11111111111111111111111111111000

其最低5位是11000,十进制就是24,所以i>>>-8就是i>>>24,i<<8 | i>>>24就是循环左移8位。

上面代码中,i>>>-distance就是 i>>>(32-distance),i<<-distance就是i<<(32-distance)。

7.3 valueOf

创建包装类对象时,可以使用静态的valueOf方法,也可以直接使用new,但建议使用valueOf,为什么呢?我们来看valueOf的代码:

public static Integer valueOf(int i) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

它使用了IntegerCache,这是一个私有静态内部类,代码如下所示:

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) {
            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);
        }
        high = h;

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

    private IntegerCache() {}
}

IntegerCache表示Integer缓存,其中的cache变量是一个静态Integer数组,在静态初始化代码块中被初始化,默认情况下,保存了从-128到127,共256个整数对应的Integer对象。

在valueOf代码中,如果数值位于被缓存的范围,即默认-128到127,则直接从IntegerCache中获取已预先创建的Integer对象,只有不在缓存范围时,才通过new创建对象。

通过共享常用对象,可以节省内存空间,由于Integer是不可变的,所以缓存的对象可以安全的被共享。Boolean/Byte/Short/Long/Character都有类似的实现。这种共享常用对象的思路,是一种常见的设计思路,为享元模式,英文叫Flyweight,即共享的轻量级元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值