Thứ Sáu, 21 tháng 6, 2013

Tìm hiểu về Object Serialization và thuật toán Serialization trong Java

Serialization là tiến trình xử lý (process) lưu trạng thái của object (object’s state) thành dạng chuỗi các byte (sequence of bytes).

Deserialization là tiến trình tái cấu trúc lại từ các bytes thành một đối tượng (live object).

Tại sao lại cần đến Serialization?

Một hệ thống enterprise điển hình thường có các thành phần nằm phân tán rải rác trên các hệ thống và mạng khác nhau. Trong Java mọi thứ đều được miêu tả như là một object. Nếu 2 thành phần Java cần liên lạc với nhau, ta cần phải có một cơ chế để chúng trao đổi dữ liệu. Serialization được định nghĩa cho mục đích này, và các thành phần Java sẽ sử dụng giao thức (protocol) này để truyền các object qua lại với nhau.

Hình minh họa:




Làm thế nào để Serialize một object?

Để Serialize một object ta cần đảm bảo rằng class của object đó cài đặt giao diện java.io.Serializable. Đây là một giao diện trống, ko có method nào cần cài đặt cả.

Thông thường thuật toán Serialization sẽ thực hiện các công việc sau:


  • Ghi xuống các siêu dữ liệu (metadata) về class (ví dụ như tên của class, version của class, tổng số các field của class,….) , của đối tượng đó.

  • Ghi đệ quy các thông tin chi tiết của các lớp cha cho tới khi nó gặp class Object.

  • Sau khi hoàn tất việc ghi các siêu dữ liệu, tiến trình sẽ bắt đầu ghi các dữ liệu thật sự của các đối tượng


Bước kế típ ta sẽ thực sự thực hiện Serialize 1 object. Để làm đươợc điều này ta cần gọi method writeObject() của class java.io.ObjectOutputStream.


import java.io.Serializable;


class parent implements Serializable {
int parentVersion = 10;
}


class contain implements Serializable{
int containVersion = 11;
}

public class SerialTest extends parent implements Serializable {
int version = 66;
contain con = new contain();

public int getVersion() {
return version;
}

public static void main(String args[]) throws IOException {
FileOutputStream fos = new FileOutputStream(“temp.out”);
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerialTest st = new SerialTest();
oos.writeObject(st);
oos.flush();
oos.close();
}
}

Một kết xuất mẫu của các byte sau ghi được ghi ra:

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 6573 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 0776 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 094C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 7265 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 000D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 7000 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 7461 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 000E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 7870 00 00 00 0B


Trình tự của thuật toán:



Ý nghĩa của các byte

  • Bắt đầu bằng các thông tin của giao thức Serialization (serialization protocol):

    • AC ED: STREAM_MAGIC. Cho biết đây là giao thức serialization (serialization protocol).

    • 00 05: STREAM_VERSION. Version của Serialization.

    • 0×73: TC_OBJECT. Cho biết đây là một đối tượng mới.



  • Bước đầu tiên của thật toán là ghi các thông tin của class gắn liền với đối tượng, trong trường hợp này là SerialTest.

    • 0×72: TC_CLASSDESC. Specifies that this is a new class.

    • 00 0A: Length of the class name.

    • 53 65 72 69 61 6c 54 65 73 74: SerialTest, the name of the class.

    • 05 52 81 5A AC 66 02 F6: SerialVersionUID, the serial version identifier of this class.

    • 0×02: Various flags. This particular flag says that the object supports serialization.

    • 00 02: Number of fields in this class.



  • Kế típ thuật toán sẽ ghi xuống filed int version = 66

    • 0×49: Field type code. 49 represents “I”, which stands for Int.

    • 00 07: Length of the field name.

    • 76 65 72 73 69 6F 6E: version, the name of the field.



  • Thuật toán sẽ ghi field kế típ đó là field contain con = new contain();. Đây là một object nên thuật toán sẽ ghi một ký hiệu chuẩn JVM trên filed này ( như là một cách để đánh dấu)

    • 0×74: TC_STRING. Represents a new string.

    • 00 09: Length of the string.

    • 4C 63 6F 6E 74 61 69 6E 3B: Lcontain;, the canonical JVM signature.
      0×78: TC_ENDBLOCKDATA, the end of the optional block data for an object.



  • Bước kế típ thuật toán sẽ ghi thông tin mô tả về class parent, là lớp mà SerialTest kế thừa trực tiếp.

    • 0×72: TC_CLASSDESC. Specifies that this is a new class.

    • 00 06: Length of the class name.

    • 70 61 72 65 6E 74: SerialTest, the name of the class

    • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID, the serial version identifier of this class.

    • 0×02: Various flags. This flag notes that the object supports serialization.

    • 00 01: Number of fields in this class.



  • Kế đến thuật toán sẽ ghi các mô tả của các field trong class parent, class parent có một filed duy nhất là int parentVersion = 10;

    • 0×49: Field type code. 49 represents “I”, which stands for Int.

    • 00 0D: Length of the field name.

    • 70 61 72 65 6E 74 56 65 72 73 69 6F 6E: parentVersion, the name of the field.

    • 0×78: TC_ENDBLOCKDATA, the end of block data for this object.

    • 0×70: TC_NULL, which represents the fact that there are no more superclasses because we have reached the top of the class hierarchy.



  • Kế típ thuật toán sẽ ghi các dữ liệu thật sự của các đối tượng xuống. Bắt đầu từ class parent

    • 00 00 00 0A: 10, the value of parentVersion.



  • Tiếp theo sẽ là class SerialTest

    • 00 00 00 42: 66, the value of version.



  • Cuối cùng thuật toán sẽ bắt đầu việc ghi thông tin mô tả về class và các field của objectcontain cũng như dữ liệu thật sự của object con trong SerialTest:

    • Ghi thông tin mô tả về class contain

      • 0×73: TC_OBJECT, designating a new object.

      • 0×72: TC_CLASSDESC.

      • 00 07: Length of the class name.

      • 63 6F 6E 74 61 69 6E: contain, the name of the class.

      • FC BB E6 0E FB CB 60 C7: SerialVersionUID, the serial version identifier of this class.

      • 0×02: Various flags. This flag indicates that this class supports serialization.

      • 00 01: Number of fields in this class.



    • Ghi thông tin mô tả về các filed của class contain

      • 0×49: Field type code. 49 represents “I”, which stands for Int.

      • 00 0E: Length of the field name.

      • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, the name of the field.

      • 0×78: TC_ENDBLOCKDATA.



    • Kiểm tra class contain có lớp cha hay không?

      • 0×70: TC_NULL. Vì class contain ko có lớp cha nên thuật toán sẽ ghi NULL để thông báo.



    • Ghi xuống dữ liệu thật sự của đối tượng con trong SerialTest

      • 00 00 00 0B: 11, the value of containVersion.






Lớp StringUtils:

import java.io.*;


public class StringUtils {

static final byte[] HEX_CHAR_TABLE =
{
(byte)’0′, (byte)’1′, (byte)’2′, (byte)’3′,
(byte)’4′, (byte)’5′, (byte)’6′, (byte)’7′,
(byte)’8′, (byte)’9′, (byte)’A', (byte)’B',
(byte)’C', (byte)’D', (byte)’E', (byte)’F’
};

public static String getHexString(byte[] raw) throws UnsupportedEncodingException
{
byte[] hex = new byte[2 * raw.length];
int index = 0;

for (byte b : raw)
{
int v = b & 0xFF;
hex[index++] = HEX_CHAR_TABLE[v >>> 4];
hex[index++] = HEX_CHAR_TABLE[v & 0xF];
}
return new String(hex, “ASCII”);
}

public static String convertToHexString(byte[] b) throws Exception {
String result = “”;

for (int i=0; i < b.length; i++)
{
result += Integer.toString( ( b[i] & 0xff ) + 0×100, 16).substring( 1 );
}

return result;
}

public static String formatHexString(String s)
{
StringBuffer buffer = new StringBuffer();

for (int i = 0; i < s.length(); i++)
{
if ((i + 1) % 2 == 0)
{
buffer.append(s.charAt(i – 1))
.append(s.charAt(i))
.append(” “);
}

if ((i + 1) % 30 == 0)
{
buffer.append(“\n”);
}
}

return buffer.toString();
}
}

