Java进制转换原理详解

在使用Java进行网络编程时,常常需要进行进制转换,而在进行这类操作时,往往需要对进制与Java数据类型有较深入的理解,才能确保在编程时不会出现错误。同时,深入了解进制能写出更加高效的代码。

本文先从计算机进制的两个基本概念入手,带读者简单的了解计算机进制的原理,然后再对Java数据类型和数据之间的转化展开论述,最后才对数据类型间的进制转化做详细说明。

本文由浅入深,适合初学者和进阶者阅读。

符号位 和 补码

在计算机中,分有符号和无符号的两种数据类型,无符号的数据类型只能表示正数,而有符号的数据类型则可以表示负数。为了能将数据表示为负数,在有符号的数据类型中,会将二进制数的第一位作为符号位,0表示正数,1则表示负数。通过判断第一位的值,计算机就能判断这个数是正数还是负数。

Java不像c/c++这样的语言,不能声明无符号的数据类型,所以Java的所有数据类型都是有符号的。

什么是补码? 

补码是计算机中用来表示负数,使得负数能够使用加法器参与加法运算的一种码。

在计算中,正数采用原码存储,原码即为本身数值,而负数采用补码的方式进行存储,这样做的目的是方便计算机运算,降低电路设计的复杂性。

如何快速求得负数补码?

补码的转换规则为符号位不变,其余逐位求反再加1

关于补码和符号位在此我不进行详尽的解释,因为超出了本文的论述范围,可以阅读我的博客 二进制补码计算原理详解 进行了解,博客地址:https://blog.csdn.net/zhuozuozhi/article/details/80896838

特别说明一点,符号位是可以直接参与运算的。

示例:使用二进制表示以下数值

byte positiveNumber = 19 ;  // 0 0010011

byte negativeNumber = -45;// 1 1010011   (【原】1 0101101 -> 【反】1 1010010 -> 【补】1 1010011 )

在上面的例子中,数据类型为byte,所以占一个字节,即8 bit,其中1位充当符号位,那么还有七位充当数据位,所以byte的取值范围从0~255变成-128~127。19为正数,所以二进制数第一位为0;而-45为负数,所以它的符号位为1,在计算机中采用补码的形式存储负数。-45的负数原码为 1 0101101,按照补码转换规则,即求得负数补码为 1 1010011

Java数据类型

在说明数据类型之前,笔者要特别说明一点,Java是一门面向对象的高级编程语言,他为一般高级编程提供便利性,所以隐藏了底层的一些细节,这才导致底层相对不容易被操作和使用。笔者之所以强调数据类型与数据类型的转换规则,是为讲解进制转换规则做好准备。

随便翻开一本Java入门书籍,里面都有基本类型的的占用字节和取值范围。最好记住这些内容。如下图是摘自《Java程序设计》的一张数据类型表。

Java进制转换原理详解_第1张图片

数据的表示范围大小从小到大的排序为:byte < short < int < long < float < double

需要注意的是:float 和 double是实数类型的数据,虽然只占用四个字节和8个字节,但是他们的数据类型中存储了小数点、指数、有效位数及其他一些数据,所以他们的取值范围远大于相同字节数的int和long类型。

数据类型之间的转换规则

在进行数据类型转换的时候有如下几种情况:

数据范围小的数据向数据范围大的数据转换时,无需要进行特殊转换,数值保持不变

示例:byte类型向short类型转换

byte b = 64;  

 short s = b;   //数值不变,不报错

如果数据范围大的数据类型数据范围小的数据类型转换时,小范围的数据可能无法表示该数值,所以需要强转

示例 : short类型向byte类型转换

short s = 125;

byte b = (byte)s; // b = 125; 125在数据范围内,没有变化

short s2= 129;

byte b = (byte)s; //b = -127;   129不在byte数据范围内,数值被强制转换,与原数据不一致。

分析上面例子的两个强转,由于byte的数值表示范围在-128 - 127,所以s 的值在范围内,能够顺利强转不出错,而s2却出现偏差,这就不得不了解它们的转换规则。

