JavaRush /จาวาบล็อก /Random-TH /การทำให้เป็นอนุกรมตามที่เป็นอยู่ ส่วนที่ 1
articles
ระดับ

การทำให้เป็นอนุกรมตามที่เป็นอยู่ ส่วนที่ 1

เผยแพร่ในกลุ่ม
เมื่อมองแวบแรก การทำให้ซีเรียลไลซ์ดูเหมือนเป็นกระบวนการที่ไม่สำคัญ จริงๆ แล้วอะไรจะง่ายกว่านี้ล่ะ? ประกาศคลาสเพื่อใช้อินเทอร์เฟซjava.io.Serializable- เท่านี้ก็เรียบร้อย คุณสามารถทำให้ชั้นเรียนเป็นอนุกรมได้โดยไม่มีปัญหา การทำให้เป็นอนุกรมตามที่เป็นอยู่  ตอนที่ 1 - 1ตามทฤษฎีแล้วนี่เป็นเรื่องจริง ในทางปฏิบัติมีรายละเอียดปลีกย่อยมากมาย สิ่งเหล่านี้เกี่ยวข้องกับประสิทธิภาพ การดีซีเรียลไลซ์ และความปลอดภัยของคลาส และยังมีอีกหลายแง่มุม รายละเอียดปลีกย่อยดังกล่าวจะมีการหารือ บทความนี้สามารถแบ่งออกเป็นส่วนต่างๆ ดังต่อไปนี้:
  • รายละเอียดปลีกย่อยของกลไก
  • เหตุใดจึงจำเป็น?Externalizable
  • ผลงาน
  • แต่ในทางกลับกัน
  • ความปลอดภัยของข้อมูล
  • การทำให้เป็นอนุกรมของวัตถุSingleton
มาดูส่วนแรกกันดีกว่า -

รายละเอียดปลีกย่อยของกลไก

