Java位运算基础与基本应用

一、位运算基础

最近在项目中有个串口通信的实现和IC卡块通信的需要,和单片机通信过程中处理常见的串口中起始位、数据位、验位和停止位之外,还有数据在不同进制的转换和位运算,我是个半路出家的码农,所以开始搞得是一头雾水。现在学习温故一下位运算基础知识。目前数值在电脑存依然是以0和1的不同组合的二进制形式。先熟悉一下位运算基础知识:机器数、真值、原码、补码、反码

Java中可以进行位运算的类型有lng,int,short,byte,char;但在实际运算中:byte、short、char先转换为长度为32位的int类型,然后进行位运算的;long长度为64可以直接进行位运算,所以int和long是可以直接进行位运算

(一)、机器数和真值

在计算机中参与运算的数有两类:无符号数字和有符号数字;计算机在运算中的数字都是放在寄存器中,通常称寄存器中的位数为机器字长;无符号数即没有到符号是数字【符号包括0、1,+、-】,在寄存器中每一位均可存放数值,以机器字长16位为例,在寄存器中无符号位数的取值范围0~65535;而有符号数即由符号【在寄存器中占一个数字位】和数值两部分组成,符号数的取值范围即为:+32767~ -3276;机器数与真值就是有符号数字。

我们经常见到的是符号数字一般有两种表示符号的方式:
1.用“+”“-”等符号表示数值的符号;
2.用1和0的数字表示符号即符号数字化;

1、机器数

机器数:符号”数字化”的数称为机器数,原码、反码、补码都是机器数;

个人理解机器数就是数字在机器中存储、传递、运算时以机器能识别的方式存在数字的表现形式或者是状态(二进制形式);

2、真值

真值:直接用符号“+”和“-”表示正负和绝对值组成的数成为真值;
常见的的8进制、10进制、16进制数都是真值;
例如:-5,5,0xFF等等;

个人理解真值就是日常生活中被人们使用最频繁的数字表现形式;

(二)、原码、反码、补码

原码、补码、反码是一个定点数在计算机的3种表示法,三种表示方法均有符号位和数值位两部分,其中符号位都是0表示“正”,1表示“负”,而数值位三种表示方法却各不相同。目前在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;加法和减法可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路等等原因最终采取了补码。

原码、补码、反码基本特点:

1.符号数字化最高位【红色位置】就是符号位
  0000 0000
  其中,0:表示正数,1:表示负数;其他为绝对值【蓝色位置】

2.真值为正数时:原码=补码=反码;且符号位都为:0
3.真值为负数时:原码、补码和反码的符号位都为:-1,反码是原码的“每位取反”,补码是原码的“取反加1”

1、原码

原码:一种计算机中对数字的二进制定点表示方法。

这里写图片描述
个人理解是原码是在真值(二进制表达形式)前加上符号位0或1就是了;

原码特点:

  1. 表示简单,易于同真值之间进行转换,同时原码是人脑最容易理解和计算的表示方式,也是计算机中最简单的编码方式;
  2. 原码中不同符号加法运算或者同符号减法运算的时候,需要将两个值的绝对值进行比较,然后进行加减操作 ,最后符号位绝对值大的决定;即原码中符号位不能直接参与运算,必须和其他位分开处理,这就增加了硬件的开销和软件算法的复杂性,于是反码应用而生
  3. 绝对值相等的正数和负数的原码相加不为“0”【正负相加不等于0】 ;
    1000 00010000 0001 =1000 0010 (-2)
    比较鸡血的是原码中0分为:+0和-0:
    +0 :0000 0000 ;  -0:1000 0000

2、反码

反码:反码是数值存储的一种,反码在具体的表现形式为将原码的符号位不变其余位置取反
这里写图片描述
反码特点:

  1. 正数的反码和原码表现形式完全相同;负数的反码,符号位为“1”,数值位在原码数据位上按位取反;
    +0 :原码:0000 0000 反码:0000 0000; - 0:原码: 1000 0000 反码:1111 1111
  2. 反码解决原码中正数与负数相加不为0的问题【参与运算数字绝对值大于0】
    1000 00010111 1110 =1111 1111 (-0)
  3. 反码中依然存在+0和-0;

