Android(Java)中获取Buffer/内存流地址及读写应用方法问询
嘿,在Android Java开发里琢磨Buffer内存地址和读写位置的事儿是吧?这问题在做底层交互、性能优化的时候特别常见,我给你捋得明明白白的:
一、先搞懂:Java层直接拿内存地址的限制
Java是托管语言,普通堆内对象的内存地址由JVM全权管理,咱们没法直接获取。但如果用的是NIO的DirectByteBuffer(堆外直接内存),倒是有途径拿到它的内存地址——不过得踩几个坑:
用Unsafe类获取DirectByteBuffer地址
Unsafe是Java提供的底层工具类,能直接操作内存,但它属于非公开API,Android 9+还会限制反射访问,所以只适合内部调试或者非公开应用:
import sun.misc.Unsafe; import java.nio.ByteBuffer; import java.lang.reflect.Field; public class BufferAddressHelper { public static long getDirectBufferAddress(ByteBuffer buffer) throws Exception { if (!buffer.isDirect()) { throw new IllegalArgumentException("只支持直接内存Buffer哦"); } // 反射获取Unsafe实例 Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); // 读取DirectByteBuffer的address字段 Field addressField = ByteBuffer.class.getDeclaredField("address"); addressField.setAccessible(true); return addressField.getLong(buffer); } }
⚠️ 注意:Unsafe没有边界检查,写错地址直接导致JVM崩溃,务必谨慎使用。
二、更可靠的方式:通过JNI获取内存地址
如果要做跨语言交互(比如和C/C++代码配合),JNI是官方推荐的稳妥方案,完全不受Java层的内存限制:
1. Java层代码
public class NativeBufferUtils { static { System.loadLibrary("buffer-native"); // 加载编译好的so库 } // 声明native方法:获取Buffer内存地址 public native long getDirectBufferAddress(ByteBuffer buffer); // 声明native方法:通过地址+偏移写入数据 public native void writeToBufferAtOffset(long baseAddr, int offset, byte[] data); }
2. JNI层(C++)代码
#include <jni.h> #include <cstring> extern "C" JNIEXPORT jlong JNICALL Java_com_example_yourapp_NativeBufferUtils_getDirectBufferAddress(JNIEnv *env, jobject thiz, jobject buffer) { // 直接获取DirectBuffer的内存地址 void* addr = env->GetDirectBufferAddress(buffer); return reinterpret_cast<jlong>(addr); } extern "C" JNIEXPORT void JNICALL Java_com_example_yourapp_NativeBufferUtils_writeToBufferAtOffset(JNIEnv *env, jobject thiz, jlong baseAddr, jint offset, jbyteArray data) { jbyte* dataPtr = env->GetByteArrayElements(data, nullptr); jsize dataLen = env->GetArrayLength(data); // 计算目标内存地址:基地址+偏移量 void* targetAddr = reinterpret_cast<void*>(baseAddr + offset); // 拷贝数据到指定位置 memcpy(targetAddr, dataPtr, dataLen); // 释放数组元素 env->ReleaseByteArrayElements(data, dataPtr, JNI_ABORT); }
三、不用内存地址也能指定读写位置(更安全的标准做法)
其实绝大多数场景下,完全没必要直接操作内存地址——NIO的Buffer本身就提供了position、limit、mark三个核心属性来控制读写位置,这是Java层最安全、最符合规范的用法:
- 设置读写起始位置:用
buffer.position(int newPosition),比如从第5个字节(索引4)开始读:buffer.position(4) - 限制读写范围:用
buffer.limit(int newLimit),比如只允许读到第10个字节:buffer.limit(10) - 标记与重置:
buffer.mark()标记当前position,之后用buffer.reset()回到标记点 - 翻转读写模式:
buffer.flip()把写模式切换为读模式,自动将position设为0,limit设为原position值
示例代码:
// 分配1KB的堆外直接内存 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 写入数据 buffer.put("Hello Android".getBytes()); // 切换为读模式 buffer.flip(); // 从第6个字节(索引5)开始读 buffer.position(5); byte[] result = new byte[7]; buffer.get(result); // result里会是 "Android"
四、非要用内存地址指定读写位置?(极端场景)
如果是和硬件交互、做极致性能优化这类极端场景,用Unsafe或JNI直接操作地址即可:
Unsafe方式写入指定偏移位置
public static void writeToAddress(long baseAddr, int offset, byte[] data) throws Exception { Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); // 直接把字节数组写入到基地址+偏移的位置 unsafe.putByteArray(baseAddr + offset, data, 0, data.length); }
⚠️ 再次提醒:一定要确保offset+数据长度不超过Buffer的总容量,否则会破坏内存导致崩溃。
内容的提问来源于stack exchange,提问作者Hossein Yazdanfar




