java中的位操作

         java代码最终要转换成机器码执行,机器码都是01的表示(二进制),所以理论上来说二进制的操作是最快的。如果你经常看源码的话,肯定经常能看到java源码中有很多位移操作。

        但是,现代jvm确实做了太多的优化,使得这种从计算机一开始就已经有的理论显得没那么重要了。。    看测试代码:

private static void test(){
		int max = 1000000;
		int j = 0;
		long start = System.currentTimeMillis();
		for(int i = 0; i < max; i++){
			j = i * 2;
		}
		long end1 = System.currentTimeMillis(); 
		for(int i = 0; i < max; i++){
			j = i >> 1;
		}
		long end2 = System.currentTimeMillis(); 
		for(int i = 0; i < max; i++){
			j = i + i;
		}
		long end3 = System.currentTimeMillis(); 
		
		for(int i = 0; i < max; i++){
			j = i * 16;
		}
		long end4 = System.currentTimeMillis(); 
		for(int i = 0; i < max; i++){
			j = i << 4;
		}
		long end5 = System.currentTimeMillis(); 
		for(int i = 0; i < max; i++){
			j = i + i + i + i + i + i + i + i + i + i + i + i + i + i + i + i;
		}
		long end6 = System.currentTimeMillis(); 
		System.out.println("第一个测试,两倍:---------------------");
		System.out.println("乘法消耗时间为:\t"+(end1 - start));
		System.out.println("位移消耗时间为:\t"+(end2 - end1));
		System.out.println("加法消耗时间为:\t"+(end3 - end2));
		
		System.out.println("第一个测试,十六倍:------------------");
		System.out.println("乘法消耗时间为:\t"+(end4 - end3));
		System.out.println("位移消耗时间为:\t"+(end5 - end4));
		System.out.println("加法消耗时间为:\t"+(end6 - end5));
	}

     分别模拟1000000万次乘法,加法和位移操作,然后看他们执行的时间。执行结果:

第一个测试,两倍:---------------------
乘法消耗时间为:	12
位移消耗时间为:	2
加法消耗时间为:	3
第一个测试,十六倍:------------------
乘法消耗时间为:	3
位移消耗时间为:	3
加法消耗时间为:	5

      可以看到位移确实是最快的,但是*16明显比*2快很多,而且100万次的位移操作,才比相应的加法和乘法快上几个毫秒,几乎可以忽略不计了。。。

     综上,可以认为:   位移操作在现代jvm面前,是几乎没有时间优势的。

       不过位移操作还是很方便的。

       在java中,位操作符主要包含下面四个:

       按位与&, 按位或|, 按位异或^, 按位取反~, 它们的运算规则为:

        &          两位全1得1,其他得0

        |            两位全0得0,其他得1

        ^           两位一位0,一位1得1,其他得0

        ~           一位运算符,取反。

       位的移位操作包括三个操作符:

       算数右移>>    低位溢出,符号位不变(最高位,负1正0),符号位补溢出的高位。

               比如,-2在java中保存为补码,111。。10,如果向右移1位,那么除符号位外其他31位向右移1位,变成1_111。。。11然后用符号位,即1补上因为移位溢出的高位,即(_)处,变成111。。。111,即-1。

      算数左移<<,符号位不变,其他位向左移,低位不变。

      逻辑右移>>>,低位溢出,高位补0。跟算数右移不同的是他高位补0,而不是补符号位。

       

       一个经典的利用位操作和位移操作的算法就是计算int型数i的二进制表示中,1的个数。下面两个方法来计算:

       

/**  
	* getOneSize: 获取二进制i中1的个数----初始化一个数x=1,然后令i & x,如果结果为ux,说明i第最后一位是1,然后x向左移一位,再次执行该操作。
	* @param i
	* @return 
	* int  返回类型   
	*/
	private static int getOneSize(int i){
		int x = 0X1;
		int size = 0;
		for(int j = 0; j < 32; j++){
			if((x & i) == x){
				size++;
			}
			x = x << 1;
		}
		return size;
	}
/**  
	* getOneSize2: 获取二进制i中1的个数:i和1进行&操作,结果为1说明i的最后一位是1,然后i做逻辑右移一位的操作,直到i变成0为止。
	* @param i
	* @return 
	* int  返回类型   
	*/
	private static int getOneSize2(int i){
		int x = 0X1;
		int size = 0;
		while(i != 0){
			size += i & x;
			i = i >>> 1;
		}
		return size;
	}

 测试代码:

System.out.println(getOneSize(123123));
		System.out.println(getOneSize2(123123));

 结果:

10
10

        第二种方法比第一种方法要快,因为第一种方法是需要比对32位的,第二种只需要比对数字i的最高位为1的位数。

        然后,今天在测试的时候发现一个比较好玩的现象:

System.out.println(0xFFFFFFFF >>> 31 >>> 1);
		System.out.println(0xFFFFFFFF >>> 32);

 有兴趣的童鞋可以回去自己执行一下试试。这两行执行结果是不一样的,这是因为jvm对位移操作的优化所至。

猜你喜欢

转载自709002341.iteye.com/blog/2275680
今日推荐