反码: 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1);

反码表示方式是用来处理负数的,反码可以有效解决绝对值大于0原码中正数与负数相加不为0的问题,但是在反码中依然存在+0和-0和其它问题,因此反码和原码一样并未被广泛应用,为此补码应用而生;

3、补码

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,将加、减运算简化为单纯的相加运算,以便于在计算机中实现各种运算。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路;
整数补码定义:
这里写图片描述
式中:X:为二进制真值;n:为二进制真值整数的位数;
小数补码定义:
这里写图片描述
式中:X:为二进制真值;n:为二进制真值整数的位数;

补码特点

  1. 负数的补码:符号位不变,低位起遇到的第一个1之前的0不改变,保留第一个1,然后按位取反

二、位运算和基本应用

位运算是针对二进制的每一位进行的运算,它是专门针对数字0和1进行的操作。程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算是直接对整数在内存中的二进制位进行操作吗,位运算即可以节约内存,同时使程序速度更快效率更高。

Java位用算运算符又可以分为 逻辑运算符 和 位移运算符 ;
逻辑运算符有:按位与 &、按位或 |、取反 ~、按位异或^;
位移运算符有:左移 <<、右移 >>、无符号右移 >>>;
同时所有的位运算符中,除~以外,其余均为二元运算符操作数,并且位运算的运算对象为整型和字符型数据 。

(一)、逻辑运算符

在逻辑运算符&、|、~、^中,特别需要中需要特别注意的是:机器数的符号位也是参与运算的

1、& 与运算符

与运算是将参与用算两个二进制数进行&用算,如果两个二进制位都是1,则与用算的结果为1,其他全都为0;

范例 结果 范例 结果
0&0 0 0&1 0
1&0 0 1&1 1

2、| 或运算符

或用算是将参与用算两个二进制数进行 | 用算,如果两个二进制位都是0,则与运算的结果为0,其他全都为1,即只要其中一个数字的二进制位是1,这个二进制位的运算结果就为1;符号位也是同样的操作

范例 结果 范例 结果
0|0 0 0|1 1
1|0 1 1|1 1

3、~ 取反运算符

取反运算是只针对一个数据进行操作,如果二进制是0,则取反为1,如果二进制是1,则取反为0;符号位也是同样的操作

范例 结果 范例 结果
~0 1 ~1 0
~0000 0001 1111 1110 ~1111 1111 0000 0000

4、^ 异或运算符

异或^运算是将参与运算的两个二进制进行“异或”运算,如果二进制位相同,则结果为1,否则为0;

范例 结果 范例 结果
0^0 0 0^1 1
1^0 1 1^1 0

基本应用:
不进位加法: a ^ b就是a和b相加之后,该进位的地方不进位的结果
任何一个数字异或他自己都等于0;
任何一个数字与0异或保留原值;

(二)、位移运算符

位移运算符基本规律
我们把一个数字在位运算时在内存占用空间大小自定义为:内存长度length

short,byte,char,int在位运算时先转换int 所以内存长度length=32long 在位运算时内存长度length=64
  1. Java中任意一个可以进行位运算的数m,左移(右移)n时,若n等于m的内存长度(或者内存长度整数倍),位运算结果等于m本身;
  2. 在位移(右移)运算m<< n(m>>n)的计算中,若n为正数,则实际移动的位数为(n%m的内存长度),若n为负数,则实际移动的位数为(m的内存长度+n%m的内存长度)),右移,同理。
  3. 位运算m左移n结果等于m* 2 n ,对应着右移m>>n结果等于m/ 2 n ;

1、<< 左移运算符

定义

左移运算是将操作数二进制值逐位左移若干位,左移过程中符号位不变高位溢出并舍弃,低位补0;

范例 结果 范例 结果
00000001<<2 00000100 10000001<<2 10000100
01100001<<2 00000100 11100001<<2 10000100
基本应用

根据<< 左移运算的定义可知:
数字a进行左移b运算结果等于a* 2 b ,即:a<< b=a* 2 b

2、>> 右移

定义

右移运算是将操作数二进制值逐位右移若干位,右移过程中符号位不变低位溢出并舍弃,并用符号位补溢出的高位[即负数补1,正数补0];