ก่อนอื่นคำถามสั้น ๆ มีกี่วิธีในการทำให้วัตถุสามารถซีเรียลไลซ์ได้? การปฏิบัติแสดงให้เห็นว่านักพัฒนามากกว่า 90% ตอบคำถามนี้ในลักษณะเดียวกันโดยประมาณ (ขึ้นอยู่กับถ้อยคำ) - มีเพียงวิธีเดียวเท่านั้น ในขณะเดียวกันก็มีสองคน ไม่ใช่ทุกคนที่จำอันที่สองได้ ไม่ต้องพูดถึงอะไรที่เข้าใจได้เกี่ยวกับฟีเจอร์ของมันเลย แล้ววิธีการเหล่านี้มีอะไรบ้าง? ทุกคนจำอันแรกได้ นี่เป็นการดำเนินการตามที่กล่าวไปแล้วjava.io.Serializableและไม่ต้องใช้ความพยายามใดๆ วิธีที่สองก็คือการนำอินเทอร์เฟซไปใช้เช่นกัน แต่เป็นวิธีอื่น: java.io.Externalizable. ซึ่งแตกต่างจากjava.io.Serializableมีสองวิธีที่จำเป็นต้องดำเนินการ- writeExternal(ObjectOutput)และ readExternal(ObjectInput)วิธีการเหล่านี้ประกอบด้วยตรรกะการทำให้เป็นอนุกรม/ดีซีเรียลไลซ์ ความคิดเห็นSerializableต่อไปนี้ บางครั้งฉันจะอ้างถึงการทำให้เป็นอนุกรมโดยมีการนำไปใช้ เป็นมาตรฐาน และการใช้งาน Externalizableเป็นแบบขยาย อื่นความคิดเห็น. ตอนนี้ฉันจงใจไม่แตะต้องตัวเลือกการควบคุมการทำให้ซีเรียลไลซ์มาตรฐานเช่นการกำหนดreadObjectและwriteObjectเพราะ ฉันคิดว่าวิธีการเหล่านี้ค่อนข้างไม่ถูกต้อง วิธีการเหล่านี้ไม่ได้ถูกกำหนดไว้ในอินเทอร์เฟซSerializableและในความเป็นจริงแล้ว เป็นเพียงอุปกรณ์ประกอบฉากเพื่อแก้ไขข้อจำกัด และทำให้การทำให้ซีเรียลไลซ์มาตรฐานมีความยืดหยุ่น วิธีการที่ให้ความ ยืดหยุ่นExternalizableนั้นถูกสร้างไว้ในนั้นตั้งแต่เริ่มต้น ลองถามคำถามอีกข้อหนึ่ง การทำให้เป็นอนุกรมมาตรฐานทำงานอย่างไรโดยใช้java.io.Serializable? และมันทำงานผ่าน Reflection API เหล่านั้น. คลาสจะถูกแยกวิเคราะห์เป็นชุดของฟิลด์ ซึ่งแต่ละฟิลด์จะถูกเขียนไปยังเอาท์พุตสตรีม ฉันคิดว่าชัดเจนว่าการดำเนินการนี้ไม่เหมาะสมที่สุดในแง่ของประสิทธิภาพ เราจะหาคำตอบในภายหลัง มีความแตกต่างที่สำคัญอีกประการหนึ่งระหว่างวิธีการซีเรียลไลเซชันทั้งสองวิธีที่กล่าวถึง กล่าวคือในกลไกการดีซีเรียลไลเซชัน เมื่อใช้ การดีSerializableซีเรียลไลซ์จะเกิดขึ้นดังนี้: หน่วยความจำถูกจัดสรรสำหรับวัตถุ หลังจากนั้นฟิลด์ของวัตถุจะถูกเติมด้วยค่าจากสตรีม ไม่ได้เรียกตัวสร้างของวัตถุ ที่นี่เราต้องพิจารณาสถานการณ์นี้แยกกัน โอเค คลาสของเราเป็นแบบซีเรียลไลซ์ได้ แล้วพ่อแม่ของเขาล่ะ? ไม่จำเป็นเลย! ยิ่งไปกว่านั้น หากคุณสืบทอดคลาสจากObject- คลาสพาเรนต์จะไม่สามารถซีเรียลไลซ์ได้อย่างแน่นอน และถึงแม้ว่าObjectเราจะไม่รู้อะไรเกี่ยวกับสาขานี้เลย แต่มันก็อาจมีอยู่ในชั้นเรียนผู้ปกครองของเราเอง จะเกิดอะไรขึ้นกับพวกเขา? พวกเขาจะไม่เข้าสู่สตรีมการทำให้เป็นอนุกรม พวกเขาจะใช้ค่าอะไรในการดีซีเรียลไลซ์? ลองดูตัวอย่างนี้:
package ru.skipy.tests.io;

import java.io.*;

/**
 * ParentDeserializationTest
 *
 * @author Eugene Matyushkin aka Skipy
 * @since 05.08.2010
 */
public class ParentDeserializationTest {

    public static void main(String[] args){
        try {
            System.out.println("Creating...");
            Child c = new Child(1);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            c.field = 10;
            System.out.println("Serializing...");
            oos.writeObject(c);
            oos.flush();
            baos.flush();
            oos.close();
            baos.close();
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            System.out.println("Deserializing...");
            Child c1 = (Child)ois.readObject();
            System.out.println("c1.i="+c1.getI());
            System.out.println("c1.field="+c1.getField());
        } catch (IOException ex){
            ex.printStackTrace();
        } catch (ClassNotFoundException ex){
            ex.printStackTrace();
        }
    }

    public static class Parent {
        protected int field;
        protected Parent(){
            field = 5;
            System.out.println("Parent::Constructor");
        }
        public int getField() {
            return field;
        }
    }

