一、序列化與反序列化
序列化:指堆內(nèi)存中的java對(duì)象數(shù)據(jù),通過(guò)某種方式把對(duì)存儲(chǔ)到磁盤(pán)文件中,或者傳遞給其他網(wǎng)絡(luò)節(jié)點(diǎn)(網(wǎng)絡(luò)傳輸)。這個(gè)過(guò)程稱為序列化,通常是指將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)化成二進(jìn)制的過(guò)程。
即將對(duì)象轉(zhuǎn)化為二進(jìn)制,用于保存,或者網(wǎng)絡(luò)傳輸。
反序列化:把磁盤(pán)文件中的對(duì)象數(shù)據(jù)或者把網(wǎng)絡(luò)節(jié)點(diǎn)上的對(duì)象數(shù)據(jù),恢復(fù)成Java對(duì)象模型的過(guò)程。也就是將在序列化過(guò)程中所生成的二進(jìn)制串轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或者對(duì)象的過(guò)程
與序列化相反,將二進(jìn)制轉(zhuǎn)化成對(duì)象。
二、序列化的作用
立即學(xué)習(xí)“Java免費(fèi)學(xué)習(xí)筆記(深入)”;
① 想把內(nèi)存中的對(duì)象保存到一個(gè)文件中或者數(shù)據(jù)庫(kù)中時(shí)候;
② 想用套接字在網(wǎng)絡(luò)上傳送對(duì)象的時(shí)候;
③ 想通過(guò)RMI傳輸對(duì)象的時(shí)候
一些應(yīng)用場(chǎng)景,涉及到將對(duì)象轉(zhuǎn)化成二進(jìn)制,序列化保證了能夠成功讀取到保存的對(duì)象。
三、java的序列化實(shí)現(xiàn)
要實(shí)現(xiàn)對(duì)象的序列化,最直接的操作就是實(shí)現(xiàn)Serializable接口
使用IO流中的對(duì)象流可以實(shí)現(xiàn)序列化操作,將對(duì)象保存到文件,再讀取出來(lái)。
首先創(chuàng)建一個(gè)對(duì)象,并實(shí)現(xiàn)Serializable接口:
import java.io.Serializable; public class User implements Serializable{ private static final long serialVersionUID = 1L; private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } }
用對(duì)象流寫(xiě)一個(gè)保存對(duì)象與讀取對(duì)象的工具類:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializeUtil { // 保存對(duì)象,序列化 public static void saveObject(Object object) throws Exception { ObjectOutputStream out = null; FileOutputStream fout = null; try { fout = new FileOutputStream("D:/1.txt"); out = new ObjectOutputStream(fout); out.writeObject(object); } finally { fout.close(); out.close(); } } // 讀取對(duì)象,反序列化 public static Object readObject() throws Exception { ObjectInputStream in = null; FileInputStream fin = null; try { fin = new FileInputStream("D:/1.txt"); in = new ObjectInputStream(fin); Object object = in.readObject(); return object; } finally { fin.close(); in.close(); } } }
測(cè)試:
public class Main { public static void main(String[] args) { User user = new User(); user.setName("旭旭寶寶"); user.setAge(33); // 保存 try { SerializeUtil.saveObject(user); } catch (Exception e) { System.out.println("保存時(shí)異常:" + e.getMessage()); } // 讀取 User userObject; try { userObject = (User) SerializeUtil.readObject(); System.out.println(userObject); } catch (Exception e) { System.out.println("讀取時(shí)異常:" + e.getMessage()); } } }
測(cè)試結(jié)果:
這里我們成功的進(jìn)行了一次將對(duì)象保存到文件中,再讀取了出來(lái)。如果此時(shí),我們不實(shí)現(xiàn)序列化接口,就會(huì)出現(xiàn)異常了。我們?nèi)∠麑?shí)現(xiàn)的Serialiable接口代碼:
public class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } }
再測(cè)試Main方法:
可以看到此時(shí)報(bào)錯(cuò)了,此時(shí)使用 e.printStackTrace();查看異常的詳細(xì)信息:
可以看到Unknown Source,因?yàn)闆](méi)有進(jìn)行序列化,所以無(wú)法進(jìn)行保存與讀取。
四、序列化ID的作用
可以看到,我們?cè)谶M(jìn)行序列化時(shí),加了一個(gè)serialVersionUID字段,這便是序列化ID
private static final long serialVersionUID = 1L;
這個(gè)序列化ID起著關(guān)鍵的作用,它決定著是否能夠成功反序列化!java的序列化機(jī)制是通過(guò)判斷運(yùn)行時(shí)類的serialVersionUID來(lái)驗(yàn)證版本一致性的,在進(jìn)行反序列化時(shí),JVM會(huì)把傳進(jìn)來(lái)的字節(jié)流中的serialVersionUID與本地實(shí)體類中的serialVersionUID進(jìn)行比較,如果相同則認(rèn)為是一致的,便可以進(jìn)行反序列化,否則就會(huì)報(bào)序列化版本不一致的異常。
即序列化ID是為了保證成功進(jìn)行反序列化
五、默認(rèn)的序列化ID
當(dāng)我們一個(gè)實(shí)體類中沒(méi)有顯式的定義一個(gè)名為“serialVersionUID”、類型為long的變量時(shí),Java序列化機(jī)制會(huì)根據(jù)編譯時(shí)的class自動(dòng)生成一個(gè)serialVersionUID作為序列化版本比較,這種情況下,只有同一次編譯生成的class才會(huì)生成相同的serialVersionUID。譬如,當(dāng)我們編寫(xiě)一個(gè)類時(shí),隨著時(shí)間的推移,我們因?yàn)樾枨蟾膭?dòng),需要在本地類中添加其他的字段,這個(gè)時(shí)候再反序列化時(shí)便會(huì)出現(xiàn)serialVersionUID不一致,導(dǎo)致反序列化失敗。那么如何解決呢?便是在本地類中添加一個(gè)“serialVersionUID”變量,值保持不變,便可以進(jìn)行序列化和反序列化。
如果沒(méi)有顯示指定serialVersionUID,會(huì)自動(dòng)生成一個(gè)。 只有同一次編譯生成的class才會(huì)生成相同的serialVersionUID。 但是如果出現(xiàn)需求變動(dòng),Bean類發(fā)生改變,則會(huì)導(dǎo)致反序列化失敗。為了不出現(xiàn)這類的問(wèn)題,所以我們最好還是顯式的指定一個(gè) serialVersionUID。
六、序列化的其他問(wèn)題
1、靜態(tài)變量不會(huì)被序列化( static,transient)
2、當(dāng)一個(gè)父類實(shí)現(xiàn)序列化,子類自動(dòng)實(shí)現(xiàn)序列化,不需要顯式實(shí)現(xiàn)Serializable接口。
3、當(dāng)一個(gè)對(duì)象的實(shí)例變量引用其他對(duì)象,序列化該對(duì)象時(shí)也把引用對(duì)象進(jìn)行序列化。
子類序列化時(shí): 如果父類沒(méi)有實(shí)現(xiàn)Serializable接口,沒(méi)有提供默認(rèn)構(gòu)造函數(shù),那么子類的序列化會(huì)出錯(cuò); 如果父類沒(méi)有實(shí)現(xiàn)Serializable接口,提供了默認(rèn)的構(gòu)造函數(shù),那么子類可以序列化,父類的成員變量不會(huì)被序列化。如果父類 實(shí)現(xiàn)了Serializable接口,則父類和子類都可以序列化。
七、使用效率更高的序列化框架—Protostuff
其實(shí)java的原生序列化方式(通過(guò)實(shí)現(xiàn)Serialiable接口),效率并不是最高的。
github上有一個(gè)分析序列化效率的項(xiàng)目:https://github.com/eishay/jvm-serializers/wiki
其中看的出來(lái)性能最優(yōu)的為google開(kāi)發(fā)的colfer ,但是由于colfer的使用難度太大,而更多的都是使用protostuff序列化框架。適用改框架要引入兩個(gè)庫(kù)(core與runtime)。
①github地址:https://github.com/protostuff/protostuff
③如果用Maven,則添加依賴:
<dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.5.9</version> </dependency>
<dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.5.9</version> </dependency>
修改Main代碼
import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtobufIOUtil; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.Schema; import com.dyuproject.protostuff.runtime.RuntimeSchema; public class Main { public static void main(String[] args) { User user = new User(); user.setName("旭旭寶寶"); user.setAge(33); Schema<User> schema = RuntimeSchema.getSchema(User.class); // 保存對(duì)象,序列化,轉(zhuǎn)化二進(jìn)制數(shù)據(jù) LinkedBuffer buffer = LinkedBuffer.allocate(512); final byte[] protostuff; try { protostuff = ProtobufIOUtil.toByteArray(user, schema, buffer); } finally { buffer.clear(); } // 讀取對(duì)象,反序列化 User userObject = schema.newMessage(); ProtostuffIOUtil.mergeFrom(protostuff, userObject, schema); System.out.println(userObject); } }
User類,并未實(shí)現(xiàn)Serializable接口
public class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } }
測(cè)試結(jié)果:
若要要整合Redis使用,也可以寫(xiě)成一個(gè)工具類:
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtobufIOUtil; import com.dyuproject.protostuff.Schema; import com.dyuproject.protostuff.runtime.RuntimeSchema; public class SerializeUtil { private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") public static <T> byte[] serializer(T obj) { Class<T> clazz = (Class<T>) obj.getClass(); Schema<T> schema = getSchema(clazz); return ProtobufIOUtil.toByteArray(obj, schema, LinkedBuffer.allocate(256)); } public static <T> T deSerializer(byte[] bytes, Class<T> clazz) { T message; try { message = clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } Schema<T> schema = getSchema(clazz); ProtobufIOUtil.mergeFrom(bytes, message, schema); return message; } @SuppressWarnings("unchecked") public static <T> Schema<T> getSchema(Class<T> clazz) { Schema<T> schema = (Schema<T>) cachedSchema.get(clazz); if (schema == null) { schema = RuntimeSchema.createFrom(clazz); if (schema != null) { cachedSchema.put(clazz, schema); } } return schema; } }
這樣即使我們的User類就不用再實(shí)現(xiàn)Serialiable接口了,同樣可以進(jìn)行序列化,效率也更高。
php中文網(wǎng),大量的免費(fèi)Java入門(mén)教程,歡迎在線學(xué)習(xí)!
以上就是java如何序列化的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!
java怎么學(xué)習(xí)?java怎么入門(mén)?java在哪學(xué)?java怎么學(xué)才快?不用擔(dān)心,這里為大家提供了java速學(xué)教程(入門(mén)到精通),有需要的小伙伴保存下載就能學(xué)習(xí)啦!
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號(hào)
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://www.miracleart.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)