Java位运算技巧

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/minaki_/article/details/81980079

       位运算作为底层的基本运算操作,往往是和'高效'二字沾边,适当的运用位运算来优化系统的核心代码,会让你的代码变得十分的精妙。以下是我所遇之的一些简单的位运算技巧作为博文记录。

1.获得int型最大值

    public static void main(String[] args) {
        int maxInt = (1 << 31) - 1;
        int maxInt1 = ~(1 << 31);
        int maxInt2 = (1 << -1) - 1;
        int maxInt3 = (-1>>>1);
        System.out.println("十进制: "+ maxInt +" ,二进制: " + Integer.toBinaryString(maxInt));
        System.out.println("十进制: "+ maxInt1 +" ,二进制: " + Integer.toBinaryString(maxInt1));
        System.out.println("十进制: "+ maxInt2 +" ,二进制: " + Integer.toBinaryString(maxInt2));
        System.out.println("十进制: "+ maxInt3 +" ,二进制: " + Integer.toBinaryString(maxInt3));
    }   
     /** ~output~
     十进制: 2147483647 ,二进制: 1111111111111111111111111111111
     十进制: 2147483647 ,二进制: 1111111111111111111111111111111
     十进制: 2147483647 ,二进制: 1111111111111111111111111111111
     十进制: 2147483647 ,二进制: 1111111111111111111111111111111
     */

    exp:int类型为32位,要获得int的最大值,只需要最高位为0(正数),其余位为1,便可得到最大数[01111111 11111111 11111111 11111111](2进制)、[0xFFFFFFF](16进制)。

2.获得int型最小值

    public static void main(String[] args) {
        int minInt = 1 << 31;
        int minInt1 = -1 << 31;
        int minInt2 = 1 << -1;
        System.out.println("十进制: "+ minInt +" ,二进制: " + Integer.toBinaryString(minInt));
        System.out.println("十进制: "+  minInt1 +" ,二进制: " + Integer.toBinaryString(minInt1));
        System.out.println("十进制: "+  minInt2 +" ,二进制: " + Integer.toBinaryString(minInt2));
        System.out.println("十进制: "+  0x80000000 +" ,二进制: " + Integer.toBinaryString(0x80000000));
    }
    /** ~output~
     十进制: -2147483648 ,二进制: 10000000000000000000000000000000
     十进制: -2147483648 ,二进制: 10000000000000000000000000000000
     十进制: -2147483648 ,二进制: 10000000000000000000000000000000
     十进制: -2147483648 ,二进制: 10000000000000000000000000000000
     */

    exp:int类型为32位,要获得int的最大值,只需要最高位为1(负数),其余位为0,便可得到最大数[10000000 00000000 00000000 00000000](2进制)、[0x80000000](16进制)。

       负数最小二进制和正数最大二进制似乎有很大区别,这是因为cpu中只有加法器,减法只是加法的一种形式,而计算机是如何通过加法来计算减法的呢?

计算机对负数的实际表示是补码形式,补码的计算是以负数绝对值的原码(二进制)取反[不操作符号位],再加1得到,举个例子:  

  1.      -7 的绝对值原码(二进制) = 1  000  0111            # 最高位为负数标记
  2.      1  000  0111取反  1 111 1000                            # 符号位不取反
  3.      取反后加1  =  1 111 1000  + 1 = 1 111 1001      # 则得到-7的补码 1 111 1001
  4.      计算 6 -  7 = 6 + (-7) = 0 000 0110 + 1 111 1001 = 1 111 1111
  5.      可以看到得到的结果为1 111 1111 最高位为1 ,结果为负数,是补码的形式。我们反向推理,1 111 1111 - 1 = 1111 1110,取反(最高位符号位不操作) 1 111 1110取反得到原码1 000 0001,所以可知十进制值 -1。