    public static class Child extends Parent implements Serializable{
        protected int i;
        public Child(int i){
            this.i = i;
            System.out.println("Child::Constructor");
        }
        public int getI() {
            return i;
        }
    }
}
มีความโปร่งใส - เรามีคลาสพาเรนต์ที่ไม่สามารถซีเรียลไลซ์ได้และคลาสลูกที่ทำให้ซีเรียลไลซ์ได้ และนี่คือสิ่งที่เกิดขึ้น:
Creating...
Parent::Constructor
Child::Constructor
Serializing...
Deserializing...
Parent::Constructor
c1.i=1
c1.field=5
นั่นคือในระหว่างการดีซีเรียลไลซ์ ตัวสร้างที่ไม่มีพารามิเตอร์ของคลาส NON-serializable ระดับบนสุดจะถูกเรียกว่า และหากไม่มีคอนสตรัคเตอร์ดังกล่าว จะเกิดข้อผิดพลาดระหว่างการดีซีเรียลไลเซชัน ตัวสร้างของอ็อบเจ็กต์ลูกซึ่งเป็นอันที่เรากำลังทำการดีซีเรียลไลซ์นั้นไม่ได้ถูกเรียก ตามที่กล่าวไว้ข้างต้น นี่คือลักษณะการทำงานของกลไกมาตรฐานเมื่อSerializableใช้ เมื่อใช้แล้วExternalizableสถานการณ์จะแตกต่างออกไป ขั้นแรก เรียกตัวสร้างที่ไม่มีพารามิเตอร์ จากนั้นจึงเรียกเมธอด readExternal บนอ็อบเจ็กต์ที่สร้างขึ้น ซึ่งอ่านข้อมูลทั้งหมดจริงๆ ดังนั้นคลาสใด ๆ ที่ใช้อินเทอร์เฟซภายนอกจะต้องมีตัวสร้างสาธารณะโดยไม่มีพารามิเตอร์! ยิ่งไปกว่านั้น เนื่องจากลูกหลานทั้งหมดของคลาสดังกล่าวจะได้รับการพิจารณาให้ใช้งานอินเทอร์เฟซExternalizableพวกเขาจึงต้องมีคอนสตรัคเตอร์แบบไร้พารามิเตอร์ด้วย! ไปต่อกันดีกว่า มีตัวแก้ไขฟิลด์เช่นtransient. หมายความว่าฟิลด์นี้ไม่ควรถูกทำให้เป็นอนุกรม อย่างไรก็ตาม ตามที่คุณเข้าใจ คำแนะนำนี้มีผลกับกลไกการทำให้เป็นอนุกรมมาตรฐานเท่านั้น เมื่อใช้แล้วจะExternalizableไม่มีใครรบกวนการทำให้ฟิลด์นี้เป็นอนุกรมและลบออก หากมีการประกาศเขตข้อมูลชั่วคราว เมื่อวัตถุถูกดีซีเรียลไลซ์ จะใช้ค่าเริ่มต้น อีกจุดที่ค่อนข้างละเอียดอ่อน ด้วยการทำให้เป็นอนุกรมมาตรฐาน ฟิลด์ที่มีตัวแก้ไขstaticจะไม่ทำให้เป็นอนุกรม ดังนั้น หลังจากการดีซีเรียลไลซ์ ฟิลด์นี้จะไม่เปลี่ยนค่า แน่นอนว่าในระหว่างการนำไปใช้งานExternalizableไม่มีใครรบกวนการทำให้ซีเรียลไลซ์และดีซีเรียลไลซ์ฟิลด์นี้ แต่ฉันขอแนะนำอย่างยิ่งว่าอย่าทำเช่นนี้เพราะ สิ่งนี้สามารถนำไปสู่ข้อผิดพลาดเล็กน้อยได้ ฟิลด์ที่มีตัวแก้ไขจะถูกfinalทำให้เป็นอนุกรมเหมือนกับฟิลด์ทั่วไป มีข้อยกเว้นประการหนึ่ง - ไม่สามารถดีซีเรียลไลซ์ได้เมื่อใช้ Externalizable เนื่องจากfinal-поляจะต้องเริ่มต้นในตัวสร้าง และหลังจากนั้นจะไม่สามารถเปลี่ยนค่าของฟิลด์นี้ใน readExternal ได้ ดังนั้น หากคุณต้องการซีเรียลไลซ์อ็อบเจ็กต์ที่มีfinal-field คุณจะต้องใช้ซีเรียลไลซ์มาตรฐานเท่านั้น อีกจุดที่หลายคนไม่รู้ การทำให้เป็นอนุกรมมาตรฐานจะคำนึงถึงลำดับในการประกาศฟิลด์ในคลาส ไม่ว่าในกรณีใด นี่เป็นกรณีในเวอร์ชันก่อนหน้า ใน JVM เวอร์ชัน 1.6 ของการใช้งาน Oracle ลำดับไม่สำคัญอีกต่อไป ประเภทและชื่อของฟิลด์มีความสำคัญ องค์ประกอบของวิธีการมีแนวโน้มที่จะส่งผลกระทบต่อกลไกมาตรฐาน แม้ว่าโดยทั่วไปแล้วฟิลด์ต่างๆ อาจยังคงเหมือนเดิมก็ตาม เพื่อหลีกเลี่ยงปัญหานี้ จึงมีกลไกดังต่อไปนี้ สำหรับแต่ละคลาสที่ใช้อินเทอร์เฟซSerializableจะมีการเพิ่มฟิลด์อีกหนึ่งฟิลด์ในขั้นตอนการคอมไพล์ -private static final long serialVersionUID. ฟิลด์นี้มีตัวระบุเวอร์ชันเฉพาะของคลาสซีเรียลไลซ์ คำนวณตามเนื้อหาของคลาส - ฟิลด์ ลำดับการประกาศ วิธีการ ลำดับการประกาศ ดังนั้น เมื่อมีการเปลี่ยนแปลงใดๆ ในคลาส ฟิลด์นี้จะเปลี่ยนค่าของมัน ฟิลด์นี้ถูกเขียนลงในสตรีมเมื่อคลาสถูกทำให้เป็นอนุกรม อย่างไรก็ตาม นี่อาจเป็นกรณีเดียวที่ฉันทราบเมื่อมีstaticการซีเรียลไลซ์ -field ในระหว่างการดีซีเรียลไลซ์ ค่าของฟิลด์นี้จะถูกเปรียบเทียบกับค่าของคลาสในเครื่องเสมือน หากค่าไม่ตรงกัน ข้อยกเว้นเช่นนี้จะเกิดขึ้น:
java.io.InvalidClassException: test.ser2.ChildExt;
    local class incompatible: stream classdesc serialVersionUID = 8218484765288926197,
                                   local class serialVersionUID = 1465687698753363969
