Программирование на Java
Шрифт:
Расширение стандартной сериализации
Некоторым сложно организованным классам требуется особый подход для сериализации. Для расширения стандартного механизма можно объявить в классе два метода с точно такой сигнатурой:
private void writeObject(
java.io.ObjectOutputStream out)
throws IOException;
private void readObject(
java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
Если в классе объявлены такие методы, то при сериализации объекта для записи его состояния будет вызван writeObject, который должен сгенерировать последовательность байт и записать ее в поток out, полученный в качестве аргумента. При этом можно вызвать стандартный механизм записи объекта
out.defaultWriteObject;
Этот метод запишет все не- transient и не- static поля в поток данных.
В свою очередь, при десериализации метод readObject должен считать данные из потока in (также полученного в качестве аргумента) и восстановить значения полей класса. При реализации этого метода можно обратиться к стандартному механизму с помощью метода:
in.defaultReadObject;
Этот метод считывает описание объекта из потока и присваивает значения соответствующих полей в текущем объекте.
Если же процедура сериализации в корне отличается от стандартной, то для таких классов предназначен альтернативный интерфейс java.io.Externalizable.
При использовании этого интерфейса в поток автоматически записывается только идентификация класса. Сохранить и восстановить всю информацию о состоянии экземпляра должен сам класс. Для этого в нем должны быть объявлены методы writeExternal и readExternal интерфейса Externalizable. Эти методы должны обеспечить сохранение состояния, описываемого полями самого класса и его суперкласса.
При восстановлении Externalizable -объекта экземпляр создается путем вызова конструктора без аргументов, после чего вызывается метод readExternal.
Метод writeExternal имеет сигнатуру:
void writeExternal(ObjectOutput out)
throws IOException;
Для сохранения состояния вызываются методы ObjectOutput, с помощью которых можно записать как примитивные, так и объектные значения. Для корректной работы в соответствующем методе
void readExternal(ObjectInput in)
throws IOException,ClassNotFoundException;
эти значения должны быть считаны в том же самом порядке.
Классы Reader и Writer и их наследники
Рассмотренные классы – наследники InputStream и OutputStream – работают с байтовыми данными. Если с их помощью записывать или считывать текст, то сначала необходимо сопоставить каждому символу его числовой код. Такое соответствие называется кодировкой.
Известно, что Java использует кодировку Unicode, в которой символы представляются двухбайтовым кодом. Байтовые потоки зачастую работают с текстом упрощенно – они просто отбрасывают старший байт каждого символа. В реальных же приложениях могут использовать различные кодировки (даже для русского языка их существует несколько). Поэтому в версии Java 1.1 появился дополнительный набор классов, основывающийся на типах Reader и Writer. Их иерархия представлена на рис. 15.2.
Эта иерархия очень схожа с аналогичной для байтовых потоков InputStream и OutputStream. Главное отличие между ними – Reader и Writer работают с потоком символов ( char ). Только чтение массива символов в Reader описывается методом read(char[]), а запись в Writer – write(char[]).
В таблице 15.1 приведены соответствия классов для байтовых и символьных потоков.
Рис. 15.2. Иерархия классов Reader и Writer.
Таблица 15.1. Соответствие классов для байтовых и символьных потоков.
Байтовый поток | Символьный поток |
---|---|
InputStream | Reader |
OutputStream | Writer |
ByteArrayInputStream | CharArrayReader |
ByteArrayOutputStream | CharArrayWriter |
Нет | InputStreamReader |
Нет аналога | OutputStreamWriter |
FileInputStream | FileReader |
FileOutputStream | FileWriter |
FilterInputStream | FilterReader |
FilterOutputStream | FilterWriter |
BufferedInputStream | BufferedReader |
BufferedOutputStream | BufferedWriter |
PrintStream | PrintWriter |
DataInputStream | Нет аналога |
DataOutputStream | Нет аналога |
ObjectInputStream | Нет аналога |
ObjectOutputStream | Нет аналога |
PipedInputStream | PipedReader |
PipedOutputStream | PipedWriter |
StringBufferInputStream | StringReader |
Нет аналога | StringWriter |
LineNumberInputStream | LineNumberReader |
PushBackInputStream | PushBackReader |
SequenceInputStream | Нет аналога |
Как видно из таблицы, различия крайне незначительны и предсказуемы.
Например, конечно же, отсутствует преобразование в символьное представление примитивных типов Java и объектов ( DataInput/Output, ObjectInput/Output ). Добавлены классы-мосты, преобразующие символьные потоки в байтовые: InputStreamReader и OutputStreamWriter. Именно на их основе реализованы FileReader и FileWriter. Метод available класса InputStream в классе Reader отсутствует, он заменен методом ready, возвращающим булевое значение, – готов ли поток к считыванию (то есть будет ли считывание произведено без блокирования).
В остальном же использование символьных потоков идентично работе с байтовыми потоками. Так, программный код для записи символьных данных в файл будет выглядеть примерно следующим образом:
String fileName = "d:\\file.txt";
//Строка, которая будет записана в файл
String data = "Some data to be written and read.\n";
try {
FileWriter fw = new FileWriter(fileName);
BufferedWriter bw = new BufferedWriter(fw);
System.out.println("Write some data to file: " + fileName);
// Несколько раз записать строку
for(int i=(int)(Math.random*10);--i>=0;)
bw.write(data);
bw.close;
// Считываем результат
FileReader fr = new FileReader(fileName);
BufferedReader br = new BufferedReader(fr);
String s = null;
int count = 0;
System.out.println("Read data from file: " + fileName);
// Считывать данные, отображая на экран
while((s=br.readLine)!=null)
System.out.println("row " + ++count + " read:" + s);