首先我们分析一下为什么short类型的129转化成byte类型,为什么会变成-127?观察short类型的129,short占两个字节,即16位,他的的二进制表示为 0 0000000 10000001 , 当我们强制转化成byte时,由于byte只有8bit,这就导致数据位溢出,short只能存储16位中的8位,Java会自动舍弃前面8位,保留后面8位,所以强转后就变成了 1 0000001。由于在Java中,第一位为符号位,这就导致了这个数强转之后被识别成了一个负数。而负数在符号位当中是以补码的形式存储,系统认为这是一个负数补码,我们从这个补码反求原码来确定整个负数的数值是多少,【补】1 0000001 -> [反] 1 0000000 -> [原]1 1111111 得到原码 1 1111111 ,所以这个数值才被强转为-127。

这是Byte类型转换图

Java进制转换原理详解_第2张图片

byte类型的取值范围为-128~127,当某个数值到达上限127时,若再+1则会变成-128,周而复始。

当一个数值大于byte类型时,若要将他强转为byte类型时,则需要遵循此循环。

题外补充:浮点类型与整数的相关强制转换

我们知道,Int类型的最大值是4个字节31位表示的最大数,即 2147483647;如果浮点类型的数值在Integer之内,那么会直接省略小数部分,而当浮点数大于Integer范围的时候,会有一些不同。

示例: 浮点类型转long类型,int类型

double d = 2147483649.123d; //一个略大于Int类型表示范围的数
long l = (long)l;//l = 2147483649 //实际上,double转long类型,只要不超过Long类型表示的最大数,都是省略小数点以后取整即可

int i = (int)d; //2147483647

注意,当浮点类型的示数超过int 类型上限时,i就会固定取int 类型的最大值

byte b = (byte)d;//b=-1,

浮点转byte类型时,先将double转成int,再从int转到byte,当double超过Int上限时int会取最大值,再转化为byte时即固定为-1,short类型也是类似。

数据类型之间转换的二进制规则

上一节中已经详细论述了Java的数据类型转换规则,在这一节,将从二进制角度看数据转换是如何进行的。

数据范围小的数据向数据范围大的数据转换时,数值保持不变,那么二进制数是如何变化的?

示例:正数byte类型向short类型转化

 byte b = 64;  // 二进制表示: 0 1000000

 short s = b;   // 二进制表示: 0 0000000 01000000

 正数byte在向short类型转化时,s的高地址位会自动补0

 例3:byte类型负数向short数据类型转化

 byte b= -64 // 二进制表示: 1 1000000 = -64

 short s = b //二进制表示:   1 1111111 11000000 = -64

 在这个例子中,-64为负数,s的高位地址会自动补1,确保s反求原码还等于-64

负数向范围更大的类型转换时,原理上是先求负数原码,在数据高位上填充0,扩充到固定的数据类型宽度,然后再求补码。

 在实际编程中,这个隐式转换会带来很多问题,比如你想把byte类型数据当做无符号类型使用,需要将无符号数据放在int数据类型中计算,但是Java并不知道,会在前面自动填充的1可能会导致计算出错。好在Java包装类中提供了向上无符号转型的方法

示例:利用Byte.toUnsignedShort()获取向上转型获得无符号Short数据

byte b = -45// 1 1010011

short s1 = b; //1 1111111 11010011

int s2 = Byte.toUnsignedInt(b); // 得到 0 0000000 11010011 = 211
 如果在使用过程中想要向上转型,想得到无符号位的数据,那么就需要使用Byte提供的toUnsignedInt(),其他包装类也有提供向上无符号转型的方法。当然,熟练的可以不使用这些方法,直接进行位运算就好,这也是我推荐的

 

如果数据范围大的数据类型数据范围小的数据类型转换时,直接截断高位数据

示例:short类型的129强转为byte类型

short s = 129;

byte b = (byte)s; // b = -127

当进行强制转化时,s的数据高位都被舍弃,只留下 10000001,因为最高位为1,计算机判定为负数补码,补码10000001表示 -127,所以就是强转后就是-127。

 

最后,来讲下Java二进制字节级操作及位级操作的常用操作。

再次强调,由于Java数据类型没有提供无符号的数据类型,所以在进行这类操作时务必要注意数值的取值范围。

将数值转为二进制、八进制数、十六进制、任意进制字符串

int i = 100;