อย่างไรก็ตาม มีวิธีที่จะหากไม่เลี่ยงก็หลอกลวงเช็คนี้ สิ่งนี้มีประโยชน์หากมีการกำหนดชุดของฟิลด์คลาสและลำดับไว้แล้ว แต่วิธีการของคลาสอาจมีการเปลี่ยนแปลง ในกรณีนี้ การทำให้ซีเรียลไลซ์ไม่มีความเสี่ยง แต่กลไกมาตรฐานจะไม่อนุญาตให้ข้อมูลถูกดีซีเรียลไลซ์โดยใช้รหัสไบต์ของคลาสที่แก้ไข แต่อย่างที่ฉันพูดไปแล้วเขาสามารถถูกหลอกได้ กล่าวคือกำหนดฟิลด์ในคลาสด้วยprivate static final long serialVersionUIDตนเอง โดยหลักการแล้ว มูลค่าของฟิลด์นี้สามารถเป็นอะไรก็ได้อย่างแน่นอน บางคนชอบที่จะตั้งค่าให้เท่ากับวันที่ที่มีการแก้ไขโค้ด บางคนถึงกับใช้ 1L หากต้องการรับค่ามาตรฐาน (ค่าที่คำนวณภายใน) คุณสามารถใช้ยูทิลิตี้ซีเรียลเวอร์ที่รวมอยู่ใน SDK ได้ เมื่อกำหนดด้วยวิธีนี้ ค่าของฟิลด์จะได้รับการแก้ไข ดังนั้นการดีซีเรียลไลซ์จะได้รับอนุญาตเสมอ ยิ่งไปกว่านั้น ในเวอร์ชัน 5.0 สิ่งต่อไปนี้ปรากฏในเอกสารประกอบโดยประมาณ: ขอแนะนำอย่างยิ่งให้คลาสที่ซีเรียลไลซ์ได้ทั้งหมดประกาศฟิลด์นี้อย่างชัดเจน เนื่องจากการคำนวณเริ่มต้นนั้นไวต่อรายละเอียดของโครงสร้างคลาสมาก ซึ่งอาจแตกต่างกันไปขึ้นอยู่กับการใช้งานคอมไพเลอร์ และทำให้เกิดInvalidClassExceptionผลลัพธ์ที่ไม่คาดคิด . การดีซีเรียลไลซ์ เป็นการดีกว่าที่จะประกาศฟิลด์นี้เป็นprivateเพราะ มันหมายถึงคลาสที่ถูกประกาศเท่านั้น แม้ว่าตัวดัดแปลงจะไม่ได้ระบุไว้ในข้อกำหนดก็ตาม ตอนนี้ให้เราพิจารณาแง่มุมนี้ สมมติว่าเรามีโครงสร้างคลาสนี้:
public class A{
    public int iPublic;
    protected int iProtected;
    int iPackage;
    private int iPrivate;
}

