精华内容
下载资源
问答
  • java 二进制以及二进制运算

    千次阅读 2019-01-09 23:02:50
    虽然现在很少用到二进制,可是一些源码中会经常遇到,比如: //HashMap中的hash方法: static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>&...

    虽然现在很少用到二进制,可是一些源码中会经常遇到,比如:

    //HashMap中的hash方法: 
    static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }

    这里记录一些二进制知识。

    一:负数如何显示其二进制。

    比如5的二进制数为:00000000  00000000 00000000 00000101(int 类型占用4个字节,每个字节8位).

    那么-5的二进制是多少了。对于负数,这里需要理解反码和补码
    1、所谓原码就是二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。
    2、反码表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外
    原码10010= 反码11101 (10010,1为符号码,故为负)
    (11101) 二进制= -13 十进制
    3、补码表示法规定:正数的补码与其原码相同;负数的补码是在其反码的末位加1。
    举一例,我们来看整数-5在计算机中如何表示。
    假设这也是一个int类型,那么:
    1、先取1的原码:00000000 00000000 00000000 00000101
    2、得反码: 11111111 11111111 11111111 11111010
    3、得补码: 11111111 11111111 11111111 11111011

    可以在控制台输出测试:

    输出-5的二进制数
    System.out.println(Integer.toBinaryString(-5));// 11111111 11111011

    二:二进制常用的运算。(^异或运算,与运算&,|运算,左移运算<<,右移运算(有符号)>>,无符号又移>>>)

    1.^异或运算。二进制数 相同为0.不同为1;

    /**
    	 *  ^异或运算。二进制数 相同为0.不同为1; 
    	 */
    	@Test
    	public void testOr(){
    		int first=3;//                  0011------------3
    		int second=4;//                 0100------------4
    		int three=first^second;//       0111------------7
    		System.out.println(three);//7
    	}

    2.&运算。只有都为1时才为1.否则为0

    @Test
    	/**
    	 * 二进制数 只有都为1时才为1.否则为0
    	 */
    	public void testAnd(){
    		int first=12;//-----------------00001100   值为 12
    		int second=22;//----------------00010100   值为 22
    		int three=first&second;//-------00000100   值为 4
    		System.out.println(three);//---4
    	}

    3.|运算。(参加运算的两个对象只要有一个为1,其值为1。)

    @Test  //(参加运算的两个对象只要有一个为1,其值为1。)
    	public void testOrAnd(){
    		System.out.println(5|2);//7
    		System.out.println(Integer.toBinaryString(5));// 00000000 00000000 00000000 00000101
    		System.out.println(Integer.toBinaryString(2));// 00000000 00000000 00000000 00000010
    		System.out.println(Integer.toBinaryString(7));// 00000000 00000000 00000000 00000111
    	}

    4.左移运算(<<)

       如果是整数,直接转为二进制数进行左移,低位补0.

      如果是负数。首先也要转为二进制数(通过反码,补码机制)。转移完后,将二进制数加一,取反码。得最终的结果

      下面给的例子是 -5<< 结果为20.

    /**
    	 * 左移运算(<<)   二进制数向左移动,低位用0补齐
    	 * 反码是除符号位(最高位)外取反
    	 */
    	@Test
    	public void testLeftMove(){
    		int first=33;//              00100001 值为33
    		int two=first<<2;//          10000100 值为132
    		System.out.println(two);  //132
    		//如果是负数
    		//-5 的二进制,先是算5 的二进制       00000000 00000000 00000000 00000101  然后进行取反(除最高位外)
    		//-5的反码是                        11111111 11111111 11111111 11111010
    		//   补码    反码+1                 11111111 11111111 11111111 11111011
    		//-----------------------------------计算 -5<<2的值----------------------------
    		//首先-5的补码左移两位,低位补0得:
    		//                              11111111 11111111 11111111 11101100
    		//然后 减一得:
    		//                             11111111 11111111 11111111 11101011
    		//最后取反即可:
    		//                             00000000 00000000 00000000 00010100  值为20
    		  System.out.println(-5<<2);//20
    		System.out.println(Integer.toBinaryString(-5));//11111111 11111111 11111111 11111011
    	}

    5.右移运算(>>).如果是正数,高位补0.如果是负数,高位补1。

        都是先转换为二进制再进行位移运算。如果是负数,也是要借助反码和补码。

    /**
    	 * 右移(>>) 二进制数整体向右移动。如果是正数,高位补0.如果是负数,高位补1
    	 */
    	@Test
    	public void testRightMove(){
    		//-5的二进制       11111111 11111111 11111111 11111011
    		//右移1位              11111111 11111111 11111111 111111101
    		
    		System.out.println(-5>>1);//-3    
    		System.out.println(Integer.toBinaryString(-3));//11111111111111111111111111111101
    		
    		System.out.println(-2147483647>>5);
    		System.out.println(Integer.toBinaryString(-2147483647>>5));
    		System.out.println(Integer.toBinaryString(-2147483647));
    	}

    6.无符号的右移运算(>>>)

      即无论是正数还是负数,高位都补0.

    下面代码可见>>>运算不再采用补码和反码机制了。不管是正数还是负数,最终都是正数。所以直接    将转移后的二进制转换为正数。如果是负数采用>>>的话,也为正数了。

    	/**
    	 * 无论正负高位同一补0。
    	 */
    	@Test
    	public void testRightMoveThree(){
    		System.out.println(-5>>>2);                               //1073741822
    		System.out.println(Integer.toBinaryString(-5));           //11111111 11111111 11111111 11111011
    		System.out.println(Integer.toBinaryString(1073741822));//   00111111 11111111 11111111 11111110
    	}

     

    展开全文
  • Java二进制加减乘除

    千次阅读 2018-02-23 13:00:39
    引子 某天研究 fail-fast机制的时候,去看了看hashCode的实现... + s[n-1]于是很不解,这个公式很明显会溢出(超过2^32),尝试了几次发现系统会输出hashCode为负数的值,就默默地去回顾一下二进制加减乘除准备工...

        引子

        某天研究 fail-fast机制的时候,去看了看hashCode的实现方式,然后发现每个对象的实现都不一样;于是研究一个String的;于是看到公式:

    s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

    于是很不解,这个公式很明显会溢出(超过2^32),尝试了几次发现系统会输出hashCode为负数的值,就默默地去回顾一下二进制的加减乘除


    准备工作:

    -2 = 0xfffffffe
    -1 = 0xffffffff
    0 = 0x0
    1 = 0x1
    2 = 0x2
    max = 0x7fffffff = 2147483647
    min = 0x80000000 = -2147483648
    正数与二进制的相互转换,就是简单的二进制与十进制的相互转换
    负数与二进制的相互转换:
    1)二进制 -> 负数 :0x80000000  取反  0x7fffffff(2147483647)  忽略符号加一 (2147483648)  结果(- 2147483648 )
    2)负数 -> 二进制 : - 2147483648 忽略符号减一(- 2147483647[ 0x7fffffff ])   取反( 0x80000000 ) 结果( 0x80000000 )
    PS:以上就是专业术语"补码"的含义,而补码存在的意义在于:让所有的加法能够使用同一种电路完成


    接下来就是四则运算:加减乘除
    1,加法[全加器]
    案例一:
    3 + 5 = 8
    ==>
      0 0 1 1  -> 3
    + 0 1 0 1  -> 5
    ----------
    = 1 0 0 0  -> 8
    因此可以看出,二进制之间的加法就是,和小学学的十进制的"尾数相加,然后进位

    案例二:
    1 + -1(0x7fffffff) = 0
    ==>
     ?_ 0 0 ... 1  -> 1
    +?_ 1 1 ... 1  -> -1
    --------------
    =1_ 0 0 ... 0  -> 0
    因此可以看出,二进制之间的加法,最高位表示的不是具体的数值,进位之后应该被舍去

    2,减法 [全加器]
    减法可以实现的方案:1)类似十进制的减法直接去位 2)减法就是另类的加法
    而计算机采用的是,方法2,原因:直接可以使用全加器

    例如:a - b = a + (-b)
    而通过之前的准备可以看出:二进制中,b的负数就是b的补码
    因此,0xa - 0xb = 0xa + 0xb(补码[取反,忽略符号加一])

    3,乘法[乘法器,芯片]
    乘法可以实现方案:1) 类似十进制的乘法  2) 多个加法
    而计算机采用的是,方法1,原因:多个加法的算法复杂度成二次方增长

    案例一:
    3 * 5 = 15
    ==>
    0 0 1 1 -> 3
    * 0 1 0 1 -> 5
    -------------
    0 1 0 1 -> 5
    + 0 1 0 1 -> 10
    -------------
    1 1 1 1 -> 15
    PS:
    1)如果有符号,符号位另算,即:同号得正,不同号得负原则
    2)如果有位数移除,则高位直接舍去,然后得出剩下的二进制对应的值

    4,除法[芯片]
    除法可以实现方案:1)类似十进制的除法 2)多个减法,直到负数
    而计算机采用的是,方法1,原因:多个减法的算法复杂的成二次方增长
    14 / 3 = 4 余 2
    14(1110[被除数])、3(11[除数])、4([结果])、2([余数])
    ==>
    1 0 0 --> 结果[4]
    -----------
    1 1 / 1 1 1 0
    ---------------
    1 1
    ---------------
    0 1
    ---------------
    1 0
    ---------------
    1 0 --> 余数[2]
    PS:
    1)如果有符号,符号位另算, 即:同号得正,不同号得负原则

    回顾之前的问题,公式溢出问题:

    s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

    就很容易解释了,

    1,31^(n-1)的结果,高位会被省去

    2,加法结果的结果,高位会被省去,这也是为什么会计算出负数的原因




    展开全文
  • 目录 前言 正文 二进制 二进制与编码 Java中的二进制 便捷的进制转换API 如何证明Long是64位的? Java中的位运算 简单运算 &:按位与 |:按位或 ~:按位非 ^:按位异或 按位左移 >>:按位右移 >>>:无符号右移 复合...

    基础不牢,地动山摇。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。

    在这里插入图片描述

    ✍前言

    你好,我是YourBatman。

    本号正在连载Jackson深度解析系列,虽然目前还只讲到了其流式API层面,但已接触到其多个Feature特征。更为重要的是我在文章里赞其设计精妙,处理优雅,因此就有小伙伴私信给我问这样的话:
    在这里插入图片描述
    题外话:Jackson这个话题本就非常小众,看着阅读量我自己都快没信心写下去。但自己说过的话就是欠下的债,熬夜也得把承诺的付费内容给公开完了,毕竟还有那么几个人在白嫖不是😄。

    话外音:以后闷头做事,少吹牛逼┭┮﹏┭┮

    虽然小众,竟然还有想深入了解一波的小伙伴,确实让我为之振奋了那么三秒。既然如此那就干吧,本文就先行来认识认识Java中的位运算。位运算在Java中很少被使用,那么为何Jackson里爱不释手呢?一切就为两字:性能/高效。用计算机能直接看懂的语言跟它打交道,你说快不快,不用多想嘛。
    在这里插入图片描述

    ✍正文

    提及位运算,对绝大多数Java程序员来说,是一种既熟悉又陌生的感觉。熟悉是因为你在学JavaSE时肯定学过,并且在看一些开源框架(特别是JDK源码)时都能看到它的身影;陌生是因为大概率我们不会去使用它。当然,不能“流行”起来是有原因的:不好理解,不符合人类的思维,阅读性差……

    小贴士:一般来说,程序让人看懂远比被机器看懂来得更重要些

    位运算它在low-level的语言里使用得比较多,但是对于Java这种高级语言它就很少被提及了。虽然我们使用得很少但Java也是支持的,毕竟很多时候使用位运算才是最佳实践

    位运算在日常开发中使用得较少,但是巧妙的使用位运算可以大量减少运行开销,优化算法。一条语句可能对代码没什么影响,但是在高重复,大数据量的情况下将会节省很多开销。

    二进制

    在了解什么是位运算之前,十分有必要先科普下二进制的概念。

    二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是逢二进一,借位规则是借一当二。因为它只使用0、1两个数字符号,非常简单方便,易于用电子方式实现。

    小贴士:半导体开代表1,关代表0,这也就是CPU计算的最底层原理😄

    先看一个例子:

    1011(二进制)+ 11(二进制) 的和?
    结果为:1110(二进制)
    

    二进制理解起来非常非常的简单,比10进制简单多了。你可能还会思考二进制怎么和十进制互转呢?毕竟1110这个也看不到啊。有或者往深了继续思考:如何转为八进制、十六进制、三十二进制…进制转换并非本文所想讲述的内容,请有兴趣者自行度娘。

    二进制与编码

    这个虽然和本文内容关联系并不是很大,但顺带捞一捞,毕竟编码问题在开发中还是比较常见的。

    计算机能识别的只有1和0,也就是二进制,1和0可以表达出全世界的所有文字和语言符号。那如何表达文字和符号呢?这就涉及到字符编码了。字符编码强行将每一个字符对应一个十进制数字(请注意字符和数字的区别,比如0字符对应的十进制数字是48),再将十进制数字转换成计算机理解的二进制,而计算机读到这些1和0之后就会显示出对应的文字或符号。

    • 一般对英文字符而言,一个字节表示一个字符,但是对汉字而言,由于低位的编码已经被使用(早期计算机并不支持中文,因此为了扩展支持,唯一的办法就是采用更多的字节数)只好向高位扩展
    • 字符集编码的范围utf-8>gbk>iso-8859-1(latin1)>ascll。ascll编码是美国标准信息交换码的英文缩写,包含了常用的字符,如阿拉伯数字,英文字母和一些打印符号共255个(一般说成共128个字符问题也不大)

    UTF-8:一套以 8 位为一个编码单位的可变长编码,会将一个码位(Unicode)编码为1到4个字节(英文1字节,大部分汉字3字节)。

    Java中的二进制

    在Java7版本以前,Java是不支持直接书写除十进制以外的其它进制字面量。但这在Java7以及以后版本就允许了:

    • 二进制:前置0b/0B
    • 八进制:前置0
    • 十进制:默认的,无需前置
    • 十六进制:前置0x/0X
    @Test
    public void test1() {
        //二进制
        int i = 0B101;
        System.out.println(i); //5
        System.out.println(Integer.toBinaryString(i));
        //八进制
        i = 0101;
        System.out.println(i); //65
        System.out.println(Integer.toBinaryString(i));
        //十进制
        i = 101;
        System.out.println(i); //101
        System.out.println(Integer.toBinaryString(i));
        //十六进制
        i = 0x101;
        System.out.println(i); //257
        System.out.println(Integer.toBinaryString(i));
    }
    

    结果程序,输出:

    5
    101
    65
    1000001
    101
    1100101
    257
    100000001
    

    说明:System.out.println()会先自动转为10进制后再输出的;toBinaryString()表示转换为二进制进行字符串进行输出。

    便捷的进制转换API

    JDK自1.0开始便提供了非常便捷的进制转换的API,这在我们有需要时非常有用。

    @Test
    public void test2() {
        int i = 192;
        System.out.println("---------------------------------");
        System.out.println("十进制转二进制:" + Integer.toBinaryString(i)); //11000000
        System.out.println("十进制转八进制:" + Integer.toOctalString(i)); //300
        System.out.println("十进制转十六进制:" + Integer.toHexString(i)); //c0
        System.out.println("---------------------------------");
        // 统一利用的为Integer的valueOf()方法,parseInt方法也是ok的
        System.out.println("二进制转十进制:" + Integer.valueOf("11000000", 2).toString()); //192
        System.out.println("八进制转十进制:" + Integer.valueOf("300", 8).toString()); //192
        System.out.println("十六进制转十进制:" + Integer.valueOf("c0", 16).toString()); //192
        System.out.println("---------------------------------");
    }
    

    运行程序,输出:

    ---------------------------------
    十进制转二进制:11000000
    十进制转八进制:300
    十进制转十六进制:c0
    ---------------------------------
    二进制转十进制:192
    八进制转十进制:192
    十六进制转十进制:192
    ---------------------------------
    

    如何证明Long是64位的?

    我相信每个Javaer都知道Java中的Long类型占8个字节(64位),那如何证明呢?

    小贴士:这算是一道经典面试题,至少我提问过多次~

    有个最简单的方法:拿到Long类型的最大值,用2进制表示转换成字符串看看长度就行了,代码如下:

    @Test
    public void test3() {
        long l = 100L;
        //如果不是最大值 前面都是0  输出的时候就不会有那么长了(所以下面使用最大/最小值示例)
        System.out.println(Long.toBinaryString(l)); //1100100
        System.out.println(Long.toBinaryString(l).length()); //7
    
        System.out.println("---------------------------------------");
    
        l = Long.MAX_VALUE; // 2的63次方 - 1
        //正数长度为63为(首位为符号位,0代表正数,省略了所以长度是63)
        //111111111111111111111111111111111111111111111111111111111111111
        System.out.println(Long.toBinaryString(l));
        System.out.println(Long.toBinaryString(l).length()); //63
    
        System.out.println("---------------------------------------");
    
        l = Long.MIN_VALUE; // -2的63次方
        //负数长度为64位(首位为符号位,1代表负数)
        //1000000000000000000000000000000000000000000000000000000000000000
        System.out.println(Long.toBinaryString(l));
        System.out.println(Long.toBinaryString(l).length()); //64
    }
    

    运行程序,输出:

    1100100
    7
    ---------------------------------------
    111111111111111111111111111111111111111111111111111111111111111
    63
    ---------------------------------------
    1000000000000000000000000000000000000000000000000000000000000000
    64
    

    说明:在计算机中,负数以其正值的补码的形式表达。因此,用同样的方法你可以自行证明Integer类型是32位的(占4个字节)。

    Java中的位运算

    在这里插入图片描述

    Java语言支持的位运算符还是非常多的,列出如下:

    • &:按位与
    • |:按位或
    • ~:按位非
    • ^:按位异或
    • <<:左位移运算符
    • >>:右位移运算符
    • >>>:无符号右移运算符

    以 外,其余均为二元运算符,操作的数据只能是整型(长短均可)或者char字符型。针对这些运算类型,下面分别给出示例,一目了然。

    既然是运算,依旧可以分为简单运算和复合运算两大类进行归类和讲解。

    小贴士:为了便于理解,字面量例子我就都使用二进制表示了,使用十进制(任何进制)不影响运算结果

    简单运算

    简单运算,顾名思义,一次只用一个运算符。

    &:按位与

    操作规则:同为1则1,否则为0。仅当两个操作数都为1时,输出结果才为1,否则为0。

    说明:1、本示例(下同)中所有的字面值使用的都是十进制表示的,理解的时候请用二进制思维去理解;2、关于负数之间的位运算本文章统一不做讲述

    @Test
    public void test() {
        int i = 0B100; // 十进制为4
        int j = 0B101; // 十进制为5
    
        // 二进制结果:100
        // 十进制结果:4
        System.out.println("二进制结果:" + Integer.toBinaryString(i & j));
        System.out.println("十进制结果:" + (i & j));
    }
    

    |:按位或

    操作规则:同为0则0,否则为1。仅当两个操作数都为0时,输出的结果才为0。

    @Test
    public void test() {
        int i = 0B100; // 十进制为4
        int j = 0B101; // 十进制为5
    
        // 二进制结果:101
        // 十进制结果:5
        System.out.println("二进制结果:" + Integer.toBinaryString(i | j));
        System.out.println("十进制结果:" + (i | j));
    }
    

    ~:按位非

    操作规则:0为1,1为0。全部的0置为1,1置为0。

    小贴士:请务必注意是全部的,别忽略了正数前面的那些0哦~

    @Test
    public void test() {
        int i = 0B100; // 十进制为4
    
        // 二进制结果:11111111111111111111111111111011
        // 十进制结果:-5
        System.out.println("二进制结果:" + Integer.toBinaryString(~i));
        System.out.println("十进制结果:" + (~i));
    }
    

    ^:按位异或

    操作规则:相同为0,不同为1。操作数不同时(1遇上0,0遇上1)对应的输出结果才为1,否则为0。

    @Test
    public void test() {
        int i = 0B100; // 十进制为4
        int j = 0B101; // 十进制为5
    
        // 二进制结果:1
        // 十进制结果:1
        System.out.println("二进制结果:" + Integer.toBinaryString(i ^ j));
        System.out.println("十进制结果:" + (i ^ j));
    }
    

    <<:按位左移

    操作规则:把一个数的全部位数都向左移动若干位。

    @Test
    public void test() {
        int i = 0B100; // 十进制为4
    
        // 二进制结果:100000
        // 十进制结果:32 = 4 * (2的3次方)
        System.out.println("二进制结果:" + Integer.toBinaryString(i << 2));
        System.out.println("十进制结果:" + (i << 3));
    }
    

    左移用得非常多,理解起来并不费劲。x左移N位,效果同十进制里直接乘以2的N次方就行了,但是需要注意值溢出的情况,使用时稍加注意。

    >>:按位右移

    操作规则:把一个数的全部位数都向右移动若干位。

    @Test
    public void test() {
        int i = 0B100; // 十进制为4
    
        // 二进制结果:10
        // 十进制结果:2
        System.out.println("二进制结果:" + Integer.toBinaryString(i >> 1));
        System.out.println("十进制结果:" + (i >> 1));
    }
    

    负数右移:

    @Test
    public void test() {
        int i = -0B100; // 十进制为-4
    
        // 二进制结果:11111111111111111111111111111110
        // 十进制结果:-2
        System.out.println("二进制结果:" + Integer.toBinaryString(i >> 1));
        System.out.println("十进制结果:" + (i >> 1));
    }
    

    右移用得也比较多,也比较理解:操作其实就是把二进制数右边的N位直接砍掉,然后正数右移高位补0,负数右移高位补1

    >>>:无符号右移

    注意:没有无符号左移,并没有<<<这个符号的

    它和>>有符号右移的区别是:无论是正数还是负数,高位通通补0。所以说对于正数而言,没有区别;那么看看对于负数的表现:

    @Test
    public void test() {
        int i = -0B100; // 十进制为-4
    
        // 二进制结果:11111111111111111111111111111110(>>的结果)
    	// 二进制结果:1111111111111111111111111111110(>>>的结果)
        // 十进制结果:2147483646
        System.out.println("二进制结果:" + Integer.toBinaryString(i >>> 1));
        System.out.println("十进制结果:" + (i >>> 1));
    }
    

    我特意把>>的结果放上面了,方便你对比。因为高位补的是0,所以就没有显示啦,但是你心里应该清楚是怎么回事。

    复合运算

    广义上的复合运算指的是多个运算嵌套起来,通常这些运算都是同种类型的。这里指的复合运算指的就是和=号一起来使用,类似于+= -=。本来这属于基础常识不用做单独解释,但谁让A哥管生管养,管杀管埋呢😄。

    混合运算:指同一个算式里包含了bai多种运算符,如加减乘除乘方开du方等。

    以&与运算为例,其它类同:

    @Test
    public void test() {
        int i = 0B110; // 十进制为6
        i &= 0B11; // 效果同:i = i & 3
    
    	// 二进制结果:10
    	// 十进制结果:2
        System.out.println("二进制结果:" + Integer.toBinaryString(i));
        System.out.println("十进制结果:" + (i));
    }
    

    复习一下&的运算规则是:同为1则1,否则为0

    位运算使用场景示例

    位运算除了高效的特点,还有一个特点在应用场景下不容忽视:计算的可逆性。通过这个特点我们可以用来达到隐蔽数据的效果,并且还保证了效率。

    在JDK的原码中。有很多初始值都是通过位运算计算的。最典型的如HashMap:

    HashMap:
    	
    	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    	static final int MAXIMUM_CAPACITY = 1 << 30;
    

    位运算有很多优良特性,能够在线性增长的数据中起到作用。且对于一些运算,位运算是最直接、最简便的方法。下面我安排一些具体示例(一般都是面试题),感受一把。

    判断两个数字符号是否相同

    同为正数or同为负数都表示相同,否则为不同。像这种小小case用十进制加上>/<比较符当然可以做,但用位运算符处理来得更加直接(效率最高):

    @Test
    public void test4() {
        int i = 100;
        int j = -2;
    
        System.out.println(((i >> 31) ^ (j >> 31)) == 0);
    
        j = 10;
        System.out.println(((i >> 31) ^ (j >> 31)) == 0);
    }
    

    运行程序,输出:

    false
    true
    

    int类型共32bit,右移31位那么就只剩下1个符号位了(因为是带符号右移动,所以正数剩0负数剩1),再对两个符号位做^异或操作结果为0就表明二者一致。

    复习一下^异或操作规则:相同为0,不同为1

    判断一个数的奇偶性

    在十进制数中可以通过和2取余来做,对于位运算有一个更为高效的方式:

    @Test
    public void test5() {
        System.out.println(isEvenNum(1)); //false
        System.out.println(isEvenNum(2)); //true
        System.out.println(isEvenNum(3)); //false
        System.out.println(isEvenNum(4)); //true
        System.out.println(isEvenNum(5)); //false
    }
    
    /**
     * 是否为偶数
     */
    private static boolean isEvenNum(int n) {
        return (n & 1) == 0;
    }
    

    为何&1能判断基偶性?因为在二进制下偶数的末位肯定是0,奇数的最低位肯定是1
    而二进制的1它的前31位均为0,所以在和其它数字的前31位与运算后肯定所有位数都是0(无论是1&0还是0&0结果都是0),那么唯一区别就是看最低位和1进行与运算的结果喽:结果为1表示奇数,反则结果为0就表示偶数。

    交换两个数的值(不借助第三方变量)

    这是一个很古老的面试题了,交换A和B的值。本题如果没有括号里那几个字,是一道大家都会的题目,可以这么来解:

    @Test
    public void test6() {
        int a = 3, b = 5;
        System.out.println(a + "-------" + b);
        a = a + b;
        b = a - b;
        a = a - b;
        System.out.println(a + "-------" + b);
    }
    

    运行程序,输出(成功交换):

    3-------5
    5-------3
    

    使用这种方式最大的好处是:容易理解。最大的坏处是:a+b,可能会超出int型的最大范围,造成精度丢失导致错误,造成非常隐蔽的bug。所以若你这样运用在生产环境的话,是有比较大的安全隐患的。

    小贴士:如果你们评估数字绝无可能超过最大值,这种做法尚可。当然如果你是字符串类型,请当我没说

    因为这种方式既引入了第三方变量,又存在重大安全隐患。所以本文介绍一种安全的替代方式,借助位运算的可逆性来完成操作:

    @Test
    public void test7() {
        // 这里使用最大值演示,以证明这样方式是不会溢出的
        int a = Integer.MAX_VALUE, b = Integer.MAX_VALUE - 10;
        System.out.println(a + "-------" + b);
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        System.out.println(a + "-------" + b);
    }
    

    运行程序,输出(成功完成交换):

    2147483647-------2147483637
    2147483637-------2147483647
    

    由于全文都没有对a/b做加法运算,因此不能出现溢出现象,所以是安全的。这种做法的核心原理依据是:位运算的可逆性,使用异或来达成目的。

    位运算用在数据库字段上(重要)

    这个使用case是极具实际应用意义的,因为在生产上我以用过多次,感觉不是一般的好。

    业务系统中数据库设计的尴尬现象:通常我们的数据表中可能会包含各种状态属性, 例如 blog表中,我们需要有字段表示其是否公开,是否有设置密码,是否被管理员封锁,是否被置顶等等。 也会遇到在后期运维中,策划要求增加新的功能而造成你需要增加新的字段,这样会造成后期的维护困难,字段过多,索引增大的情况, 这时使用位运算就可以巧妙的解决。

    举个例子:我们在网站上进行认证授权的时候,一般支持多种授权方式,比如:

    • 个人认证 0001 -> 1
    • 邮箱认证 0010 -> 2
    • 微信认证 0100 -> 4
    • 超管认证 1000 -> 8

    这样我们就可以使用1111这四位来表达各自位置的认证与否。要查询通过微信认证的条件语句如下:

    select * from xxx where status = status & 4;
    

    要查询既通过了个人认证,又通过了微信认证的:

    select * from xxx where status = status & 5;
    

    当然你也可能有排序需求,形如这样:

    select * from xxx order by status & 1 desc
    

    这种case和每个人都熟悉的Linux权限控制一样,它就是使用位运算来控制的:权限分为 r 读, w 写, x 执行,其中它们的权值分别为4,2,1,你可以随意组合授权。比如 chomd 7,即7=4+2+1表明这个用户具有rwx权限,

    注意事项

    1. 需要你的DB存储支持位运算,比如MySql是支持的
    2. 请确保你的字段类型不是char字符类型,而应该是数字类型
    3. 这种方式它会导致索引失效,但是一般情况下状态值是不需要索引的
    4. 具体业务具体分析,别一味地为了show而用,若用错了容易遭对有喷的

    流水号生成器(订单号生成器)

    生成订单流水号,当然这其实这并不是一个很难的功能,最直接的方式就是日期+主机Id+随机字符串来拼接一个流水号,甚至看到非常多的地方直接使用UUID,当然这是非常不推荐的。

    UUID是字符串,太长,无序,不能承载有效的信息从而不能给定位问题提供有效帮助,因此一般属于备选方案

    今天学了位运算,有个我认为比较优雅方式来实现。什么叫优雅:可以参考淘宝、京东的订单号,看似有规律,实则没规律

    • 不想把相关信息直接暴露出去。
    • 通过流水号可以快速得到相关业务信息,快速定位问题(这点非常重要,这是UUID不建议使用的最重要原因)。
    • 使用AtomicInteger可提高并发量,降低了冲突(这是不使用UUID另一重要原因,因为数字的效率比字符串高)

    在这里插入图片描述

    实现原理简介

    此流水号构成:日期+Long类型的值 组成的一个一长串数字,形如2020010419492195304210432。很显然前面是日期数据,后面的一长串就蕴含了不少的含义:当前秒数、商家ID(也可以是你其余的业务数据)、机器ID、一串随机码等等。

    各部分介绍:

    1. 第一部分为当前时间的毫秒值。最大999,所以占10位
    2. 第二部分为:serviceType表示业务类型。比如订单号、操作流水号、消费流水号等等。最大值定为30,足够用了吧。占5位
    3. 第三部分为:shortParam,表示用户自定义的短参数。可以放置比如订单类型、操作类型等等类别参数。最大值定为30,肯定也是足够用了的。占5位
    4. 第四部分为:longParam,同上。用户一般可放置id参数,如用户id、商家id等等,最大支持9.9999亿。绝大多数足够用了,占30位
    5. 第五部分:剩余的位数交给随机数,随机生成一个数,占满剩余位数。一般至少有15位剩余(此部分位数是浮动的),所以能支持2的15次方的并发,也是足够用了的
    6. 最后,在上面的long值前面加上日期时间(年月日时分秒)

    这是A哥编写的一个基于位运算实现的流水号生成工具,已用于生产环境。考虑到源码较长(一个文件,共200行左右,无任何其它依赖)就不贴了,若有需要,请到公众号后台回复流水号生成器免费获取

    ✍总结

    位运算在工程的角度里缺点还是蛮多的,在实际工作中,如果只是为了数字的计算,是不建议使用位运算符的,只有一些比较特殊的场景,使用位运算去做会给你柳暗花明的感觉,如:

    • N多状态的控制,需要兼具扩展性。比如数据库是否状态的字段设计
    • 对效率有极致要求。比如JDK
    • 场景非常适合。比如Jackson的Feature特针值

    切忌为了炫(zhuang)技(bi)而使用,炫技一时爽,掉坑火葬场;小伙还年轻,还望你谨慎。代码在大多情况下,人能容易读懂比机器能读懂来得更重要

    ✔推荐阅读:

    ♥关注A哥♥

    AuthorA哥(YourBatman)
    个人站点www.yourbatman.cn
    E-mailyourbatman@qq.com
    微 信fsx641385712
    活跃平台
    公众号BAT的乌托邦(ID:BAT-utopia)
    知识星球BAT的乌托邦
    每日文章推荐每日文章推荐

    BAT的乌托邦

    展开全文
  • 计算机中使用二进制表示一个数字,使用科学计数的形式表示浮点数,在Java中使用IEEE 754标准做为二进制浮点数算术标准。IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度...

    前言

    计算机中使用二进制表示一个数字,使用科学计数的形式表示浮点数,在Java中使用IEEE 754标准做为二进制浮点数算术标准。IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。有符号数的三种表示方式分别为原码、反码、补码。符号位都是用0表示正1表示负,数值位的表示方法各不相同。在计算中数值统一使用补码表示和存储,使用补码的原因在于可以将符号位与数值位统一处理;同时也可以将加法与减法统一处理。

    在讲二进制补码前先介绍下“”的概念:“模”是指一个计量系统的计数范围,可以将模比喻成一个水桶,水桶有容量大小,这个容量大小就“模”,超过水桶容量的水会溢出。计算机也可以看成一个计量系统,对于数值也会有位数限制,例如uint32的类型值二进制位数是32,计量范围是 0 ∼ 2 32 − 1 0\sim2^{32}-1 02321,那模就是 2 32 2^{32} 232,超过模的大小就会出现溢出,溢出的位会被自然舍弃。

    下面用一个8位的二进制数来看看计算结果,模为 2 8 2^{8} 28

    11111111 + 00000001 = 100000000 \begin{array}{cr} &11111111\\ +&00000001\\ \hline =&100000000 \end{array} +=1111111100000001100000000

    二进制 11111111 11111111 11111111置换为十进制数为255,上面的列式用十进制表示为255+1=256,256超过了模 2 8 2^8 28,最后的计算结果为是0,而不是256,因为结果中的高位(第9位)被自然舍弃掉了。

    任何有模的计量器均可化减法为加法运算,转换的方法为取反后加1。

    假设有一个圆盘,将圆盘等分为12个扇区,这样就是一个模计算器,模=12,将每个扇区顺序标号0~11;10号扇区中有一个小球,需要将小球顺序的移动到8号扇区;方法有两种,第一种是顺时针跳10个扇区到8号扇区即10+10=12+8=8,第二种是逆时针跳两个扇区到8号扇区即10-2=8,即10+10=10-2=10+12-2(mod 12);在12为模的系统中加10与减2的效果是一样的,因此只要是减2的运算都可以用加10代替,反过来也是一样加10的运算可以用减2代替,在这样的系统中2和10就互补了。那在以12为模的系统中还有其它这样的补数组合吗?有的,例如:11和1,6和6等,只要两个整数加起来等于模就可以互补。

    将上面这个模系统应用到计算机中同样也是可行的,在上面8位二进制的计算系统里模是 2 8 2^8 28,同样减法可以转换成加法,加法可以转换成减法,减法转换成加法时只要把减数转换成相对应的补数就行,把这个补数用在计算机中表示就是补码。

    补码

    求二进制的补码比较简单,只需要将每一个取反就可以得到补码,例如:

    11110011 ⇒ 00001100 01010101 ⇒ 10101010 11110011 \Rightarrow 00001100 \\ 01010101 \Rightarrow 10101010 11110011000011000101010110101010

    但数值为有正负的问题,在计算中会使用1个高位来表示符号,0代表正数,1代表负数。在计算来符号的二进制数补码时符号位不参与进来。

    正数的补码

    与原码的二进制是一样的,例如+7:

    00000111

    在8位系统中二进制的表示。

    负数的补码

    负数的补码是将原码中除符号位的其它所有位取反(0变1 ,1变0)后加1 。

    例如:-7

    原码:10000111 ->除符号位取反(11111000)->加1(00000001)->补码(11111001)

    所有-7的补码是11111001.

    0的补码

    数值0的补码表示是唯一的。

    [+0]补码=[+0]反码=[+0]原码=00000000

    [-0]补码=[11111111]反码+1=00000000

    补码的意义:

    1. 解决了符号的表示问题
    2. 可以将减法运算转换为加法运算
    3. 在计算机中用电子器件实现补码非常容易
    4. 补码表示统一了符号位和数值位,使得符号位与数值位可以一起参与运算。

    二进制加减法运算

    加法

    二进制加法运算方法,可以使用进位法 ;先将两个十进制数置换成二进制数,再将二进制数右对齐。

    进 位 : 100000010 A : 10001001 B : + 10000101 = 100001110 \begin{array}{cr} 进位:&&100000010\\ A:&&10001001\\ B:&+&10000101\\ \hline &=&100001110 \end{array} :A:B:+=1000000101000100110000101100001110

    上下对应位置相加,值为0对应结果位为0,值为1对应结果位为1,值为2对应结果位为0并进行进位。

    约定右边为第一位,
    第一位:A与B都是1,相加值为2进行一次进位,并在结果位上写上0
    第二位:A与B都是0,但进位上是1,相加值为1,不需要进位,结果为1
    第三位:A为0B为1,结果和第二位相同
    第四位:与第三位相同
    第五位:A与B都为0,进位上也是0,相加值为0,结果为0
    ……

    最后第八位上和第一位的计算方法一样,进位可以直接写在结果中的第九位。最后结果为100001110。由于出现了溢出(假设使用的是模为 2 8 2^8 28的计算系统),最终结果将舍弃第九位(00001110)。

    减法

    二进制加法运算可以使用进位法,相对应的减法也有一个与进位法相对应的运算方法借位法,借位法的运算方法与进位法相反,还是用上面的例子:

    借 位 00001100 A : 10110001 B : − 10100101 = 00001100 \begin{array}{cr} 借位&&00001100\\ A:&&10110001\\ B:&-&10100101\\ \hline &=&00001100 \end{array} A:B:=00001100101100011010010100001100

    原理:

    1. 相同位都是1时,值位为0
    2. 相同位都是0时,值位为0
    3. 相同位A为1,B为0时,值位为1
    4. 相同位A为0,B为1时,向前借位,借位的变化在A上体现,值位为1,被借位A上的值由1变为0
    5. 借位时如果前两位都是0,第三位是1时,借位将在第三位进行,第三位由1变为0,前两位都变为1后再使用1~4的步骤计算。

    二进制减法运算除了使用借位法,还可以将减法转成加法,上面做的模、补码这些知识的铺垫也是为了本篇的主角补全法,补全法的核心思想就是使用补码进行运算。

    首先将被减数转换成补码 10100101 → 01011011 10100101 \rightarrow 01011011 1010010101011011 ,再使用补码做加法运算结果为:

    A : 10110001 B : + 10100101 = 101010110 \begin{array}{cr} A:&&10110001\\ B:&+&10100101\\ \hline &=&101010110 \end{array} A:B:+=1011000110100101101010110

    模为 2 8 2^8 28的计算系统下需要舍弃一个高位,得到的最后结果为01010110 。

    补全法还可以用在十进制的计算模中,假设模=100,计算 56 − 17 = ? 56-17=? 5617=? ,可以先找到17在模=100中的补数82,再使用$56+82=139$139超过了100,将百位自然舍弃,最后结果为39。

    往期推荐:
    知识点: Java ReentrantReadWriteLock 读写锁看共享锁与排他锁 理论 原理
    知识点: Java公平锁与非公平锁 原理讲解ReentrantLock 锁的饥饿效应及解决办法
    知识点: JAVA 悲观锁与乐观锁原理分析 ABA与自旋效率问题分析及解决
    Java ThreadLocal 有泄漏内存的风险怎么搞?分析下原理吧

    展开全文
  • 剩余7位表示数值,根据二进制的计算方法,byte8位一共可以表示-128~127。 为什么正数只能表示到127,而负数可以表示到-128呢? 这涉及到了0的问题 1000 0000 按照之前的规定来看,首位1代表符号位,说明是负数,后7...
  • 计算机进制转换详解以及Java二进制运算方法

    千次阅读 热门讨论 2020-06-01 22:42:59
    本文介绍了计算机进制的基本概念,随后给出了常见进制转换的方法,然后介绍了整数的二进制的计算规则,最后说明了一些二进制计算需要注意的地方(坑)。
  • 目录 JDK自带的进制转换 ...java中可以直接声明二进制、八进制、十进制、十六进制 例如: 二级制: int bin = 0b1100010; 八进制: int oct = 0142; 十进制: int dec = 98; 十六进制: int hex = 0x
  • 最近对电脑中,数据的运算过程感兴趣了,前面写了两篇相关的博文,一篇是整数的存储原理https://editor.csdn.net/md/?articleId=103569332 一篇是负数的位移运算https://editor.csdn.net/md/?articleId=103638244 有...
  • 二进制的位运算

    2019-02-17 11:50:13
    Java中的二进制运算分为: 按位取反 按位与 按位或 按位异或 左移 右移 一:按位取反 ~ 顾名思义,就是将二进制位的每一位取反 例: 000101100 ~000101100 =111010011 但是在上机运行的时候,System.out....
  • JAVA二进制基础

    2017-11-09 15:10:52
    二进制与进制转换 二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”...当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码
  • Java中所有的数据都是以二进制数据的形式进行计算的,即如果有一个int型变量,要采用位运算时必须将其变为二进制数据。   二、位运算符  三、示例代码 package com.wbf.binary; import org.junit.Test; ...
  • java 二进制的问题

    2016-07-22 16:30:09
    现在在使用的计算机基本上都是二进制进行运算的。那么二进制有什么好处了?  (1)技术实现简单,计算机是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用“1”和“0”表示。
  • java 二进制与运算符

    2021-04-10 10:07:16
    二进制 十进制 1 2 4 8 16 32 二进制 1 10 100 1000 10000 100000 图1 图1是做进制运算时候,方便计算的一...
  • 1.将一个二进制数向左位移n位就等于该数乘以2的n次方,当乘法运算中的某个数符合这个特点的时候,可以用位移运算代替乘法运算,从而提高效率。 package pp.suanfa; /** * 乘法运算转位移运算 * * @author ...
  • 按位逻辑运算,属性设置按位逻辑运算在高...有一点要注意就是,所有按位运算都是二进制位的按位运算,如果数据采用十进制或者十六进制表示,实际上也是采用二进制的按位运算。按位与: & 0&0=0; 1&0=0; 0&1=0; 1&1=1
  • Java二进制补码示例

    2019-05-09 21:45:44
    二进制表示负数的问题 我们知道十进制数1的二进制码用int型表示就是0000 0000 0000 0000 0000 0000 0000 0001 二进制码实际上并不能表示负数, 因为二进制数最小就是0000 0000 0000 0000 0000 0000 0000 0000 , ...
  • 我们平时认识的数字比如1、2、3、4等数字叫做十进制数字,我们可以看懂,但是计算机无法运算,如果计算机要计算这些数字就得将这些数字转换成计算机能读懂的数据,计算只能读懂二进制数字,二进制的数字有什么特征呢...
  • java中的二进制以及基本位运算

    万次阅读 多人点赞 2018-06-30 17:50:57
    二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师...那么Java中的二进制又是怎么样的呢...
  • C++ 二进制运算

    2013-06-21 11:46:18
    关于二进制的各种运算, 挺不错的 很全了
  • 问题: 最近在做Java web项目中需要计算金额总和,在这里出现了一个问题是我以前没有关注到的: System.out.println(2.0-1.1);...对此我找了一些解答,主要是二进制机器在处理十分之一(0.1)上出现的问题。 ...
  • 二进制加,减法,23个位运算技巧

    千次阅读 2019-04-06 20:36:22
    二进制加,减法 二进制最高位为1时表示负数,为0时表示正数。 **原码:**一个正数,转换为二进制位就是这个正数的原码。负数的绝对值转换成二进制位然后在高位补1就是这个负数的原码。 举例说明:  int类型的 3 的...
  • 二进制常用运算 ... 计算机的逻辑运算的算术运算的主要区别是:逻辑运算是按位进行的,位与位之间不像加减运算那样有进位或借位的联系。 逻辑运算主要包括三种基本运算:逻辑加法(又称“或”运算)、逻辑...
  • 原题地址: 传送门 ... Binary Tree Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 499 Accepted Sub
  • java加减二进制实现

    2017-07-19 18:59:00
    Java中整数基本类型有byte,short,int,long,大小分别为1、2、4、8个字节,一个字节大小为8位,也就是8个二进制码(0/1)组成。  计算机中二进制码分为原码,反码,补码。在计算机中正数以原码存储,负数以补码...
  • 二进制的加减法_二进制加减法

    万次阅读 2020-07-25 16:25:50
    二进制减法 1)二进制加法 (1) Binary Addition) Since binary numbers consist of only two digits 0 and 1, so their addition is different from decimal addition. Addition of binary numbers can be done ...
  • java二进制,字节数组,字符,十六进制,BCD编码转换 java中byte用二进制表示占用8位,而我们知道16进制的每个字符需要用4位二进制位来表示(23 + 22 + 21 + 20 = 15),所以我们就可以把每个byte转换成两...
  • 此时的二进制数为补码,因为负数的补码是反码1 先一变为反码 1111 0101 反码再取反变为原码 1000 1010 (-10) 所以9的按位取反结果为-10 负数 负数的反码是符号位不变,其余位取反。 负数的补码是反码1。 ...
  • java二进制操作计算的总结

    千次阅读 2019-03-17 07:52:26
    一直以来对二进制的操作计算不太清楚,这次找了一些资料完整地进行了一些总结。 一、进制类别和关系: 二进制,十进制,十六进制的区别和进制之间的相互转换方法(概念性的东西,可以百度)。 n位的二进制能够表示...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,108
精华内容 10,443
关键字:

java二进制加减运算

java 订阅