范例 结果 范例 结果
00000100>>2 00000001 10000100>>2 11100001
00000111>>2 00000001 10000111>>2 11100001
基本应用

根据左移应用得出结论为:
数字a进行右移b运算结果等于a/ 2 b ,即:a>> b=a/ 2 b

3、>>> 无符号右移

无符号右移定义

无符号右移运算是将操作数所有二进制值逐位右移若干位,包括最高位符号位,也跟着右移,低位溢出并舍弃高位补0
注意,无符号右移(>>>)中的符号位(最高位)也跟着变;

范例 结果 范例 结果
00000100>>>2 00000001 10000100>>>2 00100001
00000111>>>2 00000001 10000111>>>2 00100001
基本应用

在计算机系统中通常是以补码的形式存在的,以16位int数据为例,其中最为特殊的就-1的补码-1的补码为:1111 1111 1111 1111;通过-1>>>n可以获取(二进制)从低位到指定位n全部为1的值;同时使用-1<< m可以获取从指定位m到符号位全部为1的值;通过(-1>>>n)&(-1<< m)就可以获取二进制中指定位置(低位到高位)全部为1的int值;

三、位用算的实际应用

(一)、基本应用

Java
//十进制到十六进制
Integer.toHexString(int i);
//十进制到八进制
Integer.toOctalString(int i);
//十进制到二进制
Integer.toBinaryString(int i);
//十六进制到十进制
Integer.parseInt(“0xff”, 16);
//八进制到十进制
Integer.parseInt(“0123”, 8);
//二进制到十进制
Integer.parseInt(“1010”, 2);

1、指定位置清零

//指index位置清零 ;index低位向高位数为
public static int setIndexZero(int num ,int index){
int i=1<<index;
return num & ~i ;
}
//将数据值为零
public static int setZero(int num){
return num&0x0;
}

2、获取指定位数据

//获取整型数的低八位的数字
public static int getRsualt(int unm){
return unm & 0xFF;
}
//获取整型数的高八位的数字
public static int getRsualt(int unm){
return unm & 0xFF00;
}

3、指定位段为1的正数

//二级制表达式中start到offset为1的正数
 public static int getBitValue(int start, int offset) {
        int e = -1 >>> (32 - offset);
        int d = -1 << start;
        return e & d;
    }

4、判断奇偶数

//位用算方法是否为偶数
public static boolean isOdd(int i){
    return (i & 1) != 0;
}
//其他方法
public static boolean isOdd(int i){
    return i%2!=0;
}

5、设定一个数据的指定位

//设置整型数低四位全部为1,即返回最小为15的数字;
public int setResult(int num){
return num|OXF;
}
//设置整型数最低位为1,即返回奇数
public int setOdd(){
return num|1;
}

6、定位翻转

//整型数低八位数据进行翻位的操作
public static int turn(int num){
return num^0xFF;
}
//整型数高八位数据进行翻位的操作
public static int turn(int num){
return num ^ 0xFF00;
}

7、数值交换

//交换两整形数[不需要引入第三个变量]
public static void swap(int a, int b){
a^=b;
b^=a;
a^=b;
}
/**
*交换两整形数[不需要引入第三个变量],
*风险:a和b为两个很大数,a+b结果超过Integer.MAX_VALUE的话,结果就是错误的
*/
public static void swap(int a, int b){
a=a+b;  
b=a-b;      
a=a-b;       
}

#

8、平均值

比如有两个int类型变量x、y,首先要求x+y的和,再除以2,但是有可能x+y的结果会超过int的最大表示范围,所以位运算就派上用场啦。
(x&y)+((x^y)>>1);
3. 对于一个大于0的整数,判断它是不是2的几次方
((x&(x-1))==0)&&(x!=0);

  1. 求绝对值
    int abs( int x )
    {
    int y ;
    y = x >> 31 ;
    return (x^y)-y ; //or: (x+y)^y
    }
  2. 取模运算,采用位运算实现:
    a % (2^n) 等价于 a & (2^n - 1)
  3. 乘法运算 采用位运算实现
    a * (2^n) 等价于 a << n
  4. 除法运算转化成位运算
    a / (2^n) 等价于 a>> n
  5. 求相反数
    (~x+1)
    10 a % 2 等价于 a & 1

