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程序设计》的一张数据类型表。

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类型转换图

Java基本数据类型转换原理及位级操作技巧

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

还有其他一些操作,比较常用且易理解,并未列举。

本文纯属个人理解,如有错误,请勿拍砖。