Integer.toBinaryString(i); //转二进制  = 1100100

Integer.toOctalString(i);//转8进制  = 144

Integer.toHexString(i);//转16进制 = 64

Integer.toString(int value,int radix); //转任意进制

使用使用有以下几点注意点

1. short和byte没有提供转进制字符串,实际上也不太需要,如果byte和short需要打印,可以先强转为int类型,使用Integer的方法进行打印,但是为负数的时候打印可能会有偏差,需要注意。

2. 打印时高位不会补0,比如打印int类型的二进制字符串,高位不会补0,需要自己格式化输出

从二进制、八进制、十六进制、任意进制字符串转到指定数值

1.几乎每个包装类型下面都有一个   valueOf(string value,int radix) 方法,可以使用这个方法转化

2.进制字符串的前缀(0x,0b..)不需要带,带了会报错。

格式化输出进制数

int i = 10;

String.format("%02x",i); // 0a

16进制可以直接补充,二进制需要自己实现

向上无符号转型

示例:byte通过方法向上转型获取int类型

Byte.toUnsignedInt(b); //其他包装类类似

 

示例: byte通过位运算向上转型获得short数据

byte b = -45; //1 1010011

short s = (short)(((int)b) & 0xff)  //211

解析Java中的类型转化不太方便,因为默认数值类型是int,所以所有的类型转化都要先转成int,再强转为自己想要的类型,在本例中,b为byte类型,且为负数,b强转为int类型的二进制表示为 1 1111111 11111111 11111111 11010011, 数值 0xff的二进制表示为 0 0000000 00000000 00000000 11111111,两者通过与位运算,

1 1111111 11111111 11111111 11010011

0 0000000 00000000 00000000 11111111  &

0 0000000 00000000 00000000 11010011  //即211

再通过将至转换为short,截断前16位,得到

0 0000000 11010011 , 得211

读取两个byte数值转化为一个short类型数据

byte b = -45; //1 1010011
byte b2 = 11; //0 0001011
short s = (short)(((int)b & 0xff << 8) | ((int)b2 & 0xff)) ; //-11509 

解析, 首先通过(int)b & 0xff 获得整数值211,二进制数表示为  0 0000000 00000000 00000000 11010011,然后对这个数进行左移位8位

 0 0000000 00000000 00000000 11010011 << 8 ,得 0 0000000 00000000 11010011 00000000,再计算  ((int)b2 & 0xff) 得 0 0000000 00000000 00000000 00001011

将两者进行或位运算

0 0000000 00000000 11010011 00000000

0 0000000 00000000 00000000 00001011 |

0 0000000 00000000 11010011 00001011

最后转为short截断前16位,算得

1 1010011 00001011,即-11509

实际上,在位运算中,看十进制数没意义,因为二进制运算的结果在十进制什么都看不出来

判断二进制数的某位是0还是1

示例:判断byte数据的第五位是0还是1

byte b = -45; //1 1010011;

boolean bool = (b & 0x10) != 0 

解析:0x10的二进制数表示为 0001 0000

00000000 00000000 00000000 11010011

00000000 00000000 00000000 00010000 &

00000000 00000000 00000000 00010000 //16

通过与位计算,0x10除了第五位是1,其他位都是0,那么其他位进行与位运算,都会变成0,而第五位如果是1,那么得1,否则为0,而如果所有位都为0,那么所得结果为0,否则就是有值,以此来判断该位是否有值

思考:如果某数据类型判断的某两位为1还是0,该如何做,这个留给读者自己思考吧

补充:

十六进制,二进制如何快速转化?

示例:十六进制、二进制快速转化技巧

0101 1010 转16进制

一位十六进制对应4位二进制数,比如上面的二进制数,前四位得到的结果是 5,而后四位得到的数值为10,即a,所以这个数的十六进制表示数为 0x5a,反之,十六进制转二进制就是把一位16进制数转成4位二进制数。

位运算是如何进行的?

位运算就是将前后两个数值的每个位逐一进行布尔运算,算得想要的数值

示例:与位运算

0010 0010

1110 0001 & 

上下对应逐一进行布尔运算,得

0010 0000 

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

你可能感兴趣的:(二进制,Java)