JAVA基本数据类型与byte互相转换(位运算,原码,补码)
最近整理了之前的一些知识,做了一个java中byte类型与int ,long转换以及将字节信息表示成字符串形式的工具。对前面的只是做了一下复习整理。
原码、反码、补码:
首先这三者都是二进制码即(0,1组合)。
原码:最高位为符号位,0表示正数,1表示负数,剩余部分表示真值。
反码:正数的反码是他本身,负数的反码是在原码基础上符号位不变,其余部分按位取反。
补码:反码基础上末位加1。
举个例子(以数值-8的单字节表示为例):
原码:1000 1000(红色为符号位,其余所有位组成数值)
反码:1111 0111
补码:1111 1000
注意:计算机中保存的数据都是以补码形式存在或计算的。原码是方便你计算的,纸上算数用的东西。
位运算:
顾名思义,位运算就是用来操作比特位的。需要用到的几个位运算符:
&按位与(全真为真,一假则假)(1为真,0为假)
|按位或(一真为真,全假则假)
>> 带符号左移 >>> 无符号左移
<<右移
java中强制类型转换到底做了什么?
首先你得知道java中对于小于四字节的基本数据类型(short,char,boolean,byte,float)内部进行运算时全部转为四字节。当然int和float本身就是四字节的。long和double都是八字节。
通过一个测试类来分析一下这个问题:
public class Test1 {
public static void main(String[] args) {
//-256:原码:10000000 00000000 00000001 00000000 --->补码:11111111 11111111 11111110 00000000
//低8位为:00000000 结果0
int num = -256;
byte bNum = (byte) num;
System.out.println(bNum);
//-4:原码10000000 00000000 00000000 00000100 --->补码:11111111 11111111 11111111 11111100
//低8位:11111100(补码) ----->原码:10000100 结果-4
num = -4;
bNum = (byte)num;
System.out.println(bNum);
//5201314:原码==补码(正数):00000000 01001111 01011101 10100010
//低8位为:10100010(补码) ---> 原码:11011110 == -94
num = 5201314;
bNum = (byte) num;
System.out.println(bNum);
}
}
三个输出:与注释中的分析一样int类型强转为byte时可以确定他就是直接砍掉前面24位的。long型也一样。它将自己保存的补码信息拿出来,只保留最后八位。
对一个运算过程的分析思考:
计算机底层的运算都是针对补码的运算(得在强调一遍)
在下面的例子中我定义了一个byte类型的变量b,简单测试分析了一下 b >>> 4的过程(其实也是为了加深我对原码补码的理解,之前只是记得知识确实感觉自己有些晕)。分析写在注释中:
public class Test2 {
public static void main(String[] args) throws Exception {
byte b = (byte) 0xCA; //-54 --- 原码 1011 0110---补码 1100 1010
System.out.println("------------------测试(byte)(b >>> 4)的整个过程-----------------------------");
//java中基本数据类型在进行运算时 变量b操作数小于四字节,好的转为int型:
//b扩充为4字节后(注意高位补的是符号位 b的补码的符号位现在是1 !!!) :
//11111111 11111111 11111111 11001010(补码)
//无符号右移(高位全部补0)4位之后,符号位变为0(正数的原码补码相同)
//00001111 11111111 11111111 11111100(补码) 不出问题的话结果应该是:268435452
System.out.println((b >>> 4)); //输出:268435452
//int类型又转换为byte类型了:直接砍掉前面24位结果如下:
//11111100(补码) 好的我们现在要在纸上算出来值是多少了,来先将其转换为原码10000100 结果为-4
System.out.println((byte)(b >>> 4)); //输出为-4
//如果此时在给(byte)(b >>> 4)的结果来个 &0x0F
//java:好的先将byte转为int型(补符号位)----->结果:11111111 11111111 11111111 11111111 11111100
//0x0F: 00000000 00000000 00000000 00001111
//两个按位与的结果为:00000000 00000000 00000000 00001100----结果: 12
System.out.println((byte)(b >>> 4) & 0x0F); //输出12
}
}
显然跟分析的一样,所以那一套确实得到了验证。
编写工具类:
功能一:实现将字节信息以字符串形式显示出来(16进制表示):
public class MecBinary {
private static final String HEX = "0123456789ABCDE";
/**
* 将字节数组中的字节转换成字符串形式表示的16进制信息
* @param bytes 字节数组
* @param len 要处理的长度
* @param offset 字节数组的起始处理位置
* @return
*/
public static String binaryToHexString(byte[] bytes, int len, int offset) {
StringBuffer res = new StringBuffer();
for(int index = offset; index < len + offset; index++) {
//取出一个字节
byte b = bytes[index]; //假如取出来的字节为 0xCA ----二进制为 1100(C) 1010(A)
/*
* 现在的目的是将这个字节转换为字符串形式的CA (HEX中下标为12 与 10的字符组合)
* >>表示带符号右移 >>>表示无符号右移,高位补0
* 当 b >> 4 ---> 1111 1100
*/
res.append(HEX.charAt(b >> 4) & 0x0F); //取出高四位对应的16进制字符
res.append(HEX.charAt(b & 0x0F)); //取出低四位对应的16进制字符
}
return res.toString();
}
/**
* 将字符串表示的16进制信息转换成字节
* @param str
* @return
*/
public static byte[] hexStringToBinary(String str) {
if(str == null || str.isEmpty()) {
return null;
}
int len = str.length();
/*
* 首先需要明确的是字符串中的字符个数不能是奇数个, 因为一个字节8个比特位 ,16进制数可用4位表示
* 也就是说两个字符代表一个字节
*/
if(len % 2 != 0) {
return null;
}
byte[] result = new byte[len / 2];
for(int index = 0; index < len; index += 2) {
/*
* 以CA为例 :
* 取出字符C对应的下标---12 00000000 00000000 00000000 00001100(原码==补码)
* 取出字符A对应的下标---10 00000000 00000000 00000000 00001010(原码==补码)
* 现在的目的是得到 单字节:1100 1010
* 将12左移4位 与 10进行&运算 ===> (12 << 4) & 10
* 即二进制形式变为:00000000 00000000 00000000 11001010 然后转换为单字节去掉其前面24位。即只保留 1100 1010
*/
int hVal = HEX.indexOf(str.charAt(index));
int lVal = HEX.indexOf(str.charAt(index + 1));
//index 只可能是 0 2 4 6 8... 我们要的是:0,1,2,3,4... index >> 1 表示 / 2
result[index >> 1] = (byte) ((hVal << 4) | lVal);
}
return result;
}
}
测试类:读取一个mp3文件中的部分字节在写入到另一个文件中,看看是否一样。
public class Test3 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\leftHand.mp3");
byte[] buffer = new byte[16];
fis.read(buffer);
String hexStr = MecBinary.binaryToHexString(buffer, 16, 0);
System.out.println(hexStr);
//将转换过来的字符串转换成字节存储进字符数组中。并写入文件
FileOutputStream fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.mp3");
buffer = MecBinary.hexStringToBinary(hexStr);
fos.write(buffer);
fis.close();
fos.close();
}
}
显示结果:
功能二:实现int类型与byte数组中字节之间的互转:
/*
* 将int类型的数据转换成字节存储进字节数组中 int类型占4个字节
*/
public static byte[] intToBytes(int val) {
byte[] result = new byte[4];
/*
* 比如现在val如下:高-->低
* 字节1 字节2 字节3 字节4
* 00000011 00110000 00110000 11001010
*/
for(int index = 0; index < 4; index++) {
/*
* index << 3 四次取值分别为 0,8,16,24 即相当于遍历每个字节了
* 强转为byte类型时他会直接砍掉前面24位。
* 这个是由低位到高位处理的,即存储的顺序是 [字节4,字节3,字节2,字节1]
*/
result[index] = (byte) (val >> (index << 3));
}
return result;
}
/**
* 将int类型数据转换成字节数组并加到buffer的指定位置处
* @param buffer
* @param offset
* @param intValue
* @return
*/
public static byte[] intToBytes(byte[] buffer, int offset, int val) {
byte[] bytes = intToBytes(val);
for(int index = 0; index < 4; index++) {
buffer[offset + index] = bytes[index];
}
return buffer;
}
public static int bytesToInt(byte[] buffer, int offset) {
int result = 0;
for(int index = 3; index >= 0; index--) {
/*
* 由高位到低位处理 这里一定要加0xFF 此处暂且记为 标号1处。后面将说明为什么必须加
*/
result <<= 8;
result |= buffer[index + offset] & 0xFF;
}
return result;
}
public static int bytesToInt(byte[] bytes) {
return bytesToInt(bytes, 0);
}
对于上述bytesToInt(byte[] buffer, int offset)方法中即标记1处为什么一定要加0xFF解释,在不加的情况下测试一下:
public class TestBinary {
public static void main(String[] args) {
System.out.println(MecBinary.bytesToInt(MecBinary.intToBytes(128)));
}
}
输出结果为-128(反转换出的结果是错的。。。)分析:
128的原码:00000000 00000000 00000000 10000000(正数补码相同)
128通过intToBytes转换之后数组内部情况[10000000,00000000,00000000,00000000]
然后通过bytesToInt转化过程:
前三此转换都没出错 经过三次转换后result的结果为 00000000 00000000 00000000 00000000
最后一次发生了什么?
result << 8的结果:00000000 00000000 00000000 00000000
result = result | bytes[0] 出问题了:
bytes[0]--->10000000 java先将其转化为int型(补符号位!!!)结果为:
11111111 11111111 11111111 10000000 result与此值进行按位或运算结果为:
11111111 11111111 11111111 10000000(补码)这是个负数。来我们算一下它的值,将其转为原码如下:
10000000 00000000 00000000 10000000 -----这是 -128
所以我们要加上 &0xFF 这样可以才可以保证不出错。
功能三:实现long类型与byte数组中字节之间的互转:这个就跟上面操作int的道理一样了没什么可分析的,贴上代码:
public static byte[] longToBytes(long val) {
byte[] result = new byte[8];
for(int index = 0; index < 8; index++) {
result[index] = (byte) (val >> (index << 3));
}
return result;
}
public static byte[] longToBytes(byte[] buffer, int offset, long val) {
byte[] bytes = longToBytes(val);
for(int index = 0; index < 8; index++) {
buffer[offset + index] = bytes[index];
}
return buffer;
}
public static long bytesToLong(byte[] buffer, int offset) {
long result = 0;
for(int index = 7; index >= 0; index--) {
result <<= 8;
result |= buffer[index + offset] & 0xFF;
}
return result;
}
public static long bytesToLong(byte[] bytes) {
return bytesToLong(bytes, 0);
}
最后来综合测试一下功能一功能二:
public class TestBinary {
public static void main(String[] args) {
//测试将两个int类型数据一个long类型数据存入字节数组中并反解析出来
byte[] buffer = new byte[16];
MecBinary.intToBytes(buffer, 0, 87);
MecBinary.intToBytes(buffer, 4, -99);
MecBinary.longToBytes(buffer, 8, 5201314L);
System.out.println(MecBinary.bytesToInt(buffer, 0));
System.out.println(MecBinary.bytesToInt(buffer, 4));
System.out.println(MecBinary.bytesToLong(buffer, 8));
}
}
输出结果: