一、unsafe api 详情 ---jdk1.8

概述

Unsafe是Java中一个底层类,包含了很多基础的操作,比如数组操作、对象操作、内存操作、CAS操作、线程(park)操作、栅栏(Fence)操作,JUC包、一些三方框架都使用Unsafe类来保证并发安全。

API 详情

1. object field( memory offset ) operate

// object field( memory offset ) operate /
//Object get 指定对象的偏移地址数据
public native int getInt(Object o, long offset);
public native boolean getBoolean(Object o, long offset);
public native byte    getByte(Object o, long offset);
public native short   getShort(Object o, long offset);
public native char    getChar(Object o, long offset);
public native long    getLong(Object o, long offset);
public native float   getFloat(Object o, long offset);
public native double  getDouble(Object o, long offset);
public native Object getObject(Object o, long offset);

//Object put  设置指定对象偏移地址数据
public native void putInt(Object o, long offset, int x);
public native void    putBoolean(Object o, long offset, boolean x);
public native void    putByte(Object o, long offset, byte x);
public native void    putShort(Object o, long offset, short x);
public native void    putChar(Object o, long offset, char x);
public native void    putLong(Object o, long offset, long x);
public native void    putFloat(Object o, long offset, float x);
public native void    putDouble(Object o, long offset, double x);
public native void putObject(Object o, long offset, Object x);

// getInt getBoolean getByte getShort getChar getLong getFloat getDouble getObject
// putInt putBoolean putByte putShort putChar putLong putFloat putDouble putObject

2. memory(physical memory) java base type operate

// memory(physical memory) java base type operate /
//get指定内存地址数据(address)

public native byte    getByte(long address);
public native short   getShort(long address);
public native char    getChar(long address);
public native int     getInt(long address);
public native long    getLong(long address);
public native float   getFloat(long address);
public native double  getDouble(long address);

//设置指定内存地址数据(address)
public native void    putByte(long address, byte x);
public native void    putShort(long address, short x);
public native void    putChar(long address, char x);
public native void    putInt(long address, int x);
public native void    putLong(long address, long x);
public native void    putFloat(long address, float x);
public native void    putDouble(long address, double x);

// getInt getBoolean getByte getShort getChar getLong getFloat getDouble 
// putInt putBoolean putByte putShort putChar putLong putFloat putDouble 

4. address operate

// address operate /

/**
 * 分配内存
 * 从给定的内存地址获取本机指针。如果地址为零,或者没有指向从allocateMemory获得的块,则结果是未定义的。
 * 如果本机指针的宽度小于64位宽,则将其作为无符号数字扩展为Java长度。指针可以通过任何给定的字节偏移量进行索引,
 * 只需将该偏移量(作为简单整数)添加到表示指针的长值即可。从目标地址实际读取的字节数可以通过查询addressSize来确定。
 **/
public native long getAddress(long address);

/**
 * 将本机指针存储到给定的内存地址中。如果地址为零,或者没有指向从allocateMemory获得的块,则结果是未定义的。
 * 实际写入目标地址的字节数可以通过查询addressSize来确定。
 * 将本机指针存储到给定的内存地址中。如果地址为零,或者没有指向从allocatemmemory (long)中获得的块,那么结果是未定义的。
 **/
public native void putAddress(long address, long x);

/** 报告通过putAddress存储的本机指针的大小(以字节为单位)。该值将为4或8。请注意,其他基元类型(如存储在本机内存块中)的大小完全由其信息内容决定。 **/
public native int addressSize();

public static final int ADDRESS_SIZE = theUnsafe.addressSize();

/** 报告本机内存页的大小(以字节为单位)。该值将始终为二次方。 **/
public native int pageSize();

5. memory operate

// memory operate /

/// wrappers for malloc, realloc, free:

/**
 * 分配一个新的本地内存块,其大小以字节为单位。内存的内容未初始化;它们通常是垃圾。生成的本机指针永远不会为零,
 * 并且将对所有值类型进行对齐。通过调用freeMemory来处理此内存,或者通过重新分配内存来调整其大小。
 * 投掷: IllegalArgumentException–如果大小为负数或对于本机size_t类型太大 OutOfMemoryError–如果系统拒绝分配
 * 另请参阅:getByte(long),putByte(长,字节)
 **/
public native long allocateMemory(long bytes);


