流水账一下对二进制理解

昨天研究hashMap底层。
发现jdk的工程师对于hashMap定义容量的处理超级聪明。
对于手动输入容量值,底层会计算出一个比她大,而且最近的一个 二次幂的数
比如输入7,会得到8,输入9,会得到16,输入17会32。
底层用的是位运算。因为2次幂数二进制都是1开头后面全是0。
2==>10
4==>100
8==>1000
16==>10000

二进制规范(int)一下如下
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0100
0000 0000 0000 0000 0000 0000 0000 1000
0000 0000 0000 0000 0000 0000 0001 0000

所以只要知道输入的值的二进制,前面加个1,后面清零即可得到最邻近的二次幂数

static final int tableSizeFor(int cap) {
        int n = cap - 1;
        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;
    }

上面是jdk工程师是这样写的,通过右移并对自己进行或运算,“把自己全部变成1”,然后再+1得到结果。
看完先默默膜拜个一秒钟。
自个儿的回顾了一下二进制,大概就是自问自答,自己考自己吧。 然后发现自己对于“ 怎么用位运算快速得到 int 区间的最大值和最小值 ”竟然回答不出来!

事发在昨天晚上,今天早上自己跟自己较劲了一整个上午时间,流水账一下总结:
因为 int 类 占4个字节,每个字节占8个bit位置。所以每个int都需要占用32个bit的空间。
最小当然是全部都是0,最大是全部都是1。
微笑脸,这是错的。
因为一开始的那一位是用来代表正负的(1代表负,0代表正数),
所以实际上可以用来存值只有31个bit的位置。
而每个bit有两个状态:1和0。所以 int 可以代表的范围:
开头第一个bit是0时(正数) 有 2个2个2…一共有31个2。即2的31次方。
开头第一个bit是1时(负数) 有 2个2个2…一共有31个2…
所以一共有2的31次方再乘2 也就是2的32次方。

这样从后往前推的理解跟一开始学计算机时候理解的:“32个bit 每个bit代表1和0 所以int的取值范围是2的32次方”,丰富多了。
顺带还解决了我以前的一个疑惑:
为什么科教数上对于 int 的定义都是
-2147483648~ -1,0 ~2147483647
而不是
-2147483647~ 0, 1~2147483648
因为0 二进制的32个0,它的第一位也是0。

这过程还复习了一些很陌生的运算符:“~” 和 “>>>”
“~”是二进制的取反,比如
0的二进制:0000 0000 0000 0000 0000 0000 0000 0000
~0的二进制:1111 1111 1111 1111 1111 1111 1111 1111
那~0后再全部用 >>1 移一个bit的位置不就是
0111 1111 1111 1111 1111 1111 1111 1111
了吗,这不就是正数时候 int 的最大值
理论没错,但是使用 >>1 时候会根据最开始的位数进行补充。
换句话说,因为~0的第一位是1 所以补充的也是1,嗯,帮倒忙变成了:
1111 1111 1111 1111 1111 1111 1111 1111
//扶额。并没有任何变化! 我希望补充的是0而不是1可以吗
可以!
使用 “>>>1” 就可以了
所以 int 的最大值可以是 ~0>>>1
通过这样得到 int 的极限值 比以前的那种通过 int 溢出得到边缘数好玩多了!
/*
多手尝试了一下<<1和<<<1会不会也是这种套路
结果是输入第三个<后直接就冒红警告了
*/
还有一些跟常识有点冲突的理解,
比如上面的 ~0 二进制是全部都是1。用常规的理解就是,负数情况的极限值
即int 的负边缘数: -2147483648 ?
按这个套路,二进制是
1000 0000 0000 0000 0000 0000 0000 0001
的时候,就是负数时候第一个数,即右边缘数, -1 ?
答案是错的。这就是跟常规的冲突所在了。
我们普通的理解下 前面加个 “-” 后面的数字越大,其实质上是越小,因为她是朝着负的方向头走的,所以走的越远,越小。
但是这里不能这样套路,int 的第一个 bit 不可以直接用 “1是 - ”,“0是+” 进行直接翻译。

想透彻了解有点辛苦,大概就是 二进制10为什么是int的2 这样的问题。理解10变成2的话就自然懂了。
从简单推导也可以辩证出。 比如简单的二进制加减 这个入手。
1+1 二进制
0000 0000 0000 0000 0000 0000 0000 0001
+
0000 0000 0000 0000 0000 0000 0000 0001
逢2进1。变成
0000 0000 0000 0000 0000 0000 0000 0010

自然也不难理解 为什么int的最大值 2147483647 即二进制
0111 1111 1111 1111 1111 1111 1111 1111
为什么加一变成负数。因为加1后变成了
1000 0000 0000 0000 0000 0000 0000 0000
也就是符号被填充了1,成了负数。

回到刚才,0-1大家都知道等于-1。
即全部bit都是零的二进制减1,需要一直问前面的借借数,一直到最左边顶端的0也成为了1。
即全部1
1111 1111 1111 1111 1111 1111 1111 1111
代表的是 0-1= -1
这就是负数情况下的 “最大” 值。

计算机是对的。觉得冲突其实很简单,因为我们口头上说最大负数时候有时候会理解成那个“最左边”的数
比如
-999,-998…-1,0,1…998,999
的-999
然而“最大的负数”其实一直都不是 -999 而是 -1 啊。
所以 int 的最小负数 也就是开头是1 后面全是0
1000 0000 0000 0000 0000 0000 0000 0000
代表的是
-2147483648
一点毛病都没有!

-2147483648 + 1 = -2147483647 (1000 0000 0000 0000 0000 0000 0000 0001)
-2147483647 + 1 = -2147483646 (1000 0000 0000 0000 0000 0000 0000 0010)

一直加上去 直到到了 int 的最大负数 -1 (1111 1111 1111 1111 1111 1111 1111 1111)
-1+1 又变成了全部都是0
嗯,-1 + 1 当然等于0
没毛病!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值