public class B extends A implements Serializable{}
กล่าวอีกนัยหนึ่ง เรามีคลาสที่สืบทอดมาจากพาเรนต์ที่ไม่สามารถซีเรียลไลซ์ได้ เป็นไปได้ไหมที่จะซีเรียลไลซ์คลาสนี้ และสิ่งที่จำเป็นสำหรับสิ่งนี้ จะเกิดอะไรขึ้นกับตัวแปรของคลาสพาเรนต์? คำตอบคือสิ่งนี้ ใช่Bคุณสามารถซีเรียลไลซ์อินสแตนซ์ของคลาสได้ สิ่งที่จำเป็นสำหรับสิ่งนี้? แต่คลาสจำเป็นต้องAมี Constructor ที่ไม่มีพารามิเตอร์publicหรือprotected. จากนั้น ในระหว่างการดีซีเรียลไลซ์ ตัวแปรคลาสทั้งหมดAจะถูกเตรียมใช้งานโดยใช้ตัวสร้างนี้ ตัวแปรคลาสBจะเริ่มต้นด้วยค่าจากสตรีมข้อมูลแบบอนุกรม ตามทฤษฎีแล้วมันเป็นไปได้ที่จะกำหนดBวิธีการที่ฉันพูดถึงในตอนต้น - readObjectและwriteObject- ที่จุดเริ่มต้นในชั้นเรียนซึ่งจะดำเนินการ (ยกเลิก) การทำให้อนุกรมของตัวแปรคลาสBผ่านin.defaultReadObject/out.defaultWriteObjectจากนั้น (ยกเลิก) การทำให้อนุกรมของตัวแปรที่มีอยู่ จากคลาสA(ในกรณีของเราคือiPublicและiProtectedหากอยู่ในแพ็คเกจเดียวกันiPackageกับ) อย่างไรก็ตามในความคิดของฉัน ควรใช้การทำให้เป็นอนุกรมแบบขยายสำหรับสิ่งนี้จะดีกว่า ประเด็นถัดไปที่ฉันอยากจะพูดถึงคือการทำให้วัตถุเป็นอนุกรมของวัตถุหลายชิ้น สมมติว่าเรามีโครงสร้างคลาสดังต่อไปนี้: BA
public class A implements Serializable{
    private C c;
    private B b;
    public void setC(C c) {this.c = c;}
    public void setB(B b) {this.b = b;}
    public C getC() {return c;}
    public B getB() {return b;}
}
public class B implements Serializable{
    private C c;
    public void setC(C c) {this.c = c;}
    public C getC() {return c;}
}
public class C implements Serializable{
    private A a;
    private B b;
    public void setA(A a) {this.a = a;}
    public void setB(B b) {this.b = b;}
    public B getB() {return b;}
    public A getA() {return a;}
}
การทำให้เป็นอนุกรมตามที่เป็นอยู่  ส่วนที่ 1 - 2จะเกิดอะไรขึ้นถ้าคุณซีเรียลไลซ์อินสแตนซ์ของคลาสA? มันจะลากไปตามอินสแตนซ์ของคลาสBซึ่งในทางกลับกันจะลากไปตามอินสแตนซ์Cที่มีการอ้างอิงถึงอินสแตนซ์Aซึ่งเป็นอันเดียวกับที่มันเริ่มต้นทั้งหมด วงจรอุบาทว์และการเรียกซ้ำไม่สิ้นสุด? โชคดีที่ไม่มี ลองดูรหัสทดสอบต่อไปนี้:
// initiaizing
A a = new A();
B b = new B();
C c = new C();
// setting references
a.setB(b);
a.setC(c);
b.setC(c);
c.setA(a);
c.setB(b);
// serializing
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(a);
oos.writeObject(b);
oos.writeObject(c);
oos.flush();
oos.close();
// deserializing
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
A a1 = (A)ois.readObject();
B b1 = (B)ois.readObject();
C c1 = (C)ois.readObject();
// testing
System.out.println("a==a1: "+(a==a1));
System.out.println("b==b1: "+(b==b1));
System.out.println("c==c1: "+(c==c1));
System.out.println("a1.getB()==b1: "+(a1.getB()==b1));
System.out.println("a1.getC()==c1: "+(a1.getC()==c1));
System.out.println("b1.getC()==c1: "+(b1.getC()==c1));
System.out.println("c1.getA()==a1: "+(c1.getA()==a1));
System.out.println("c1.getB()==b1: "+(c1.getB()==b1));
เรากำลังทำอะไรอยู่? เราสร้างอินสแตนซ์ของคลาสAและให้ลิงก์ถึงกัน จากนั้นทำให้คลาสแต่ละคลาสเป็นBอนุกรม Cจากนั้นเราจะดีซีเรียลไลต์พวกมันกลับและดำเนินการตรวจสอบเป็นชุด จะเกิดอะไรขึ้นตามมา:
a==a1: false
b==b1: false
c==c1: false
a1.getB()==b1: true
a1.getC()==c1: true
b1.getC()==c1: true
c1.getA()==a1: true
c1.getB()==b1: true
แล้วคุณเรียนรู้อะไรจากการทดสอบนี้ได้บ้าง? อันดับแรก. การอ้างอิงอ็อบเจ็กต์หลังจากการดีซีเรียลไลซ์จะแตกต่างจากการอ้างอิงก่อนหน้านี้ กล่าวอีกนัยหนึ่ง ในระหว่างการทำให้เป็นอนุกรม/ดีซีเรียลไลซ์ ออบเจ็กต์จะถูกคัดลอก บางครั้งวิธีนี้ใช้ในการโคลนวัตถุ ข้อสรุปที่สองมีความสำคัญมากกว่า เมื่อซีเรียลไลซ์/ดีซีเรียลไลซ์ออบเจ็กต์หลายรายการที่มีการอ้างอิงโยง การอ้างอิงเหล่านั้นยังคงใช้ได้หลังจากการดีซีเรียลไลซ์ กล่าวอีกนัยหนึ่ง หากก่อนที่จะทำให้เป็นอนุกรม พวกเขาชี้ไปที่วัตถุหนึ่ง จากนั้นหลังจากการดีซีเรียลไลซ์ พวกเขาจะชี้ไปที่วัตถุหนึ่งด้วย การทดสอบเล็กๆ อีกอันเพื่อยืนยันสิ่งนี้:
B b = new B();
C c = new C();
b.setC(c);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(b);
oos.writeObject(c);
oos.writeObject(c);
oos.writeObject(c);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
B b1 = (B)ois.readObject();
C c1 = (C)ois.readObject();
C c2 = (C)ois.readObject();
C c3 = (C)ois.readObject();
System.out.println("b1.getC()==c1: "+(b1.getC()==c1));
System.out.println("c1==c2: "+(c1==c2));
System.out.println("c1==c3: "+(c1==c3));
วัตถุคลาสBมีการอ้างอิงถึงวัตถุCคลาส เมื่อทำให้เป็นอนุกรมbมันจะถูกทำให้เป็นอนุกรมพร้อมกับอินสแตนซ์ของคลาสСหลังจากนั้นอินสแตนซ์เดียวกันของ c จะถูกทำให้เป็นอนุกรมสามครั้ง จะเกิดอะไรขึ้นหลังจากการดีซีเรียลไลซ์?
b1.getC()==c1: true
c1==c2: true
c1==c3: true
อย่างที่คุณเห็น จริง ๆ แล้ววัตถุดีซีเรียลไลซ์ทั้งสี่นั้นเป็นตัวแทนของวัตถุเดียว - การอ้างอิงถึงวัตถุนั้นเท่ากัน เหมือนกับที่เคยเป็นมาก่อนที่จะทำให้เป็นอนุกรม อีกจุดที่น่าสนใจ - จะเกิดอะไรขึ้นถ้าเราใช้พร้อมกันExternalizableและSerializable? เช่นเดียวกับคำถามนั้น ช้างปะทะวาฬ ใครจะเอาชนะใครได้? จะเอาชนะExternalizable. กลไกการทำให้เป็นอนุกรมจะตรวจสอบการมีอยู่ของมันก่อน จากนั้นจึงตรวจสอบการมีอยู่ของมันเท่านั้นSerializableดังนั้น หากคลาส B ซึ่งใช้Serializableสืบทอดมาจากคลาส A ซึ่งใช้ ฟิลด์Externalizableของคลาส B จะไม่ถูกทำให้เป็นอนุกรม จุดสุดท้ายคือมรดก เมื่อสืบทอดจากคลาสที่นำไปใช้Serializableก็ไม่จำเป็นต้องดำเนินการใดๆ เพิ่มเติม การทำให้เป็นอนุกรมจะขยายไปถึงคลาสย่อยด้วย เมื่อสืบทอดจากคลาสที่นำไปใช้Externalizableคุณต้องแทนที่เมธอด readExternal และ writeExternal ของคลาสพาเรนต์ มิฉะนั้น ฟิลด์ของคลาสย่อยจะไม่ถูกทำให้เป็นอนุกรม ในกรณีนี้ คุณควรจำไว้ว่าให้เรียกใช้เมธอดพาเรนต์ ไม่เช่นนั้นฟิลด์พาเรนต์จะไม่ถูกทำให้เป็นอนุกรม * * * เราน่าจะอธิบายรายละเอียดเสร็จแล้ว อย่างไรก็ตาม มีประเด็นหนึ่งที่เราไม่ได้พูดถึงซึ่งเป็นประเด็นระดับโลก กล่าวคือ -

