您好,登錄后才能下訂單哦!
本篇內容主要講解“怎么理解序列化中的反射”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么理解序列化中的反射”吧!
序列化大家都不陌生,說白了就是把當前類對象的狀態保存為二進制,然后被用來持久化或者網絡傳輸;常用的RPC框架在數據傳輸前都會進行序列化操作,主流的RPC框架包含了多種序列化方式比如protobuf,fastjson,kryo,hessian,java內置序列化等等,大致可以分為二進制和字符串(json字符串)。
因為需要把當前類對象狀態保存為二進制,所以往往需要獲取所有類屬性,這時候大部分的序列化方式都用到了反射,通過反射獲取所有類屬性獲取方法,然后獲取到屬性值,大致如下:
//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); }
但是反射往往在性能上被大家所懷疑,所以出現了類似protobuf采用自動生成序列化代碼的方式,fastjson使用ASM代替反射的方式;下面我們先用簡單的測試來對比一下各種方式的性能,看反射是否真的慢;
在windows10+jdk8環境下分別對直接,反射,以及ASM調用方法分別進行壓力測試,看起消耗的時間,測試中可以多次執行,取穩定的值;以下測試分別從Person對象通過方法獲取屬性值,如下:
public class Person {private String id;private String name; public String getId() {return id; }public String getName() {return name; } }
直接調用也就是我們平時最常用的方式,直接通過對象調用方法名稱獲取屬性值,我們在壓測的時候會分別輪詢兩個方法:
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"); }
多次測試結果大概在90ms左右,直接調用速度是最快的,但是需要我們手動的寫每個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都繼承于統一的抽象類,這樣提供一整套規范;有個缺點就是每次修改需要手動改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"); }
經測試時間大概維持在205ms左右,和直接調用還是存在一定差距的,不過jdk每一輪的升級,都在提升性能,比如jdk7中引入的MethodHandle,模擬字節碼層面的調用;
反射是讀取持久堆上存儲的類信息,而ASM是直接處理.class字節碼的,無需加載類,我們這里使用ReflectASM來進行測試;
ReflectASM 是一個非常小的 Java 類庫,通過代碼生成來提供高性能的反射處理,自動為 get/set 字段提供訪問類,訪問類使用字節碼操作而不是 Java 的反射技術,因此非常快。
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"); }
經測試時間維持在110ms左右,速度還是很快的,快趕上直接調用了;其中為了獲得最大性能,應使用方法或字段索引而不是名稱;
可以看到雖然反射性能一直在提升,但是相比直接調用和ASM的方式還是有一點差距;但其實如果用在RPC上這點時間在整個網絡傳輸上來說可以說微乎其微;如果對性能極度追求,可以考慮使用直接調用或者ASM的方式;
關于直接調用上面說到protobuf,通過工具生成序列化代碼,但是這種方式每次改動都要手動生成代碼,有點麻煩,是否可以直接利用lombok這種框架做一個擴展,自動生成序列化代碼,其實lombok底層也用到ASM,直接生成字節碼代碼,提供序列化注解
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE) public @interface Serialize { }
然后可以直接把注解應用到bean中,直接幫助我們生成序列化代碼,就像@Getter/@Setter一樣;相當于直接調用和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(); } }
到此,相信大家對“怎么理解序列化中的反射”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。