JavaRush /مدونة جافا /Random-AR /التسلسل كما هو. الجزء 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
أي أنه أثناء إلغاء التسلسل، يُطلق على المُنشئ الذي لا يحتوي على معلمات للفئة الأصلية غير القابلة للتسلسل اسم . وإذا لم يكن هناك مثل هذا المُنشئ، فسيحدث خطأ أثناء إلغاء التسلسل. لم يتم استدعاء منشئ الكائن الفرعي، الذي نقوم بإلغاء تسلسله، كما ذكرنا أعلاه. هذه هي الطريقة التي تتصرف بها الآليات القياسية عند استخدامها Serializable. عند استخدامه، Externalizableالوضع مختلف. أولاً، يتم استدعاء المُنشئ بدون معلمات، ثم يتم استدعاء الأسلوب readExternal على الكائن الذي تم إنشاؤه، والذي يقرأ بالفعل جميع بياناته. لذلك، يجب أن يكون لدى أي فئة تطبق واجهة قابلة للتخصيص مُنشئ عام بدون معلمات! علاوة على ذلك، نظرًا لأن جميع أحفاد هذه الفئة سيتم اعتبارهم أيضًا منفذين للواجهة Externalizable، فيجب أن يكون لديهم أيضًا مُنشئ بدون معلمات! دعنا نذهب أبعد من ذلك. يوجد معدّل حقل مثل transient. وهذا يعني أن هذا الحقل لا ينبغي أن يكون متسلسلاً. ومع ذلك، كما تفهم بنفسك، تؤثر هذه التعليمات فقط على آلية التسلسل القياسية. عند استخدامه، Externalizableلا أحد يهتم بتسلسل هذا الحقل، وكذلك طرحه. إذا تم الإعلان عن حقل عابر، فعند إلغاء تسلسل الكائن، فإنه يأخذ القيمة الافتراضية. نقطة أخرى خفية إلى حد ما. staticباستخدام التسلسل القياسي، لا يتم إجراء تسلسل للحقول التي تحتوي على المُعدِّل . وبناء على ذلك، بعد إلغاء التسلسل، لا يغير هذا الحقل قيمته. بالطبع، أثناء التنفيذ، Externalizableلا أحد يهتم بإجراء تسلسل وإلغاء تسلسل هذا الحقل، لكنني أوصي بشدة بعدم القيام بذلك، لأنه هذا يمكن أن يؤدي إلى أخطاء طفيفة. يتم تسلسل الحقول التي تحتوي على مُعدِّل finalمثل الحقول العادية. مع استثناء واحد - لا يمكن إلغاء تسلسلها عند استخدام Externalizable. لأنه final-поляيجب تهيئتها في المُنشئ، وبعد ذلك سيكون من المستحيل تغيير قيمة هذا الحقل في readExternal. وفقًا لذلك، إذا كنت بحاجة إلى إجراء تسلسل لكائن يحتوي على finalحقل، فسيتعين عليك فقط استخدام التسلسل القياسي. نقطة أخرى لا يعرفها الكثير من الناس. يأخذ التسلسل القياسي في الاعتبار الترتيب الذي يتم به إعلان الحقول في الفصل الدراسي. على أية حال، كان هذا هو الحال في الإصدارات السابقة؛ في JVM الإصدار 1.6 من تطبيق Oracle، لم يعد الترتيب مهمًا، ونوع الحقل واسمه مهمان. من المحتمل جدًا أن يؤثر تكوين الطرق على الآلية القياسية، على الرغم من حقيقة أن الحقول قد تظل كما هي بشكل عام. لتجنب ذلك، هناك الآلية التالية. إلى كل فئة تنفذ الواجهة Serializable، يتم إضافة حقل آخر في مرحلة التجميع -private static final long serialVersionUID. يحتوي هذا الحقل على معرف الإصدار الفريد للفئة المتسلسلة. يتم حسابه بناءً على محتويات الفصل - الحقول وترتيب إعلانها وطرقها وترتيب إعلانها. وعليه، مع أي تغيير في الفصل، فإن هذا الحقل سوف يتغير قيمته. تتم كتابة هذا الحقل إلى الدفق عند إجراء تسلسل للفئة. بالمناسبة، ربما تكون هذه هي الحالة الوحيدة التي أعرفها عندما staticيتم إجراء تسلسل للحقل. أثناء إلغاء التسلسل، تتم مقارنة قيمة هذا الحقل بقيمة الفئة الموجودة في الجهاز الظاهري. إذا كانت القيم غير متطابقة، فسيتم طرح استثناء مثل هذا:
java.io.InvalidClassException: test.ser2.ChildExt;
    local class incompatible: stream classdesc serialVersionUID = 8218484765288926197,
                                   local class serialVersionUID = 1465687698753363969
ومع ذلك، هناك طريقة لخداع هذا الشيك، إن لم يكن تجاوزه. يمكن أن يكون هذا مفيدًا إذا كانت مجموعة حقول الفئة وترتيبها محددة بالفعل، ولكن قد تتغير أساليب الفئة. في هذه الحالة، لا يكون التسلسل في خطر، لكن الآلية القياسية لن تسمح بإلغاء تسلسل البيانات باستخدام الرمز الثانوي للفئة المعدلة. ولكن، كما قلت، يمكن خداعه. وهي تحديد الحقل في الفصل يدويًا private static final long serialVersionUID. من حيث المبدأ، يمكن أن تكون قيمة هذا المجال أي شيء على الإطلاق. يفضل بعض الأشخاص تعيينه مساويًا لتاريخ تعديل الكود. حتى أن البعض يستخدم 1 لتر. للحصول على القيمة القياسية (التي يتم حسابها داخليًا)، يمكنك استخدام الأداة المساعدة serialver المضمنة في 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إلى مُنشئ بدون معلمات، publicأو protected. Aبعد ذلك، أثناء إلغاء التسلسل، ستتم تهيئة كافة متغيرات الفئة باستخدام هذا المُنشئ. ستتم تهيئة متغيرات الفئة Bبالقيم من دفق البيانات المتسلسلة. من الناحية النظرية، من الممكن تحديد Bالطرق التي تحدثت عنها في البداية في الفصل - readObjectو writeObject- في البداية والتي يتم من خلالها إجراء (إلغاء) تسلسل متغيرات الفصل Bمن خلال in.defaultReadObject/out.defaultWriteObject، ثم (إلغاء) تسلسل المتغيرات المتاحة من الفصل A(في حالتنا iPublic، هذه هي iProtectedو iPackage، إذا Bكانت في نفس الحزمة مثل A). ومع ذلك، في رأيي، من الأفضل استخدام التسلسل الموسع لهذا الغرض. النقطة التالية التي أود أن أتطرق إليها هي تسلسل كائنات متعددة. لنفترض أن لدينا هيكل الفئة التالية:
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 للفئة الأصلية. وإلا فلن يتم إجراء تسلسل لحقول الفئة التابعة. في هذه الحالة، عليك أن تتذكر استدعاء الأساليب الأصلية، وإلا فلن يتم إجراء تسلسل للحقول الأصلية. * * * ربما انتهينا من التفاصيل. ومع ذلك، هناك قضية واحدة ذات طبيعة عالمية لم نتطرق إليها. يسمى -

لماذا تحتاج إلى خارجي؟

لماذا نحتاج إلى التسلسل المتقدم على الإطلاق؟ الجواب بسيط. أولا، أنه يعطي المزيد من المرونة. ثانيًا، يمكن أن يوفر في كثير من الأحيان مكاسب كبيرة من حيث حجم البيانات المتسلسلة. ثالثا، هناك جانب مثل الأداء، والذي سنتحدث عنه أدناه . يبدو أن كل شيء واضح مع المرونة. في الواقع، يمكننا التحكم في عمليات التسلسل وإلغاء التسلسل كما نريد، مما يجعلنا مستقلين عن أي تغييرات في الفصل (كما قلت أعلاه، يمكن أن تؤثر التغييرات في الفصل بشكل كبير على إلغاء التسلسل). لذلك، أريد أن أقول بضع كلمات عن المكاسب في الحجم. لنفترض أن لدينا الفئة التالية:
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