比如在Java中声明了一个 int变量,这个变量在栈内存中分配内存地址,那当int值保存到栈内存中时是用大端还是小端?如何验证?
在 Java 中,int 类型变量在栈内存中的存储方式(大端或小端)是由 底层硬件架构
和 JVM 的实现
决定的。一般来说:
- x86 和 x86-64 平台上的 JVM 通常会按照 小端序 存储。
- ARM 平台可能会采用大端或小端,取决于其配置。
验证一个 int 变量在栈内存中是大端还是小端存储,需要通过一些间接方式,因为 Java 不允许直接操作内存地址。可以借助 JNI(Java Native Interface) 或 Unsafe 类来实现。
方法 1:通过 JNI 检查栈内存中的字节顺序
我们可以通过 JNI 编写一个本地方法来直接读取栈内存中保存的 int 变量的字节序。
class EndiannessChecker {// 声明本地方法external fun checkEndianness(value: Int)companion object {init {// 加载本地库System.loadLibrary("endianness")}}
}fun main() {val testValue = 0x12345678 // 一个明确的 32 位整数EndiannessChecker().checkEndianness(testValue)
}
#include <jni.h>
#include <stdio.h>// 实现 JNI 方法
extern "C"
JNIEXPORT void JNICALL
Java_EndiannessChecker_checkEndianness(JNIEnv *env, jobject obj, jint value) {unsigned char *bytePointer = (unsigned char *)&value;printf("Integer value in Hex: 0x%x\n", value);printf("Bytes in memory: ");for (int i = 0; i < sizeof(value); i++) {printf("0x%02x ", bytePointer[i]);}printf("\n");// 判断字节顺序if (bytePointer[0] == 0x78) {printf("Stack stores integers in Little-Endian order\n");} else if (bytePointer[0] == 0x12) {printf("Stack stores integers in Big-Endian order\n");} else {printf("Unknown endianness\n");}
}
运行结果:
-
如果是小端存储:
Integer value in Hex: 0x12345678 Bytes in memory: 0x78 0x56 0x34 0x12 Stack stores integers in Little-Endian order
-
如果是大端存储:
Integer value in Hex: 0x12345678 Bytes in memory: 0x12 0x34 0x56 0x78 Stack stores integers in Big-Endian order
在一台Android设备上运行,结果显示为小端。
方法2:用 Unsafe 验证字节序
import sun.misc.Unsafe
import java.lang.reflect.Fieldobject UnsafeHelper {val unsafe: Unsafeinit {val unsafeField: Field = Unsafe::class.java.getDeclaredField("theUnsafe")unsafeField.isAccessible = trueunsafe = unsafeField.get(null) as Unsafe}
}fun main() {val unsafe = UnsafeHelper.unsafe// 定义一个整数变量val testValue = 0x12345678println("Test Value (Hex): 0x${testValue.toString(16)}")// 在堆内存中分配 4 个字节来存储整数val memoryAddress = unsafe.allocateMemory(4)try {// 将整数值写入内存unsafe.putInt(memoryAddress, testValue)// 逐字节读取并打印内存中的值println("Bytes in memory:")for (i in 0 until 4) {val byteValue = unsafe.getByte(memoryAddress + i).toInt() and 0xFFprint("0x${byteValue.toString(16).padStart(2, '0')} ")}println()// 根据第一个字节的值判断字节序val firstByte = unsafe.getByte(memoryAddress).toInt() and 0xFFwhen (firstByte) {0x78 -> println("System is Little-Endian")0x12 -> println("System is Big-Endian")else -> println("Unknown Endianness")}} finally {// 释放分配的内存unsafe.freeMemory(memoryAddress)}
}
在Windows中运行,结果如下:
Test Value (Hex): 0x12345678
Bytes in memory:
0x78 0x56 0x34 0x12
System is Little-Endian
在Android中没有Unsafe
类。
方法3:使用ByteOrder.nativeOrder()
最简单,在Windows中在Android中皆可运行:
println(ByteOrder.nativeOrder())
我在Windows和Android中运行结果均为:LITTLE_ENDIAN
当我们使用一些对象来存储整数值时,它会用大端还是小端呢?这个不用猜,直接看JDK文档即可,比如,使用ByteBuffer
可以把int保存到字节数组中,如下:
fun main() {val byteBuffer = ByteBuffer.allocate(4)byteBuffer.asIntBuffer().put(0x12345678)
}
在ByteBuffer中有一个order()函数可以返回缓冲区的字节顺序,文档描述如下:
获取此缓冲区的字节顺序。
在读写多字节值以及为此字节缓冲区创建视图时使用该字节顺序。新创建的字节缓冲区的顺序始终为 BIG_ENDIAN。
所以,我们创建的ByteOrder
默认使用大端。实验如下:
fun main() {val byteBuffer = ByteBuffer.allocate(4)byteBuffer.asIntBuffer().put(0x12345678)// 以16进制的方式打印byteBuffer中的每个字节for (i in 0 until byteBuffer.limit()) {print("0x${String.format("%02X", byteBuffer.get(i))} ")}
}
运行结果如下:
0x12 0x34 0x56 0x78
我们也可以改变ByteBuffer
的字节顺序:
fun main() {val byteBuffer = ByteBuffer.allocate(4)byteBuffer.order(ByteOrder.LITTLE_ENDIAN)byteBuffer.asIntBuffer().put(0x12345678)// 以16进制的方式打印byteBuffer中的每个字节for (i in 0 until byteBuffer.limit()) {print("0x${String.format("%02X", byteBuffer.get(i))} ")}
}
运行结果如下:
0x78 0x56 0x34 0x12
另外写数据还有输出流,当写出整型数据时,使用大端还是小端,查看JDK文档即可,比如:DataOutputStream.writeInt(0x12345678)
,对于该函数,文档描述如下:
将一个 int 值以 4-byte 值形式写入基础输出流中,先写入高字节。
先写高字节,所以它是大端。
如果就想以小端方式写怎么办?可以自定义一个输出流,以小端方式写即可,如下:
public class LittleEndianOutputStream extends FilterOutputStream {public LittleEndianOutputStream(OutputStream out) {super(out);}public void writeInt(int v) throws IOException {out.write(v & 0xFF); // 写入最低字节out.write((v >>> 8) & 0xFF); // 写入次低字节out.write((v >>> 16) & 0xFF); // 写入次高字节out.write((v >>> 24) & 0xFF); // 写入最高字节}
}
反过来,如果要以大端写,无非就是调一下顺序:
public final void writeInt(int v) throws IOException {out.write((v >>> 24) & 0xFF);out.write((v >>> 16) & 0xFF);out.write((v >>> 8) & 0xFF);out.write((v) & 0xFF);
}
是好用的还是使用ByteBuffer
,因为可以给这个ByteBuffer
指定ByteOrder
为大端或小端。