Tôi biết câu hỏi này thực sự cũ và có câu trả lời được chấp nhận, nhưng khi nó xuất hiện rất cao trong tìm kiếm google, tôi nghĩ tôi đã cân nhắc vì không có câu trả lời nào được đưa ra trong ba trường hợp tôi cho là quan trọng - trong tâm trí tôi sử dụng chính cho những điều này phương pháp. Tất nhiên, tất cả đều cho rằng thực sự cần một định dạng tuần tự hóa tùy chỉnh.
Lấy ví dụ các lớp sưu tập. Việc xê-ri hóa mặc định danh sách được liên kết hoặc BST sẽ dẫn đến việc mất dung lượng rất lớn với hiệu suất tăng rất ít so với việc chỉ tuần tự hóa các phần tử theo thứ tự. Điều này thậm chí còn đúng hơn nếu bộ sưu tập là hình chiếu hoặc chế độ xem - giữ tham chiếu đến cấu trúc lớn hơn so với API công khai của nó.
Nếu đối tượng được tuần tự hóa có các trường không thay đổi cần tuần tự hóa tùy chỉnh, giải pháp ban đầu writeObject/readObject
là không đủ, vì đối tượng được giải tuần tự được tạo trước khi đọc phần của luồng được viết writeObject
. Hãy thực hiện tối thiểu danh sách liên kết này:
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...
}
Cấu trúc này có thể được tuần tự hóa bằng cách viết đệ quy head
trường của mỗi liên kết, theo sau là một null
giá trị. Tuy nhiên, việc khử định dạng như vậy trở nên không thể: readObject
không thể thay đổi giá trị của các trường thành viên (hiện đã được sửa thành null
). Đây là writeReplace
/ readResolve
cặp:
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;
}
}
}
Tôi xin lỗi nếu ví dụ trên không biên dịch (hoặc làm việc), nhưng hy vọng nó đủ để minh họa quan điểm của tôi. Nếu bạn nghĩ rằng đây là một ví dụ rất xa, hãy nhớ rằng nhiều ngôn ngữ chức năng chạy trên JVM và cách tiếp cận này trở nên thiết yếu trong trường hợp của chúng.
Chúng ta có thể muốn thực sự giải tuần tự hóa một đối tượng của một lớp khác với những gì chúng ta đã viết cho ObjectOutputStream
. Đây sẽ là trường hợp với các khung nhìn như java.util.List
triển khai danh sách hiển thị một lát cắt dài hơn ArrayList
. Rõ ràng, tuần tự hóa toàn bộ danh sách sao lưu là một ý tưởng tồi và chúng ta chỉ nên viết các yếu tố từ lát cắt đã xem. Tại sao lại dừng lại ở đó và có một mức độ vô dụng sau khi giải trừ? Chúng ta có thể chỉ cần đọc các phần tử từ luồng vào một ArrayList
và trả lại trực tiếp thay vì gói nó trong lớp xem của chúng ta.
Ngoài ra, có một lớp đại biểu tương tự dành riêng cho tuần tự hóa có thể là một lựa chọn thiết kế. Một ví dụ tốt sẽ được sử dụng lại mã serialization của chúng tôi. Ví dụ: nếu chúng ta có một lớp trình xây dựng (tương tự StringBuilder cho String), chúng ta có thể viết một ủy nhiệm tuần tự hóa tuần tự hóa bất kỳ bộ sưu tập nào bằng cách viết một trình xây dựng trống vào luồng, theo sau là kích thước bộ sưu tập và các phần tử được trả về bởi trình lặp của colection. Deserialization sẽ liên quan đến việc đọc trình xây dựng, nối thêm tất cả các phần tử đã đọc sau đó và trả về kết quả cuối cùng build()
từ các đại biểu readResolve
. Trong trường hợp đó, chúng ta sẽ chỉ cần triển khai tuần tự hóa trong lớp gốc của hệ thống phân cấp bộ sưu tập và không cần thêm mã từ các triển khai hiện tại hoặc tương lai, miễn là chúng triển khai trừu tượng iterator()
vàbuilder()
phương thức (phương thức sau để tạo lại bộ sưu tập cùng loại - bản thân nó sẽ là một tính năng rất hữu ích). Một ví dụ khác là có một hệ thống phân cấp lớp mà mã chúng tôi không kiểm soát hoàn toàn - (các) lớp cơ sở của chúng tôi từ thư viện bên thứ ba có thể có bất kỳ số trường riêng nào chúng tôi không biết và có thể thay đổi từ phiên bản này sang phiên bản khác, phá vỡ các đối tượng nối tiếp của chúng tôi. Trong trường hợp đó, sẽ an toàn hơn khi ghi dữ liệu và xây dựng lại đối tượng theo cách thủ công về khử lưu huỳnh.
String.CaseInsensitiveComparator.readResolve()