ทำไมคุณถึงต้องการ Externalizable?

เหตุใดเราจึงต้องมีการซีเรียลไลซ์ขั้นสูงเลย? คำตอบนั้นง่าย ประการแรก มันให้ความยืดหยุ่นมากกว่ามาก ประการที่สอง มักจะให้ประโยชน์อย่างมากในแง่ของปริมาณข้อมูลซีเรียลไลซ์ ประการที่สาม มีแง่มุมเช่นประสิทธิภาพซึ่งเราจะพูดถึงด้านล่าง . ทุกอย่างดูชัดเจนด้วยความคล่องตัว แท้จริงแล้ว เราสามารถควบคุมกระบวนการซีเรียลไลซ์เซชันและดีซีเรียลไลเซชันตามที่เราต้องการ ซึ่งทำให้เราเป็นอิสระจากการเปลี่ยนแปลงใดๆ ในคลาส (ดังที่ผมได้กล่าวไว้ข้างต้น การเปลี่ยนแปลงในคลาสสามารถส่งผลกระทบอย่างมากต่อการดีซีเรียลไลซ์) ดังนั้นฉันจึงอยากจะพูดสักสองสามคำเกี่ยวกับปริมาณที่เพิ่มขึ้น สมมติว่าเรามีคลาสต่อไปนี้:
public class DateAndTime{