/**
 * 将新的本地内存块调整为给定的字节大小。超过旧块大小的新块的内容未初始化;它们通常是垃圾。当且仅当请求的大小为零时,
 * 生成的本机指针将为零。生成的本机指针将针对所有值类型进行对齐。通过调用freeMemory来处理此内存,或者通过重新分配内存来调整其大小。
 * 传递给此方法的地址可能为null,在这种情况下将执行分配。
 * 投掷 IllegalArgumentException–如果大小为负数或对于本机size_t类型太大 OutOfMemoryError–如果系统拒绝分配
 **/
public native long reallocateMemory(long address, long bytes);



/**
 * 将给定内存块中的所有字节设置为固定值(通常为零)。
 * 该方法通过两个参数来确定块的基址,因此它提供了(实际上)双寄存器寻址模式,如getInt(Object,long)中所述。当对象引用为null时,偏移量提供一个绝对的基地址。
 * 存储是以大小由地址和长度参数确定的相干(原子)单元为单位的。如果有效地址和长度都是偶数模8,则存储以“长”为单位。如果有效地址和长度是(分别)偶数模4或2,则存储以“int”或“short”为单位。
 * 自:1.7
 * 将给定内存块中的所有字节设置为固定值(通常是0)。内存块的地址由对象引用o和偏移地址共同决定,如果对象引用o为null,offset就是绝对地址。第三个参数就是内存块的大小,
 * 如果使用allocateMemory进行内存开辟的话,这里的值应该和allocateMemory的参数一致。value就是设置的固定值,一般为0(这里可以参考netty的DirectByteBuffer)。
 **/
public native void setMemory(Object o, long offset, long bytes, byte value);

public void setMemory(long address, long bytes, byte value) {
        setMemory(null, address, bytes, value);
    }

/**
 * 将给定内存块中的所有字节设置为另一个块的副本。
 * 该方法通过两个参数来确定每个块的基址,因此它提供了(实际上)双寄存器寻址模式,如getInt(Object,long)中所述。当对象引用为null时,偏移量提供一个绝对的基地址。
 * 传输以大小由地址和长度参数确定的相干(原子)单元为单位。如果有效地址和长度都是偶数模8,则传输以“长”为单位进行。如果有效地址和长度是(分别)偶数模4或2,则传输以“int”或“short”为单位。
 * 自:1.7
 * copyMemory 从 srcBase 的 偏移量 srcOffset 开始,拷贝到 destBase 的 destOffset,拷贝 bytes 个字节。
 **/
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);

public void copyMemory(long srcAddress, long destAddress, long bytes) {
        copyMemory(null, srcAddress, null, destAddress, bytes);
    }


/**
 * 处理从allocateMemory或reallocateMemory获得的本地内存块。传递给此方法的地址可能为null,在这种情况下不采取任何操作。
 * 另请参阅 分配内存
 **/
public native void freeMemory(long address);







/// random queries ---随机查询

public static final int INVALID_FIELD_OFFSET   = -1;

6. static field operate

// static field operate /

/**
 * 报告给定字段在其类的存储分配中的位置。不要期望在此偏移量上执行任何类型的运算;它只是一个cookie,被传递给不安全的堆内存访问器。
 * 任何给定的字段都将始终具有相同的偏移量和基数,并且同一类的两个不同字段都不会具有相同的偏置量和基数。
 * 从1.4.1开始,字段的偏移量表示为长值,尽管Sun JVM不使用最高有效的32位。然而,将静态字段存储在绝对地址的JVM实现可以使用长偏移量和空基指针,
 * 以getInt(Object,long)可用的形式来表达字段位置。因此,将移植到64位平台上的此类JVM的代码必须保留静态字段偏移的所有位。
 * 另请参阅:getInt(对象,长)
 * 回给定的静态属性在它的类的存储分配中的位置(偏移地址)。
 **/
public native long staticFieldOffset(Field f);





/**
 * 结合staticFieldBase报告给定静态字段的位置。
 * 不要期望在此偏移量上执行任何类型的运算;它只是一个cookie,被传递给不安全的堆内存访问器。
 * 任何给定的字段都将始终具有相同的偏移量,并且同一类的两个不同字段都不会具有相同的偏置量。
 * 从1.4.1开始,字段的偏移量表示为长值,尽管Sun JVM不使用最高有效的32位。很难想象JVM技术需要超过几个比特来编码非数组对象中的偏移量。然而,为了与此类中的其他方法保持一致,该方法将其结果报告为长值。
 * 另请参阅:getInt(对象,长)
 * 返回给定的非静态属性在它的类的存储分配中的位置(偏移地址)。
 **/
public native long objectFieldOffset(Field f);





