十年学会编程

原文

研究表明 (HayesBloom)在任何一种领域内,象下棋、作曲、绘画、钢琴演奏、游泳、网球、以及原子物理学和拓扑学,等等,要达到专家水平大约都要花十年时间。没有真正的捷径:即使是莫扎特,4岁时就是音乐神童,13年后才开始写出世界级的作品。在另一方面,披头士似乎在1964年的EdSullivan表演上一炮走红。但他们从1957年就开始表演,在获得大众青睐后,他们的第一个重大成功,Sgt.Peppers,是1967年发行的。Samuel Johnson (塞缪尔·约翰逊,英国辞典编纂家及作家)认为要花比十年更长的时间:“在任何领域中出类拔萃都要用毕生的劳作来取得;它不可能用较低的代价获得。” 而Chaucer(乔叟,英国诗人)感叹到:“人生短暂,学海无涯。”

脚本小子

还记得初中那会,大家上网都很直接(各种意义上的直接),我家的电脑就经常中毒,QQ就不知道被盗走了多少遍,也就这种环境接触到了各种奇奇怪怪东西,这其中就有关于黑客技术圈子,现在还记得网上找QQ病毒的教材, 然后和制作视频的人加了上了好友(那会大家都觉得自己好牛X,发了视频都会留QQ)。后来自己东抄抄西抄抄,生搬硬套的也开始自己录视频,QQ加了几千个人,想起来真是好玩。

印象中花时间最多的是病毒免杀,简单说就是通过一些手段让病毒不再被杀毒软件检查得到,那时候根本不懂编程,只会学着视频教材对病毒进行各种加工,这个过程有点像养蛊,由于不懂原理,这过程对于我来说就是一个黑盒子。 要说收获也有,就是懂得了重装系统,然后比一般人更会操作电脑,如此而已。

(其实还有,2022年公司被勒索病毒攻击,因为有这方面的积累,成为第一批开始行动的人,虽然事后也没有统计帮上了多少忙,但没关系)

然后上了高中后就开始少有接触,直至上了大学才又是捡回来,开始接触Delphi,VB,C++,汇编… 然后不知道怎么着的,在网易云课堂找到了C语言课程,总算是正儿八经的开始入门了。

转存一篇2019年前写的《流水账一下对二进制理解》

图文无关

昨天研究HashMap(版本8)底层。
发现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,后面清零即可得到最邻近的二次幂数

/**
 * Returns a power of two size for the given target capacity.
 */
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
没毛病!

source https://blog.csdn.net/yongroot/article/details/89431136

CSDN是越来越讨厌了,大概这辈子都不会再用。。