乘(除) 2 n

//给一个数值乘2的n次方
public static int   pow(int src,int n){
        //src * Math.pow(2, n)
        return src<<n;
    }
 //给一个数值除以2的n次方
 public static int  div(int src,int n){
        //src / Math.pow(2, n);
        return  src>>n;
    }

给出两个32位的整数N和M,以及两个二进制位的位置i和j。写一个方法来使得N中的第i到j位等于M(M会是N中从第i为开始到第j位的子串)

//根据题意,有一个想法,将n中第i位到第j位先置为0,然后,按位或上m << i即可。
  public static int updateBit(int src, int start, int end, int target) {
        int e = -1 >>> (32 - end);
        int d = -1 << start;
        int m = e & d;//得到start到end为1其他位位0的int值
        int j = (~m) & src;//~m得到start到end为0,其他位为1的,j为
        return j | (target << start);
    }

(三)、实际应用

1.权限控制

通过“|”和“&”位运算符,基于二进制对象实现的权限管理具有:速度快效率高,简单灵活、占用空间小等优点,但是这种权限控制手段相对不安全的易于破解,同时由于受到机器字长数据长度的限制,例是在Java中使用int类型最多只能有31种权限,long型可以有63种权限;

在Linux操作系统中文件三种rwx权限就是使用位用算实现的;

Linux权限中有rwx三类权限:
     r =1 << 0=1,表示可读;
     w =1 << 1=2,表示可写;
     x=1 << 2=4,表示可执行;
     最高权限:rwx=r|w|x=7;
Linux权限中u g o三类身份:
      u 表示“用户(user)”;
      g 表示“同组(group)用户”,即和文件属主有相同组ID的所有用户;
      o 表示“其他(others)用户”;
例如:创建u g o都有rwx权限的文件 命令: mkdir -m 777 [filename];

Java代码实现通过位运算实现增删改查的权限控制;

//原始权限
   private int insertAuth = 1 << 0;//增权限
    private int updateAuth = 1 << 1;//修改权限
    private int queryAuth = 1 << 2;//查询权限
    private int deleteAuth = 1 << 3;//删除权限
//用户权限
    //增删改查所有的权限
    public int boosAuth = insertAuth | updateAuth | queryAuth | deleteAuth;
    //增改查的权限
    public int managerAuth = insertAuth | updateAuth | queryAuth;
    //增查的权限
    public int salesAuth = insertAuth | queryAuth;

    //判断是否有插入的权限
    public boolean isInsertAuth(int currentAuth) {
        return (currentAuth & insertAuth) > 0;
    }
    //判断是否有查询的权限
    public boolean isQueryAuth(int currentAuth) {
        return (currentAuth & queryAuth) > 0;
    }
    //判断是否有修改的权限
    public boolean isUpdateAuth(int currentAuth) {
        return (currentAuth & updateAuth) > 0;
    }
    //判断是否有删除的权限
    public boolean isDeleteAuth(int currentAuth) {
        return (currentAuth & deleteAuth) > 0;
    }

2、byte与十六进制数的转换

转换基本原理
Java中byte每个字符是由8个bit组成的,而16进制中每个字符由4个bit组成的[16进制中最大为:0xF(15)转为二进制为:1111组成的4个bit]。所以我们可以把一个byte转换成两个用16进制字符,即把高4位和低4位转换成相应的16进制字符,并组合这两个16进制字符串,从而得到byte的16进制字符串。同理,相反的转换也是将两个16进制字符转换成一个byte。
具体操作:
(1)、二进制字节转十六进制:第一步:将字节高4位与0xF0做”&”操作,然后再左移4位,得到字节高位的十六进制;第二步:将字节低4位与0x0F做”&”操作,得到低位的十六进制,将两个十六进制数拼接即为二进制的十六进制。
(2)、十六进制转二进制:第一步:将十六进制字符对应的十进制数字右移动4为,二进制作为字节高4位;第二步:将字节低4位的十六进制字符对应的十进制数字与第一步得到的十进制数做”|”运算,即可得到十六进制的二进制字节;

 /**
     * byte[]数组转换为16进制的字符串。
     *
     * @param data 要转换的字节数组。
     * @return 转换后的结果。
     */
    public static final String byteArrayToHexString(byte[] data) {
        StringBuilder sb = new StringBuilder(data.length * 2);
        for (byte b : data) {
            int v = b & 0xff;
            if (v < 16) {
                sb.append('0');
            }
            sb.append(Integer.toHexString(v));
        }
        return sb.toString().toUpperCase(Locale.getDefault());
    }