  private short year;
  private byte month;
  private byte day;
  private byte hours;
  private byte minutes;
  private byte seconds;

}
ที่เหลือไม่สำคัญ ฟิลด์ต่างๆ สามารถสร้างเป็นประเภท int ได้ แต่จะปรับปรุงผลกระทบของตัวอย่างเท่านั้น แม้ว่าในความเป็นจริงแล้ว ฟิลด์ต่างๆ อาจถูกพิมพ์intด้วยเหตุผลด้านประสิทธิภาพ ประเด็นไหนก็ชัดเจน ชั้นเรียนแสดงถึงวันที่และเวลา มันเป็นเรื่องที่น่าสนใจสำหรับเราเป็นหลักจากมุมมองของการทำให้เป็นอนุกรม บางทีสิ่งที่ง่ายที่สุดที่จะทำคือการจัดเก็บการประทับเวลาแบบง่ายๆ เป็นประเภทยาวเช่น เมื่อทำให้เป็นอนุกรมจะใช้เวลา 8 ไบต์ นอกจากนี้ วิธีการนี้ต้องใช้วิธีการในการแปลงส่วนประกอบเป็นค่าเดียวและย้อนกลับ เช่น – การสูญเสียผลผลิต ข้อดีของวิธีนี้คือวันที่บ้าสุดๆ ซึ่งสามารถใส่ได้ 64 บิต นี่เป็นส่วนสำคัญของความปลอดภัย ซึ่งส่วนใหญ่มักไม่จำเป็นในความเป็นจริง คลาสที่ระบุข้างต้นจะใช้เวลา 2 + 5*1 = 7 ไบต์ บวกค่าใช้จ่ายสำหรับชั้นเรียนและ 6 สนาม มีวิธีการบีบอัดข้อมูลนี้หรือไม่? แน่นอน. วินาทีและนาทีอยู่ในช่วง 0-59 เช่น เพื่อเป็นตัวแทน 6 บิตก็เพียงพอแล้วแทนที่จะเป็น 8 ชั่วโมง - 0-23 (5 บิต) วัน - 0-30 (5 บิต) เดือน - 0-11 (4 บิต) รวมทุกอย่างโดยไม่คำนึงถึงปี - 26 บิต ยังเหลืออีก 6 บิตสำหรับขนาดของ int ตามทฤษฎีแล้ว ในบางกรณี อาจเพียงพอสำหรับหนึ่งปี ถ้าไม่เช่นนั้น การเพิ่มไบต์อื่นจะเพิ่มขนาดของฟิลด์ข้อมูลเป็น 14 บิต ซึ่งให้ช่วง 0-16383 นี่เกินพอในการใช้งานจริง โดยรวมแล้ว เราได้ลดขนาดของข้อมูลที่จำเป็นในการจัดเก็บข้อมูลที่จำเป็นลงเหลือ 5 ไบต์ หากไม่ถึง 4 ข้อเสียจะเหมือนกับในกรณีก่อนหน้า - หากคุณเก็บวันที่ที่บรรจุไว้แสดงว่าจำเป็นต้องใช้วิธีการแปลง แต่ฉันต้องการทำเช่นนี้: เก็บไว้ในฟิลด์แยกกันและทำให้เป็นอนุกรมในรูปแบบแพ็คเกจ นี่คือที่ที่เหมาะสมที่จะใช้Externalizable:
// data is packed into 5 bytes:
//  3         2         1
// 10987654321098765432109876543210
// hhhhhmmmmmmssssssdddddMMMMyyyyyy yyyyyyyy
public void writeExternal(ObjectOutput out){
    int packed = 0;
    packed += ((int)hours) << 27;
    packed += ((int)minutes) << 21;
    packed += ((int)seconds) << 15;
    packed += ((int)day) << 10;
    packed += ((int)month) << 6;
    packed += (((int)year) >> 8) & 0x3F;
    out.writeInt(packed);
    out.writeByte((byte)year);
}

