您好,登錄后才能下訂單哦!
本篇內(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)用方法名稱獲取屬性值,我們在壓測的時候會分別輪詢兩個方法:
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文件,然后重新生成代碼;
使用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是直接處理.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í)!
免責(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)容。