3.乘以2的m次方或除以2的m次方

	// 计算n*(2^m)
    public static int mulTwoPower(int n,int m){
        return n << m;
    }

    // 计算n/(2^m)
    public static int divTwoPower(int n,int m){
        return n >> m;
    }
    public static void main(String[] args) {
        System.out.println("5 * 2 * 2 * 2 = "+ mulTwoPower(5, 3) );
        System.out.println("6 / 2 / 2 = "+ divTwoPower(6, 2) );
    }
    /** ~output~
        5 * 2 * 2 * 2 = 40
        6 / 2 / 2 = 1
    */

我们知道十进制是逢十进一,二进制是逢二进一 ,十进制*10,扩大原来的10倍,尾部多一个0,二进制*2,扩大原来的2倍,尾数也多一个0,这便相当于二进制数左移<<1位,0000 1111 * 2 = 0001 1110, 除以2则反之。

4.判断奇偶数                          

    // true 奇数   false 偶数
    public static boolean isOddNumber(int n){
        return (n & 1) == 1;
    }

    public static void main(String[] args) {
        System.out.println("5 是 "+ isOddNumber(5) );
        System.out.println("1234 是 "+ isOddNumber(1234) );
    }
    /** ~output~
         5 是 true
        1234 是 false
     */

    这里知识是二进制最尾部的尾数为1,则此数必为奇数,尾数为0,此数必为偶数。所以通过与运算便可确定这个数的奇偶性。

5.对2的n次方取余

    public static int indexFor(int m, int n){
        return  m & (n - 1);
    }

    public static void main(String[] args) {
        System.out.println("19 与 16 求余 = "+ indexFor(19, 16) );
        System.out.println("19 与 16 求余 = "+ 19 % 16 );
    }
    /** ~output~
         19 与 16 求余 = 3
         19 与 16 求余 = 3
     */

此方法中n为2的指数值,则其二进制形式的表示中只存在一个1,其余位都为0,例如: 0000 1000、0100 0000、0010 0000等等。

则n-1的二进制形式就为1的位数变为0,其右边位全变为1,例如16的二进制  0001 0000 -1 = 0000 1111

测试m为19的二进制 0001 0011 & 0000 1111 = 0000 0011 = 3,地位保留的结果便是余数。

此位运算也是HashMap中确定元素键(key)值所在哈希数组下标位置的核心方法,此位运算(hash & (length - 1))的效率极高于hash % length的求余, 所以也解释了为什么HashMap的扩容始终为2的倍数(2的指数值)。

6.快速幂算法,求n的m次方

    // 快速幂算法求n的m次方
    public static int power(int n, int m) {
        int temp = 1, base = n;
        while (m != 0)  {
            if ((m & 1) == 1) {  // 判断奇偶,
                temp = temp * base;
            }
            base = base * base;
            m >>= 1;   // 舍弃尾部位
        }
        return temp;
    }

    public static void main(String[] args) {
        System.out.println("3的3次方 = " + power(3,3));
        System.out.println("5的7次方 = " + power(5,7));
    }
    /** ~output~
     3的3次方 = 27
     5的7次方 = 78125
     */

我们知道,求n的m次方最简单暴力的方法是循环m次求n的乘积,如今的计算机非常强大,这方面的性能似乎完全可以忽略优化,但对于微型机来说,代码优化应该是你时时刻刻需要考虑的,我们也从另外的问题出发:如何使用更少的时间复杂度去实现n的m次方呢?

快速幂算法,下面来看下他的实现原理。

在数学中,存在等式n^m = n^(m1+m2+m3+.....+mk) = n^m1 * n^m2 * n^m3 * ...* n^mk, 且m1 + m2 + m3 +....+mk = m

我们计算m的二进制,如上示例幂数7的二进制=0000 0111,他的十进制计算为: 1+2+4,所以5^7 = 5^(1+2+4) = 5^1 * 5^2 * 5^4,可以看出时间复杂度为f(n)=lgn

猜你喜欢

转载自blog.csdn.net/minaki_/article/details/81980079