最近写了些关于 Bit 操作的代码,才发现使用左移操作符时,当右侧操作数(需要移动的位数)大于左侧操作数比特个数时,实际移动位数等同于右侧操作数对左侧操作数比特个数求余。可能说的不是很明白,可以用一个简单的示例来说明,比如以下代码:
1 | public class BitShiftTest { |
起初我认为以上代码中的 j
对应的值应该为 0,但是并不是这样的,以上代码的输出如下:
1 | 8 |
我们知道 int 占用 4 个字节,即 32 个比特,那么当我们对变量 i
进行左移 35 位时,此时等于将变量 i
左移 3 位。关于左移操作符,在 15.19. Shift Operators - JLS 中对此有如下说明:
If the promoted type of the left-hand operand is int, only the five lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x1f (0b11111). The shift distance actually used is therefore always in the range 0 to 31, inclusive.
即当变量为 int 类型时,实际移动的位数为右操作数的低 5 位比特对应的数值,即在文首的实例代码中移动 35 位时,实际移动的距离为 35 & 0b11111 = 3
,即移动 3 位,即移动的距离等同于 35 % 32 = 3
。
同理,关于 long 类型数值的左移操作亦是同理:
If the promoted type of the left-hand operand is long, then only the six lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x3f (0b111111). The shift distance actually used is therefore always in the range 0 to 63, inclusive.
关于左移操作符,在 RoaringBitmap 的 BitmapContainer.java 中有如下应用:
1 | /** |
在未压缩的 BitmapContainer
中,Bitmap 底层由 long 数组实现,我们知道一个 long 型数字占用 8 个字节,即 64 个比特,那么 add
方法中的第一行即为计算 i
对应 long[] bitmap
中的索引,即应该存储值的 long 数值的索引,此处的 i >>> 6
即等于 i / 64
,即使用移位操作相比除法计算效率更优。在 add
方法中的第二行,将数组中之前的值 previous
与 1L << i
进行 或 运算,此处的左移操作符按照 JLS 中的规定,即可将对应的 比特位 置为 1,而不需要我们显式将 i
对 64 取余再进行左移操作。
同理,在 Guava 的 LockFreeBitArray 实现中,也用到了左移操作符,其 set
方法源码位于 BloomFilterStrategies.java at v31.1:
1 | static final class LockFreeBitArray { |
整个方法的实现与 RoaringBitmap 中 BitmapContainer
的 add
方法实现几乎一致,使用左移操作符时还特意加了注释 only cares about low 6 bits of bitIndex
,即 JLS 中的规定,该方法与 BitmapContainer
的 add
方法唯一不同的是此方法底层数据结构为 AtomicLongArray
,即线程安全的数组,而在 BitmapContainer
中,long[] bitmap
并不是线程安全的。