/**
 * 结合staticFieldOffset报告给定静态字段的位置。
 * 获取基“Object”(如果有的话),通过它可以通过getInt(Object,long)等方法访问给定类的静态字段。此值可能为null。
 * 此值可能指的是一个“cookie”对象,不能保证它是一个真正的对象,并且除了作为此类中get和put例程的参数外,不应以任何方式使用它。
 * 返回给定的静态属性的位置,配合staticFieldOffset方法使用。实际上,这个方法返回值就是静态属性所在的Class对象的一个内存快照。
 **/
public native Object staticFieldBase(Field f);

7. array operate

// array operate /

/**
 * 报告给定数组类的存储分配中第一个元素的偏移量。如果arrayIndexScale为同一类返回非零值,
 * 则可以使用该比例因子和此基本偏移量来形成新的偏移量,以访问给定类的数组元素。
 * 另请参阅: getInt(Object,long),putInt(对象,long,int)
 * Arrays和其他的java对象都有对象的头信息,存储在实际数据的最前面。arrayBaseoffset:返回数组第一个元素地址相对于数组起始地址的偏移量(也就是对象头的长度)。
 **/
public native int arrayBaseOffset(Class<?> arrayClass);

public static final int ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);

public static final int ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);

public static final int ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);

public static final int ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);

public static final int ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);

public static final int ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);

public static final int ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);

public static final int ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);

public static final int ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);

/**
 * 报告给定阵列类的存储分配中寻址元素的比例因子。
 * 然而,“窄”类型的数组通常无法与getByte(Object,int)等访问器一起正常工作,因此此类的比例因子报告为零。
 * 另请参阅: arrayBaseOffset,getInt(Object,long),putInt(对象,long,int)
 * 数组中元素的大小通过arrayIndexScale获得,即第n+1个元素的开始位置应该是 :arrayOffset + n * arrayScale
 **/
public native int arrayIndexScale(Class<?> arrayClass);

public static final int ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);

public static final int ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);

public static final int ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);

public static final int ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);

public static final int ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);

public static final int ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);

public static final int ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);

public static final int ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);

public static final int ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);

8. class memory operate

// class memory operate /

/** 告诉虚拟机定义一个类,而不进行安全检查。默认情况下,类加载器和保护域来自调用方的类。 **/
//定义一个类,返回类实例,此方法会跳过JVM的所有安全检查。默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例应该来源于调用者。
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);

/**
 * 定义一个类,但不要让类加载器或系统字典知道它。
 * 对于每个CP条目,相应的CP补丁必须为null或具有与其标签匹配的格式:
 * Integer、Long、Float、Double:java.lang中对应的包装器对象类型
 * Utf8:字符串(如果用作签名或名称,则必须具有合适的语法)
 * 类:任何java.lang.Class对象
 * 字符串:任何对象(不仅仅是java.lang.String)
 * InterfaceMethodRef:(NYI)要在调用站点的参数上调用的方法句柄
 * 参数: 用于链接、访问控制、保护域和类加载器的hostClass上下文
 * 参数: 类文件的数据字节
 * 参数: cpPatches在存在非null条目的情况下,替换数据中相应的CP条目
 * 定义一个匿名类,与Java8的lambda表达式相关,会用到该方法实现相应的函数式接口的匿名类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
 * hostClass:宿主类。
 * data:字节码字节数组。
 * cpPatches:替换常量池(Constant Pool)中的字面量得到的引用数组。
 **/
 
/**
 * VM Anonymous Class可以看作一种模板机制,如果程序要动态生成很多结构相同、只是若干变量不同的类的话,可以先创建出一个包含占位符常量的正常类作为模板,
 * 然后利用sun.misc.Unsafe#defineAnonymousClass()方法,传入该类(host class,宿主类或者模板类)以及一个作为"constant pool path"的数组来替换指定的常量为任意值,
 * 结果得到的就是一个替换了常量的VM Anonymous Class。
 * VM Anonymous Class从VM的角度看是真正的"没有名字"的,在构造出来之后只能通过Unsafe#defineAnonymousClass()返回出来一个Class实例来进行反射操作。
 * 定义匿名类  defineAnonymousClass方法可以创建一个不被虚拟机及系统字典所知的类。
 **/
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

/** 分配一个实例,但不运行任何构造函数。初始化类(如果尚未初始化)。 **/
public native Object allocateInstance(Class<?> cls) throws InstantiationException;

/** 检测给定的类是否需要初始化。这通常需要与获取类的静态字段基结合使用。退货:仅当对ensureClassInitialized的调用无效时为false  **/
public native boolean shouldBeInitialized(Class<?> c);

/** 请确保给定的类已初始化。这通常需要与获取类的静态字段基结合使用。 **/
public native void ensureClassInitialized(Class<?> c);

9. thread lock and block operate

// thread lock and block operate /

/** park阻塞线程 **/
public native void park(boolean isAbsolute, long time);

/**
 * 释放被park创建的在一个线程上的阻塞。
 * 这个方法也可以被使用来终止一个先前调用park导致的阻塞。
 * 这个操作是不安全的,因此必须保证线程是存活的(thread has not been destroyed)。
 * 从Java代码中判断一个线程是否存活的是显而易见的,但是从native代码中这机会是不可能自动完成的。
 **/
public native void unpark(Object thread);

10. 同步机制 operate

// 同步机制  operate /
//example: synchronized reentrantlock lock

/** Lock the object. It must get unlocked via monitorExit. 进入对象的监视器 **/
@Deprecated
public native void monitorEnter(Object o);

/** Unlock the object. It must have been locked via monitorEnter. 退出对象的监视器**/ 
@Deprecated
public native void monitorExit(Object o);

/** Tries to lock the object. Returns true or false to indicate whether the lock succeeded. If it did, the object must be unlocked via monitorExit. **/
@Deprecated
public native boolean tryMonitorEnter(Object o);

11. CAS操作

/ CAS操作 /

/** Atomically update Java variable to x if it is currently holding expected.  Returns: true if successful **/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);

/** Atomically update Java variable to x if it is currently holding expected.  Returns: true if successful **/
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

/** 同上 **/
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);

12. Volatile原子有序操作

/ Volatile原子有序操作 / 

/**
 * 使用volatile加载语义从给定的Java变量中获取引用值。否则与getObject相同(Object,long)
 * 此方法和上面的getObject功能类似,不过附加了'volatile'加载语义,也就是强制从主存中获取属性值。
 * 类似的方法有getIntVolatile、getDoubleVolatile等等。这个方法要求被使用的属性被volatile修饰,否则功能和getObject方法相同。
 **/
public native Object getObjectVolatile(Object o, long offset);
public native int     getIntVolatile(Object o, long offset);
public native boolean getBooleanVolatile(Object o, long offset);
public native byte    getByteVolatile(Object o, long offset);
public native short   getShortVolatile(Object o, long offset);
public native char    getCharVolatile(Object o, long offset);
public native long    getLongVolatile(Object o, long offset);
public native float   getFloatVolatile(Object o, long offset);
public native double  getDoubleVolatile(Object o, long offset);




/**
 * 此方法和上面的putObject功能类似,不过附加了'volatile'加载语义,
 * 也就是设置值的时候强制(JMM会保证获得锁到释放锁之间所有对象的状态更新都会在锁被释放之后)更新到主存,
 * 从而保证这些变更对其他线程是可见的。类似的方法有putIntVolatile、putDoubleVolatile等等。
 * 这个方法要求被使用的属性被volatile修饰,否则功能和putObject方法相同。
 **/
public native void    putObjectVolatile(Object o, long offset, Object x);
public native void    putIntVolatile(Object o, long offset, int x);
public native void    putBooleanVolatile(Object o, long offset, boolean x);
public native void    putByteVolatile(Object o, long offset, byte x);
public native void    putShortVolatile(Object o, long offset, short x);
public native void    putCharVolatile(Object o, long offset, char x);
public native void    putLongVolatile(Object o, long offset, long x);
public native void    putFloatVolatile(Object o, long offset, float x);
public native void    putDoubleVolatile(Object o, long offset, double x);

13. 有序操作

/ 有序操作 /

/*
 * 设置o对象中offset偏移地址offset对应的Object型field的值为指定值x。
 * 这是一个有序或者有延迟的putObjectVolatile方法,并且不保证值的改变被其他线程立即看到。
 * 只有在field被volatile修饰并且期望被修改的时候使用才会生效。
 * 类似的方法有putOrderedInt和putOrderedLong。
 **/
public native void    putOrderedObject(Object o, long offset, Object x);

public native void    putOrderedInt(Object o, long offset, int x);

public native void    putOrderedLong(Object o, long offset, long x);

14. memory 屏障 禁止重排保证

/ memory 	 屏障 禁止重排保证 / 
	
/** 确保在围栏(屏障)之前的装载与围栏(屏障)之后的装载之间没有重新排序。 1.loadFence(加载屏障):在读取从存储器或其他设备中读取的数据之前,必须确保所有读或写访问都已完成。**/
public native void loadFence();	

/** 确保围栏(屏障)前的写操作没有重新排序,围栏(屏障)后的写操作没有装载或重新排序。2.storeFence(存储屏障):在将数据写入内存或其他设备之前,必须确保先完成了所有读取或写入访问 **/
public native void storeFence();