Method xử lý đọc các bytes từ một file

public static byte[] getBytesFromFile(File file) throws IOException {


InputStream is = new FileInputStream(file);

// Get the size of the file
long length = file.length();

// Create the byte array to hold the data
byte[] bytes = new byte[(int)length];

if (length > Integer.MAX_VALUE)
{
throw new IOException(“File is too large”);
}

// Read in the bytes
int offset = 0;
int numRead = 0;

while (offset < bytes.length && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0)
{
offset += numRead;
}

// Ensure all the bytes have been read in
if (offset < bytes.length)
{
throw new IOException(“Could not completely read file ” + file.getName());
}

// Close the input stream and return bytes
is.close();
return bytes;
}

Link đọc thêm: http://www.javaworld.com/community/node/2915

Lược dịch từ JavaWorld. :D

Object Serialization


Ở bài Các bước căn bản tạo đối tượng trong java, chúng ta đã nói cách tạo 1 danh sách các đối tượng. Ở bài Sử dụng text file để lưu trữ dữ liệu dạng CSDL chúng ta cũng nói về cách dùng TextFile lưu trữ danh sách các đối tượng nhưng cách này không được tốt bởi nó lưu trữ dạng text và việc khôi phục từng dòng text cũng như ký tự phân cách,… làm cho việc này chỉ là ở mức demo cho vui mà thôi. Java có 1 kỹ thuật rất hay dùng để serial 1 đối tượng bất kỳ và gửi xuống tập tin hoặc gửi qua mạng.

Giả sử chúng ta đã có lớp Thisinh.java và lớp Phongthi.java. Bây giờ chúng ta cần chỉnh sửa 1 vài vần đề nữa, cụ thể đó là:

  • Đối với các lớp cần serial, chúng ta phải cho lớp đó implements interface java.io.Serialiable.

  • Tạo 1 lớp chuyên dùng cho việc serial đối tượng với 2 phương thức SerialObject và DeserialObject.


Code cho lớp Thisinh.java






public class Thisinh implements java.io.Serializable{
//Các instatance data
private String soBD;
private String hoTen;
private float diemToan;
private float diemLy;
private float diemHoa;… }

Code cho lớp Phongthi.java






public class Phongthi implements java.io.Serializable{
private static final long serialVersionUID = 7576163164455567730L;
//các thuộc tính của đối tượng phòng thi
private String msPT;
private String diachiPT;
private int luongTS;… }

Code cho lớp dùng để serial đối tượng, lớp MyDBengine.java






package serial;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class MyDBengine {
/**
* Serial 1 đối tượng xuống file
* @param fileName file chỉ định
* @param h: đối tượng cần serial
* @throws Exception
*/
public void SerialObject(String fileName, Object obj) throws Exception{
//Tạo luồng ghi file
FileOutputStream fs=new FileOutputStream(fileName);
//Tạo luồng để serial đối tượng
ObjectOutputStream os=new ObjectOutputStream(fs);
//chuyển tải đối tượng tới đích (tập tin)
os.writeObject(obj);
//đóng luồng
fs.close();os.close();
}
/**
* Khôi phục(deserial) 1 đối tượng đã được serial trước đó
* lên bộ nhớ.
* @param fileName: file chỉ định
* @return đối tượng đã được phục hồi
* @throws Exception
*/
public Object DeserialObject(String fileName) throws Exception{
Object kp=null;
//Tạo luồng đọc file đã được serial
FileInputStream fi=new FileInputStream(fileName);
//Tạo luồng để Deserialize đối tượng
ObjectInputStream ois=new ObjectInputStream(fi);
//Tiến hành khôi phục đối tượng
kp=ois.readObject();
//đóng luồng
fi.close();ois.close();
return kp;
}
}

Như vậy, với lớp này chúng ta có thể dùng nó cho các lần serial bất kỳ đối tượng nào xuống file.

Chúc các bạn thành công!

0 nhận xét:

Đăng nhận xét