/**
     * 16进制的字符串转换为byte[]数组。
     *
     * @param hex要转换的16进制的字符串。
     * @return 转换后的字节数组。
     */
public static byte[] hexStringToByte(String hex) {   
      if (hex== null || hex.equals("")) {
            return null;
        }
     //将小写转化为大写字母,去除空格
    hex=hex.toUpperCase().replace(" ", "")
    //每两个字符表示转化为一个字节,所以length/2
    int len = (hex.length() / 2); 
    //将String转换为Char[]
    byte[] result = new byte[len];  
    //new出新的byte[]
    char[] achar = hex.toCharArray();   
    for (int i = 0; i < len; i++) {   
    //byte是由8个bit组成,16进制字符是由4个bit组成,因此将两个16进制字符转换成一个byte;
     int pos = i * 2;   
     result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));   
    }   
    return result;   
}  

private static byte toByte(char c) {   
    byte b = (byte) "0123456789ABCDEF".indexOf(c);   
    return b;   
} 

HashMap 中的为用算:

《编程珠玑》之位运算知识
//https://blog.csdn.net/recsysml/article/details/17922303

深入学习有趣的位运算
//https://blog.csdn.net/dc_726/article/details/7036533

简单的字符串加密算法
//https://blog.csdn.net/danjuan123/article/details/52325198

//https://blog.csdn.net/brok1n/article/details/50163943
原加密解密系列文章之 - ASCII 加密解密(最简单的加密解密) 上

自己独立设计的字符串加密算法
//https://blog.csdn.net/CXXSoft/article/details/1109356

//优秀程序员不得不知道的20个位运算技巧
//https://blog.csdn.net/zmazon/article/details/8262185

//Java位运算总结:位运算用途广泛
//https://blog.csdn.net/linbilin_/article/details/50608757
求平均值,比如有两个int类型变量x、y,首先要求x+y的和,再除以2,但是有可能x+y的结果会超过int的最大表示范围,所以位运算就派上用场啦。
(x&y)+((x^y)>>1);
对于一个大于0的整数,判断它是不是2的几次方
((x&(x-1))==0)&&(x!=0);

byte的最大值为:0111 1111= 2 7 -1=127

例:byte与十六进制数的转换

原理分析:
Java中byte每个字符是由8个bit组成的,而16进制中每个字符由4个bit组成的[16进制中最大为:0xF(15)转为二进制为:1111组成的4个bit]。所以我们可以把一个byte转换成两个用16进制字符,即把高4位和低4位转换成相应的16进制字符,并组合这两个16进制字符串,从而得到byte的16进制字符串。同理,相反的转换也是将两个16进制字符转换成一个byte。

在Java中字节与十六进制的相互转换主要思想有两点:

将16进制的String转换为bytep[]

Java中byte与16进制字符串的互相转换
java中byte转换int时为何与0xff进行与运算
//https://blog.csdn.net/androiddeveloper_lee/article/details/6619414
bytes[0] = (byte) (data & 0xff):变量data与 0xff进行按位与运算(这里就是将高8位置0),然后强制转换成byte类型,赋值给byte数组的元素byte[0]
bytes1 = (byte) ((data & 0xff00) >> 8):变量data与 0xff进行按位与运算(这里就是将低8位置0),然后将结果右移8位(高位补0),然后强制转换成byte类型,赋值给byte数组的元素byte1

讲讲二进制、字节、16进制

//补码总结/补码
//http://www.baike.com/wiki/%E8%A1%A5%E7%A0%81&prd=button_doc_entry

//原码, 反码, 补码 详解
http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html

//理解java移位运算符 https://www.cnblogs.com/winsker/p/6728672.html

猜你喜欢

转载自blog.csdn.net/black_bird_cn/article/details/80171652
今日推荐