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));
	}

}

输出结果: