Java 大端、小端存储,short、int 转 byte数组,& 0xFF 清除高位符号扩展,保留低 8 位。

字节序:
指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序,有大端和小端两种方式
大端:
指高位字节存放在内存的低地址端,低位字节存放在内存的高地址端。
小端:
指低位字节放在内存的低地址端,高位字节放在内存的高地址端。

获取CPU使用的存储方式

import java.nio.ByteOrder;

public class Client {

  public static void main(String[] args) {
    System.out.println(ByteOrder.nativeOrder());  //LITTLE_ENDIAN
  }
}

示例:

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

public class Client {

  public static void main(String[] args) {
        System.out.println(ByteOrder.nativeOrder());

        int x = 0x01020304;
        ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[4]);
        byteBuffer.order(ByteOrder.BIG_ENDIAN);
        byteBuffer.asIntBuffer().put(x);
        String before = Arrays.toString(byteBuffer.array());
        System.out.println("默认字节序:" + byteBuffer.order().toString() + "," + "内存数据:" + before);

        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.asIntBuffer().put(x);
        String after = Arrays.toString(byteBuffer.array());
        System.out.println("小端字节序:" + byteBuffer.order().toString() + "," + "内存数据:" + after);
  }
}

// 默认字节序:BIG_ENDIAN,内存数据:[1, 2, 3, 4]
// 小端字节序:LITTLE_ENDIAN,内存数据:[4, 3, 2, 1]

类型转换

Java 中,short 2个字节8位、int 4个字节32位、byte 1个字节 8位
& :相同数为1 不同数为0,“0001 1000” & “1111 1111 ” = 0001 1000

& 0xFF :清除高位符号扩展,保留低 8 位。


@Test
void byteTest() {

    byte hex1 = (byte) 127;
    byte hex2 = (byte) 383;
    byte hex3 = (byte) 0x7F;

    RandomAccessFile writeFile = null;
    try {
        //写入头文件
        writeFile = new RandomAccessFile("D:\\temp\\hex.txt", "rw");
        byte[] bytes = new byte[3];
        bytes[0] = hex1;
        bytes[1] = hex2;
        bytes[2] = hex3;
        writeFile.write(bytes, 0, bytes.length);
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
    } finally {
        try {
            if (writeFile != null) {
                writeFile.close();
            }
        } catch (Exception ex) {

        }
    }
    logger.info("成功");
}
 

@Test
void lowerOrderTest() {
    // 65304 = 1111 1111 0001 1000

    int h1 = ((65304));  //直接返回原数据 => 65304
    int h2 = ((65304 >> 4)); //1111 1111 0001 1000 => 右移4位,高位补0 => 0000 1111 1111 0001 => 1111 1111 0001 => 4081

    /**
     *  取低8位(FF)
     *  0xFF = 11111111
     *  &:相同数为1 不同数为0
     *  1111 1111 0001 1000
     *            1111 1111
     *  0000 0000 0001 1000
     *  11000 => 24
     */
    int h3 = ((65304) & 0xFF);

    /**
     *  >> 4  先右移4位, 高位补0
     *  1111 1111 0001 1000
     *  0000 1111 1111 0001
     *  & 0xFF 取低8位
     *  0000 1111 1111 0001
     *            1111 1111
     *  0000 0000 1111 0001
     *  11110001 => 241
     */
    int h4 = ((65304 >> 4) & 0xFF);

    logger.info("\n{}\n{}\n{}\n{}",h1,h2,h3,h4);
}
 short、int 转byte数组
/**
 * 32536 => 0111 1111 0001 1000 最左边为符号位
 * 默认从低位到高位的顺序取值
 * b[0] = 111 1111 0001 1000 & 0xFF = 0001 1000 = 24
 * b[1] = 111 1111 0001 1000 >> 8 & 0xFF = 0111 1111 = 127
 */
public static byte[] shortToBytes(short shortValue, ByteOrder byteOrder) {
    byte[] b = new byte[Short.BYTES];
    if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
        b[0] = (byte) (shortValue & 0xFF);
        b[1] = (byte) ((shortValue >> Byte.SIZE) & 0xff);
    } else {
        b[1] = (byte) (shortValue & 0xFF);
        b[0] = (byte) ((shortValue >> Byte.SIZE) & 0xff);
    }
    return b;
}


/**
 * int 转 byte 同理
 */
public static byte[] intToBytes(int intValue, ByteOrder byteOrder) {

    if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
        return new byte[]{
                (byte) (intValue & 0xFF),
                (byte) ((intValue >> 8) & 0xFF),
                (byte) ((intValue >> 16) & 0xFF),
                (byte) ((intValue >> 24) & 0xFF)
        };

    } else {
        return new byte[]{
                (byte) ((intValue >> 24) & 0xFF),
                (byte) ((intValue >> 16) & 0xFF),
                (byte) ((intValue >> 8) & 0xFF),
                (byte) (intValue & 0xFF)
        };
    }
}

longToBytes

/**
 * @param longValue
 * @return
 */
public static byte[] longToBytes(long longValue) {
    return longToBytes(longValue, DEFAULT_ORDER);
}

public static byte[] longToBytes(long longValue, ByteOrder byteOrder) {
    byte[] result = new byte[Long.BYTES];
    if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
        for (int i = 0; i < result.length; i++) {
            result[i] = (byte) (longValue & 0xFF);
            longValue >>= Byte.SIZE;
        }
    } else {
        for (int i = (result.length - 1); i >= 0; i--) {
            result[i] = (byte) (longValue & 0xFF);
            longValue >>= Byte.SIZE;
        }
    }
    return result;
}

byte[] to long


public static long bytesToLong(byte[] bytesValue) {
    return bytesToLong(bytesValue, DEFAULT_ORDER);
}

public static long bytesToLong(byte[] bytesValue, ByteOrder byteOrder) {
    if (bytesValue.length > Long.BYTES) {
        throw new IllegalArgumentException("Byte array too long (max 8 bytes)");
    }
    long result = 0;
    if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
        // 小端序:低字节在前(索引 0 是最低字节)
        for (int i = 0; i < bytesValue.length; i++) {
            result |= (bytesValue[i] & 0xFFL) << (i * 8);
        }
    } else {
        // 大端序:高字节在前(索引 0 是最高字节)
        for (int i = 0; i < bytesValue.length; i++) {
            result |= (bytesValue[i] & 0xFFL) << ((bytesValue.length - 1 - i) * 8);
        }
    }
    return result;
}

参考 longToBytes 方法,我们可以写出对应的 bytesToLong 方法,按照指定的字节序(ByteOrder)将 byte[] 转换为 long

实现代码

import java.nio.ByteOrder;

public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) {
    if (bytes.length > Long.BYTES) {
        throw new IllegalArgumentException("Byte array too long (max 8 bytes)");
    }

    long result = 0;
    if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
        // 小端序:低字节在前(索引 0 是最低字节)
        for (int i = 0; i < bytes.length; i++) {
            result |= (bytes[i] & 0xFFL) << (i * 8);
        }
    } else {
        // 大端序:高字节在前(索引 0 是最高字节)
        for (int i = 0; i < bytes.length; i++) {
            result |= (bytes[i] & 0xFFL) << ((bytes.length - 1 - i) * 8);
        }
    }
    return result;
}

使用示例

byte[] bytes = HexUtil.decodeHex("00001A48"); // 4 字节数据 [0x00, 0x00, 0x1A, 0x48]

long bigEndianValue = bytesToLong(bytes, ByteOrder.BIG_ENDIAN); // 0x00001A48 → 6728
long littleEndianValue = bytesToLong(bytes, ByteOrder.LITTLE_ENDIAN); // 0x481A0000 → 1208483840

System.out.println("Big-Endian: " + bigEndianValue);    // 6728
System.out.println("Little-Endian: " + littleEndianValue); // 1208483840

说明:

  1. 大端序(Big-Endian):高位字节在前(索引 0 是最高字节),适用于网络传输、Java 默认存储方式。
    • 00 00 1A 480x00001A486728
  2. 小端序(Little-Endian):低位字节在前(索引 0 是最低字节),适用于 x86 CPU、Windows。
    • 00 00 1A 480x481A00001208483840
  3. & 0xFFL:确保 byte 转换为 long 时是无符号的(防止符号位扩展)。
  4. 移位计算
    • 大端序:最高字节左移 (length-1-i)*8 位。
    • 小端序:最低字节左移 i*8 位。

更简洁的实现(使用 ByteBuffer

如果允许使用 java.nio.ByteBuffer,可以更简单地实现:

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) {
    ByteBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder);
    if (bytes.length == Long.BYTES) {
        return buffer.getLong();
    } else if (bytes.length == Integer.BYTES) {
        return buffer.getInt() & 0xFFFFFFFFL; // 无符号 int → long
    } else {
        throw new IllegalArgumentException("Byte array must be 4 or 8 bytes long");
    }
}

总结

  • 手动实现:适用于无 ByteBuffer 依赖的场景,灵活处理任意长度 byte[]
  • ByteBuffer 实现:代码更简洁,但要求 byte[] 长度匹配 long(8 字节)或 int(4 字节)。

在 Java 的 ByteBuffer 中,allocate(8)wrap(bytes) 是两种不同的初始化方式,主要区别在于内存分配方式初始数据。以下是它们的详细对比:


1. ByteBuffer.allocate(8)

特点

  • 新分配一个固定大小的堆内存(Heap Buffer),默认填充 0
  • 适用于从头构建 ByteBuffer,比如手动写入数据。
  • 如果后续 put(byte[]) 的字节数 < 8,剩余部分保持 0

示例

ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
buffer.put(new byte[]{0x12, 0x34}); // 只写入 2 字节
buffer.flip();

// 读取 8 字节(剩余 6 字节是 0)
long value = buffer.getLong();
System.out.println(value); // 0x0000000000001234 → 4660

内存示意图

初始状态:[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
写入后:[0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

2. ByteBuffer.wrap(bytes)

特点

  • 直接包装现有的 byte[](不重新分配内存,共享底层数组)。
  • 适用于直接操作已有数据,避免拷贝。
  • 如果 bytes.length < 8,读取 getLong() 会抛出 BufferUnderflowException

示例

byte[] bytes = {0x12, 0x34};
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);

// 会抛出异常,因为 bytes.length=2 < 8
long value = buffer.getLong(); // ❌ BufferUnderflowException

修正方案

如果 byte[] 不足 8 字节,需要手动补零:

byte[] bytes = {0x12, 0x34};
ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
buffer.position(8 - bytes.length); // 自动补零
buffer.put(bytes); // 前 2 字节写入,剩余 6 字节自动补 0
buffer.flip();
long value = buffer.getLong(); // ✅ 0x0000000000001234 → 4660

内存示意图

原始数组:[0x12, 0x34]
包装后直接操作原数组,无法扩展长度。

关键区别总结

特性 allocate(8) wrap(bytes)
内存来源 新分配堆内存(全零初始化) 直接包装现有 byte[](共享内存)
是否自动补零 ✅ 是(如果写入数据不足 8 字节) ❌ 否(原数组长度固定)
适用场景 需要构建新 ByteBuffer(如编码) 直接操作现有数据(如解码)
安全性 安全(固定长度 8) 需确保 bytes.length >= 8

如何选择?

  1. 如果数据长度可能不足 8 字节 → 用 allocate(8) + put(bytes)(自动补零)。
  2. 如果数据长度确定是 8 字节 → 用 wrap(bytes)(零拷贝,更高效)。
  3. 如果数据来自网络/文件 → 通常用 wrap(bytes),但需检查长度。

正确用法示例

// 场景1:数据长度不确定(自动补零)
byte[] partialData = {0x11, 0x22};
ByteBuffer buffer1 = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
buffer1.position(8 - partialData.length); // 自动补零
buffer1.put(partialData);
buffer1.flip();
long result1 = buffer1.getLong(); // 0x0000000000001122 → 4386

// 场景2:数据长度固定 8 字节(零拷贝)
byte[] fullData = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0x88};
ByteBuffer buffer2 = ByteBuffer.wrap(fullData).order(ByteOrder.BIG_ENDIAN);
long result2 = buffer2.getLong(); // 0x1122334455667788 → 1234605616436508552

常见问题

Q1: 为什么 wrap(bytes) 读取不足 8 字节会抛异常?

因为 getLong() 严格要求剩余字节数 ≥ 8,而 wrap(bytes) 不会自动扩容。

Q2: 哪种方式性能更好?

  • wrap(bytes) 更高效(无拷贝),但要求数据长度正确。
  • allocate(8) 更安全,但多一次内存分配和可能的补零操作。

Q3: 如何避免 BufferUnderflowException

  • 检查长度:if (bytes.length >= Long.BYTES) { ... }
  • 或用 allocate(8) 自动补零。

通过理解这两种方式的区别,可以更灵活地处理 byte[]long 的转换!

posted @ 2022-05-07 11:17  VipSoft  阅读(415)  评论(0)    收藏  举报