ฉันรู้ว่าคำถามนี้เก่าจริงๆและมีคำตอบที่เป็นที่ยอมรับ แต่เมื่อมันปรากฏขึ้นสูงมากในการค้นหาของ Google ฉันคิดว่าฉันจะชั่งใจเพราะไม่มีคำตอบที่ครอบคลุมสามกรณีที่ฉันคิดว่าสำคัญ - ในใจของฉันการใช้งานหลักสำหรับสิ่งเหล่านี้ วิธีการ แน่นอนทุกคนคิดว่าจำเป็นต้องมีรูปแบบการจัดลำดับแบบกำหนดเอง
ยกตัวอย่างเช่นคลาสคอลเลกชัน การจัดลำดับตามค่าเริ่มต้นของรายการที่เชื่อมโยงหรือ BST จะส่งผลให้สูญเสียพื้นที่อย่างมากและได้รับประสิทธิภาพน้อยมากเมื่อเทียบกับการจัดลำดับองค์ประกอบตามลำดับ สิ่งนี้จะเป็นจริงยิ่งขึ้นหากคอลเล็กชันเป็นแบบฉายภาพหรือมุมมอง - เก็บข้อมูลอ้างอิงไปยังโครงสร้างที่ใหญ่กว่าที่เปิดเผยโดย API สาธารณะ
หากวัตถุเนื่องมีเขตข้อมูลไม่เปลี่ยนรูปซึ่งเป็นอันดับความต้องการที่กำหนดเอง, การแก้ปัญหาเดิมwriteObject/readObject
ไม่เพียงพอเป็นวัตถุ deserialized ถูกสร้างขึ้นก่อนที่จะwriteObject
อ่านส่วนหนึ่งของกระแสที่เขียนใน นำรายการที่เชื่อมโยงไปใช้เพียงเล็กน้อยนี้:
public class List<E> extends Serializable {
public final E head;
public final List<E> tail;
public List(E head, List<E> tail) {
if (head==null)
throw new IllegalArgumentException("null as a list element");
this.head = head;
this.tail = tail;
}
//methods follow...
}
โครงสร้างนี้สามารถทำให้เป็นอนุกรมได้โดยการเขียนhead
ฟิลด์ของทุกลิงก์ซ้ำๆ ตามด้วยnull
ค่า อย่างไรก็ตามreadObject
การกำหนดค่าเริ่มต้นของรูปแบบดังกล่าวจะกลายเป็นไปไม่ได้: ไม่สามารถเปลี่ยนค่าของช่องสมาชิกได้ (แก้ไขเป็นnull
) มาที่นี่writeReplace
/ readResolve
คู่:
private Object writeReplace() {
return new Serializable() {
private transient List<E> contents = List.this;
private void writeObject(ObjectOutputStream oos) {
List<E> list = contents;
while (list!=null) {
oos.writeObject(list.head);
list = list.tail;
}
oos.writeObject(null);
}
private void readObject(ObjectInputStream ois) {
List<E> tail = null;
E head = ois.readObject();
if (head!=null) {
readObject(ois); //read the tail and assign it to this.contents
this.contents = new List<>(head, this.contents)
}
}
private Object readResolve() {
return this.contents;
}
}
}
ขออภัยหากตัวอย่างด้านบนไม่ได้รวบรวม (หรือใช้งานได้) แต่หวังว่าจะเพียงพอที่จะอธิบายประเด็นของฉัน หากคุณคิดว่านี่เป็นตัวอย่างที่ดึงมาได้ไกลมากโปรดจำไว้ว่าภาษาที่ใช้งานได้จำนวนมากทำงานบน JVM และแนวทางนี้มีความสำคัญในกรณีของพวกเขา
เราอาจต้องการ deserialize ออบเจ็กต์ของคลาสที่แตกต่างจากที่เราเขียนลงในไฟล์ObjectOutputStream
. กรณีนี้จะเกิดขึ้นกับมุมมองเช่นการjava.util.List
ใช้งานรายการซึ่งแสดงส่วนที่ยาวArrayList
ขึ้น เห็นได้ชัดว่าการจัดลำดับรายการสำรองทั้งหมดเป็นความคิดที่ไม่ดีและเราควรเขียนองค์ประกอบจากชิ้นส่วนที่ดูเท่านั้น ทำไมถึงหยุดอยู่แค่นั้นและมีระดับความไม่เป็นประโยชน์หลังจาก deserialization? เราสามารถอ่านองค์ประกอบจากสตรีมเป็นArrayList
และส่งคืนได้โดยตรงแทนที่จะรวมไว้ในคลาสมุมมองของเรา
หรืออีกวิธีหนึ่งการมีคลาส delegate ที่คล้ายกันสำหรับการทำให้เป็นอนุกรมอาจเป็นทางเลือกในการออกแบบ ตัวอย่างที่ดีคือการนำรหัสอนุกรมของเรากลับมาใช้ใหม่ ตัวอย่างเช่นถ้าเรามีคลาส builder (คล้ายกับ StringBuilder สำหรับ String) เราสามารถเขียนผู้แทนการทำให้เป็นอนุกรมซึ่งทำให้คอลเลกชันเป็นลำดับโดยการเขียนตัวสร้างว่างลงในสตรีมตามด้วยขนาดคอลเลกชันและองค์ประกอบที่ส่งคืนโดยตัววนซ้ำของการรวบรวม ซีเรียลไลซ์จะเกี่ยวข้องกับการอ่านสร้าง, ผนวกองค์ประกอบอ่านต่อมาทั้งหมดและกลับมาผลสุดท้ายจากผู้ได้รับมอบหมายbuild()
readResolve
ในกรณีนี้เราจำเป็นต้องใช้การทำให้เป็นอนุกรมในคลาสรูทของลำดับชั้นการรวบรวมเท่านั้นและไม่จำเป็นต้องใช้รหัสเพิ่มเติมจากการใช้งานในปัจจุบันหรือในอนาคตหากพวกเขาใช้นามธรรมiterator()
และbuilder()
วิธีการ (หลังสำหรับการสร้างคอลเลกชันประเภทเดียวกัน - ซึ่งจะเป็นคุณสมบัติที่มีประโยชน์มากในตัวเอง) อีกตัวอย่างหนึ่งคือการมีลำดับชั้นของคลาสซึ่งโค้ดที่เราควบคุมไม่ได้ทั้งหมด - คลาสพื้นฐานของเราจากไลบรารีบุคคลที่สามอาจมีฟิลด์ส่วนตัวจำนวนเท่าใดก็ได้ที่เราไม่รู้อะไรเลยและอาจเปลี่ยนจากเวอร์ชันหนึ่งไปเป็นอีกเวอร์ชันหนึ่ง วัตถุต่อเนื่องของเรา ในกรณีนี้การเขียนข้อมูลและสร้างออบเจ็กต์ใหม่ด้วยตนเองจะปลอดภัยกว่าในการ deserialization
String.CaseInsensitiveComparator.readResolve()