自从看了cnblog上那篇讲“野生”程序员的文章,我也时常反思作为科班出身的自己,是不是还带着一些“野生”的做派。我们往往80%的时间在做一些纯业务的事情上,而往往百分之90的时间里面我们的业务量不足够大到需要考虑性能的地步。每天重复地活着,有什么意义呢?
所以常常有人说,算法和数据结构,设计模式,都是并没有什么卵用。我一直对这种观点很反感,计算机编程是 一门科学,科学在于高效。如果我们不考虑大流量、高并发,我们可以很任性地写任何功能,大不了加载缓慢。我们忽略那些看似不重要,或者用不上的黑科技,以为一直呆在地球表面上是最安全的。
我在知乎里面写过一个回复:我见过十年的码畜,也见过一年成架构的神人。之所以有的人用一年走完别人十年走不完的技术道路,是因为关注代码和业务背后的细节,不断积累迭代。最近在看《代码之美》这本书,有些感触。我用一个最不被大家重视的算法为例:
二分法查找。
我对这个算法记忆犹新,大一的时候曹海老师在纸上给我推演了其中过程。我用java代码实现如下:
public class TestBinary { /** * 二分法查询 * * @param arr * @param value * @return */ public static int binary(int[] arr, int value) { if (arr.length <= 0) System.out.println("no array to search"); int low = 0;// 低位 int high = arr.length -1; //高位 if (low <= high) { int mid = (low + high) /2; if (arr[mid] == value) return mid; if (arr[mid] > value) { high = mid - 1; } if (arr[mid] > value) { low = mid + 1; } } return -1; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub int[] a = {1, 2, 9, 33, 43, 52}; int value = binary(a, 9); System.out.println(value); } }
这是最常规的写法,看上去也没什么问题。但是如果我们要查找的数组变得越来越大,就会出现性能和整型溢出的问题。先说性能吧,其中int mid = (low+high)/2;这句在循环中,不断地在栈上分配临时变量,这也是一种消耗,可以把它写在循环外。溢出问题也是在这句,如果high变量(也就是数组长度)足够大,超过32位机器的2^31 -1的局限,或者64位机的整型极限,那么就会溢出为负数。有人建议写成:
mid = (low + high) >>> 1;
用位运算来防止整型溢出。对位运算不太熟悉的童鞋可以去看看相关资料。
很多人看到这里就感觉皆大欢喜的happy ending,但是我还不想就这么草率地结束。
这里面就存在一个悖论,java不能造出一个大于最大整型数值长度的数组,那么我该如何检验这个溢出问题呢。而且凭什么说用>>> 1就可以解决。我freephp第一个不服!
如果一直纠结造出数组来验证,那么就走不通了。所以我们换个思路,其实本质就是很大的两个数相加除2或用位运算会不会整型溢出的测试。代码如下:
System.out.println("除以2:"+ ((1000000000+Integer.MAX_VALUE) /2));
System.out.println("除以2:"+ ((1000000000+Integer.MAX_VALUE) >>> 1));
输出结果为:
除以2:-573741824
除以2:1573741823
可见位运算可以保证整型不溢出。
(待续ing)
.-' _..`. / .'_.'.' | .' (.)`. ;' ,_ `. .--.__________.' ; `.;-'| ./ /| | / `..'`-._ _____, ..' / | | | | / /| | | | / / | | | | /_/ |_| |_| \_ |__ |__ |__ |__