溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么理解序列化中的反射

發(fā)布時間:2021-10-21 13:43:58 來源:億速云 閱讀:177 作者:iii 欄目:編程語言

本篇內(nèi)容主要講解“怎么理解序列化中的反射”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“怎么理解序列化中的反射”吧!

前言

序列化大家都不陌生,說白了就是把當(dāng)前類對象的狀態(tài)保存為二進(jìn)制,然后被用來持久化或者網(wǎng)絡(luò)傳輸;常用的RPC框架在數(shù)據(jù)傳輸前都會進(jìn)行序列化操作,主流的RPC框架包含了多種序列化方式比如protobuf,fastjson,kryo,hessian,java內(nèi)置序列化等等,大致可以分為二進(jìn)制和字符串(json字符串)。

反射

因為需要把當(dāng)前類對象狀態(tài)保存為二進(jìn)制,所以往往需要獲取所有類屬性,這時候大部分的序列化方式都用到了反射,通過反射獲取所有類屬性獲取方法,然后獲取到屬性值,大致如下:

//1.方法Method[] methods = obj.getClass().getDeclaredMethods();for(Method method : methods) {
    method.invoke(obj);
}//2.字段Field fields[] = obj.getClass().getDeclaredFields();for (Field field : fields) {
    field.get(obj);
}

但是反射往往在性能上被大家所懷疑,所以出現(xiàn)了類似protobuf采用自動生成序列化代碼的方式,fastjson使用ASM代替反射的方式;下面我們先用簡單的測試來對比一下各種方式的性能,看反射是否真的慢;

性能測試

在windows10+jdk8環(huán)境下分別對直接,反射,以及ASM調(diào)用方法分別進(jìn)行壓力測試,看起消耗的時間,測試中可以多次執(zhí)行,取穩(wěn)定的值;以下測試分別從Person對象通過方法獲取屬性值,如下:

public class Person {private String id;private String name;    public String getId() {return id;
    }public String getName() {return name;
    }
}

直接調(diào)用

直接調(diào)用也就是我們平時最常用的方式,直接通過對象調(diào)用方法名稱獲取屬性值,我們在壓測的時候會分別輪詢兩個方法:

public static void test() {
    Person person = new Person("10001", "zhaohui");long startTime = System.currentTimeMillis();for (int i = 0; i < 1_0000_0000; i++) {if (i % 2 == 0) {
            person.getId();
        } else {
            person.getName();
        }
    }long endTime = System.currentTimeMillis();
    System.out.println("Manual time:" + (endTime - startTime) + "ms");
    }

多次測試結(jié)果大概在90ms左右,直接調(diào)用速度是最快的,但是需要我們手動的寫每個bean的序列化代碼,或者像protobuf一樣使用工具給我們生成所有的序列化代碼,比如生成Person的序列化代碼:

 public void writeTo(com.google.protobuf.CodedOutputStream output)throws java.io.IOException {getSerializedSize();if (((bitField0_ & 0x00000001) == 0x00000001)) {      output.writeInt32(1, id_);
    }if (((bitField0_ & 0x00000002) == 0x00000002)) {      output.writeBytes(2, getNameBytes());
    }getUnknownFields().writeTo(output);
 }

可以看到每個生成的bean都自動生成了序列化代碼,并且所有的bean都繼承于統(tǒng)一的抽象類,這樣提供一整套規(guī)范;有個缺點就是每次修改需要手動改proto文件,然后重新生成代碼;

反射調(diào)用

使用jdk提供的反射機制,獲取Methods,然后獲取屬性值,具體代碼如下:

    public static void test() throws Exception {long startTime = System.currentTimeMillis();
        Person person = new Person("10001", "zhaohui");
        Method[] ms = Person.class.getDeclaredMethods();for (int i = 0; i < 1_0000_0000; i++) {
            ms[i & ms.length - 1].invoke(person);
        }long endTime = System.currentTimeMillis();
        System.out.println("Reflex time:" + (endTime - startTime) + "ms");
    }

經(jīng)測試時間大概維持在205ms左右,和直接調(diào)用還是存在一定差距的,不過jdk每一輪的升級,都在提升性能,比如jdk7中引入的MethodHandle,模擬字節(jié)碼層面的調(diào)用;

ASM調(diào)用

反射是讀取持久堆上存儲的類信息,而ASM是直接處理.class字節(jié)碼的,無需加載類,我們這里使用ReflectASM來進(jìn)行測試;

ReflectASM 是一個非常小的 Java 類庫,通過代碼生成來提供高性能的反射處理,自動為 get/set 字段提供訪問類,訪問類使用字節(jié)碼操作而不是 Java 的反射技術(shù),因此非??臁?/blockquote>
    public static void test() {
        Person person = new Person("10001", "zhaohui");long startTime = System.currentTimeMillis();

        MethodAccess methodAccess = MethodAccess.get(Person.class);
        String[] mns = methodAccess.getMethodNames();int len = mns.length;int indexs[] = new int[len];for (int i = 0; i < len; i++) {
            indexs[i] = methodAccess.getIndex(mns[i]);
        }for (int i = 0; i < 1_0000_0000; i++) {
            methodAccess.invoke(person, indexs[i & len - 1]);
        }long endTime = System.currentTimeMillis();
        System.out.println("ASM time:" + (endTime - startTime) + "ms");
    }

經(jīng)測試時間維持在110ms左右,速度還是很快的,快趕上直接調(diào)用了;其中為了獲得最大性能,應(yīng)使用方法或字段索引而不是名稱;

總結(jié)

可以看到雖然反射性能一直在提升,但是相比直接調(diào)用和ASM的方式還是有一點差距;但其實如果用在RPC上這點時間在整個網(wǎng)絡(luò)傳輸上來說可以說微乎其微;如果對性能極度追求,可以考慮使用直接調(diào)用或者ASM的方式;

思考

關(guān)于直接調(diào)用上面說到protobuf,通過工具生成序列化代碼,但是這種方式每次改動都要手動生成代碼,有點麻煩,是否可以直接利用lombok這種框架做一個擴展,自動生成序列化代碼,其實lombok底層也用到ASM,直接生成字節(jié)碼代碼,提供序列化注解

@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)
public @interface Serialize {
}

然后可以直接把注解應(yīng)用到bean中,直接幫助我們生成序列化代碼,就像@Getter/@Setter一樣;相當(dāng)于直接調(diào)用和ASM方式的一種整合;類似如下代碼:

@Serializepublic class Person {private String id;private String name;    //自動生成public byte[] serialize(){
        ByteBuffer bb = ByteBuffer.allocate(100);
        bb.put(id.getBytes());
        bb.put(name.getBytes());return bb.array();
    }
}

到此,相信大家對“怎么理解序列化中的反射”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

jdk
AI