You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动