บทความส่วนใหญ่เกี่ยวข้องกับปัญหาต่อไปนี้:
1. การทำให้เป็นอนุกรมของวัตถุ Java
แพลตฟอร์ม Java ช่วยให้เราสามารถสร้างวัตถุ Java ที่นำกลับมาใช้ใหม่ได้ในหน่วยความจำ แต่โดยทั่วไปวัตถุเหล่านี้สามารถมีอยู่ได้ก็ต่อเมื่อ JVM กำลังทำงานเช่นวัฏจักรชีวิตของวัตถุเหล่านี้จะไม่นานกว่า JVM อย่างไรก็ตามในแอปพลิเคชันจริงอาจจำเป็นต้องบันทึก (คงอยู่) วัตถุที่ระบุหลังจาก JVM หยุดและอ่านวัตถุที่บันทึกไว้อีกครั้งในอนาคต การทำให้เป็นอนุกรม Object Java สามารถช่วยให้เราใช้ฟังก์ชั่นนี้ได้
การใช้การทำให้เป็นอนุกรมวัตถุ Java เมื่อบันทึกวัตถุสถานะของมันจะถูกบันทึกเป็นชุดของไบต์และในอนาคตไบต์เหล่านี้จะถูกรวมเข้ากับวัตถุ จะต้องสังเกตว่าการทำให้เป็นอนุกรมของวัตถุช่วย "สถานะ" ของวัตถุนั่นคือตัวแปรสมาชิก จากนี้เราจะเห็นได้ว่าการทำให้เป็นอนุกรมของวัตถุไม่ได้มุ่งเน้นไปที่ตัวแปรคงที่ในชั้นเรียน
นอกเหนือจากการใช้การทำให้เป็นอนุกรมของวัตถุเมื่อยังคงมีวัตถุยังคงมีการใช้งานอนุกรมของวัตถุเมื่อใช้ RMI (การเรียกใช้วิธีระยะไกล) หรือส่งผ่านวัตถุบนเครือข่าย Java Serialization API ให้กลไกมาตรฐานสำหรับการจัดการการทำให้เป็นอนุกรมวัตถุ API นี้ใช้งานง่ายและใช้งานง่ายและจะกล่าวถึงในบทที่ตามมาของบทความนี้
arraylist คลาสสาธารณะ <e> ขยายบทคัดย่อ <e> ใช้รายการ <e>, RandomAccess, cloneable, java.io.serializable {ส่วนตัวคงที่ Long SerialVersionUid = 8683452581122892189L; วัตถุชั่วคราว [] ElementData; // ไม่ใช่ส่วนตัวเพื่อทำให้การเข้าถึงคลาสซ้อนกันง่ายขึ้นขนาด int ขนาดส่วนตัว;}2. วิธีการทำให้เป็นอนุกรมและ deserialize วัตถุ Java
ใน Java ตราบใดที่คลาสใช้อินเทอร์เฟซ java.io.serializable สามารถเป็นอนุกรมได้ นี่คือส่วนหนึ่งของรหัส:
รหัส 1 สร้างคลาสผู้ใช้สำหรับการทำให้เป็นอนุกรมและ deserialization
แพ็คเกจ com.hollis; นำเข้า java.io.serializable; นำเข้า java.util.date;/*** สร้างโดย Hollis เมื่อวันที่ 16/2/2 */ผู้ใช้คลาสสาธารณะใช้ serializable {ชื่อสตริงส่วนตัว; อายุ int ส่วนตัว; วันเกิดส่วนตัววันเกิด; เพศสตริงชั่วคราวส่วนตัว ส่วนตัวคงที่สุดท้าย Long SerialVersionUid = -684979444707546667710L; สตริงสาธารณะ getName () {ชื่อคืน; } โมฆะสาธารณะ setName (ชื่อสตริง) {this.name = name; } public int getage () {return Age; } การตั้งค่าโมฆะสาธารณะ (อายุ int) {this.age = อายุ; } วันที่สาธารณะ getBirthday () {ส่งคืนวันเกิด; } โมฆะสาธารณะ setBirthday (วันเกิดวันที่) {this.birthday = วันเกิด; } สตริงสาธารณะ getGender () {กลับเพศ; } โมฆะสาธารณะ setGender (เพศสตริง) {this.gender = เพศ; } @Override สตริงสาธารณะ toString () {return "ผู้ใช้ {" + "name = '" + ชื่อ +'/'' + ", อายุ =" + อายุ + ", เพศ =" + เพศ + ", วันเกิด =" + วันเกิด + '}'; -การสาธิตโค้ด 2 สำหรับการทำให้เป็นอนุกรมและ deserializing ผู้ใช้
แพ็คเกจ com.hollis; นำเข้า org.apache.commons.io.fileutils; นำเข้า org.apache.commons.io.ioutils; นำเข้า java.io.*; นำเข้า java.util.date;/*** สร้างโดย Hollis เมื่อวันที่ 16/2/2 */คลาสสาธารณะ serializabledemo {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// เริ่มต้นผู้ใช้วัตถุผู้ใช้ = ผู้ใช้ใหม่ (); user.setName ("Hollis"); user.setGender ("ชาย"); user.setage (23); user.setBirthday (วันที่ใหม่ ()); System.out.println (ผู้ใช้); // เขียน obj ไปยังไฟล์ objectOutputStream oos = null; ลอง {oos = new ObjectOutputStream (ใหม่ fileOutputStream ("tempfile")); oos.writeObject (ผู้ใช้); } catch (ioexception e) {e.printstacktrace (); } ในที่สุด {ioutils.closequietly (oos); } // อ่าน obj จากไฟล์ไฟล์ = ไฟล์ใหม่ ("tempfile"); ObjectInputStream ois = null; ลอง {ois = ใหม่ ObjectInputStream (ใหม่ FileInputStream (ไฟล์)); ผู้ใช้ newUser = (ผู้ใช้) ois.readObject (); System.out.println (Newuser); } catch (ioexception e) {e.printstacktrace (); } catch (classnotFoundException e) {e.printStackTrace (); } ในที่สุด {ioutils.closequietly (ois); ลอง {fileutils.forcedelete (ไฟล์); } catch (ioexception e) {e.printstacktrace (); }}}}} // output // ผู้ใช้ {name = 'hollis', อายุ = 23, เพศ = ชาย, วันเกิด = อังคาร 02 ก.พ. 02 17:37:38 CST 2016} // ผู้ใช้ {ชื่อ = 'Hollis' อายุ = 233. ความรู้ที่เกี่ยวข้องกับการทำให้เป็นอนุกรมและ deserialization
1. ใน Java ตราบใดที่คลาสใช้อินเทอร์เฟซ java.io.serializable สามารถต่อเนื่องกันได้
2. serialize และ deserialize วัตถุผ่าน ObjectOutputStream และ ObjectInputStream
3. เครื่องเสมือนจริงที่อนุญาตให้ deserialization ไม่เพียง แต่ขึ้นอยู่กับว่ารหัส ClassPath และฟังก์ชั่นมีความสอดคล้องกันหรือไม่ ประเด็นที่สำคัญมากคือ ID Serialization ของทั้งสองคลาสนั้นสอดคล้องกันหรือไม่
4. การทำให้เป็นอนุกรมไม่ได้บันทึกตัวแปรคงที่
5. ในการทำให้เป็นอนุกรมวัตถุคลาสพาเรนต์คุณต้องทำให้คลาสแม่ใช้อินเทอร์เฟซแบบอนุกรม
6. ฟังก์ชั่นของคำหลักชั่วคราวคือการควบคุมการทำให้เป็นอนุกรมของตัวแปร การเพิ่มคำหลักนี้ก่อนการประกาศตัวแปรสามารถป้องกันตัวแปรจากการถูกทำให้เป็นอนุกรมลงในไฟล์ หลังจากถูก deserialized ค่าของตัวแปรชั่วคราวจะถูกตั้งค่าเป็นค่าเริ่มต้นเช่นประเภท int คือ 0 และประเภทวัตถุเป็นโมฆะ
7. เซิร์ฟเวอร์ส่งข้อมูลวัตถุที่เป็นอนุกรมไปยังไคลเอนต์ ข้อมูลบางอย่างในวัตถุนั้นมีความละเอียดอ่อนเช่นสตริงรหัสผ่าน ฯลฯ หวังว่าฟิลด์รหัสผ่านจะถูกเข้ารหัสเมื่ออนุกรม หากลูกค้ามีคีย์ถอดรหัสสามารถอ่านรหัสผ่านได้เฉพาะเมื่อ deserializing ลูกค้าซึ่งสามารถมั่นใจได้ถึงความปลอดภัยของข้อมูลของวัตถุที่เป็นอนุกรมในระดับหนึ่ง
4. การทำให้เป็นอนุกรมของ ArrayList
ก่อนที่จะแนะนำ arraylist serialization ลองพิจารณาคำถาม:
วิธีปรับแต่งกลยุทธ์การทำให้เป็นอนุกรมและการลดลงของ deserialization
ด้วยคำถามนี้ลองดูที่ซอร์สโค้ดของ java.util.arraylist
รหัส 3
arraylist คลาสสาธารณะ <e> ขยายบทคัดย่อ <e> ใช้รายการ <e>, RandomAccess, cloneable, java.io.serializable {ส่วนตัวคงที่ Long SerialVersionUid = 8683452581122892189L; วัตถุชั่วคราว [] ElementData; // ไม่ใช่ส่วนตัวเพื่อทำให้การเข้าถึงคลาสซ้อนกันง่ายขึ้นขนาด int ขนาดส่วนตัว;}ผู้เขียนละเว้นตัวแปรสมาชิกอื่น ๆ จากรหัสข้างต้นเราสามารถรู้ได้ว่า ArrayList ใช้อินเตอร์เฟส java.io.serializable ดังนั้นเราจึงสามารถทำให้เป็นอนุกรมและ deserialize เนื่องจาก ElementData นั้นชั่วคราวเราเชื่อว่าตัวแปรสมาชิกนี้จะไม่ได้รับการจัดลำดับและเก็บรักษาไว้ มาเขียนตัวอย่างเพื่อตรวจสอบความคิดของเรา:
รหัส 4
โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น IOException, classnotFoundException {รายการ <String> stringList = new ArrayList <String> (); StringList.add ("สวัสดี"); StringList.add ("World"); StringList.add ("Hollis"); StringList.add ("Chuang"); System.out.println ("init StringList" + StringList); ObjectOutputStream ObjectOutputStream = ใหม่ ObjectOutputStream (ใหม่ fileOutputStream ("StringList")); ObjectOutputStream.writeObject (StringList); ioutils.close (ObjectOutputStream); ไฟล์ไฟล์ = ไฟล์ใหม่ ("StringList"); ObjectInputStream ObjectInputStream = ใหม่ ObjectInputStream (ใหม่ FileInputStream (ไฟล์)); รายการ <String> NewStringList = (รายการ <String>) ObjectInputStream.readObject (); ioutils.close (ObjectInputStream); if (file.exists ()) {file.delete (); } system.out.println ("New StringList" + NewStringList); } // init StringList [สวัสดีโลก, Hollis, Chang] // ใหม่ StringList [สวัสดี, World, Hollis, Chang]ใครก็ตามที่รู้ว่า ArrayList รู้ว่าชั้นพื้นฐานของ ArrayList นั้นถูกนำไปใช้ผ่านอาร์เรย์ จากนั้นอาร์เรย์ ElementData จะถูกใช้จริงเพื่อบันทึกองค์ประกอบในรายการ ด้วยวิธีการประกาศของแอตทริบิวต์นี้เรารู้ว่ามันไม่สามารถคงอยู่ผ่านการทำให้เป็นอนุกรม เหตุใดผลลัพธ์ของรหัส 4 จึงรักษาองค์ประกอบในรายการผ่านการทำให้เป็นอนุกรมและ deserialization?
5. วิธีการเขียนและ readObject
ใน ArrayList มีการกำหนดวิธีการ: WriteObject และ ReadObject
นี่คือข้อสรุป:
ในระหว่างกระบวนการทำให้เป็นอนุกรมหากมีการกำหนดวิธีการเขียนและ readObject ในคลาสที่เป็นอนุกรมเครื่องเสมือนพยายามเรียกใช้วิธีการเขียนและการอ่านในคลาสวัตถุเพื่อดำเนินการต่ออนุกรมที่ผู้ใช้กำหนดและ deserialization
หากไม่มีวิธีการดังกล่าวการโทรเริ่มต้นคือวิธีการเริ่มต้นของ ObjectOutputStream และวิธี defaultreadObject ของ ObjectInputStream
วิธีการเขียนและ readobject ที่ผู้ใช้กำหนดสามารถอนุญาตให้ผู้ใช้ควบคุมกระบวนการทำให้เป็นอนุกรมเช่นการเปลี่ยนค่าอนุกรมแบบไดนามิกในระหว่างกระบวนการทำให้เป็นอนุกรม
มาดูการใช้งานเฉพาะของทั้งสองวิธีนี้:
รหัส 5
โมฆะส่วนตัว readObject (java.io.ObjectInputStream S) พ่น java.io.ioException, classnotFoundException {elementData = emport_elementData; // อ่านในขนาดและสิ่งที่ซ่อนอยู่ s.defaultreadobject (); // อ่านในความสามารถ s.readint (); // ละเว้นถ้า (ขนาด> 0) {// เป็นเหมือน clone () จัดสรรอาร์เรย์ตามขนาดที่ไม่ได้ความจุ ensurecapacityinternal (ขนาด); วัตถุ [] a = elementData; // อ่านในทุกองค์ประกอบตามลำดับที่เหมาะสม สำหรับ (int i = 0; i <size; i ++) {a [i] = s.readObject (); -รหัส 6
โมฆะส่วนตัว writeObject (java.io.ObjectOutputStream S) พ่น java.io.ioException {// เขียนจำนวนองค์ประกอบและสิ่งที่ซ่อนอยู่ในสิ่งที่คาดหวัง modCount = modCount; S.DefaultWriteObject (); // เขียนขนาดเป็นความสามารถในการเข้ากันได้กับพฤติกรรมกับ clone () S.WriteInt (ขนาด); // เขียนองค์ประกอบทั้งหมดตามลำดับที่เหมาะสม สำหรับ (int i = 0; i <size; i ++) {s.writeObject (elementData [i]); } if (modcount! = คาดว่าจะได้รับ) {โยนใหม่พร้อมกันใหม่ Exception (); -เหตุใด ArrayList จึงจำเป็นต้องใช้วิธีนี้เพื่อให้เกิดการจัดอนุกรม?
ทำไมชั่วคราว
ArrayList เป็นอาร์เรย์แบบไดนามิก ทุกครั้งที่มันเต็มจะเพิ่มค่าความยาวที่ตั้งไว้โดยอัตโนมัติ หากความยาวการเจริญเติบโตอัตโนมัติของอาร์เรย์ถูกตั้งค่าเป็น 100 และมีเพียงองค์ประกอบเดียวเท่านั้นที่ถูกวางไว้จริงแล้ว 99 องค์ประกอบ NULL จะถูกทำให้เป็นอนุกรม เพื่อให้แน่ใจว่า nulls จำนวนมากจะไม่ได้รับการจัดลำดับในเวลาเดียวกันในระหว่างการทำให้เป็นอนุกรม ArrayList ตั้งค่าอาร์เรย์องค์ประกอบเป็นชั่วคราว
ทำไมการเขียนและ readObject
ดังที่ได้กล่าวไว้ก่อนหน้านี้เพื่อป้องกันอาร์เรย์ที่มีวัตถุว่างจำนวนมากจากการเป็นอนุกรมและเพื่อเพิ่มประสิทธิภาพการจัดเก็บอาร์เรย์ arraylist ใช้ชั่วคราวเพื่อประกาศ ElementData
อย่างไรก็ตามในฐานะที่เป็นคอลเลกชันมันเป็นสิ่งจำเป็นเพื่อให้แน่ใจว่าองค์ประกอบในนั้นสามารถคงอยู่ในระหว่างกระบวนการทำให้เป็นอนุกรม ดังนั้นองค์ประกอบในนั้นจะถูกเก็บรักษาไว้โดยการเขียนวิธีการเขียนและการอ่านใหม่
วิธี writeObject บันทึกองค์ประกอบในอาร์เรย์ ElementData ผ่านไปยังสตรีมเอาต์พุต (ObjectOutputStream)
วิธี readObject อ่านวัตถุจากสตรีมอินพุต (ObjectInputStream) และบันทึกการกำหนดไปยังอาร์เรย์ ElementData
ณ จุดนี้ลองตอบคำถามที่เราเพิ่งถาม:
1. วิธีการปรับแต่งกลยุทธ์การทำให้เป็นอนุกรมและ deserialization
คำตอบ: คุณสามารถเพิ่มวิธีการเขียนและ readobject ลงในคลาสที่เป็นอนุกรม 2. คำถามกลับมาอีกครั้ง:
แม้ว่าวิธีการเขียนและ readObject จะถูกเขียนใน ArrayList แต่วิธีการทั้งสองนี้จะไม่ปรากฏขึ้นและเรียกว่า
ดังนั้นหากคลาสมีวิธีการเขียนและ readObject แล้วสองวิธีนี้เรียกว่าอย่างไร
6. ObjectOutputStream
จากรหัส 4 เราจะเห็นว่ากระบวนการทำให้เป็นอนุกรมของวัตถุถูกนำไปใช้ผ่าน ObjectOutputStream และ ObjectInputStream ดังนั้นด้วยคำถามตอนนี้มาวิเคราะห์ว่าวิธีการเขียนและ readobject ใน ArrayList เรียกว่า
เพื่อประหยัดพื้นที่นี่คือสแต็กการโทรของ writeObject ของ ObjectOutputStream:
writeObject ---> writeObject0 ---> writeordinaryObject ---> writeerialdata ---> indeverkeWriteObject
นี่คือการดูที่ InvokeWriteObject:
Void InvokeWriteObject (Object OBJ, ObjectOutputStream) โยน ioException, unsupportedOperationException {ถ้า (writeObjectMethod! = null) {ลอง {writeObjectMethod.invoke (obj, วัตถุใหม่ [] {out}); } catch (InvocationTargetException ex) {throwable th = ex.getTargetException (); ถ้า (th instanceof ioexception) {โยน (ioexception) th; } else {Throwmiscexception (th); }} catch (unglemalaccessexception ex) {// ไม่ควรเกิดขึ้นเนื่องจากการตรวจสอบการเข้าถึงได้ถูกระงับการโยน InternalError ใหม่ (Ex); }} else {โยน unsupportedoperationException ใหม่ (); -โดยที่ writeobjectmethod.invoke (obj, วัตถุใหม่ [] {out}); เป็นกุญแจสำคัญและวิธีการเขียน rotoBjectMethod นั้นเรียกผ่านการสะท้อนกลับ นี่คือวิธีที่เจ้าหน้าที่อธิบาย writeObjectMethod:
วิธีการเขียนแบบคลาสที่กำหนดไว้หรือเป็นโมฆะถ้าไม่มี
ในตัวอย่างของเราวิธีนี้เป็นวิธีการเขียนที่เรากำหนดใน ArrayList มันถูกเรียกผ่านการสะท้อน
ณ จุดนี้ลองตอบคำถามที่เราเพิ่งถาม:
หากคลาสมีวิธีการเขียนและ readObject วิธีการทั้งสองนี้เรียกว่าอย่างไร?
คำตอบ: เมื่อใช้วิธีการเขียนของ ObjectOutputStream และวิธี readObject ของ ObjectInputStream มันจะถูกเรียกในการสะท้อน
จนถึงตอนนี้เราได้แนะนำวิธีการทำให้เป็นอนุกรมของ ArrayList ดังนั้นฉันสงสัยว่ามีใครตั้งคำถามเช่นนี้:
Serializable เป็นอินเทอร์เฟซที่ว่างเปล่า มันมั่นใจได้อย่างไรว่าวิธีการเฉพาะที่ใช้อินเทอร์เฟซนี้สามารถเป็นอนุกรมและ deserialized ได้อย่างไร
คำจำกัดความของอินเทอร์เฟซอนุกรม:
อินเทอร์เฟซสาธารณะ serializable {}ผู้อ่านสามารถลองลบรหัสที่สืบทอดมาจาก serializable ในรหัส 1 จากนั้นเรียกใช้งานรหัส 2 ซึ่งจะโยน java.io.notserializableException
ในความเป็นจริงคำถามนี้ยังตอบง่าย กลับไปที่สแต็กการโทรของ WriteObject ใน ObjectOutputStream ตอนนี้:
writeObject ---> writeObject0 ---> writeordinaryObject ---> writeerialdata ---> indeverkeWriteObject
มีส่วนหนึ่งของรหัสในวิธี writeObject0:
if (OBJ Instanceof String) {WriteString ((String) OBJ, Unshared); } อื่นถ้า (cl.isarray ()) {writearray (obj, desc, unshared); } อื่นถ้า (obj instanceof enum) {writeenum ((enum <?>) obj, desc, unhared); } อื่นถ้า (OBJ instanceof serializable) {writeordinaryObject (obj, desc, unshared); } else {if (extendedDebuginfo) {โยน notserializableException ใหม่ (cl.getName () + "/n" + debuginfostack.toString ()); } else {โยน notserializeableexception ใหม่ (cl.getName ()); -เมื่อทำการดำเนินการอนุกรมมันจะพิจารณาว่าชั้นเรียนที่จะเป็นอนุกรมนั้นเป็น enum, array และประเภท serializable หรือไม่ หากไม่เป็นเช่นนั้นจะมีการโยนแบบไม่ใช้งานได้โดยตรง
สรุป
1. หากชั้นเรียนต้องการได้รับการจัดอนุกรมจะต้องใช้อินเตอร์เฟสแบบอนุกรม มิฉะนั้นจะมีการโยนแบบไม่ใช้งานได้เนื่องจากประเภทจะถูกตรวจสอบในระหว่างการดำเนินการอนุกรมโดยกำหนดให้คลาสที่เป็นอนุกรมจะต้องเป็นของ enum, อาร์เรย์และประเภท serializable ใด ๆ
2. การเพิ่มคำหลักนี้ก่อนการประกาศตัวแปรสามารถป้องกันไม่ให้ตัวแปรถูกทำให้เป็นอนุกรมลงในไฟล์
3. การเพิ่มวิธีการเขียนและการอ่านข้อมูลลงในชั้นเรียนสามารถใช้กลยุทธ์การทำให้เป็นอนุกรมที่กำหนดเองได้
ข้างต้นเป็นเรื่องเกี่ยวกับบทความนี้ฉันหวังว่ามันจะเป็นประโยชน์กับการเรียนรู้ของทุกคน