了解原码、反码、补码以及Java中byte的读写

一、前言

平时的App应用层开发中,很少和byte打交道,最近刚做了一个和硬件设备交互的产品,通信使用UDP,交互协议就是自己定义的协议,每个数据包中分别定义不同的字节数来代表不同的意义,比如:

字节号信息内容字节数码值说明
1-2数据头20x3e80x3e8
3工作状态1bit0-bit70x00:待机;0x01:发射

要了解这块内容,必须要了解二进。字节是二进制数据的单位,一个字节通常为8位长,其中位(bit)使用的0或1表示,通常情况下byte都是无符号的,所以取值范围为0~255。

二、机器数

机器数是将符号“数字化”的数,是数字在计算机中的二级制表示形式。

数的符号数字化

现实中的数有正数和负数,由于计算机内部的硬件只能表示两种物理状态(0和1表示),因此数的正号“+”和负号“-”,在机器里用一位二进制的0或者1来区别。通常这个符号放在二进制数的最高位,称为符号位,0代表正数,1代表负数。

真值

带符号位的机器数的数值称为机器数的真值。比如

二进制 0000 0001 的真值为1;

二进制 1000 0000 的真值为-1;

三、原码、反码、补码的概率和表示方法

对于一个数,计算机要使用一定的编码方式进行存储。原码、反码、补码就是机器存储一个具体数字的编码方式。

3.1 原码

原码就是符号位加上真值的绝对值,即用第一位表示符号位,其余位表示值。

真值[3] = 原码[0000 0011]

真值[-3] = 原码[1000 0011]

因为第一位是符号号,所以8位二进制的原码取值范围(10进制表示)就是:
[1111 1111,0111 1111]即[-127,127]

原码是人脑最容易理解和计算的方式。

3.2 反码

在了解反码之前我们线看下面的运算:

3 + 3:

原码[0000 0011] + 原码[0000 0011] = 原码[0000 0110] = 真值[6]

正数的加法运算在上面的例子中使用原码进行计算是没有问题的,那么我们来看一下减法运算呢:

3 - 3:在计算机中,为了简化设计,将符号位也参与运算,因此减法可以转换为加法运算,这样计算机的运算设计就更简单了:即

3 - 3 = 3 + (-3) 转化为原码进行计算则为:

原码[0000 0011] - 原码[0000 0011] = 原码[0000 0011] + 原码[1000 0011] = 原码[1000 0110] = 真值[-6]

显然上面的计算结果是不对的。

为了解决减法的问题,出现了反码:

反码表示方法:

  1. 正数的反码是其本身
  2. 负数的反码在其原码的基础上,符号位不变,其余各个位取反

例:

[+3] = 原码[0000 0011] = 反码[0000 0011]

[ -3] = 原码[1000 0011] = 反码[1111 1100]

下面我们使用反码来计算3-3

3 - 3 = 原码[0000 0011] + 原码[1000 0011] = 反码[0000 0011] + 反码[1111 1100] = 反码[1111 1111] = 原码[1000 0000] = 真值[-0]

使用值的反码进行计算,结果中的真值正确了,但是有个符号-0,虽然人们在理解上+0和-0是一样的,但0带符号是没有任何意义的。而且会有[0000 0000]原和[1000 0000]原两个编码表示0。

于是补码出现了,为了解决0的符号以及两个编码的问题:

3.3 补码

补码表示方法:

  1. 正数的补码就是其本身
  2. 负数的补码在其原码的基础上,符号位不变,其余各个位取反,最后+1,即(在反码的基础上+1)

例:

[+3] = 原码[0000 0011] = 反码[0000 0011] = 补码[0000 0011]

[ -3] = 原码[1000 0011] = 反码[1111 1100] = 补码[1111 1101]

下面我们使用补码来计算3-3

3 - 3 = 原码[0000 0011] + 原码[1000 0011] = 反码[0000 0011] + 反码[1111 1100] =补码[0000 0011] + 补码[1111 1101] = 补码[0000 0000] = 反码[0000 0000] = 原码[0000 0000] = 真值[+0]

再计算一下3 - 4:

3 - 4 = 原码[0000 0011] + 原码[1000 0100] = 反码[0000 0011] + 反码[1111 1011] =补码[0000 0011] + 补码[1111 1100] = 补码[1111 1111] = 反码[1111 1110] = 原码[1000 0001] = 真值[-1]

从上门的例子中,可以看出使用补码进行计算,解决了使用反码进行计算时0的符号问题以及两个编码问题,这样可以使用[00000000]表示0,而且还可以使用[10000000]表示-128:

(-1) + (-127) = 补码[1000 0001] + 补码[1111 1111] = 补码[1111 1111]补 + 补码[1000 0001] = 补码[1000 0000]

-1-127的结果应该是-128,在补码运算中,[1000 0000]补就是-128. 但是需要注意,这里使用以前的-0补码来表示-128,所以-128没有原码和反码表示

因此使用补码,不仅修复了0的符号以及存在两个编码的问题,还能够多表示一个最低数,这就是为什么8位的二进制,使用原码或者反码的表示范围是[-127,127],而使用补码表示的范围是[-128,127]。

因为机器使用的是补码,所以对于编程中常用到的32位int类型,可表示的范围是[-2^31, 2^31-1]

四、了解java中的byte表示方式

通过上面的描述,不同的编码的取值范围是不一样的,下面我们来验证一下java中到底原码、反码还是补码表示。

1,验证byte的取值范围

    System.out.println(Byte.MAX_VALUE);
    System.out.println(Byte.MIN_VALUE);

输出结果为:

127
-128

由此可见,Java中使用的是补码,只有使用补码,才能有最小值为-128

2,查看二进制编码

    String s1 = String.format("%8s", Integer.toBinaryString(127 & 0xFF)).replace(' ', '0');
    String s2 = String.format("%8s", Integer.toBinaryString(-128 & 0xFF)).replace(' ', '0');
    String s3 = String.format("%8s", Integer.toBinaryString(-1 & 0xFF)).replace(' ', '0');

    System.out.println(s1);
    System.out.println(s2);
    System.out.println(s3);

输出结果为:

01111111
10000000
11111111

五、Java中向流中读取获取写入字节

5.1 Java中写入字节

字节号信息内容字节数码值说明
1-2数据头20x3e80x3e8
3工作状态1bit0-bit70x00:待机;0x01:发射
4量程1bit0-bit70x00:1km;0x01:2km
5温度1bit0-bit8-128-127度
6转速1bit0-bit80-255;无符号

字节的写入,这里我们使用DataOutputStream,这个类里面封装了相关的类型的写入操作:
如下所示:

    try {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream);
        //数据头2个字节可以用short表示
        outputStream.writeShort(0x3e8);
        //工作状态一个字节
        outputStream.writeByte(0x01);
        //量程
        outputStream.writeByte(0x01);
        //温度
        outputStream.writeByte(100);
        //转速
        outputStream.writeByte(255);

        //其他待写入的类容......

        byte[] outBytes = byteArrayOutputStream.toByteArray();

    } catch (Exception ex) {
        ex.printStackTrace();
    }

5.2 Java中按照字节数读取内容

读取字节这里使用DataInputStream,也封装了相关类型的读取操作:

    //读取内容
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outBytes);
    DataInputStream inputStream = new DataInputStream(byteArrayInputStream);

    //数据头
    System.out.println(inputStream.readShort());
    //工作状态
    System.out.println(inputStream.readByte());
    //量程
    System.out.println(inputStream.readByte());
    //温度
    System.out.println(inputStream.readByte());
    //转速 0-255,Byte的有符号最大值是127,所以这要使用无符号的byte取法
    System.out.println(inputStream.readUnsignedByte());

输出结果为:

1000
1
1
100
255

注意:

这里在进行协议读取的时候一定要看清每个字节的文档说明,不然有可能造成读取的数据不对,比如这里的转速是无符号的,

    //转速
    outputStream.writeByte(255);
    

如果在读取没有注意这个,直接使用

    System.out.println(inputStream.readByte());

得到的结果为-1,真确的应该读取无符号的byte,这样才能正确的255;

最后

原码、反码、补码的出现以及表示方法上面已经描述过了,当然了解这些的目的就是为了使用,因此后面出现了byte在Java中的相关操作,特别是和硬件通信的时候,大部分我们都定义一些指定的协议来完成。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值