美文网首页
Java中byte与short、int、long互转(包含大端,

Java中byte与short、int、long互转(包含大端,

作者: 梧叶已秋声 | 来源:发表于2022-08-10 11:09 被阅读0次

    1.转换函数

    大端:高位字节排放在内存的低地址端,低位字节排放在内存的高地址端
    小端:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

    /**
         * 将int转为高字节在前,低字节在后的byte数组(大端)
         * @param n int
         * @return byte[]
         */
        public static byte[] intToByteBig(int n) {
            byte[] b = new byte[4];
            b[3] = (byte) (n & 0xff);
            b[2] = (byte) (n >> 8 & 0xff);
            b[1] = (byte) (n >> 16 & 0xff);
            b[0] = (byte) (n >> 24 & 0xff);
            return b;
        }
     /**
      * 将int转为低字节在前,高字节在后的byte数组(小端)
      * @param n int
      * @return byte[]
      */
        public static byte[] intToByteLittle(int n) {
            byte[] b = new byte[4];
            b[0] = (byte) (n & 0xff);
            b[1] = (byte) (n >> 8 & 0xff);
            b[2] = (byte) (n >> 16 & 0xff);
            b[3] = (byte) (n >> 24 & 0xff);
            return b;
        }
        /**
         * byte数组到int的转换(小端)
         * @param bytes
         * @return
         */
        public static int bytes2IntLittle(byte[] bytes )
        {
            int int1=bytes[0]&0xff;
            int int2=(bytes[1]&0xff)<<8;
            int int3=(bytes[2]&0xff)<<16;
            int int4=(bytes[3]&0xff)<<24;
    
            return int1|int2|int3|int4;
        }
        /**
         * byte数组到int的转换(大端)
         * @param bytes
         * @return
         */
        public static int bytes2IntBig(byte[] bytes )
        {
            int int1=bytes[3]&0xff;
            int int2=(bytes[2]&0xff)<<8;
            int int3=(bytes[1]&0xff)<<16;
            int int4=(bytes[0]&0xff)<<24;
    
            return int1|int2|int3|int4;
        }
        /**
         * 将short转为高字节在前,低字节在后的byte数组(大端)
         * @param n short
         * @return byte[]
         */
        public static byte[] shortToByteBig(short n) {
            byte[] b = new byte[2];
            b[1] = (byte) (n & 0xff);
            b[0] = (byte) (n >> 8 & 0xff);
            return b;
        }
    
        /**
         * 将short转为低字节在前,高字节在后的byte数组(小端)
         * @param n short
         * @return byte[]
         */
        public static byte[] shortToByteLittle(short n) {
            byte[] b = new byte[2];
            b[0] = (byte) (n & 0xff);
            b[1] = (byte) (n >> 8 & 0xff);
            return b;
        }
        /**
         *  读取小端byte数组为short
         * @param b
         * @return
         */
        public static short byteToShortLittle(byte[] b) {
            return (short) (((b[1] << 8) | b[0] & 0xff));
        }
        /**
         *  读取大端byte数组为short
         * @param b
         * @return
         */
        public static short byteToShortBig(byte[] b) {
            return (short) (((b[0] << 8) | b[1] & 0xff));
        }
        /**
         * long类型转byte[] (大端)
         * @param n
         * @return
         */
        public static byte[] longToBytesBig(long n) {
            byte[] b = new byte[8];
            b[7] = (byte) (n & 0xff);
            b[6] = (byte) (n >> 8  & 0xff);
            b[5] = (byte) (n >> 16 & 0xff);
            b[4] = (byte) (n >> 24 & 0xff);
            b[3] = (byte) (n >> 32 & 0xff);
            b[2] = (byte) (n >> 40 & 0xff);
            b[1] = (byte) (n >> 48 & 0xff);
            b[0] = (byte) (n >> 56 & 0xff);
            return b;
        }
        /**
         * long类型转byte[] (小端)
         * @param n
         * @return
         */
        public static byte[] longToBytesLittle(long n) {
            byte[] b = new byte[8];
            b[0] = (byte) (n & 0xff);
            b[1] = (byte) (n >> 8  & 0xff);
            b[2] = (byte) (n >> 16 & 0xff);
            b[3] = (byte) (n >> 24 & 0xff);
            b[4] = (byte) (n >> 32 & 0xff);
            b[5] = (byte) (n >> 40 & 0xff);
            b[6] = (byte) (n >> 48 & 0xff);
            b[7] = (byte) (n >> 56 & 0xff);
            return b;
        }
           /**
         * byte[]转long类型(小端)
         * @param array
         * @return
         */
        public static long bytesToLongLittle( byte[] array )
        {
            return ((((long) array[ 0] & 0xff) << 0)
                    | (((long) array[ 1] & 0xff) << 8)
                    | (((long) array[ 2] & 0xff) << 16)
                    | (((long) array[ 3] & 0xff) << 24)
                    | (((long) array[ 4] & 0xff) << 32)
                    | (((long) array[ 5] & 0xff) << 40)
                    | (((long) array[ 6] & 0xff) << 48)
                    | (((long) array[ 7] & 0xff) << 56));
        }
    
        /**
         * byte[]转long类型(大端)
         * @param array
         * @return
         */
        public static long bytesToLongBig( byte[] array )
        {
            return ((((long) array[ 0] & 0xff) << 56)
                    | (((long) array[ 1] & 0xff) << 48)
                    | (((long) array[ 2] & 0xff) << 40)
                    | (((long) array[ 3] & 0xff) << 32)
                    | (((long) array[ 4] & 0xff) << 24)
                    | (((long) array[ 5] & 0xff) << 16)
                    | (((long) array[ 6] & 0xff) << 8)
                    | (((long) array[ 7] & 0xff) << 0));
        }
    

    2.Java的转换函数的简单理解

    要想理解这个函数,关键点有3点:数据类型,位移操作符,& 0xff。
    我们首先来看看 Java的数据类型。

    2.1 Java的数据类型

    image.png

    范围:


    image.png

    这里要了解原码,反码,补码,与真值。

    原码, 反码, 补码 详解
    Java不论是负数还是正数在定义、存储、计算的过程中,都是用其补码。

    來看一下int强转byte
    int a = 165在计算机中存储的 数据(机器数:一个数在计算机中的二进制表示形式)是
    0000 0000 0000 0000 0000 0000 1010 0101:
    占4 个字节,32位,最高位符号位为0,真值为165

    int强转为byte,机器数为
    1010 0101 :
    占1 个字节,8位,最高位的数据1变成了符号位,符号位变成1,真值为-37

    image.png
    int 强转为byte 丢失数据:丢失了原本符号位,并且丢失了一位数据,真值发生了改变。
    byte存储范围 byte范围 -128 - 127,也存不了165这个真值,因此存储的是-35,但是机器数是不变的。

    串口传输接口底层是按位(bit)发送的,上层是按byte发送和接收的,但协议为了方便描述,每个byte用十六进制数(0x00-0xFF)表示,范相当于十进制的0-255,而byte为八位且是有符号类型,相当于十进制的-128-127,明显0x8F-0xFF(128-255)是不能准确转换为byte的,因为165超过了byte的范围,不能直接赋值,只能强转。
    byte b = (byte) 0x5A;

    然后下层,取到该字节值后 该值进行符号扩展后 & 0xFF 转为165。
    1111 1111 1111 1111 1111 1111 1010 0101
    0000 0000 0000 0000 0000 0000 1111 1111


    0000 0000 0000 0000 0000 0000 1010 0101


    image.png

    这样才保证了真值一致性。

    符号扩展就是把所有高位重复符号位即可,0则全0,1则全1
    如果是无符号数,想要扩展n位 则是在前面添加n位0

    0000 0001扩展00 0000 0001

    补码数的符号扩展

    如果是补码数,想要扩展n位 看最高位是0还是1,是0则扩展0,是1则扩展1。

    如果想要扩展2位,如下例:
    扩展前: 1100 1111
    扩展后: 11 1100 1111
    扩展前: 0100 1111
    扩展后:00 0100 1111

    2.2 大端和小端

    举一个例子,比如数字0x12 34 56 78在内存中的表示形式。

    1)大端模式:Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。(其实大端模式才是我们直观上认为的模式,和字符串存储的模式差类似)即正序排列,高尾端;

    低地址 --------------------> 高地址

    0x0A | 0x0B | 0x0C | 0x0D

    image.png

    2)小端模式:Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。即逆序排列,低尾端;

    低地址 --------------------> 高地址

    0x0D | 0x0C | 0x0B | 0x0A


    image.png

    无论是小端模式还是大端模式,每个字节内部都是按顺序排列。

    一般操作系统都是小端,而通讯协议是大端的。

    实际中的例子

    虽然很多时候,字节序的工作已由编译器完成了,但是在一些小的细节上,仍然需要去仔细揣摩考虑,尤其是在以太网通讯、MODBUS通讯、软件移植性方面。这里,举一个MODBUS通讯的例子。在MODBUS中,数据需要组织成数据报文,该报文中的数据都是大端模式,即低地址存高位,高地址存低位。假设有一16位缓冲区m_RegMW[256],因为是在x86平台上,所以内存中的数据为小端模式:m_RegMW[0].low、m_RegMW[0].high、m_RegMW[1].low、m_RegMW[1].high……
    为了方便讨论,假设m_RegMW[0] = 0x3456; 在内存中为0x56、0x34。
    现要将该数据发出,如果不进行数据转换直接发送,此时发送的数据为0x56,0x34。而Modbus是大端的,会将该数据解释为0x5634而非原数据0x3456,此时就会发生灾难性的错误。所以,在此之前,需要将小端数据转换成大端的,即进行高字节和低字节的交换,此时可以调用步骤五中的函数BigtoLittle16(m_RegMW[0]),之后再进行发送才可以得到正确的数据。

    2.3 位移操作符,& 0xff 的作用

    2.3.1 位移操作符

    >>
    表示右移,如果该数为正,则高位补0,若为负数,则高位补1。
    >>
    表示左移,形如 a<<b,将 a 的各二进制位整体向左移 b 位,高位溢出位移出,低位补 0。
    >>>表示无符号右移。
    正数的无符号右移:与右移规则一致。
    负数的无符号右移:先将负数取反,得到反码,然后反码加1得到补码,补码再进行右移,这样得到的结果就是无符号右移的结果了。
    为负的十进制整数
    如:-64 >>> 5
    ①第一步:操作数的绝对值,转化为二进制
    100 0000
    ②第二步:补码(操作数为负数)
    1000 0000 0000 0000 0000 0000 0100 0000
    ③第三步:取反(符号位不变,其他位1、0相互转换),在第一位加上1
    1111 1111 1111 1111 1111 1111 1100 0000
    ④第四步:右移5位
    0000 0111 1111 1111 1111 1111 1111 1110
    结果为:134217726

    左移:低位补0
    右移: 高位补0或1

    2.3.2 & 0xff 的作用

    1. 取得低八位
      通常配合移位操作符>>使用

    定义为两个字节长度。这时候将两个字节长的长度信息,以Big-Endian的方式写到内存中

    out.write((message.length>>8)&0xff);//取高八位写入地址
    out.write(message.length&0xff);//取低八位写入高地址中
    

    例如,有个数字 0x1234,如果只想将低8位写入到内存中 0x1234&0xff
    0x1234表示为二进制0001 0010 0011 0100
    0xff 表示为二进制1111 1111
    两个数做与操作,显然将0xff补充到16位,就是高位补0
    此时0xff0000 0000 1111 1111

    与操作 1&0 =0 1&1 =1 这样 0x1234只能保留低八位的数 0000 0000 0011 0100也就是0x34
    2.保证机器数的一致性

    public static void main(String[] args) {
            byte b = -127;//10000001
            int a =  b;
            System.out.println(a);
            a =  b&0xff;
            System.out.println(a);
        }//输出结果-127,129
    

    b是8位的二进制数,在与上0xff(也就是 11111111),不就是其本身吗,输出在控制台结果为什么是129呢?

    首先计算机内的存储都是按照补码存储的,-127补码表示为 1000 0001

    int a = b;将byte 类型提升为int时候,b的补码提升为 32位,补码的高位补1,也就是

    1111 1111 1111 1111 1111 1111 1000 0001

    负数的补码转为原码,符号位不变,其他位取反,在加1,正数的补码,反码都是本身

    结果是 1000 0000 0000 0000 0000 0000 0111 1111表示为十进制 也是 -127

    也就是 当 byte -> int 能保证十进制数(真值)不变,但是有些时候比如文件流转为byte数组时候,我们不是关心的是十进制数(真值)有没有变,而是机器数有没有变,这时候需要&上0xff。

    本例子中,将byte转为int 高24位必将补1,此时补码显然发生变化,在与上0xff,将高24重新置0,

    这样能保证机器数不变,当然由于符号位发生变化,表示的十进制数(真值)就会变了

     1111 1111 1111 1111 1111 1111 1000 0001 
    &
     0000 0000 0000 0000 0000 0000 1111 1111
    

    结果是
    0000 0000 0000 0000 0000 0000 1000 0001

    结论:

    java中基本类型从小扩展到大的数据类型时候,正数因为符号位是0,无论如何都是补零扩展,但是负数补零扩展和补符号位扩展完全不同,
    负数补符号位扩展真值不变
    例如 byte>>>int -127自动按照补符号位扩展,在高24位补符号位1,真值不变。
    补零扩展,保证机器数的一致性,但是真值发生改变

    2.3.3 若不使用&0xff,什么情况不能正确转换?什么时候能够正确转换?

    Java int和byte数组互相转换时为什么要用到&0xff?
    1.int转byte数组时不需要用到&0xff,因为int转byte时,系统会自动将溢出的位数忽略。
    2.byte数组还原为int时,低24位截取出的3个byte符号位都为0时,不论是否使用&0xff都不会影响正常转换。

    image.png
    3.byte数组还原为int时,如果使用的int值拆分成的4个byte符号位中,低24位截取出的3个byte只要有一个符号位为1,只有使用&0xff才能正常转换。
    image.png

    参考链接:

    详解大端模式和小端模式
    大端模式和小端模式
    java基本类型与byte字节数组的转换(包含大端,小端)

    原码, 反码, 补码 详解
    补码/反码、零扩展和符号位扩展(Zero extension and Sign extension)
    笔记:扩展一个数字的位表示 无符号数的零扩展 补码数的符号扩展

    byte为什么要与上0xff?
    从强转 byte 说起
    详解 & 0xff 的作用
    Java int和byte数组互相转换时为什么要用到&0xff?

    运算符>>和>>>有什么区别
    位运算符之无符号右移(十进制整数)
    java中的左移、右移、无符号右移

    android串口通信接受自定义协议数据并解析问题

    相关文章

      网友评论

          本文标题:Java中byte与short、int、long互转(包含大端,

          本文链接:https://www.haomeiwen.com/subject/gmgowrtx.html