Java基本数据类型转换原理及位级操作技巧
在使用Java进行网络编程时,常常需要进行字节级操作和位级操作,而在进行这类操作时,往往需要对数据类型有一定的理解才能确保在编程时不会出现错误。
本文从计算机数据的表示方法入手,讲述数据类型的表示方法,然后介绍Java的基本数据类型以及其转换规则细节,最后给出一些位级操作使用技巧。
符号位和补码
在计算机中,使用二进制码的最高位当做符号位来表示数据的正负。
比如:
byte b = 64;// 01000000
byte b2 = -34 // 10100010
b的二进制表示为 01000000 因为是正数,高位为0
b2的二进制表示为 11011110,b2为负数,高位为1
正因为有了符号位,所以byte的取值范围从0~255变成-128~127
关于补码在此我不进行详尽的解释,因为超出了本文的论述范围,可以阅读我的博客 二进制补码计算原理详解 进行了解,博客地址:https://blog.****.net/zhuozuozhi/article/details/80896838
随便翻开一本Java入门书籍,里面都有基本类型的的占用字节和取值范围。最好的记住这些内容。当然,如果记不住,在使用这些变量的时候IDE也会提示你是否超出范围,但是在程序运行时可就没那么幸运了,为了避免数据超出范围的问题产生,在使用这些数据类型时心里最好有个数。如下图是摘自《Java程序设计》的一张数据类型表。
数据的取值范围大小从小到大的排序为: byte < short < int < long < float < double
需要注意的是:float 和 double是实数类型的数据,虽然只占用四个字节和8个字节,但是他们的数据类型中存储了小数点、指数、有效位数及其他一些数据,所以他们的取值范围远大于相同字节数的int和long类型。
在进行数据类型转换的时候有如下几种情况:
1.数据范围小的数据向数据范围大的数据转换时,无需要进行特殊转换 例一:正数向高位数据类型转
byte b = 64; // 二进制表示: 01000000
short s = b; // 二进制表示: 00000000 01000000
在这个例子中,可以发现,在从byte转为short时,s的高地址位补0
例二:负数向高位数据类型转
byte b= -64 // 二进制表示: 11000000 = -64
short s = b //二进制表示: 1111111111000000 = -64
在这个例子中,可以发现,低位负数在向高位转时,得到的结果仍然是-64。
如果在使用过程中想要向上转型,想得到无符号位的数据,那么就使用Byte提供的toUnsignedShort()即可
注意:负数向高位类型转时,是先求负数原码,在低地址位(数据高位)上填充0,扩充到固定的数据类型宽度,然后再求补码。
2.如果数据范围大的数据类型数据范围小的数据类型转换时,小范围的数据可能无法表示该数值。所以不仅需要强转,还需要理解他的转换规则。
这是Byte类型转换图
byte类型的取值范围为-128~127,当某个数值到达上限127时,若再+1则会变成-128,周而复始。
当一个数值大于byte类型时,若要将他强转为byte类型时,则需要遵循此循环。
例三:
short s = 129;
byte b = (byte)s; // b = -127
至于为什么会是这样的规则,其实解释起来也没有那么困难, s = 129,二进制表示为 00000000 10000001
当进行强制转化时,s的低地址位(数据高位)都被舍弃,只留下 10000001,因为最高位为1,机器判定为负数补码,补码10000001表示 -127,所以就是强转后就是-127。其实所谓的循环就是强制截断二进制数的结果。
以上讨论的仅是整数类型的结果,下面讨论浮点类型转整数类型
我们知道,Int类型的最大值是4个字节31位表示的最大数,即 2147483647
例四:
double d = 2147483649.123d; long l = (long)l;//l = 2147483649 实际上,double转long类型,只要不超过Long类型表示的最大数,都是省略小数点以后取整即可
int i = (int)d; //2147483649
注意,当浮点类型的示数超过int 类型上限时,i就会固定取int 类型的最大值
byte b = (byte)d;//b=-1,
浮点转byte类型时,先将double转成int,再从int转到byte,当double超过Int上限时int会取最大值,再转化为byte时即固定为-1,short类型也是类似。
从中可知,int是浮点到整型数据转换的中间数据。
最后,来讲下字节级操作及位级操作的常用操作。
由于Java数据类型没有提供无符号的数据类型,所以在进行这类操作时务必要注意数值的取值范围,我一般用到的方法有有以下几种。
1.如果要进行无符号操作,可以将该数据类型放在更宽的数据类型寄存器中进行操作。
例5,byte位级操作,求 byteArr数组所有数据的异或结果的byte值
short [] byteArr = {0xff,0x3e,0x7e};
short ret = 0x00;
for(int i = 0 ; i < byteArr.length; i ++ ){
ret = (ret & 0xff) ^ (byteArr[i] & 0xff);
}
System.out.println(ret & 0xff);
在这个例子中,可以发现需要的是byte类型的数据,但是却放在short类型的数据中计算。因为byteArr中的0xff > 127 ,所以编译器会不通过,只能放在更宽的数据寄存器中计算,只要放在更宽的数据寄存器中,其中&0xff操作保证只有最后几位参与运算。在例2中,我们发现,负数从byte转short时,低地址位(数据高位)会被填充1,所以&0xff,相当于清除低地址位的1,只有高地址8位有效。
2.从数据位数小的转换为数据位数宽的,可以使用包装类自带的toUnsigned*处理
byte b = -126;
short s = Byte.toUnsignedShort(b); // s = 130 0b00000000 10000010
还有其他一些操作,比较常用且易理解,并未列举。
本文纯属个人理解,如有错误,请勿拍砖。