了解原码、反码、补码以及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中的相关操作,特别是和硬件通信的时候,大部分我们都定义一些指定的协议来完成。

<think>嗯,用户的问题是关于如何在保持Javabyte数据类型的取值范围不变的情况下正确输出。首先,我需要回忆一下Javabyte类型的基本知识。根据引用[2]和引用[4],byte的取值范围是-128到127,这是因为在计算机中使用补码表示数值,所以最小值是-128,而最大值是127。用户可能遇到了byte溢出或者显示的问题,想要在不改变这个范围的情况下正确输出。 首先,用户可能遇到的情况是byte变量在运算发生了溢出,导致结果不符合预期。比如,当byte的值超过127,会变成-128,类似地,低于-128会变成127。这候,如何确保在运算过程中结果仍然正确显示呢?根据引用[2],溢出后的计算是基于补码的,所以在处理溢出,需要正确理解补码的机制。 接下来,用户提到“正确输出”,可能需要将byte的值以十进制形式显示,或者处理可能出现的溢出情况。比如,在进行加法运算,如byte b = 127 + 1,结果会是-128。这候,用户可能希望检测到溢出并给出提示,或者将结果转换为更大的数据类型如int来避免溢出。 根据引用[3],原码反码补码的概念是关键。在输出,可能需要将补码转换为真实的数值。例如,当byte的二进制表示为10000000,对应的十进制是-128。直接输出这个值会正确显示为-128,但如果在运算中溢出,可能需要手动处理。 另外,用户可能需要在输出保持byte的范围,但希望以无符号的形式显示,比如0到255。这候,可以使用位掩码将byte转换为int并调整符号。例如,(b & 0xFF)会将byte视为无符号的8位值,转换为int后范围是0到255。 此外,用户可能在使用byte进行数据传输或文件操作,需要确保数据的正确性。例如,在网络传输中,可能需要将byte转换为二进制流,并在接收端正确解析。这候,正确处理符号位非常重要,避免数据错误。 总结用户的需求,可能的解决方案包括:1. 使用更大的数据类型(如int)来存储中间结果,避免溢出;2. 在输出使用位操作处理符号位;3. 检查运算结果是否超出byte范围,并处理溢出情况;4. 使用Java提供的库方法,如Byte.toUnsignedInt()来正确转换无符号值。 需要确保回答中涵盖这些方面,并提供具体的代码示例,例如如何处理加法溢出,如何转换为无符号输出等。同,引用相关的资料,如引用[2]和引用[4]中关于补码和取值范围的解释,来支持回答的正确性。</think>在Java中,`byte`类型的取值范围固定为$-128$到$127$[^2][^4]。要确保在保持该范围不变的前提下正确输出,需特别注意补码机制和类型转换。以下是具体实现方法: --- ### 一、理解补码机制 `byte`存储的是二进制补码形式: - 正数补码原码相同,如$01111111$表示$127$ - 负数补码原码取反加$1$,如$10000000$表示$-128$[^4] --- ### 二、正确处理输出 #### 1. **避免运算溢出** ```java byte a = 127; byte b = 1; // 直接相加会溢出,结果为-128 int result = (int)a + (int)b; // 转换为int后再计算 System.out.println(result); // 输出128 ``` #### 2. **显示无符号值** 用`0xFF`掩码处理符号位: ```java byte b = -128; int unsignedValue = b & 0xFF; // 转换为0-255范围 System.out.println(unsignedValue); // 输出128 ``` #### 3. **检测溢出** 通过位运算判断是否超出范围: ```java public static byte safeAdd(byte a, byte b) { int sum = a + b; if (sum > 127 || sum < -128) { throw new ArithmeticException("Byte overflow"); } return (byte)sum; } ``` --- ### 三、应用场景示例 1. **网络协议解析** 使用`byte`处理固定长度的二进制数据,需通过掩码保证无符号解析: ```java byte packetByte = (byte)0x80; // 二进制10000000 int value = packetByte & 0xFF; // 转换为128 ``` 2. **文件读写** 处理二进制文件,用`ByteBuffer`配合类型转换: ```java ByteBuffer buffer = ByteBuffer.wrap(new byte[]{-128, 127}); int first = buffer.get() & 0xFF; // 128 int second = buffer.get() & 0xFF; // 127 ``` --- ### 四、关键原理总结 | 操作 | 方法 | 作用 | |------------|-------------------------------|--------------------------------------| | 溢出检测 | 提升为int后比较范围 | 避免数值回绕 | | 无符号显示 | `byteValue & 0xFF` | 消除符号位影响 | | 安全运算 | 使用`Math.toIntExact()`等方法 | 防止隐式类型转换导致错误[^2] | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值