热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javajava.lang.Long详解之三大显神通的位移运算

文章看过后感觉受益匪浅,所以留下了以备温故:http:www.congmo.netblog20120311Long-ByteShifting本篇主要讲述位移运算在Long

文章看过后感觉受益匪浅,所以留下了以备温故:http://www.congmo.net/blog/2012/03/11/Long-ByteShifting/

本篇主要讲述位移运算在Long中所扮演的重要角色,有些出神入化的我根本无法理解,但是我一直秉承着无论对错都要记录思考的过程的宗旨写每一篇文章。这一篇也不例外,读Long这个类确实需要比较广泛的知识面,我也是一边在OSChina和stackoverflow上提问,一边慢慢的钻研,难免会存在偏差。

先来看个简单的。

public static int signum(long i) {
  // HD, Section 2-7
  return (int) ((i >> 63) | (-i >>> 63));
}

这个函数作用就是返回参数i的符号,如果返回-1则是负数,如果返回0则是0,如果返回1则是正数。算法就是(int) ((i >> 63) | (-i >>> 63)),如果是正数的话i有符号右移63位后为0,-i无符号右移63位之后结果为1,或操作之后结果就是1.如果i为负数,那么有符号右移63位后就变成了1,然后-i无符号右移63位后就只剩下符号位,最后做或(|)操作结果就是-1. 如果参数i为0,那么移位后结果就是0.

System.out.println(Long.signum(100L));
System.out.println(Long.signum(0L));
System.out.println(Long.signum(-100L));
 
输出结果:
1
0
-1

接着是一个很少用到,但是实现方式不错的两个方法,循环左移和循环右移方法。

public static long rotateLeft(long i, int distance) {
    return (i <>> -distance);
}
public static long rotateRight(long i, int distance) {
    return (i >>> distance) | (i <<-distance);
}

实现的代码量可以说已经精简到最少了,有一点要注意的是,循环移位时,参数distance可以接受负数,当distance为负数时,这个等式是成立的,rotateLeft(i, distance) = rotateRight(i, -distance)。这个方法中有两点值得借鉴的,第一从整体上讲循环移位的实现方式;第二是distance与-distance的巧妙运用。

就拿循环左移先来说说第二点吧,前置条件,我们首先假设distance大于0,起先我是很不理解i >>> -distance的,后来在stackoverflow上发问,有人给出了解释,在移位的时候,如果distance小于0,会根据被移位数的长度进行转换。就比如说这里我们对long进行移位,那么-distance就会被转换成(64 + distance)(注,这里的distance是小于0的)。这样的话,如果distance大于0时,(i <>> -distance);就会被转化成(i <>> 64 + distance);

清楚了第二点,那么第一点也就不难理解了。用一幅图来解释循环左移。

在distance大于0的前提下,先左移distance位,然后再右移64-distance,最终用或运算相加,就是循环移位的结果。图中为了省事儿用了8位做了个演示,先左移3位,然后右移(8-3)位,或运算之后就是结果啦。关于-distance在stackoverflow上的提问在这里

下面是个更给力的方法-reverse(long i),可以说就是高效率的化身。

public static long reverse(long i) {
    // HD, Figure 7-1
    i = (i & 0x5555555555555555L) <<1 | (i >>> 1) & 0x5555555555555555L;
    i = (i & 0x0f0f0f0f0f0f0f0fL) <<4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;
    i = (i & 0x00ff00ff00ff00ffL) <<8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
    i = (i <<48) | ((i & 0xffff0000L) <<16) |
            ((i >>> 16) & 0xffff0000L) | (i >>> 48);
    return i;
}

从整体上说,这个reverse方法集移位与二分算法于一身,堪称经典。 第一步以单位为单位,奇偶位交换 第二步以两位为单位,完成前后两位的交换。 第三步以四位为单位,完成前后四位的交换。 第四步以八位为单位,完成前后八位的交换。 最后一步没有按常理继续二分,而是通过一个转换一步就完成了以16和32位为单位的交换。进而结束了整个64位的反转。

