Java中byte与short、int、long互转(包含大端,
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的数据类型
![](https://img.haomeiwen.com/i4264767/58fd64810d33fa85.png)
范围:
![](https://img.haomeiwen.com/i4264767/5d8087fa07aa42af.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
![](https://img.haomeiwen.com/i4264767/de4bc68ce911218a.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
![](https://img.haomeiwen.com/i4264767/78043abf99c75148.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
![](https://img.haomeiwen.com/i4264767/61b385ec3fcbd7ef.png)
2)小端模式:Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。即逆序排列,低尾端;
低地址 --------------------> 高地址
0x0D | 0x0C | 0x0B | 0x0A
![](https://img.haomeiwen.com/i4264767/2eee512b9efc3839.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 的作用
- 取得低八位
通常配合移位操作符>>使用
定义为两个字节长度。这时候将两个字节长的长度信息,以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
此时0xff
为 0000 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?