前言
最近一段时间在研究算法,不可避免的就会涉及到Java中对数字的各种左移与右移的运算,同时这部分内容在JDK源码中也涉及不少,尤其是List,Map的扩容机制都使用到了这部分,但是在查找相关资料的时候,发现很多博客写的太过于理论,并不利于理解,笔者希望能从代码上给大家提供一个新的理解思路。博客将会从正负数,int,long等几个角度去分别说明其中的区别。
Int型右移>>
首先看右移,Java中直接的右移是有符号的右移。因此正数和负数的右移结果是截然不同的,笔者会在代码前面放置1
和-1
作为对照。我们选择22
作为对照参数,因为22
转换位2进制以后是10110
,其中0
和1
分布比较均匀,便于观察。
public static void rightMove() {
System.out.println("正整数对照:"+Integer.toBinaryString(1));
System.out.println("正整数输出:"+Integer.toBinaryString(22));
System.out.println("正整数移动:"+ Integer.toBinaryString(22>>1));
System.out.println("--------正负号分割线--------");
System.out.println("负整数对照:"+Integer.toBinaryString(-1));
System.out.println("负整数输出:"+Integer.toBinaryString(-22));
System.out.println("正整数移动:"+ Integer.toBinaryString(-22>>1));
}
输出:
正整数对照:1
正整数输出:10110
正整数移动:1011
正整数移动:101
--------正负号分割线--------
负整数对照:11111111111111111111111111111111
负整数输出:11111111111111111111111111101010
负整数移动:11111111111111111111111111110101
负整数移动:11111111111111111111111111111010
这里可以看到对于32
位的int
型来说,高位的0
都会被忽略,因此对于正整数只能看到有真实值的部分,所以人为的恢复一下:
正整数对照:00000000000000000000000000000001
正整数输出:00000000000000000000000000010110
正整数移动:00000000000000000000000000001011 右移一位
正整数移动:00000000000000000000000000000101 右移两位
--------正负号分割线--------
负整数对照:11111111111111111111111111111111
负整数输出:11111111111111111111111111101010
负整数移动:11111111111111111111111111110101 右移一位
负整数移动:11111111111111111111111111111010 右移两位
横向对比很容易发现:>>
是有符号右移,正数右移高位补0
,负数右移高位补1
。因此当使用>>
对正数数进行右移时,32位后最终任何int型数字都会变为0
;而当使用>>
对负数进行右移时,32
位后最终任何int
型会变成32
位1
,也就是-1
。
Int型无符号右移>>>
无符号右移,也叫做逻辑右移。由于是无符号的,因此它的运算方式正数和负数就是一致的了,也就是正负数右移高位补0
。
public static void rightLogicMove() {
System.out.println("正整数对照:"+Integer.toBinaryString(1));
System.out.println("正整数输出:"+Integer.toBinaryString(22));
System.out.println("正整数移动:"+ Integer.toBinaryString(22>>>1));
System.out.println("正整数移动:"+ Integer.toBinaryString(22>>>2));
System.out.println("--------正负号分割线--------");
System.out.println("负整数对照:"+Integer.toBinaryString(-1));
System.out.println("负整数输出:"+Integer.toBinaryString(-22));
System.out.println("负整数移动:"+ Integer.toBinaryString(-22>>>1));
System.out.println("负整数移动:"+ Integer.toBinaryString(-22>>>2));
}
输出:
正整数对照:1
正整数输出:10110
正整数移动:1011
正整数移动:101
--------正负号分割线--------
负整数对照:11111111111111111111111111111111
负整数输出:11111111111111111111111111101010
负整数移动:1111111111111111111111111110101
负整数移动:111111111111111111111111111010
移动以后我们发现,正数移动的结果基本没有变化,但是负数移动后,显示上缺位置了。这是因为无符号右移,高位一律补0
,不会显示,因此也手动补全一下:
正整数对照:00000000000000000000000000000001
正整数输出:00000000000000000000000000010110
正整数移动:00000000000000000000000000001011 右移一位
正整数移动:00000000000000000000000000000101 右移两位
--------正负号分割线--------
负整数对照:11111111111111111111111111111111
负整数输出:11111111111111111111111111101010
负整数移动:01111111111111111111111111110101 右移一位
负整数移动:00111111111111111111111111111010 右移两位
这里就很明显能够看出来:>>>
所谓的无符号右移,正负数右移高位补0
。右移32
位后最终无论正负整数int
型数字都会变为0
。
Int型左移<<
左移,Java中的直接左移就是字面意义上的左移,无论是正负号都会在最右端(也就是最后面)补0
。
public static void leftMove() {
System.out.println("正整数对照:"+Integer.toBinaryString(1));
System.out.println("正整数输出:"+Integer.toBinaryString(22));
System.out.println("正整数移动:"+ Integer.toBinaryString(22<<1));
System.out.println("正整数移动:"+ Integer.toBinaryString(22<<2));
System.out.println("--------正负号分割线--------");
System.out.println("负整数对照:"+Integer.toBinaryString(-1));
System.out.println("负整数输出:"+Integer.toBinaryString(-22));
System.out.println("负整数移动:"+ Integer.toBinaryString(-22<<1));
System.out.println("负整数移动:"+ Integer.toBinaryString(-22<<2));
}
输出:
正整数对照:1
正整数输出:10110
正整数移动:101100
正整数移动:1011000
--------正负号分割线--------
负整数对照:11111111111111111111111111111111
负整数输出:11111111111111111111111111101010
负整数移动:11111111111111111111111111010100
负整数移动:11111111111111111111111110101000
同样手动对齐一下:
正整数对照:00000000000000000000000000000001
正整数输出:00000000000000000000000000010110
正整数移动:00000000000000000000000000101100 左移一位
正整数移动:00000000000000000000000001011000 左移两位
--------正负号分割线--------
负整数对照:11111111111111111111111111111111
负整数输出:11111111111111111111111111101010
负整数移动:11111111111111111111111111010100 左移一位
负整数移动:11111111111111111111111110101000 左移两位
可以看到,除了高位0
或者1
以外,左移操作基本上正负是没有区别的。因此可以得出结论:<<
是有符号左移,正数和负数左移后低位补0
,左移32
次后任何int
型数字最终都会编程0
。
Long型右移>>
Long
型的右移基本上和int
型没什么区别,都是正数右移高位补0
,负数右移高位补1
。只不过从32
位变成了64
位。
正整数对照:000000000000000000000000000000000000000000000000000000000000001
正整数输出:000000000000000000000000000000000000000000000000000000000010110
正整数移动:000000000000000000000000000000000000000000000000000000000001011
正整数移动:000000000000000000000000000000000000000000000000000000000000101
--------正负号分割线--------
负整数对照:1111111111111111111111111111111111111111111111111111111111111111
负整数输出:1111111111111111111111111111111111111111111111111111111111101010
负整数移动:1111111111111111111111111111111111111111111111111111111111110101
负整数移动:1111111111111111111111111111111111111111111111111111111111111010
Long型无符号右移>>>
从输出来看,long
型和int
型结果也是基本完全一样,也是负号变为高64
位为符号位。但是移动后高位符号位为0
了。基本上也是:无符号右移,正负数右移高位补0
。
正整数对照:1
正整数输出:10110
正整数移动:1011
正整数移动:101
--------正负号分割线--------
负整数对照:1111111111111111111111111111111111111111111111111111111111111111
负整数输出:1111111111111111111111111111111111111111111111111111111111101010
负整数移动:1111111111111111111111111110101
负整数移动:111111111111111111111111111010
由于是long
型,补全来看,64
位下由于是无符号高32
位符号位全部都是0
,只有低32
位才是有效的,补全后如下:
正整数对照:00000000000000000000000000000000 00000000000000000000000000000001
正整数输出:00000000000000000000000000000000 00000000000000000000000000010110
正整数移动:00000000000000000000000000000000 00000000000000000000000000001011
正整数移动:00000000000000000000000000000000 00000000000000000000000000000101
--------正负号分割线--------
负整数对照:11111111111111111111111111111111 11111111111111111111111111111111
负整数输出:11111111111111111111111111111111 11111111111111111111111111101010
负整数移动:00000000000000000000000000000000 01111111111111111111111111110101
负整数移动:00000000000000000000000000000000 00111111111111111111111111111010
Long型左移<<
Long
型左移和int
型左移也相差不多。无非也是把64
位直接左移,低位补0
。
正整数对照:1
正整数输出:10110
正整数移动:101100
正整数移动:1011000
--------正负号分割线--------
负整数对照:1111111111111111111111111111111111111111111111111111111111111111
负整数输出:1111111111111111111111111111111111111111111111111111111111101010
负整数移动:1111111111111111111111111111111111111111111111111111111111010100
负整数移动:1111111111111111111111111111111111111111111111111111111110101000
同样做一个long
型的完整对比:
正整数对照:00000000000000000000000000000000 00000000000000000000000000000001
正整数输出:00000000000000000000000000000000 00000000000000000000000000010110
正整数移动:00000000000000000000000000000000 00000000000000000000000000101100
正整数移动:00000000000000000000000000000000 00000000000000000000000001011000
--------正负号分割线--------
负整数对照:11111111111111111111111111111111 11111111111111111111111111111111
负整数输出:11111111111111111111111111111111 11111111111111111111111111101010
负整数移动:11111111111111111111111111111111 11111111111111111111111111010100
负整数移动:11111111111111111111111111111111 11111111111111111111111110101000
最后
左移右移都说完了,那么<<<
在哪呢?Java没有<<<
这种操作,不用妄想了。
附:Long型的测试代码
public static void rightMoveLong() {
System.out.println("正整数对照:"+Long.toBinaryString(1));
System.out.println("正整数输出:"+Long.toBinaryString(22));
System.out.println("正整数移动:"+ Long.toBinaryString(22>>1));
System.out.println("正整数移动:"+ Long.toBinaryString(22>>2));
System.out.println("--------正负号分割线--------");
System.out.println("负整数对照:"+Long.toBinaryString(-1));
System.out.println("负整数输出:"+Long.toBinaryString(-22));
System.out.println("负整数移动:"+ Long.toBinaryString(-22>>1));
System.out.println("负整数移动:"+ Long.toBinaryString(-22>>2));
}
public static void rightLogicMoveLong() {
System.out.println("正整数对照:"+Long.toBinaryString(1));
System.out.println("正整数输出:"+Long.toBinaryString(22));
System.out.println("正整数移动:"+ Long.toBinaryString(22>>>1));
System.out.println("正整数移动:"+ Long.toBinaryString(22>>>2));
System.out.println("--------正负号分割线--------");
System.out.println("负整数对照:"+Long.toBinaryString(-1));
System.out.println("负整数输出:"+Long.toBinaryString(-22));
System.out.println("负整数移动:"+ Long.toBinaryString(-22>>>1));
System.out.println("负整数移动:"+ Long.toBinaryString(-22>>>2));
}
public static void leftMoveLong() {
System.out.println("正整数对照:"+Long.toBinaryString(1));
System.out.println("正整数输出:"+Long.toBinaryString(22));
System.out.println("正整数移动:"+ Long.toBinaryString(22<<1));
System.out.println("正整数移动:"+ Long.toBinaryString(22<<2));
System.out.println("--------正负号分割线--------");
System.out.println("负整数对照:"+Long.toBinaryString(-1));
System.out.println("负整数输出:"+Long.toBinaryString(-22));
System.out.println("负整数移动:"+ Long.toBinaryString(-22<<1));
System.out.println("负整数移动:"+ Long.toBinaryString(-22<<2));
}