位运算以及逻辑运算

二进制中的原码、反码、补码

要了解位移运算符我们就要先来了解 原码、反码、补码

有符号数:

对于有符号数而言,符号的正、负机器是无法识别的,但由于正、负 “恰好是两种截然不同的状态,如果用"0’表示“正”,用"1"表示“负",这样符号也被数字化了,并且规定将它放在有效数字的前面,即组成了有符号数。所以,在二进制中使用最高位(第一位)来表示符号,最高位是0,表示正数,最高位是1,表示负数

10000000 00000000 00000000 01111100

对于有符号数而言的性质:
⑴ 二进制的最高位是符号位:0表示正数,1表示负数
(2) 正数的原码、反码、补码都一样
(3) 负数的反码=它的原码符号位不变,其他位取反(0→1 ;1→0 )
(4) 负数的补码=它的反码+1
(5) 0的反码、补码都是0
(6) 在计算机运算的时候,都是以补码的方式来运算的

有符号数运算案列

正数相加
在这里插入图片描述
正数相减
在这里插入图片描述
在这里插入图片描述

无符号数:

无符号数是针对二进制来讲的,无符号数的表数范围是非负数。全部二进制均代表数值(所有位都用于表示数的大小),没有符号位。即第一个"0"或"1"不表示正负

10000000 00000000 00000000 01111100

比如说我们 java 中 char 它就算是一个无符号数,它占16位,而我们的无符号数 是不能进行运算的。

<< 左移运算符

左移一位

在这里插入图片描述

左移一位后的数值经过计算可以发现刚好值位移前数值的两倍,等价于乘2操作,在很多情况下可以当做乘2使用,但是并不代表真正的乘2,在一些特殊情况下并不等价

左移18位

在这里插入图片描述

此时二进制表达首位为1,此时数值为-1058799616,同理,如果继续位移,左位移20位,则值为59768832又变成了正数

注意: 所以根据这个规则,如果任意一个十进制的数左位移32位,右边补位32个0,十进制岂不是都是0了?当然不是!
当int类型的数据进行左移的时候,当左移的位数大于等于32位的时候,位数会先求余数,然后再进行左移我们求得的余数位,也就是说如果真的左移32位的时候,会先进行位数求余数,即为左移32位相当于左移0位,所以左移33的值和左移一位1是一样的。

>>右移运算符(带符号的)

在这里插入图片描述

在这里插入图片描述

>>> 无符号右移运算符

在这里插入图片描述
总结: 正数的左移与右移,负数的无符号右移,就是相应的补码移位所得,在高位补0即可负数的右移,就是补码高位补1,然后按位取反加1即可

逻辑运算

掌握逻辑和运算的区别是︰将二进制数表示的信息作为四则运算的数值来处理就是算数,像图形那样,将数值处理为单纯的和1 的罗列就是逻辑
计算机能够处理的运算,大体可分为逻辑运算和算数运算,算数运算指的是加减乘除四则运算;逻辑运算指的是对二进制各个数位的0和1分别进行处理的运算,包括(~)、(&)、(丨)和(^)四种。

名称运算符
逻辑非~
逻辑与&
逻辑或
逻辑异或^

逻辑非指的是将0变成1,1变成0的取反操作

  ~ 1111 = 0000

逻辑与指的是"两个都是1时,运算结果才是1,其他情况下是0"

	0000 0011 
  & 0000 0101 
-------------------
  = 0000 0001

逻辑或指的是"至少有一方是1时,运算结果为1,其他情况下运算结果都是0"

   0000 0011 
 | 0000 0101
-------------------
 = 0000 0111

逻辑异或指的是"其中一方是1,另一方是0时运算结果才是1,其他情况下是0"

   0000 0011 
 | 0000 0101
-------------------
 = 0000 0110

我们看完前面基本的运算过后,在java的底层实现中我们知道有很多地方都是用的位运算和逻辑运算,原因应该大家都知道就是效率高,在计算机中不管是加减乘除最后做的都是加法运算,我现在给大家上主菜,然大家来看一下这种运算的巧妙。

使用实例

HashMap大家都不陌生,我们看一下HashMap 是如何初始化容量的。

static final int tableSizeFor(int cap) {
    int n = cap - 1;//这是为了处理 cap 本身就是 2 的N次方的情况
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

就是上述代码我们可以看到用到了>>>( 无符号右移) 和 |(逻辑或) 进行运算。
在这里插入图片描述
我们假设我们去传一个参数7给HashMap,我们通过计算,来看它到底会给我们new一个多大容量的数组
在这里插入图片描述

我们可以通过上面的图片看出最终结果是8,相信你应该看出来,这5个公式会通过最高位的1,拿到2个1、4个1、8个1、16个1、32个1。当然,有多少个1,取决于我们的入参有多大,但我们肯定的是经过这5个计算,得到的值是一个低位全是1的值,最后返回的时候 +1,则会得到1个比n 大的 2 的N次幂。

当然这里为什么这里是2的N次幂,我个人觉得有下面三点原因:
① 为什么不建议初始容量为奇数 ?
我们会经过tab[i = (n - 1) & hash]得到索引,因为如果为奇数,经过n - 1后为偶数,而偶数对应的二进制最低位(末尾)一定为0,在hash进行&运算时,结果值的最低位也一定是0;相比起结果可0可1的情况不仅仅是浪费了一半空间,而且也增加了哈希冲突的概率。
②为什么建议初始容量为2的幂次方而不仅仅是2的倍数?
1.计算索引时下标计算效率更快,对于2的次幂满足 hash & (n - 1) = hash % n,即可以直接取模
2.扩容更快,因为如果n为2的次幂时扩容,每次扩为原数组的 2 倍,假设 n = 32,对应的二进制则为00010000,扩容只需要将1直接往左移 1 位,但是如果 n = 10,对应二进制位00001010 ,不止一个1,所以n为2的次幂时扩容时效率更高。

当然java底层的位运算和逻辑运算也不是一成不变的,他们也在不断寻找最优的计算方式。

优化了 hash 值的计算方式,1.8之前通过一顿操作,而1.8之后只是简单的让高16位参与了运算。

// JDK 1.7
static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
// JDK 1.8
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这段代码也体现出了为什么hashmap的键值可以为null
1)如果key等于nu11:
可以看到当key等于nu11的时候也是有哈希值的,返回的是0.
2)如果key不等于nu11:
首先计算出key的hashcode赋值给h,然后与h无符号右移16位后的二进制进行按位异或得到最后的hash值

而这里为什么要高16无符号右移就是为了同时保留高位和低位的特征,达到减小hash冲突的目的,想要进一步了解的可以看这篇文章

还有就是扩容时计算节点在新表的索引位置方式的是 “tab[i = (n - 1) & hash]”,n是数组的length,其实(n - 1) & hash的本质就是hash % length(取余操作)。(因为length只能为2^n),等价于下面这个。

tab[i = (2^n - 1) & hash] == tab [i = (hash % 2^n)]

这里是源码

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

在这里插入图片描述

其实java底层大量用到了逻辑运算与位运算,像 bitCount方法(计算一个数的二进制的1的个数),虽然我一直没看懂。

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值