public void readExternal(ObjectInput in){
    int packed = in.readInt();
    year = in.readByte() & 0xFF;
    year += (packed & 0x3F) << 8;
    month = (packed >> 6) & 0x0F;
    day = (packed >> 10) & 0x1F;
    seconds = (packed >> 15) & 0x3F;
    minutes = (packed >> 21) & 0x3F;
    hours = (packed >> 27);
}
จริงๆแล้วนั่นคือทั้งหมดที่ หลังจากการซีเรียลไลซ์ เราจะมีค่าใช้จ่ายต่อคลาส สองฟิลด์ (แทนที่จะเป็น 6) และข้อมูล 5 ไบต์ ซึ่งดีขึ้นอย่างเห็นได้ชัดแล้ว บรรจุภัณฑ์เพิ่มเติมสามารถฝากไว้ที่ห้องสมุดเฉพาะทางได้ ตัวอย่างที่ให้มานั้นง่ายมาก วัตถุประสงค์หลักคือเพื่อแสดงให้เห็นว่าสามารถใช้ซีเรียลไลซ์เซชั่นขั้นสูงได้อย่างไร แม้ว่าการได้รับปริมาณข้อมูลซีเรียลไลซ์ที่เป็นไปได้นั้นยังห่างไกลจากข้อได้เปรียบหลักในความคิดของฉัน ข้อได้เปรียบหลัก นอกเหนือจากความยืดหยุ่น... (ไปยังส่วนถัดไปได้อย่างราบรื่น...) ลิงก์ไปยังแหล่งที่มา: การทำให้เป็นอนุกรมตามที่เป็นอยู่
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION