Sunday, February 2, 2014

ජාවා තුල නොයෙකුත් ආකාරයන්ගෙන් Object Serialize/Deserialize කරන අයුරු

Image credit : http://msdn.microsoft.com/en-us/library/ms233843.aspx
මෙම පාඩමේදී අපි අධ්‍යයනය කිරීමට  බලාපොරොත්තුවනුයේ Serialization/Deserialization හි වැදගත්කම සහ ප්‍රායෝගිකව ජාවා වැඩසටහනකදී එම සංකල්පයන් භාවිතාකරන්නේ කෙසේද යන්නයි. මෙහිදී අප පසුගිය පාඩම්වලදී ඉගෙනගත් Streams පිලිබඳ දැනුම ඉතා වැදගත් වන බව කිවයුතුයි.

එම පාඩම් නැවත මතක් කරගැනීමට අවැසි නම් පහත ලින්ක් එකෙන් පිවිස streams පිලිබද නැවත කියවා බලන්න.
http://javaxclass.blogspot.com/p/blog-page_5580.html


Serialization හි මූලික අරමුණ කුමක්ද?  එහි වැදගත්කම් මොනවාද?


පරිගණක වැඩසටහන් ලිවීමේදී බොහෝ අවස්ථාවලදී වැදගත් වන දෙයක් තමයි වැඩසටහන් දෙකක් හෝ කිහිපයක් අතර දත්ත හුවමාරු කරගැනීම. එහිදී දත්ත යනු යම් data structure එකක් හෝ object එකක් විය හැකියි. නමුත් data structure සහ ඔබ්ජෙක්ට්ස් ක්‍රියාකාරී අවස්ථාවේ පවතින්නේ පරිගණකයේ සසම්භාවී මතකයේ(RAM) බව අපි කවුරුත් දන්නා කරුණක්. එම නිසා ඒවා යම් මාධ්‍යයක් ඔස්සේ සම්ප්‍රේශනය කිරීමට සුදුසු තත්වයට පත්කල යුතුයි එමෙන්ම යම් මාධ්‍යයක සුරැකිව තැන්පත් කිරීමටද(save) හැකිවිය යුතුයි.  මෙම ක්‍රියාවලිය හැඳින්වෙන්නේ serialization යනුවෙන්. 
මේ සඳහා විවිධ formats භාවිතා කෙරේ
උදා : 
  • binary
  • json
  • base64
  • Google protobuff
  • bson
එසේ serialize කරන ලද ඔබ්ජෙක්ට් හෝ දත්ත ව්‍යුහයන් නැවතත් පරිගණක වැඩසටහනක් තුල ක්‍රියාකාරී අවස්ථාවේ ප්‍රයෝජනයට ගත හැකි පරිදි සකස්කිරීම සිදුකරනුයේ deserialize ක්‍රියාවලියේදී යි. මෙහිදී අදාල මාධ්‍යය තුලින් serialize කරන ලද දත්ත කියවා බලා එයට අදාල පරිදි පරිගණකයේ මතකය තුල අදාල දත්ත ව්‍යුහය හෝ ඔබ්ජෙක්ට් එක(object state) ප්‍රතිනිර්මාණය කරනු ලබයි.

Web services , RPC(remote procedure calls) භාවිතා කිරීමේදී බහුල වශයෙන් serialization/deserialization භාවිතාවේ එම අවස්ථාවන්හිදී මෙම ක්‍රියාවලිය marshalling/unmarshalling යනුවෙන් හැඳින්වේ.

ජාවා වැඩසටහන් තුල මෙම සංකල්පය ක්‍රියාවට නංවන අයුරු(implement) දැන් සලකා බලමු. මීට පෙර අපි OOP පිලිබඳ අධ්‍යයනය කර ඇති නිසා එම දැනුමද මෙම උදාහරණය ඉදිරිපත් කිරීමේදී එය වඩාත් පැහැදිලිව දැක්වීමට යොදාගෙන ඇති බව සලකන්න.

මෙහිදී serialize කිරීමට ලක්කිරීමට බලාපොරොත්තුවන object හි class එක Serializable inteface එක implement කිරීම කල යුතුය.
syntax :
public class MyClass implements Serializable {
}

මෙහිදී Serializable ඉන්ටර්ෆේස් එක implement කිරීමෙන් සිදුවනුයේ අදාල class එකෙහි object serialize කල හැකි බව සටහන් කිරීමක් වැනි දෙයකි. ඊට අමතරව එම interface එක implement කිරීමෙන් object එකෙහි behaviour එකට කිසිඳු බලපෑමක් සිදුනොවේ.

serialize කිරීමට බඳුන් කරන object සඳහා අදාල classes පහත දක්වා ඇති පරිදි වේ.
Student.java
package entities;

import java.io.Serializable;
import java.util.Map;

/**
 * Created with IntelliJ IDEA. User: kanishka Date: 1/7/14 Time: 12:13 PM To
 * change this template use File | Settings | File Templates.
 */
public class Student implements Serializable {

    private long id;
    private String name;
    private Address address;
    private Map<String, Integer> marks;
    private transient String password;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Map<String, Integer> getMarks() {
        return marks;
    }

    public void setMarks(Map<String, Integer> marks) {
        this.marks = marks;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("ID : ").append(this.id).append("\n");
        stringBuilder.append("Name : ").append(this.name).append("\n");
        stringBuilder.append("Password : ").append(this.password).append("\n");
        stringBuilder.append("Address : ").append(this.address.toString()).append("\n");

        if (this.marks != null) {
            stringBuilder.append("Subjects & Marks").append("\n");
            for (Map.Entry<String, Integer> entry : marks.entrySet()) {
                String key = entry.getKey();
                Integer val = entry.getValue();
                stringBuilder.append("\t").append("Subject : ").append(key).append("\n");
                stringBuilder.append("\t").append("Marks : ").append(val).append("\n");
            }
        }
        return stringBuilder.toString();
    }
}

Serialize කිරීමේදී අවශ්‍යනම් ඇතැම් properties නොසලකා හැරිය හැකිය. මේ සඳහා විවිධ හේතුන් තිබිය හැකිය. මෙම උදාහරණයේදී 16 වන පේලියේ ඇති password නම් property එක එවැන්නකි. මෙහිදී password එක serialize ක්‍රියාවලියේදී නොසලකා හැරීමට උපදෙස් දී ඇත්තේ transient keyword එක භාවිතයෙනි.

දැන් අපි බලමු Serialize ක්‍රියාවලිය සිදුකරන අන්දම. මෙහිදී මාවිසින් Serializer නම් interface එකක් define කර ඇත. එසේ කිරීමේ අරමුණ වනුයෙ විදිධාකාරයේ serializers වැඩසටහනට එකතු කරගැනීමයි. මෙම උදාහරණයේ එවන් serializers 2 ක් implement කර ඇත.
Serializer.java

package util;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;

/**
 * Created with IntelliJ IDEA. User: kanishka Date: 1/7/14 Time: 11:38 AM To
 * change this template use File | Settings | File Templates.
 */
public interface Serializer {

    /**
     * *
     *
     * @param serializable
     * @param filePath
     */
    public void serialize(Serializable serializable, File filePath) throws IOException;

    /**
     * *
     *
     * @param filePath
     * @return
     */
    public Object deSerialize(File filePath) throws IOException, ClassNotFoundException;
}

DefaultFileSerializer.java
package util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * Created with IntelliJ IDEA. User: kanishka Date: 1/7/14 Time: 11:50 AM To
 * change this template use File | Settings | File Templates.
 */
public class DefaultFileSerializer implements Serializer {

    @Override
    public void serialize(Serializable serializable, File filePath) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream(filePath);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(serializable);
        objectOutputStream.close();
        fileOutputStream.close();
    }

    @Override
    public Object deSerialize(File filePath) throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(filePath);
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        Object deserializedObject = objectInputStream.readObject();
        objectInputStream.close();
        fileInputStream.close();
        return deserializedObject;
    }
}


පැහැදිලි කිරීම.
මුලින්ම serialize(..) method එක සලකා බලමු.
මෙහිදී අප විසින් accept කරනු ලබන්නේ Serializable object බැවින් Serializable interface එක implement/realize කර ඇති ඕනෑම class එකක object එකක් මෙම method එක මගින් serialize කල හැකිය. දෙවන parameter එක මගින් serialize කරන ලද object එක සේව් කල යුතු file එක ලබාගනු ලැබේ.

Line 19 : serialize කරන ලද දත්ත සේව් විය යුතු file එක සඳහා file stream එකක් සාදාගනු ලැබේ
Line 20 : අප විසින් බලාපොරොත්තු වන්නේ ඔබ්ජෙක්ට් එක ලබාදී ඇති ෆයිල් එකට ලිවීමෙන් serialization ක්‍රියාවලිය සිදුකරගැනීමයි. එම නිසා file stream එක wrap කරමින් ObjectOutput stream එකක් සකසා ගත යුතුය. මෙය සකසා ගැනීමෙන් පසු අප විසින් FileOutputStream එක සමග  data සම්බන්දව සෘජුව ගනුදෙනු නොකෙරේ.
Line 21: මෙහිදී අදාල ඔබ්ජෙක්ට් එක හෝ data structure එක ObjectOutputStream එකට ලියනු ලැබේ. මෙම ObjectOutput stream එක FileOutputStream එකක් wrap කර සාදා ඇති එකක් වන නිසා එම object එක File එකට ලිවීම මෙමගින් වක්‍රාකාරව සිදුවේ.
ඉන්පසු lines දෙකෙන් සිදුකරන්නේ open කරන ලද streams වසා දැමීමයි. (java 1.7 හි එන try-with භාවිතා කරන්නේනම් මෙය අවශ්‍ය නොවේ)

මීලඟට deserialize(..) method එක සලකා බලමු.
මෙහිදී deserialize කලයුතු file එක parameter එකක් ආකාරයෙන් ලබාගැනීම සිදුකරන අතර deserialize කරන ලද Object එක return කෙරේ.
Line 28-29 : deserialize කිරීමට බලාපොරොත්තු වන file එක සඳහා FileInputStream එකක් සාදා ObjectInputStream එකක් මගින් එය wrap කෙරේ.
Line 30 : ObjectInputStream එක යොදාගෙන File එකේ ඇති object එක ලබාගනු ලැබෙන්නේ මෙහිදීය.

දැන් අපි තවත් ආකාරයකින් serialize/deserialize කරන අන්දම බලමු. මෙහිදී භාවිතා වන්නේ Base64 format එකයි.

Base64FileSerializer.java
package util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * Created with IntelliJ IDEA. User: kanishka Date: 1/7/14 Time: 11:50 AM To
 * change this template use File | Settings | File Templates.
 */
public class Base64FileSerializer implements Serializer {

    @Override
    public void serialize(Serializable serializable, File filePath) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(serializable);
        FileOutputStream fileOutputStream = new FileOutputStream(filePath);
        fileOutputStream.write(Base64.encodeToByte(byteArrayOutputStream.toByteArray(), true));
        fileOutputStream.close();
        objectOutputStream.close();
        byteArrayOutputStream.close();

    }

    @Override
    public Object deSerialize(File filePath) throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(filePath);
        DataInputStream dataInputStream = new DataInputStream(fileInputStream);
        byte[] byteContent = new byte[dataInputStream.available()];
        dataInputStream.readFully(byteContent);
        byteContent = Base64.decode(byteContent);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteContent);
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return objectInputStream.readObject();
    }
}

මෙහිදී serialize method එක තුල සිදුකර ඇත්තේ serialize කරන object එක මුලින්ම ByteArrayOutputStream එකකට ලිවීමයි(Line 22 - 24 )

ඊලඟ පියවර වශයෙන් දත්ත save කිරීමට බලාපොරොත්තුවන file එක සඳහා FileOutputStream එකක් open කරගෙන ඇත. Line 26 දී සිදුකරනුයේ එම FileOutputStream එකට Base64 ආකාරයෙන් encode කරන ලද දත්ත ලිවීමයි. මේ සදහා ByteArrayOutputStream එකෙහි ඇති දත්ත byte[] එකක් ලෙස ලබාගෙන එම byte[] (byte array) එක Base64 ආකාරයට encode කර ලබාගත් byte[] එක File එකට ලිවීමයි.

මෙහිදී Base64 ආකාරයට encode කිරීමට MigBase64 library එක භාවිතා කර ඇත.

deserialize කිරීමේදී එම ක්‍රියාවලියම ආපස්සට සිදුකර ඇත.

දැන් ඉහත serializer වර්ග දෙකම යොදාගෙන Student object එකක් serialize කරන සහ serialize කරන ලද object එක නැවත ලබාගෙන console එකෙහි දර්ශනය කරවන අන්දම බලමු.
Main.java
import entities.Address;
import entities.Student;
import util.Base64FileSerializer;
import util.Serializer;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import util.DefaultFileSerializer;

/**
 * Created with IntelliJ IDEA. User: kanishka Date: 1/7/14 Time: 11:36 AM To
 * change this template use File | Settings | File Templates.
 */
public class Main {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student stud1 = new Student();
        stud1.setId(1232L);
        stud1.setName("Kanishka Dilshan");
        stud1.setAddress(new Address("postbox 123/c", "Railway Avenue", "Galle", "SL"));
        Map<String, Integer> subjectMarksMap = new HashMap<String, Integer>();
        subjectMarksMap.put("ITA", 80);
        subjectMarksMap.put("STII", 90);
        subjectMarksMap.put("PC", 72);
        subjectMarksMap.put("DCCN", 60);
        stud1.setMarks(subjectMarksMap);

        serializeDefault(stud1);
        deserializeDefault(new File("data/stud_serialized_default"));

        serializeBase64(stud1);
        deserialzieBase64(new File("data/stud_serialized_base64"));
    }

    private static void serializeDefault(Serializable serializable) throws IOException {
        Serializer serializer = new DefaultFileSerializer();
        serializer.serialize(serializable, new File("data/stud_serialized_default"));
        System.out.println("Serialized in default format.");
    }

    private static void deserializeDefault(File path) throws IOException, ClassNotFoundException {
        Serializer serializer = new DefaultFileSerializer();
        Student stud = (Student) serializer.deSerialize(path);
        System.out.println("Deserialized default format.");
        System.out.println(stud.toString());
    }

    private static void serializeBase64(Serializable serializable) throws IOException {
        Serializer base64Serializer = new Base64FileSerializer();
        base64Serializer.serialize(serializable, new File("data/stud_serialized_base64"));
        System.out.println("Serialized in base64 format.");
    }

    private static void deserialzieBase64(File path) throws IOException, ClassNotFoundException {
        Serializer serializer = new Base64FileSerializer();
        Student stud = (Student) serializer.deSerialize(path);
        System.out.println("Deserialized base64 data.");
        System.out.println(stud.toString());
    }
}

මෙහි output එක පහත පරිදි වේ.
 මෙම ලිපියේ දක්වා ඇති උදාහරණය සඳහා අදාල සම්පූර්ණ sourcecode එක පහත git repository එකට පිවිසීමෙන් ලබාගත හැකිය.

3 comments:

  1. ඇයි දිගටම ලියනැත්තෙ.මේක ගොඩක් වටින blog එකක්.පුලුවන් තරම් අලුත් post දාන්න වෙලාව තියන විදිහට.

    ReplyDelete
  2. blog එකේ post ටිකනම් පට්ට සුපිරි... අනේ plzzzzzz දිගටම ලියන්න අයියා..... මේ programming coruse පිටින් කලොත් ලක්ෂ 2ක් විතර යනවා. දුප්පත් ඒත් දක්ෂ කොල්ලෝ;කෙල්ලෝ වෙනුවෙන් ලියන්න. ඔයාට ගොඩාරියක් පිං අයියේ...... මම නිසංසලා

    ReplyDelete
  3. ayye digatama liyamu..lessons ikmanta dennako..hugak watinawa..api ayyata pin didi igana gannawa mewayin..

    ReplyDelete