`

比反射更高效的修改字段值的方法

    博客分类:
  • java
阅读更多

        开发过程中,不少情况下都会遇到需要通过反射修改对象字段值的情况,但是很多情况下,直接反射效率比较低,而你如果读过jdk代码,可能多少会发现jdk里边经常闪现这UNSAFE的的身影,比如ConcurrentLinkedQueue里边到处都是。

        为啥jdk里边到处都用这个东西呢?YY下,一方面是因为效率高,另一方面是因为它提供了很强大底层操作的功能,比如不用AtomicObject就可以直接支持cas操作修改对象字段,可以直接申请释放内存。

        调用效率高,到底有多高呢?直接上代码:

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * -Xcomp
 * @author tianmai.fh
 * @date 2014-03-12 16:43
 */
public class FieldOffsetTest {

    static int COUNT = 50000000;
    static int LENGTH = 1024;
    static long BEAN_A_OFFSET;
    static Unsafe unsafe;
    static Field aField;
    static Field bField;

    static long BEAN_B_OFFSET;

    static {
        try {
            Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);

            Method objectFieldOffsetMethod = Unsafe.class.getDeclaredMethod("objectFieldOffset", new Class[]{Field.class});
            objectFieldOffsetMethod.setAccessible(true);

            BEAN_A_OFFSET = unsafe.objectFieldOffset(Bean.class.getDeclaredField("a"));
            BEAN_B_OFFSET = unsafe.objectFieldOffset(Bean.class.getDeclaredField("b"));
            aField = Bean.class.getDeclaredField("a");
            aField.setAccessible(true);

            bField = Bean.class.getDeclaredField("b");
            bField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    static class Bean {
        private int a;
        private int b;
        private int c[] = new int[LENGTH];
    }


    public static void testFieldOffsetAssign() throws NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        Bean bean = new Bean();
        long start = System.currentTimeMillis();
        for (int i = 0; i < COUNT; i++) {
            unsafe.putInt(bean,BEAN_A_OFFSET,i);
            unsafe.putInt(bean,BEAN_B_OFFSET,i);
        }
        long end = System.currentTimeMillis();
        System.out.println("Offset spend: " + (end - start) + " ms");

    }


    public static void testReflectAssign() throws IllegalAccessException {
        Bean bean = new Bean();
        long start = System.currentTimeMillis();
        for (int i = 0; i < COUNT; i++) {
            aField.set(bean, i);
            bField.set(bean, i);
        }
        long end = System.currentTimeMillis();
        System.out.println("Reflect spend: " + (end - start) + " ms");

    }

    public static void testDirectAssign() throws IllegalAccessException {
        Bean bean = new Bean();
        long start = System.currentTimeMillis();
        for (int i = 0; i < COUNT; i++) {
            bean.a = i;
            bean.b = i;
        }
        long end = System.currentTimeMillis();
        System.out.println("Direct spend: " + (end - start) + " ms");

    }

    public static void main(String[] args) throws NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 5; i++) {
            testDirectAssign();
            testReflectAssign();
            testFieldOffsetAssign();
            System.out.println();
        }
    }
}

 看输出结果:

Direct spend: 8 ms
Reflect spend: 2111 ms
Offset spend: 84 ms

Direct spend: 0 ms
Reflect spend: 2163 ms
Offset spend: 81 ms

Direct spend: 0 ms
Reflect spend: 2134 ms
Offset spend: 83 ms

Direct spend: 0 ms
Reflect spend: 2097 ms
Offset spend: 80 ms

Direct spend: 0 ms
Reflect spend: 2150 ms
Offset spend: 79 ms

         通过unsafe赋值耗费的时间是反射调用耗费时间的4%,一个数量级之差!当然在可以直接调用的时候呢,还是直接调用好了,效率更高!

        细读下代码,应用代码不能直接获取Unsafe实例的:

    public static Unsafe getUnsafe() {
        Class cc = sun.reflect.Reflection.getCallerClass(2);
        if (cc.getClassLoader() != null)
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

         这个类的注释上写只有信任代码才能获取这个类的实例,从源码看呢,就是BootstrapClassLoader加载的类才可以使用,也就是jdk的核心类库才可以使用它,所以我demo中就通过反射获取了。当然如果jdk升级,这个类消失或者接口变动,只有自己负责了。

A collection of methods for performing low-level, unsafe operations.Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it.

         通过Unsafe的接口可以获得字段在类中的偏移量,这个偏移量可以用来在对象上找到字段相对于对象地址的偏移量,对象在堆上也只是一块内存而已,有了对象地址作为起始地址,加上偏移量,很容易获取到字段在堆中的位置了,而且这个偏移量不会因为gc移动对象实例位置而变动:

public native long objectFieldOffset(Field f)

        剩下的就只有使用了!enjoy it ! 

分享到:
评论

相关推荐

    AppFramework数据库访问组件_代码生成插件_V1.1.rar

    最明显的就是类似于LAST_UPDATE_TIME了,通常为了保证这个字段的一致性,通常在插入新记录时采用当前数据库时间作为字段值。但IBatisNet接收的实体类对象属性都是普通C#类型,并不具备传入表达式的能力。如果采用...

    AppFramework_V1.0

    最明显的就是类似于LAST_UPDATE_TIME了,通常为了保证这个字段的一致性,通常在插入新记录时采用当前数据库时间作为字段值。但IBatisNet接收的实体类对象属性都是普通C#类型,并不具备传入表达式的能力。如果采用...

    AppFramework_V1.0_New

    最明显的就是类似于LAST_UPDATE_TIME了,通常为了保证这个字段的一致性,通常在插入新记录时采用当前数据库时间作为字段值。但IBatisNet接收的实体类对象属性都是普通C#类型,并不具备传入表达式的能力。如果采用...

    动软.Net代码生成器 v2

    采用Model+DAL +BLL+Web 的设计,主要实现在对应数据库中表的基类代码的自动生成,包括生成属性、添加、修改、删除、查询、存在性、Model 类构造等基础代码片断,支持不同3种架构代码生成,使程序员可以节省大量机械...

    asp.net知识库

    通过反射调用類的方法,屬性,字段,索引器(2種方法) ASP.NET: State Server Gems 完整的动态加载/卸载程序集的解决方案 从NUnit中理解.NET自定义属性的应用(转载) 如何在.NET中实现脚本引擎 (CodeDom篇) .NET的插件...

Global site tag (gtag.js) - Google Analytics