最近在项目中有个串口通信的实现和IC卡块通信的需要,和单片机通信过程中处理常见的串口中起始位、数据位、验位和停止位之外,还有数据在不同进制的转换和位运算,我是个半路出家的码农,所以开始搞得是一头雾水。现在学习温故一下位运算基础知识。目前数值在电脑存依然是以0和1的不同组合的二进制形式。先熟悉一下位运算基础知识:机器数、真值、原码、补码、反码
Java中可以进行位运算的类型有
lng,int,short,byte,char
;但在实际运算中:byte、short、char
先转换为长度为32位的int类型
,然后进行位运算的;long长度为64可以直接进行位运算,所以in
t和long
是可以直接进行位运算
的
原码、补码、反码是一个定点数在计算机的3种表示法,三种表示方法均有符号位和数值位两部分,其中符号位都是0表示“正”,1表示“负”,而数值位三种表示方法却各不相同。目前在 计算机系统
中,数值一律用补码
来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;加法和减法可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路等等原因最终采取了补码。
1.符号数字化最高位【红色位置】就是符号位
0000 0000
其中,0:表示正数,1:表示负数;其他为绝对值【蓝色位置】
;
2.真值为正数时:原码=补码=反码;且符号位都为:0;
3.真值为负数时:原码、补码和反码的符号位都为:-1,反码是原码的“每位取反”,补码是原码的“取反加1”;
- Java中数据不同格式转换API:
十进制到十六进制 :Integer.toHexString(int i);
十进制到八进制 :Integer.toOctalString(int i);
十进制到二进制 :Integer.toBinaryString(int i);
十六进制到十进制 :Integer.parseInt(“0xff”, 16);
八进制到十进制 :Integer.parseInt(“0123”, 8);
二进制到十进制 :Integer.parseInt(“1010”, 2);
原码: 一种计算机中对数字的二进制定点表示方法。
个人理解是原码是在真值(二进制表达形式)前加上符号位0或1就是了;
1、表示简单,易于同真值之间进行转换,原码是人脑最容易理解和计算的表示方式,也是计算机中最简单的编码方式;
2、原码中 不同符号
的 加法运算
或者 同符号
的 减法运算
的时候,需要将两个值的 绝对值
进行比较,然后进行 加减操作
,最后 符号位
由 绝对值大的
决定;即原码中符号位不能直接参与运算,必须和其他位分开处理,这就增加了硬件的开销和软件算法的复杂性,于是反码应用而生。
3、绝对值相等的正数和负数的原码相加不为“0”【正负相加不等于0】 ;
1000 0001 + 0000 0001 =1000 0010 (-2)
4、 比较鸡血的是原码中0分为:+0和-0:
+0 :0000 0000 ; -0:1000 0000
反码: 反码是数值存储的一种,反码在具体的表现形式为将 原码的符号位不变,其余位置取反。
反码: 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1);
反码表示方式是用来处理负数的,反码可以有效解决绝对值大于0原码中正数与负数相加不为0的问题,但是在反码中依然存在+0和-0和其它问题,因此反码和原码一样并未被广泛应用,为此补码应用而生;
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,将加、减运算简化为单纯的相加运算,以便于在计算机中实现各种运算。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路;
位运算是针对二进制的每一位进行的运算,它是专门针对数字0和1进行的操作。程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算是直接对整数在内存中的二进制位进行操作吗,位运算即可以节约内存,同时使程序速度更快效率更高。
Java位用算运算符又可以分为 逻辑运算符 和 位移运算符 ;
逻辑运算符有:按位与 &、按位或 |、取反 ~、按位异或^;
位移运算符有:左移 <<、右移 >>、无符号右移 >>>;
同时所有的位运算符中,除~以外,其余均为二元运算符操作数,并且位运算的运算对象为整型和字符型数据 。
在逻辑运算符&、|、~、^中,特别需要中需要特别注意的是:机器数的符号位也是参与运算的
与运算是将参与用算两个二进制数进行&用算,如果两个二进制位都是1,则与用算的结果为1,其他全都为0;
范例 | 结果 | 范例 | 结果 |
---|---|---|---|
0&0 | 0 | 0&1 | 0 |
1&0 | 0 | 1&1 | 1 |
1.取值: 与对应数据位全1的数& 与运算;
2.置零: 与对应数据位全0的数& 与运算;
或用算是将参与用算两个二进制数进行 | 用算,如果两个二进制位都是0,则与运算的结果为0,其他全都为1,即只要其中一个数字的二进制位是1,这个二进制位的运算结果就为1;符号位也是同样的操作
范例 | 结果 | 范例 | 结果 |
---|---|---|---|
0|0 | 0 | 0|1 | 1 |
1|0 | 1 | 1|1 | 1 |
1.设置指定位为1: 与对应数据位全1的数& 与运算;
取反运算是只针对一个数据进行操作,如果二进制是0,则取反为1,如果二进制是1,则取反为0;符号位也是同样的操作
范例 | 结果 | 范例 | 结果 |
---|---|---|---|
~0 | 1 | ~1 | 0 |
~0000 0001 | 1111 1110 | ~1111 1111 | 0000 0000 |
1.设置指定位为1: 与对应数据位全1的数& 与运算;
异或^运算是将参与运算的两个二进制进行“异或”运算,如果二进制位相同,则结果为1,否则为0;
范例 | 结果 | 范例 | 结果 |
---|---|---|---|
0^0 | 0 | 0^1 | 1 |
1^0 | 1 | 1^1 | 0 |
1.不进位加法:
a ^ b就是a和b相加之后,该进位的地方不进位的结果;
2.任何一个数字异或他自己都等于0;
3.任何一个数字与0异或保留原值;
1.定位翻转:与指定位全是1进行^ 异或运算;
2.数值交换:需要交换数据进行相互^ 异或运算三次;
位移运算符基本规律
short,byte,char,int
在位运算时先转换
为int
所以内存长度length=32
;long
在位运算时内存长度length=64
;
左移运算是将操作数二进制值逐位左移若干位,左移过程中符号位不变 ,高位溢出并舍弃,低位补0;
范例 | 结果 | 范例 | 结果 |
---|---|---|---|
00000001<<2 | 00000100 | 10000001<<2 | 10000100 |
01100001<<2 | 00000100 | 11100001<<2 | 10000100 |
根据<< 左移运算的定义可知:
数字a进行左移b运算结果等于a 2 b 2^b 2b,即:a<< b=a 2 b 2^b 2b;**
右移运算是将操作数二进制值逐位右移若干位,右移过程中符号位不变 ,低位溢出并舍弃,并用符号位补溢出的高位[即负数补1,正数补0];
范例 | 结果 | 范例 | 结果 |
---|---|---|---|
00000100>>2 | 00000001 | 10000100>>2 | 11100001 |
00000111>>2 | 00000001 | 10000111>>2 | 11100001 |
根据左移应用得出结论为:
数字a进行右移b运算结果等于a/ 2 b 2^b 2b,即:a>> b=a/ 2 b 2^b 2b;
无符号右移运算是将操作数所有二进制值逐位右移若干位,包括最高位符号位,也跟着右移,低位溢出并舍弃,高位补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值;
//指index位置清零 ;index低位向高位数为
public static int setIndexZero(int num ,int index){
int i=1<
//获取整型数的低八位的数字
public static int getRsualt(int unm){
return unm & 0xFF;
}
//获取整型数的高八位的数字
public static int getRsualt(int unm){
return unm & 0xFF00;
}
//二级制表达式中start到offset为1的正数
public static int getBitValue(int start, int offset) {
int e = -1 >>> (32 - offset);
int d = -1 << start;
return e & d;
}
// a % 2 等价于 a & 1
//位用算方法是否为偶数
public static boolean isOdd(int i){
return (i & 1) != 0;
}
//其他方法
public static boolean isOdd(int i){
return i%2!=0;
}
//设置整型数低四位全部为1,即返回最小为15的数字;
public int setResult(int num){
return num|OXF;
}
//设置整型数最低位为1,即返回奇数
public int setOdd(){
return num|1;
}
//整型数低八位数据进行翻位的操作
public static int turn(int num){
return num^0xFF;
}
//整型数高八位数据进行翻位的操作
public static int turn(int num){
return num ^ 0xFF00;
}
//交换两整形数[不需要引入第三个变量]
public static void swap(int a, int b){
a^=b;
b^=a;
a^=b;
}
/**
*交换两整形数[不需要引入第三个变量],
*风险:a和b为两个很大数,a+b结果超过Integer.MAX_VALUE的话,结果就是错误的
*/
比如有两个int类型变量x、y,首先要求x+y的和,再除以2,但是有可能x+y的结果会超过int的最大表示范围,所以位运算就派上用场啦。
(x&y)+((x^y)>>1);
((x&(x-1))==0)&&(x!=0);
int abs( int x )
{
int y ;
y = x >> 31 ;
return (x^y)-y ; //or: (x+y)^y
}
a % (2^n) 等价于 a & (2^n - 1)
a * (2^n) 等价于 a << n
a / (2^n) 等价于 a>> n
(~x+1)
####15. 乘(除) 2 n 2^n 2n
//给一个数值乘2的n次方
public static int pow(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);
}
通过“|”和“&”位运算符,基于二进制对象实现的权限管理具有:速度快,效率高,简单灵活、占用空间小等优点,但是这种权限控制手段相对不安全的易于破解,同时由于受到机器字长和数据长度的限制,例是在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;
}
转换基本原理
:
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 2^7 27-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