现在一步一步剖析都是如何实现的。

i = (i & 0x5555555555555555L) <<1 | (i >>> 1) & 0x5555555555555555L;

16进制的5为0101,或操作前半部分首先取出i的所有奇数位,然后整体左移一位,这样实现i的奇数位左移一位变成偶数位;或操作后半部分先右移,即将偶数位右移变成奇数位,然后再取出奇数位。这样就完成了64位中奇数位与偶数位的交换。

i = (i & 0x3333333333333333L) <<2 | (i >>> 2) & 0x3333333333333333L;

这句同样是实现交换,只不过3对应的16进制为0011,即本次交换以2个字节为单位,交换完成了4个字节的反转。 Liquid error: Flag value is invalid: -O ”” 直到这行代码,实现了以字节为单位的反转,最后仅仅使用一行代码就实现了一两个字节和四个字节为单位的反转。 为了方便画图,现在对操作进行编号,另外从以字节为单位交换开始,之前的细节忽略。
(i & 0x00ff00ff00ff00ffL) <<8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
(i <<48)
(i & 0xffff0000L) <<16)
(i >>> 16) & 0xffff0000L)
(i >>> 48);
这幅图描述每个编号代码执行之后64位的变化。

不多做解释,由于这个reverse的最后一行不是按常理”出牌”,所以我使用纯粹的二分法来实现reverse。
public static long reverse(long i) {
  // HD, Figure 7-1
  i = (i & 0x5555555555555555L) <<1 | (i >>> 1) & 0x5555555555555555L;
  i = (i & 0x3333333333333333L) <<2 | (i >>> 2) & 0x3333333333333333L;
  i = (i & 0x0f0f0f0f0f0f0f0fL) <<4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;
  i = (i & 0x00ff00ff00ff00ffL) <<8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
  i = (i & 0x0000ffff0000ffffL) <<16 | (i >>> 16) & 0x0000ffff0000ffffL;
  i = (i & 0x00000000ffffffffL) <<32 | (i >>> 32) & 0x00000000ffffffffL;
  return i;
}

至于为什么要采用那种方式,而不是用”纯粹”的二分法,在stackoverflow上有人提到,可能是因为前一种实现方式需要9个操作,而后一种实现方式需要10个操作。具体是出于怎样的目的可能只有作者才知道。关于reverse我在stackoverflow上的提问在这里。

最后看一个方法。

public static int bitCount(long i) {
    // HD, Figure 5-14
    i = i - ((i >>> 1) & 0x5555555555555555L);
    i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L);
    i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    i = i + (i >>> 32);
    return (int)i & 0x7f;    
}
这个方法是返回一个long类型的数字对应的二进制中1的个数,其实google上有很多种,这里采用的叫平行算法实现的,算法如下图。

但是这个方法的第一行又采取了一个特别的方式实现i中奇数位+偶数位,我有点儿没想明白。总体上就是像图中所示那样,相邻的两位相加的结果再相邻的四位相加,最后得到二进制中1的个数。 还有一点值得提一下,就是最后一行与上7f,因为long类型,1的个数最多也不会超过64个,所以只取最后7位即可。

推荐阅读
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 本文整理了Java中java.lang.NoSuchMethodError.getMessage()方法的一些代码示例,展示了NoSuchMethodErr ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
  • Java源代码安全审计(二):使用Fortify-sca工具进行maven项目安全审计
    本文介绍了使用Fortify-sca工具对maven项目进行安全审计的过程。作者通过对Fortify的研究和实践,记录了解决问题的学习过程。文章详细介绍了maven项目的处理流程,包括clean、build、Analyze和Report。在安装mvn后,作者遇到了一些错误,并通过Google和Stack Overflow等资源找到了解决方法。作者分享了将一段代码添加到pom.xml中的经验,并成功进行了mvn install。 ... [详细]
author-avatar
惠君宛峰6
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有