/** 确保围栏(屏障)之前的装载或存储与围栏(屏障)之后的装载或储存之间没有重新排序。 3.fullFence(全屏障):在读取和写入数据之前,必须确保所有读/写访问均已完成。**/
public native void fullFence();

15. cpu system loadavg monitor operate

// cpu system loadavg monitor operate /

/** 获取系统的平均负载值,loadavg这个double数组将会存放负载值的结果 **/
/**
 * 获取系统运行队列中分配给不同时间段内平均可用处理器的平均负载。此方法检索给定的nelem样本,并将其分配给给定loadavg数组的元素。该系统最多施加3个样本,分别代表过去1分钟、5分钟和15分钟的平均值。
 * 退货:实际检索到的样本数量;如果无法获得平均负载,则为-1。
 * 参数 loadavg一个两倍大小的nelem数组
 * 参数 nelem要检索的样本数,必须为1到3。
 **/
public native int getLoadAverage(double[] loadavg, int nelems);

16. exception

public native void throwException(Throwable ee);
	
private static void throwIllegalAccessError() {
       throw new IllegalAccessError();
    }	

17. Deprecated method

/ The following contain CAS-based Java implementations used on
// platforms not supporting native instructions

public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

public final long getAndAddLong(Object o, long offset, long delta) {
        long v;
        do {
            v = getLongVolatile(o, offset);
        } while (!compareAndSwapLong(o, offset, v, v + delta));
        return v;
    }

public final int getAndSetInt(Object o, long offset, int newValue) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, newValue));
        return v;
    }

public final long getAndSetLong(Object o, long offset, long newValue) {
        long v;
        do {
            v = getLongVolatile(o, offset);
        } while (!compareAndSwapLong(o, offset, v, newValue));
        return v;
    }

public final Object getAndSetObject(Object o, long offset, Object newValue) {
        Object v;
        do {
            v = getObjectVolatile(o, offset);
        } while (!compareAndSwapObject(o, offset, v, newValue));
        return v;
    }	
	
@Deprecated
public int fieldOffset(Field f) {
	if (Modifier.isStatic(f.getModifiers()))
		return (int) staticFieldOffset(f);
	else
		return (int) objectFieldOffset(f);
}

@Deprecated
public Object staticFieldBase(Class<?> c) {
	Field[] fields = c.getDeclaredFields();
	for (int i = 0; i < fields.length; i++) {
		if (Modifier.isStatic(fields[i].getModifiers())) {
			return staticFieldBase(fields[i]);
		}
	}
	return null;
}	
	
@Deprecated
public int getInt(Object o, int offset) {
	return getInt(o, (long)offset);
}


@Deprecated
public void putInt(Object o, int offset, int x) {
	putInt(o, (long)offset, x);
}

@Deprecated
public Object getObject(Object o, int offset) {
	return getObject(o, (long)offset);
}

@Deprecated
public void putObject(Object o, int offset, Object x) {
	putObject(o, (long)offset, x);
}

@Deprecated
public boolean getBoolean(Object o, int offset) {
	return getBoolean(o, (long)offset);
}

@Deprecated
public void putBoolean(Object o, int offset, boolean x) {
	putBoolean(o, (long)offset, x);
}

@Deprecated
public byte getByte(Object o, int offset) {
	return getByte(o, (long)offset);
}

@Deprecated
public void putByte(Object o, int offset, byte x) {
	putByte(o, (long)offset, x);
}

@Deprecated
public short getShort(Object o, int offset) {
	return getShort(o, (long)offset);
}

@Deprecated
public void putShort(Object o, int offset, short x) {
	putShort(o, (long)offset, x);
}

@Deprecated
public char getChar(Object o, int offset) {
	return getChar(o, (long)offset);
}

@Deprecated
public void putChar(Object o, int offset, char x) {
	putChar(o, (long)offset, x);
}

@Deprecated
public long getLong(Object o, int offset) {
	return getLong(o, (long)offset);
}

@Deprecated
public void putLong(Object o, int offset, long x) {
	putLong(o, (long)offset, x);
}

@Deprecated
public float getFloat(Object o, int offset) {
	return getFloat(o, (long)offset);
}

@Deprecated
public void putFloat(Object o, int offset, float x) {
	putFloat(o, (long)offset, x);
}

@Deprecated
public double getDouble(Object o, int offset) {
	return getDouble(o, (long)offset);
}


@Deprecated
public void putDouble(Object o, int offset, double x) {
	putDouble(o, (long)offset, x);
}	

链接: github 代码

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值