如何实现byte[]与Java对象属性互转?Unsafe类导入遇阻
嘿,我来帮你搞定这个字节数组和对象互转的需求!你想要的unmarshall(targetObject, byte[])和反向打包的功能,其实有好几种靠谱的实现方式,而且完全可以避开sun.misc.Unsafe的坑——毕竟那个类是JDK内部黑科技,兼容性和安全性都不太友好。下面给你分场景推荐方案:
ByteBuffer实现(适合已知固定字节结构) 这个方法完全依赖JDK自带API,不需要额外依赖,还能精准控制每个字段的字节偏移、大小端细节,完美匹配你“已知byte[]结构”的场景。
首先,先定义和byte[]结构一一对应的目标对象:
public class ProtocolMessage { // 假设byte[]结构是: // 前4字节:int类型消息ID(大端) // 接下来8字节:long类型时间戳(大端) // 最后2字节:short类型状态码(大端) private int messageId; private long timestamp; private short statusCode; // 记得加构造器、getter和setter }
然后封装unmarshall和marshall方法:
import java.nio.ByteBuffer; import java.nio.ByteOrder; public class ByteConverter { // 把byte[]解析到目标对象 public static void unmarshall(ProtocolMessage target, byte[] bytes) { // 根据实际字节结构设置大小端(这里用大端示例) ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); target.setMessageId(buffer.getInt()); target.setTimestamp(buffer.getLong()); target.setStatusCode(buffer.getShort()); } // 把对象打包成byte[] public static byte[] marshall(ProtocolMessage source) { // 分配刚好容纳所有字段的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(4 + 8 + 2).order(ByteOrder.BIG_ENDIAN); buffer.putInt(source.getMessageId()); buffer.putLong(source.getTimestamp()); buffer.putShort(source.getStatusCode()); return buffer.array(); } }
用的时候就很简单:
// 反序列化:把byte[]填充到对象 ProtocolMessage msg = new ProtocolMessage(); ByteConverter.unmarshall(msg, receivedBytes); // 序列化:把对象转成byte[] byte[] sendBytes = ByteConverter.marshall(msg);
如果你的byte[]里有字符串、嵌套结构这类复杂类型,也可以通过ByteBuffer截取字节段后手动转换(比如用new String(buffer.array(), offset, length)转字符串),完全可控。
如果你的对象结构复杂,手动写ByteBuffer太繁琐,推荐用成熟的序列化框架,比如Protostuff(基于Protobuf但不需要写proto文件),性能高还省心。
首先添加Maven依赖(Gradle用户对应调整):
<dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.7.4</version> </dependency> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.7.4</version> </dependency>
然后封装通用的转换方法:
import io.protostuff.LinkedBuffer; import io.protostuff.ProtostuffIOUtil; import io.protostuff.Schema; import io.protostuff.runtime.RuntimeSchema; public class ProtostuffConverter { // 获取对象的序列化Schema private static <T> Schema<T> getSchema(Class<T> clazz) { return RuntimeSchema.getSchema(clazz); } // 反序列化:byte[]转对象 public static <T> void unmarshall(T target, byte[] bytes) { Schema<T> schema = getSchema((Class<T>) target.getClass()); ProtostuffIOUtil.mergeFrom(bytes, target, schema); } // 序列化:对象转byte[] public static <T> byte[] marshall(T source) { Schema<T> schema = getSchema((Class<T>) source.getClass()); LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { return ProtostuffIOUtil.toByteArray(source, schema, buffer); } finally { buffer.clear(); } } }
使用起来超级简单,不管对象多复杂都能自动处理:
// 反序列化 ProtocolMessage msg = new ProtocolMessage(); ProtostuffConverter.unmarshall(msg, receivedBytes); // 序列化 byte[] sendBytes = ProtostuffConverter.marshall(msg);
如果需要精准控制字段的字节顺序或者映射关系,还可以用Protostuff的@Tag注解给字段指定顺序,完全满足定制需求。
sun.misc.Unsafe的导入问题(不推荐) 如果你非要用Unsafe,先提醒你:这个类是JDK内部API,不同版本兼容性差,还容易出内存安全问题,除非有极端性能需求,否则不建议用。不过还是给你解决导入问题的方法:
- Java 8及更早版本:可以直接导入
sun.misc.Unsafe,但IDE可能会报警告(因为是内部API),忽略即可。 - Java 9及之后版本:需要给JVM添加启动参数
--add-exports java.base/sun.misc=ALL-UNNAMED,才能允许访问这个内部类。
然后获取Unsafe实例的方式(因为Unsafe的构造器是私有的,只能通过反射获取):
import sun.misc.Unsafe; import java.lang.reflect.Field; public class UnsafeHelper { public static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } }
之后的内存操作就需要你手动处理每个字段的偏移量和类型,代码可读性极差,这里给个简单示例:
public class UnsafeConverter { public static void unmarshall(ProtocolMessage target, byte[] bytes) throws Exception { Unsafe unsafe = UnsafeHelper.getUnsafe(); // 获取messageId字段的内存偏移量 long messageIdOffset = unsafe.objectFieldOffset(ProtocolMessage.class.getDeclaredField("messageId")); // 从byte[]中读取int值,写入对象的对应字段 unsafe.putInt(target, messageIdOffset, unsafe.getInt(bytes, (long) unsafe.arrayBaseOffset(byte[].class))); // 其他字段同理,需要手动处理每个字段的偏移和类型 } public static byte[] marshall(ProtocolMessage source) throws Exception { Unsafe unsafe = UnsafeHelper.getUnsafe(); byte[] bytes = new byte[4 + 8 + 2]; // 总字节数和对象字段匹配 long byteBaseOffset = unsafe.arrayBaseOffset(byte[].class); // 把对象的messageId字段值写入byte[] long messageIdOffset = unsafe.objectFieldOffset(ProtocolMessage.class.getDeclaredField("messageId")); unsafe.putInt(bytes, byteBaseOffset, unsafe.getInt(source, messageIdOffset)); // 其他字段同理 return bytes; } }
再次强调:除非万不得已,别用Unsafe,前面两个方案足够覆盖绝大多数场景。
内容的提问来源于stack exchange,提问作者The Developer

