Что делает метод reset в классе objectoutputstream
Перейти к содержимому

Что делает метод reset в классе objectoutputstream

  • автор:

Object Output Stream Класс

Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

[Android.Runtime.Register("java/io/ObjectOutputStream", DoNotGenerateAcw=true)] public class ObjectOutputStream : Java.IO.OutputStream, IDisposable, Java.Interop.IJavaPeerable, Java.IO.IObjectOutput
[] type ObjectOutputStream = class inherit OutputStream interface IObjectOutput interface IDataOutput interface IJavaObject interface IDisposable interface IJavaPeerable

Наследование
ObjectOutputStream
Реализации

Комментарии

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream. Объекты можно считывать (воссоздать) с помощью ObjectInputStream. Постоянное хранение объектов можно выполнить с помощью файла для потока. Если поток является потоком сетевых сокетов, объекты можно восстановить на другом узле или в другом процессе.

В потоки можно записывать только объекты, поддерживающие интерфейс java.io.Serializable. Класс каждого сериализуемого объекта кодируется, включая имя класса и сигнатуру класса, значения полей и массивов объекта, а также закрытие всех других объектов, на которые ссылается исходный объект.

Метод writeObject используется для записи объекта в поток. Любой объект, включая strings и массивы, записывается с помощью writeObject. В поток можно записать несколько объектов или примитивов. Объекты должны считываться обратно из соответствующего objectInputstream с теми же типами и в том же порядке, что и они были записаны.

Примитивные типы данных также можно записать в поток с помощью соответствующих методов из DataOutput. Строки также можно записать с помощью метода writeUTF.

Механизм сериализации по умолчанию для объекта записывает класс объекта, сигнатуру класса и значения всех непереходных и нестатических полей. Ссылки на другие объекты (за исключением временных или статических полей) также вызывают запись этих объектов. Несколько ссылок на один объект кодируются с помощью механизма совместного использования ссылок, чтобы графы объектов можно было восстановить в той же форме, что и при записи исходного объекта.

Например, чтобы написать объект, который может быть прочитан в примере в ObjectInputStream:

FileOutputStream fos = new FileOutputStream("t.tmp"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeInt(12345); oos.writeObject("Today"); oos.writeObject(new Date()); oos.close(); 

Классы, требующие специальной обработки в процессе сериализации и десериализации, должны реализовывать специальные методы с точными сигнатурами:

private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException; private void writeObject(java.io.ObjectOutputStream stream) throws IOException private void readObjectNoData() throws ObjectStreamException; 

Метод writeObject отвечает за запись состояния объекта для конкретного класса, чтобы соответствующий метод readObject смог восстановить его. Методу не нужно беспокоиться о состоянии, принадлежащем к суперклассам или подклассам объекта. Состояние сохраняется путем записи отдельных полей в ObjectOutputStream с помощью метода writeObject или с помощью методов для примитивных типов данных, поддерживаемых DataOutput.

Сериализация не записывает поля объектов, не реализующих интерфейс java.io.Serializable. Подклассы объектов, которые не являются сериализуемыми, могут быть сериализуемыми. В этом случае несериализуемый класс должен иметь конструктор no-arg, чтобы разрешить инициализацию его полей. В этом случае подкласс отвечает за сохранение и восстановление состояния несериализируемого класса. Часто бывает так, что поля этого класса доступны (открытые, пакетные или защищенные) или существуют методы get и set, которые можно использовать для восстановления состояния.

Сериализацию объекта можно предотвратить путем реализации методов writeObject и readObject, которые вызывают исключение NotSerializableException. Исключение будет перехвачено ObjectOutputStream и прервет процесс сериализации.

Реализация интерфейса Externalizable позволяет объекту взять на себя полный контроль над содержимым и форматом сериализованной формы объекта. Методы интерфейса Externalizable, writeExternal и readExternal, вызываются для сохранения и восстановления состояния объектов. При реализации классом они могут записывать и считывать собственное состояние с помощью всех методов ObjectOutput и ObjectInput. Объекты отвечают за обработку любого управления версиями.

Константы перечисления сериализуются иначе, чем обычные сериализуемые или внешние объекты. Сериализованная форма константы перечисления состоит исключительно из ее имени; значения полей константы не передаются. Для сериализации константы перечисления ObjectOutputStream записывает строку, возвращенную методом имени константы. Как и другие сериализуемые или внешние объекты, константы перечисления могут функционировать как целевые объекты обратных ссылок, которые впоследствии появляются в потоке сериализации. Невозможно настроить процесс сериализации констант перечисления; все методы writeObject и writeReplace, определенные типами перечисления, игнорируются во время сериализации. Аналогичным образом все объявления полей serialPersistentFields или serialVersionUID также игнорируются. Все типы перечисления имеют фиксированный serialVersionUID 0L.

Примитивные данные, за исключением сериализуемых полей и внешних данных, записываются в ObjectOutputStream в записях блочных данных. Запись блочных данных состоит из заголовка и данных. Заголовок данных блока состоит из маркера и количества байтов, следующих за заголовком. Последовательные записи примитивных данных объединяются в одну запись блочных данных. Коэффициент блокировки, используемый для записи блочных данных, будет составлять 1024 байта. Каждая запись блочных данных заполняется до 1024 байт или записывается всякий раз, когда режим блочных данных завершается. Вызовы методов ObjectOutputStream writeObject, defaultWriteObject и writeFields изначально завершают любую существующую запись блочных данных.

Добавлено в JDK1.1.

Части этой страницы являются изменениями, основанными на работе, созданной и совместно используемой проектом Android и используемой в соответствии с условиями, Creative Commons 2.5 Attribution License.

Конструкторы

Предоставьте возможность для подклассов, которые полностью повторно используют ObjectOutputStream, чтобы не выделять частные данные, только что используемые этой реализацией ObjectOutputStream.

Конструктор, используемый при создании управляемых представлений объектов JNI; вызывается средой выполнения.

Создает объект ObjectOutputStream, который выполняет запись в указанный выходной поток.

Свойства

Возвращает класс среды выполнения данного объекта Object .

Дескриптор базового экземпляра Android.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

Этот API поддерживает инфраструктуру Mono для Android и не предназначен для использования непосредственно из кода.

Этот API поддерживает инфраструктуру Mono для Android и не предназначен для использования непосредственно из кода.

Методы

Подклассы могут реализовать этот метод, чтобы данные класса могли храниться в потоке.

Подклассы могут реализовать этот метод для хранения пользовательских данных в потоке вместе с дескрипторами для динамических прокси-классов.

Создает и возвращает копию этого объекта.

Закрывает этот выходной поток и освобождает все системные ресурсы, связанные с этим потоком.

Запись нестатических и временных полей текущего класса в этот поток.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

Удалите все буферизированные данные в ObjectOutputStream.

Включите для потока замену объектов в потоке.

Указывает, равен ли какой-то другой объект этому объекту.

Сбрасывает этот выходной поток и принудительно записывает все буферизированные выходные байты.

Возвращает значение хэш-кода для объекта.

Вызывается сборщиком мусора для объекта , когда сборка мусора определяет, что больше нет ссылок на объект .

Пробуждение одного потока, ожидающего на мониторе этого объекта.

Активирует все потоки, ожидающие на мониторе этого объекта.

Извлеките объект , используемый для буферизации постоянных полей для записи в поток.

Этот метод позволяет доверенным подклассам ObjectOutputStream заменять один объект другим во время сериализации.

Сброс не учитывает состояние всех объектов, уже записанных в поток.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

Возвращает строковое представление объекта.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

Укажите версию протокола потока для использования при записи потока.

Заставляет текущий поток ждать, пока он не будет пробужден, как правило, из-за или .

Заставляет текущий поток ждать, пока он не будет пробужден, как правило, из-за em>, <>прерывания < или>em, либо до истечения определенного количества реального времени.

Заставляет текущий поток ждать, пока он не будет пробужден, как правило, из-за em>, <>прерывания < или>em, либо до истечения определенного количества реального времени.

Записывает байты b.length из указанного массива байтов в этот выходной поток.

Записывает байты len из указанного массива байтов, начиная со смещения off в этот выходной поток.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

Записывает логическое значение.

Записывает 8-разрядный байт.

Записывает строку в виде последовательности байтов.

Записывает 16-разрядную символь.

Записывает строку в виде последовательности символов char.

Запишите указанный дескриптор класса в ObjectOutputStream.

Записывает 64-разрядный двойник.

Запишите буферизированные поля в поток.

Записывает 32-разрядное значение float.

Записывает 32-разрядный int.

Записывает 64-разрядную длину.

Запишите указанный объект в ObjectOutputStream.

Метод, используемый подклассами для переопределения метода writeObject по умолчанию.

Записывает 16-разрядный короткий фрагмент.

Предоставляется метод writeStreamHeader, чтобы подклассы могли добавлять или добавлять в поток собственный заголовок.

Записывает объект unshared в ObjectOutputStream.

Примитивная запись данных этой строки в измененном формате UTF-8.

Явные реализации интерфейса

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

Методы расширения

Выполняет преобразование типа, проверенное средой выполнения Android.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

Потоки вывода, OutputStream

Стандартная библиотека Java имеет весьма развитые средства вывода данных. Все возможности вывода данных сосредоточены в пакете java.io.

Существуют две параллельные иерархии классов вывода : OutputStream и Writer. Класс Writer введен в последних версиях Java.

В данной статье рассматривается вопрос использования потоков для вывода данных в файл. Иерархии выходных OutputStream потоков представлена на следующем рисунке.

Иерархия OutputStream

Поток Stream— это абстрактное значение источника или приёмника данных, которые способны обрабатывать информацию. Есть два типа потоков: байтовые и символьные. В некоторых ситуациях символьные потоки более эффективны, чем байтовые. Классы, производные от классов OutputStream или Writer, имеют методы с именами write() для записи одиночных байтов или массива байтов (отвечают за вывод данных).

Выходной поток OutputStream

Класс OutputStream — это абстрактный класс, определяющий байтовый поток вывода. Наследники данного класса определяют куда направлять данные: в массив байтов, в файл или канал. Из массива байт можно создать текстовую строку String.

Методы класса OutputStream :

  • void write(int b) записывает один байт в выходной поток. Аргумент этого метода имеет тип int, что позволяет вызывать write, передавая ему выражение, при этом не нужно выполнять приведение его типа к byte.
  • void write(byte b[]) записывает в выходной поток весь указанный массив байтов.
  • void write(byte b[], int off, int len) записывает в поток len байтов массива, начиная с элемента b[off].
  • void flush() очищает любые выходные буферы, завершая операцию вывода.
  • void close() закрывает выходной поток. Последующие попытки записи в этот поток будут возбуждать IOException.

Класс ByteArrayOutputStream

Класс ByteArrayOutputStream представляет поток вывода, использующий массив байтов в качестве места вывода. Чтобы создать объект данного класса, можно использовать один из его конструкторов :

ByteArrayOutputStream() ByteArrayOutputStream(int size)

Первый конструктор создает массив данных для хранения байтов длиной в 32 байта, а второй конструктор создает массив длиной size.

Примеры использования класса ByteArrayOutputStream :

import java.io.ByteArrayOutputStream; public class TestBOS < public static void main(String[] args) < ByteArrayOutputStream bos; bos = new ByteArrayOutputStream(); String text = "Hello World!"; byte[] buffer = text.getBytes(); try< bos.write(buffer); >catch(Exception e) < System.out.println(e.getMessage()); >// Преобразование массива байтов в строку System.out.println(bos.toString()); // Вывод в консоль по символьно byte[] array = bos.toByteArray(); for (byte b: array) < System.out.print((char)b); >System.out.println(); > >

В классе ByteArrayOutputStream метод write записывает в поток некоторые данные (массив байтов). Этот массив байтов записывается в объекте ByteArrayOutputStream в защищенное поле buf, которое представляет также массив байтов (protected byte[] buf). Так как метод write может вызвать исключение, то вызов этого метода помещается в блок try..catch.

Используя методы toString() и toByteArray(), можно получить массив байтов buf в виде текста или непосредственно в виде массива байт.

С помощью метода writeTo можно перенаправить массив байт в другой поток. Данный метод в качестве параметра принимает объект OutputStream, в который производится запись массива байт :

Для ByteArrayOutputStream не надо явным образом закрывать поток с помощью метода close.

Класс FileOutputStream

Класс FileOutputStream создаёт объект класса OutputStream, который можно использовать для записи байтов в файл. Это основной класс для работы с файлами. Создание нового объекта не зависит от того, существует ли заданный файл или нет. Если файл отсутствует, то будет создан новый файл. В случае попытки открытия файла, доступного только для чтения, будет вызвано исключение.

FileOutputStream имеет следующий конструкторы:

public FileOutputStream(File file) throws FileNotFoundException; public FileOutputStream(String name) throws FileNotFoundException; public FileOutputStream(String name, boolean append) throws FileNotFoundException;

Смысл конструкторов последнего понятен из их описания. Но имеется несколько нюансов :

  • При открытии файла на запись, если файл не существует, то он будет создан.
  • Если файл существует, то он будет полностью обновлен. Т.е. если открыть и сразу закрыть файл, то содержимое файла будет уничтожено; реальный файл на диске станет нулевой длины.
  • Исключением для предыдущего правила является последний из конструкторов. Если третьему параметру append присвоить значение true, то можно будет дописывать в конец файла.

Какой-либо дополнительной функциональности по сравнению с базовым классом FileOutputStream не добавляет.

ByteArrayOutputStream bos = new ByteArrayOutputStream(); String text = «Hello Wolrd!»; byte[] buffer = text.getBytes(); try < bos.write(buffer); >catch(Exception e) < System.out.println(e.getMessage()); >try < FileOutputStream fos = new FileOutputStream("hello.txt"); bos.writeTo(fos); >catch(IOException e)

Класс BufferedOutputStream

Класс BufferedOutputStream создает буфер для потоков вывода. Этот буфер накапливает выводимые байты без постоянного обращения к устройству. И когда буфер заполнен, производится запись данных.

import java.io.*; . String text = «Hello world!»; // строка для записи FileOutputStream fos = new FileOutputStream(«file.txt»); try < BufferedOutputStream bos = new BufferedOutputStream(fos); // Переводим текст в байты byte[] buffer = text.getBytes(); bos.write(buffer, 0, buffer.length); >catch(IOException e)

Класс BufferedOutputStream в конструкторе принимает в качестве параметра объект OutputStream — в примере это файловый поток вывода FileOutputStream.

BufferedOutputStream не добавляет много новой функциональности, он просто оптимизирует действие потока выводаи его следует использовать для организации более эффективного буферизованного вывода в поток.

Класс DataOutputStream

Класс DataOutputStream позволяет писать данные в поток через интерфейс DataOutput, который определяет методы, преобразующие элементарные значения в форму последовательности байтов. Такие потоки облегчают сохранение в файле двоичных данных.

Для записи каждого из примитивных типов предназначен свой метод класса DataOutputStream:

  • writeByte(int value) — записывает в поток 1 байт
  • writeChar(int value) — записывает 2х-байтовое значение char
  • writeInt(int value) — записывает в поток целочисленное значение int
  • writeShort(int v) — записывает в поток значение short
  • writeFloat(float value) — записывает в поток 4-байтовое значение float
  • writeDouble(double value) — записывает в поток 8-байтовое значение double
  • writeBoolean(boolean value) — записывает в поток булевое однобайтовое значение
  • writeLong(long value) — записывает в поток значение long
  • writeUTF(String value) — записывает в поток строку в кодировке UTF-8

import java.io.*; . FileOutputStream fos = new FileOutputStream(«c://data.bin»); // запись в файл try (DataOutputStream dos = new DataOutputStream(fos)) < // записываем значения dos.writeUTF("Киса Воробьянинов"); dos.writeInt(30); dos.writeDouble(20.58); dos.writeBoolean(falss); System.out.println("Запись в файл выполнена"); >catch(IOException e)

Класс PrintStream

PrintStream является именно тем классом, который используется для вывода информации в консоль. Когда мы с помощью вызова System.out.println() пишем в консоль некоторую информацию, то тем самым используется PrintStream, так как переменная out класса System представляет объект класса PrintStream, а метод println() — это метод класса PrintStream.

Но PrintStream можно использовать для записи информации в поток вывода. Например, запишем информацию в файл:

import java.io.*; . String text = «Hello, World!»; // строка для записи FileOutputStream fos = new FileOutputStream(«C:/data.txt»); try < PrintStream printStream = new PrintStream(fos)); printStream.println(text); System.out.println("Запись в файл выполнена"); >catch(IOException e)

В данном примере используется конструктор PrintStream, который в качестве параметра принимает поток вывода FileOutputStream. Можно было бы также использовать конструктор с указанием названия файла для записи: PrintStream (String filename).

С помощью метода println() производится запись информации в выходной поток — то есть в объект FileOutputStream. В случае с выводом на консоль с помощью System.out.println() в качестве потока вывода выступает консоль.

Для вывода информации в выходной поток PrintStream использует следующие методы:

println(): вывод строковой информации с переводом строки print(): вывод строковой информации без перевода строки printf(): форматированный вывод

Следующий код показывает возможности использования форматированного вывода класса PrintStream :

int i = 15; printStream.printf("Квадрат числа %d равен %d \n", i, i*i);

Класс ObjectOutputStream

Класс ObjectOutputStream используется для сериализации объектов в поток. Сериализация представляет процесс записи состояния объекта в поток, соответственно процесс извлечения или восстановления состояния объекта из потока называется десериализацией. Сериализация очень удобна, когда идет работа со сложными объектами.

Для создания объекта ObjectOutputStream необходимо в конструктор передать поток, в который будет производится запись объектов.

Для записи данных ObjectOutputStream использует ряд методов, среди которых можно выделить следующие:

Метод Описание
void close() закрывает поток
void flush() сбрасывает содержимое буфера в выходной поток и очищает его
void write(byte[] buf) записывает в поток массив байтов
void write(int val) записывает в поток один младший байт из val
void writeBoolean(boolean val) записывает в поток значение boolean
void writeByte(int val) записывает в поток один младший байт из val
void writeChar(int val) записывает в поток значение типа char, представленное целочисленным значением
void writeDouble(double val) записывает в поток значение типа double
void writeFloat(float val) записывает в поток значение типа float
void writeInt(int val) записывает целочисленное значение
void writeLong(long val) записывает значение типа long
void writeShort(int val) записывает значение типа short
void writeUTF(String str) записывает в поток строку в кодировке UTF-8
void writeObject(Object obj) записывает в поток отдельный объект

Представленные методы охватывают весь спектр данных, которые можно сериализовать.

Пример использования класса ObjectOutputStream :

import java.io.*; class Person implements Serializable < private static final long serialVersionUID = 1L; public String name ; public int age ; public double height ; public boolean married; Person(String name,int age,double height,boolean married) < this.name = name; this.age = age; this.height = height; this.married = married; >> public class Example < public static void main(String[] args) < FileOutputStream fos; fos = new FileOutputStream("c:/data/persons.dat"); try < ObjectOutputStream oos; Person person; oos = new ObjectOutputStream(fos); person = new Person("Остап Бендер",35,175,false); oos.writeObject (person); >catch(Exception e) < System.out.println(e.getMessage()); >> >

Необходимо принимать во внимание, что сериализовать можно только те объекты, которые реализуют интерфейс Serializable.

Класс PipedOutputStream

Пакет java.io содержит класс PipedOutputStream, который может быть подключен к PipedInputStream, используемый для установления связи между двумя каналами. Данные в PipedOutputStream передаются в потоке Thread, который отправляет их в подключенный PipedInputStream, где данные также читаются, но в другом потоке.

То есть, класс PipedOutputStream предназначен для передачи информации между программами через каналы (pipes).

Наиболее часто используемые методы класса PipedOutputStream :

  • void write(int b) — запись байта в канал
  • void write(byte[] bytes, int off, int len) — запись определенного количества len байт начиная со смещения off массив bytes
  • connect(PipedInputStream pis) — установление связи в каналом ввода pis
  • close() — закрытие канала
  • flush() — сброс данных в канал

Все методы класса могут вызвать исключение IOException.

Пример использования класса PipedOutputStream :

import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; public class Example < public static void main(String[] args) < PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(); byte[] bytes = "Hello, World!".getBytes(); try < // Установление связи между "трубами" pos.connect(pis); // Запись данных в PipedOutputStream for (int i = 0; i < bytes.length; i++) pos.write(bytes[i]); // Чтение данных из PipedInputStream int c; while((c = pis.read() ) != -1) < System.out.print((char) c); >> catch (IOException ioe) < System.out.println(ioe); >> >

Что делает метод reset в классе objectoutputstream

Java Serialization API используется множеством других Java API (например, RMI и JavaBeans) для сохранения объектов за пределами жизненного цикла виртуальной машины. Вы также можете самостоятельно использовать Java Serialization API для сохранения объектов в собственных целях. Несмотря на простоту основ сериализации Java, в использовании API существуют некоторые сложные моменты. В этой статье Тодд Гриньер (Todd Greanier) откроет вам секреты использования Java Serialization API.

Все мы знаем о том, что Java позволяет создавать в памяти объекты для многократного использования. Однако все эти объекты существуют лишь до тех пор, пока выполняется создавшая их виртуальная машина. Было бы неплохо, если бы создаваемые нами объекты могли бы существовать и вне пределов жизненного цикла виртуальной машины, не так ли? Что же, используя сериализацию объектов вы можете разложить свои объекты на байты и затем использовать их наиболее эффективным образом.

Сериализация объектов — это процесс сохранения состояния объектов в виде последовательности байтов, а также процесс восстановления в дальнейшем из этих байтов «живых» объектов. Java Serialization API предоставляет разработчикам Java стандартный механизм управления сериализацией объектов. API мал и легок в применении, а его классы и методы просты для понимания.

В этой статье мы увидим каким образом можно сохранять Java объекты и, начав с основ, закончим знакомством с более сложными концепциями. Мы рассмотрим три различных способа выполнения сериализации (используя протокол по умолчанию, изменяя протокол по умолчанию и создавая свой собственный протокол) и познакомимся с ситуациями, возникающими при использовании этих схем сохранения, такими как кэширование объектов, контроль версий и проблемы с быстродействием.

Прочитав статью вы получите полное представление об этом мощном, но зачастую плохо понимаемом Java API.

Начнем с начала: Механизм используемый по умолчанию

Давайте начнем с основ. Для сохранения объекта в Java мы должны иметь объект, нуждающийся в сохранении и этот объект должен быть отмечен как сериализуемый. Это осуществляется путем реализации объектом интерфейса java.io.Serializable, что является для API знаком того, что объект может быть разложен на байты, а затем вновь восстановлен.

Давайте взглянем на сохраняемый класс, используемый в нашей статье для демонстрации механизма сериализации:

10 import java.io.Serializable; 20 import java.util.Date; 30 import java.util.Calendar; 40 public class PersistentTime implements Serializable 50 < 60 private Date time; 70 80 public PersistentTime() 90 < 100 time = Calendar.getInstance().getTime(); 110 >120 130 public Date getTime() 140 < 150 return time; 160 >170 >

Как вы видите, единственное чем этот класс отличается от обычного класса — то, что он реализует интерфейс java.io.Serializable в 40-й строке. Будучи совершенно пустым, интерфейс Serializable является лишь маркерным интерфейсом — он позволяет механизму сериализации определить, может ли данный класс быть сохранен. Итак, мы можем сформулировать первое правило сериализации:

Правило №1: Сохраняемый объект должен реализовать интерфейс Serializable или унаследовать эту реализацию от вышестоящего по иерархии объекта.

Следующим шагом является, собственно, сохранение объекта. Оно выполняется при помощи класса java.io.ObjectOutputStream. Этот класс является фильтрующим потоком (filter stream) — он окружает низкоуровневый поток байтов (называемый узловым потоком (node stream)) и предоставляет нам поток сериализации. Узловые потоки могут быть использованы для записи в файловую систему, или даже в сокеты. Это означает, что мы с легкостью можем передавать разложенные на байты объекты по сети и затем восстанавливать их на других компьютерах!

Давайте взглянем на код, используемый для сохранения объекта PersistentTime:

10 import java.io.ObjectOutputStream; 20 import java.io.FileOutputStream; 30 import java.io.IOException; 40 public class FlattenTime 50 < 60 public static void main(String [] args) 70 < 80 String filename = "time.ser"; 90 if(args.length >0) 100 < 110 filename = args[0]; 120 >130 PersistentTime time = new PersistentTime(); 140 FileOutputStream fos = null; 150 ObjectOutputStream out = null; 160 try 170 < 180 fos = new FileOutputStream(filename); 190 out = new ObjectOutputStream(fos); 200 out.writeObject(time); 210 out.close(); 220 >230 catch(IOException ex) 240 < 250 ex.printStackTrace(); 260 >270 > 280 >

Реальная работа выполняется в 200-й строке, когда мы вызываем метод ObjectOutputStream.writeObject(), который запускает механизм сериализации и объект разлагается на байты (в данном случае в файл).

Для восстановления объекта из файла можно использовать следующий код:

10 import java.io.ObjectInputStream; 20 import java.io.FileInputStream; 30 import java.io.IOException; 40 import java.util.Calendar; 50 public class InflateTime 60 < 70 public static void main(String [] args) 80 < 90 String filename = "time.ser"; 100 if(args.length >0) 110 < 120 filename = args[0]; 130 >140 PersistentTime time = null; 150 FileInputStream fis = null; 160 ObjectInputStream in = null; 170 try 180 < 190 fis = new FileInputStream(filename); 200 in = new ObjectInputStream(fis); 210 time = (PersistentTime)in.readObject(); 220 in.close(); 230 >240 catch(IOException ex) 250 < 260 ex.printStackTrace(); 270 >280 catch(ClassNotFoundException ex) 290 < 300 ex.printStackTrace(); 310 >320 // распечатать восстановленное время 330 System.out.println("Время разложения: " + time.getTime()); 340 System.out.println(); 350 // распечатать текущее время 360 System.out.println("Текущее время: " + Calendar.getInstance().getTime()); 370 > 380>

В этом коде в 210-й строке происходит восстановление объекта при помощи вызова метода ObjectInputStream.readObject(). Метод считывает последовательность байтов, которую мы перед этим сохранили в файле, и создает «живой» объект, полностью повторяющий оригинал. Поскольку readObject() может считывать любой сериализуемый объект, необходимо его присвоение соответствующему типу. Таким образом, из системы, в которой происходит восстановление объекта, должен быть доступен файл класса. Другими словами, при сериализации не сохраняется ни файл класса объекта, ни его методы, сохраняется лишь состояние объекта.

Затем, в 360-й строке, мы просто вызываем метод getTime(), чтобы получить время у разложенного объекта. Время разложенного объекта сравнивается с текущим временем, дабы показать что механизм действительно работает так, как мы ожидаем.

Несериализуемые объекты

Основной механизм сериализации объектов Java прост для применения, но есть еще кое-что, что вам необходимо знать. Как упоминалось ранее, сохраняться могут лишь объекты, помеченные как Serializable. Класс java.lang.Object не реализует этот интерфейс, поэтому не все объекты Java могут быть автоматически сохранены. Хорошая новость заключается в том, что большая часть из них, включая AWT и компоненты Swing GUI, строки и массивы — сериализуемые.

В то же время, некоторые системные классы, такие как Thread, OutputStream и его подклассы, и Socket — не сериализуемые На самом деле даже если бы они были сериализуемыми, ничего бы не изменилось. К примеру, поток, запущенный в моей виртуальной машине, использует системную память. Его сохранение и последующее восстановление в вашей виртуальной машине ни к чему не приведет. Другой важный момент, вытекающий из того, что java.lang.Object не реализует интерфейс Serializable, заключается в том, что любой созданный вами класс, который расширяет только Object (и больше никакие сериализуемые классы) не может быть сериализован до тех пор, пока вы сами не реализуете этот интерфейс (как было показано в предыдущем примере).

Такая ситуация вызывает проблему: что если у нас есть класс, который содержит экземпляр Thread? Можем ли мы в этом случае сохранить объект такого типа? Ответ положительный, поскольку мы имеем возможность сообщить механизму сериализации о своих намерениях, пометив объект Thread нашего класса как нерезидентный (transient).

Предположим, нам нужно создать класс, выполняющий анимацию. В своем примере я не стал приводить код анимации, ограничившись только общим описанием класса:

10 import java.io.Serializable; 20 public class PersistentAnimation implements Serializable, Runnable 30 < 40 transient private Thread animator; 50 private int animationSpeed; 60 public PersistentAnimation(int animationSpeed) 70 < 80 this.animationSpeed = animationSpeed; 90 animator = new Thread(this); 100 animator.start(); 110 >120 public void run() 130 < 140 while(true) 150 < 160 // выполнение анимации 170 >180 > 190 >

При создании экземпляра класса PersistentAnimation создается и запускается поток animator. В 40-й строке мы пометили этот поток как transient, дабы сообщить механизму сериализации о том, что поле не должно сохраняться вместе с остальными состояниями этого объекта (в нашем случае, полем speed). Резюме: вы должны помечать как transient все поля, которые либо не могут быть сериализованы, либо те, которые вы не хотите сериализовать. Сериализация не заботится о модификаторах доступа, таких как private. Все резидентные поля рассматриваются как части состояния сохраняемого объекта, предназначенные для сохранения.

Следовательно нам нужно добавить еще одно правило. Итак, вот оба правила относительно сохраняемых объектов:

  • Правило №1: Сохраняемый объект должен реализовать интерфейс Serializable или унаследовать эту реализацию от вышестоящего по иерархии объекта.
  • Правило №2: Сохраняемый объект должен пометить все свои несериализуемые поля как transient.

Изменение протокола по умолчанию

Давайте перейдем ко второму способу реализации сериализации: изменение протокола по умолчанию. Хотя в анимационном коде, рассмотренном выше, был показан способ использования потока с объектом, обеспечив при этом его сериализацию, для того чтобы понять суть проблемы нужно разобраться в том, каким образом Java создает объекты. Задумайтесь, когда мы создаем объект при помощи ключевого слова new, конструктор объекта вызывается только при создании нового экземпляра объекта. Запомним этот факт и вновь взглянем на наш анимационный код. Сначала мы создаем экземпляр объекта PersistentAnimation, который запускает поток анимации. Затем мы сериализуем его при помощи кода:

PersistentAnimation animation = new PersistentAnimation(10); FileOutputStream fos = . ObjectOutputStream out = new ObjectOutputStream(fos); out.writeObject(animation);

Все кажется в порядке, но только до тех пор, пока мы не прочитаем объект используя вызов метода readObject(). Помните, конструктор вызывается только при создании нового экземпляра объекта. Здесь же мы не создаем нового экземпляра, мы просто восстанавливаем сохраненный объект. В результате анимационный объект отработает лишь однажды, при первом создании экземпляра этого объекта, что делает процесс его сохранения бессмысленным, не так ли?

Что же, есть и хорошая новость. Мы можем заставить наш объект работать так, как нам хочется, перезапуская анимацию при восстановлении объекта. Чтобы сделать это мы можем, например, создать вспомогательный метод startAnimation(), выполняющий те же функции, что и наш конструктор. Затем мы можем вызывать этот метод из конструктора, после каждой загрузки объекта. Неплохо, но несколько сложно. Теперь все, кто захочет использовать анимационный объект, должны знать о необходимости вызова этого метода после обычного процесса десериализации, что никак не вписывается в тот единообразный механизм, который Java Serialization API обещает разработчикам.

Однако, существует другое странное и хитрое решение. Используя встроенную возможность механизма сериализации, разработчики могут реализовать нормальный процесс поместив в свои файлы классов два метода:

  • private void writeObject(ObjectOutputStream out) throws IOException;
  • private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

Обратите внимание, что оба метода (совершенно справедливо), объявлены как private, поскольку это гарантирует что методы не будут переопределены или перезагружены. Весь фокус в том, что виртуальная машина при вызове соответствующего метода автоматически проверяет, не были ли они объявлены в классе объекта. Виртуальная машина в любое время может вызвать private методы вашего класса, но другие объекты этого сделать не смогут. Таким образом обеспечивается целостность класса и нормальная работа протокол сериализации. Протокол сериализации всегда используется одинаково, путем вызова ObjectOutputStream.writeObject() или ObjectInputStream.readObject(). Таким образом, даже если в классе присутствуют эти специализированные private методы, сериализация объектов будет работать так же, как и для любых других вызываемых объектов.

Учитывая это, давайте взглянем на исправленную версию PersistentAnimation, в которую включены эти private методы для контроля над процессом десериализации через псевдо-конструктор:

10 import java.io.Serializable; 20 public class PersistentAnimation implements Serializable, Runnable 30 < 40 transient private Thread animator; 50 private int animationSpeed; 60 public PersistentAnimation(int animationSpeed) 70 < 80 this.animationSpeed = animationSpeed; 90 startAnimation(); 100 >110 public void run() 120 < 130 while(true) 140 < 150 // do animation here 160 >170 > 180 private void writeObject(ObjectOutputStream out) throws IOException 190 < 200 out.defaultWriteObject(); 220 >230 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException 240 < 250 // наш "псевдо-конструктор" 260 in.defaultReadObject(); 270 // теперь мы вновь получили "живой" объект, поэтому давайте перестроим и запустим его 280 startAnimation(); 290 300 >310 private void startAnimation() 320 < 330 animator = new Thread(this); 340 animator.start(); 350 >360 >

Обратите внимание на первые строки новых private методов. Эти вызовы выполняют операции, созвучные их названию — они выполняют по умолчанию запись и чтение разложенных объектов, что важно, поскольку мы не заменяем нормальный процесс, а лишь дополняем его. Эти методы работают, потому что вызов ObjectOutputStream.writeObject() соответствует протоколу сериализации. Сначала объект проверяется на реализацию Serializable, а затем проверяется на наличие этих private методов. Если они есть, им в качестве параметра передается класс потока, через использование которого осуществляется управление кодом.

Эти private методы могут использоваться для внесения любого рода изменений в процесс сериализации. Например, для вывода объектов в поток может быть использована шифровка, а для ввода — дешифровка (при записи и чтении байтов данные записываются даже без применения технологии запутывания (obfuscation)). Методы могут использоваться также для сохранения в потоке дополнительных данных, например кода версии. Ваши возможности поистине не ограничены.

Остановите сериализацию!

О’кей, мы уже кое-что узнали о процессе сериализации, а теперь давайте двигаться дальше. Что если вы создали класс, чей суперкласс сериализуемый, но при этом вы не хотите чтобы ваш класс был сериализуемым? Вы не можете «разреализовать» интерфейс, поэтому если суперкласс реализует Serializable, то и созданный вами новый класс также будет реализовать его (в соответствии с двумя рассмотренными выше правилами). Чтобы остановить автоматическую сериализацию вы можете снова применить private методы для создания исключительной ситуации NotSerializableException. Вот как это можно сделать:

10 private void writeObject(ObjectOutputStream out) throws IOException 20 < 30 throw new NotSerializableException("Не сегодня!"); 40 >50 private void readObject(ObjectInputStream in) throws IOException 60

Любая попытка записать или прочитать этот объект теперь приведет к возникновению исключительной ситуации. Запомните, если методы объявлены как private, никто не сможет модифицировать ваш код не изменяя исходный код класса. Java не позволяет переопределять такие методы.

Создание своего собственного протокола: интерфейс Externalizable

Наше обсуждение было бы неполным без упоминания третьей возможности сериализации: создания собственного протокола с интерфейсом Externalizable. Вместо реализации интерфейса Serializable, вы можете реализовать интерфейс Externalizable, который содержит два метода:

  • public void writeExternal(ObjectOutput out) throws IOException;
  • public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

Для создания собственного протокола нужно просто переопределить эти два метода. В отличие от двух рассмотренных ранее вариантов сериализации, здесь ничего не делается автоматически. Протокол полностью в ваших руках. Хотя это и наболее сложный способ, при этом он наиболее контролируемый. Возьмем, к примеру, ситуацию с альтернативным типом сериализации: запись и чтение PDF файлов Java приложением. Если вы знаете как читать и записывать PDF файлы (требуется определенная последовательность байт), вы можете создать протокол с учетом специфики PDF используя методы writeExternal и readExternal.

Так же, как и в рассмотренных случаях, нет никакой разницы в том, как используется класс, реализующий Externalizable. Вы просто вызываете методы writeObject() или readObject() и, вуаля, эти расширяемые методы будут вызываться автоматически.

Нюансы

Существует несколько моментов, имеющих отношение к протоколу сериализации, которые могут показаться очень странными для непосвященного разработчика. Разумеется, цель этой статьи посвятить вас в них! Поэтому давайте обсудим некоторые нюансы, попробуем понять почему они появились и как с ними обращаться.

Кэширование объектов в потоке

Во-первых, рассмотрим ситуацию, когда объект однажды уже записанный в поток, спустя какое-то время записывается в него снова. По умолчанию, ObjectOutputStream сохраняет ссылки на объекты, которые в него записываются. Это означает, что если состояние записываемого объекта, который уже был записан, будет записано снова, новое состояние не сохраняется! Следующий фрагмент кода демонстрирует эту проблему:

10 ObjectOutputStream out = new ObjectOutputStream(. ); 20 MyObject obj = new MyObject(); // должен быть Serializable 30 obj.setState(100); 40 out.writeObject(obj); // сохраняет объект с состоянием = 100 50 obj.setState(200); 60 out.writeObject(obj); // не сохраняет новое состояние объекта

Существует два способа взять ситуацию под контроль. Во-первых, вы можете каждый раз после вызова метода записи убеждаться в том, что поток закрыт. Во-вторых, вы можете вызвать метод ObjectOutputStream.reset(), который сигнализирует потоку о том, что необходимо освободить кэш от ссылок, которые он хранит, чтобы новые вызовы методов записи действительно записывали данные. Будьте осторожны, reset очищает весь кэш объекта, поэтому все ранее записанные объекты могут быть перезаписаны заново.

Контроль версий

Для второго случая представим что мы создали класс, затем создали его экземпляр, который записали в поток объекта. Этот разложенный на байты объект какое-то время находился в файловой системе. Тем временем вы обновляете файл класса, например, добавив в него новое поле. Что произойдет если затем вы попробуете прочитать разложенный объект?

Плохая новость заключается в том, что возникнет исключительная ситуация, а именно java.io.InvalidClassException, потому что всем классам, которые могут быть сохранены, присваивается уникальный идентификатор. Если идентификатор класса не совпадает с идентификатором разложенного объекта, возникает исключительная ситуация. Однако, если задуматься над этим, зачем нужны все эти исключительные ситуации, если вы всего лишь добавили новое поле? Разве нельзя установить в поле значение по умолчанию, а после сохранено?

Да, но это потребует легких манипуляций с кодом. Идентификатор, который является частью всех классов, хранится в поле, которое называется serialVersionUID. Если вы хотите контролировать версии, вы должны вручную задать поле serialVersionUID и убедиться в том, что оно такое же, и не зависит от изменений, внесенных вами в объект. Вы можете использовать утилиту, входящую в состав JDK, которая называется serialver, чтобы посмотреть какой код будет присвоен по умолчанию (это просто hash код объекта по умолчанию).

Вот пример использования serialver с классом Baz:

> serialver Baz > Baz: static final long serialVersionUID = 10275539472837495L;

Просто скопируйте возвращенную строку с идентификатором версии и поместите ее в ваш код. (В Windows вы можете запустить эту утилиту с опцией -show, чтобы упростить процедуру копирования и вставки). Теперь, если вы внесли какие-либо изменения в файл класса Baz, просто убедитесь что указан тот же идентификатор версии и все будет в порядке.

Контроль за версией прекрасно работает до тех пор, пока вносимые изменения совместимы. К совместимым изменениям относят добавление и удаление методов и полей. К несовместимым изменениям относят изменение иерархии объектов или прекращение реализации интерфейса Serializable. Полный перечень совместимых и несовместимых изменений приведен в Спецификации сериализации Java (см. Ссылки).

Обсуждение производительности

И третий момент. Механизм, используемый по умолчанию, несмотря на свою простоту, далеко не самый производительный. Я выполнил запись объект Data в файл 1’000 раз и повторил эту процедуру 100 раз. Среднее время записи объекта Data было 115 миллисекунд. Затем я вручную записал объект Data, используя стандартные инструменты ввода/вывода и повторил эту же операцию. Среднее время — 52 миллисекунды. Почти вдвое меньше! Это весьма обычный компромисс между простотой и производительностью, и сериализация — не исключение. Если скорость имеет для вашего приложения ключевое значение, вы можете выбрать метод создания собственного протокола.

Отдельного обсуждения заслуживает вышеупомянутый факт, что ссылки на объекты кэшируются в поток вывода. Согласно этому, система не может выполнять сбор мусора для записанных в поток объектов если поток не был закрыт. Лучшее решение (как всегда при помощи операций ввода/вывода) — это как можно скорее закрывать потоки после выполнения записи.

Заключение

Сериализация в Java проста для понимания и почти настолько же проста в реализации. Знание трех различных способов реализации сериализации должно помочь вам привязке API к вашей задаче. В этой статье вы познакомились с множеством механизмов сериализации и, я надеюсь, это пролило свет на эти вопросы, а не запутало вас еще больше. И, после прочтения, как это бывает в программировании, у вас появилось такое чувство, что вы давно знакомы с этим API. В статье изложены основы API Java сериализации, но перед использованием спецификации я рекомендую получше изучить ее во всех подробностях

Об авторе

Тодд Гриньер (Todd Greanier), технический директор ComTech Training приступил к обучению и разработке на языке Java сразу же, как только тот был представлен широкой общественности. Являясь экспертом по распределенным Java технологиям, он проводил обучение работе с классами для широчайшего спектра тематик, включая JDBC, RMI, CORBA, UML, Swing, сервлеты/JSP, безопасность, JavaBeans, Enterprise Java Beans и многопоточность. Он также организовывал специальные семинары для корпораций с учетом специфики их требований. Тодд живет на севере шатата Нью-Йорк вместе с женой Стэйси (Stacey) и кошкой Бин (Bean).

Ссылки
  • Прочитайте Спецификацию сериализации Java объектов для того чтобы глубже овладеть секретами протокола: http://java.sun.com/products/jdk/1.2/docs/guide/serialization/spec/serialTOC.doc.html
  • «Java ввод/вывод (Java I/O)», Элиота Расти Харольда (Elliotte Rusty Harold) (O’Reilly & Associates, 1999) также предлагает глубокое изучение Java Serialization API: http://www.amazon.com/exec/obidos/ASIN/1565924851/qid=962052367/104-9228407-2216730

Система ввода/вывода

Java имеет в своём составе множество классов, связанных с вводом/выводом данных.

Поток

При работе с данными ввода/вывода вам будет часто попадаться термин Поток (Stream). Поток — это абстрактное представление устройства ввода или вывода данных, которые способны обрабатывать информацию. Вы в реальности не видите, как действительно идёт обработка данных в устройствах ввода/вывода, так как это сложно и вам это не нужно. Это как с телевизором — вы не знаете, как сигнал из кабеля превращается в картинку на экране, но вполне можете переключаться между каналами через пульт.

Можно записывать данные в поток и читать данные из потока. Визуально поток можно представить как последовательность байтов, которые перемещаются в программу или из неё.

Поток вывода — это данные, которые записываются в поток. Поток вывода может поступать в любое устройство, куда можно передать последовательность байтов — файл на жёстком диске, файл на удалённой системе, экран компьютера (символьные данные) или принтер.

Данные читаются из потока ввода — это может быть любая последовательность байтов — из файла, клавиатура и т.д.

Методы потокового ввода и вывода допускают запись и считывание за одну операцию небольшого объёма данных, одного байта или символа. Передача данных таким образом очень неэффективна, поэтому часто поток оснащается буфером. В этом случае мы получаем буферизованный поток. Буфер — просто участок памяти, который используется для объединения данных. Это обеспечивает передачу данных большими блоками.

Есть два типа потоков: байтовые и символьные. В некоторых ситуациях символьные потоки более эффективны, чем байтовые.

За ввод и вывод отвечают разные классы Java. Классы, производные от базовых классов InputStream или Reader, имеют методы с именами read() для чтения отдельных байтов или массива байтов (отвечают за ввод данных). Классы, производные от классов OutputStream или Write, имеют методы с именами write() для записи одиночных байтов или массива байтов (отвечают за вывод данных).

Подробнее о классе InputStream

Класс OutputStream

Класс OutputStream — это абстрактный класс, определяющий потоковый байтовый вывод.

В этой категории находятся классы, определяющие, куда направляются ваши данные: в массив байтов (но не напрямую в String; предполагается что вы сможете создать их из массива байтов), в файл или канал.

BufferedOutputStream Буферизированный выходной поток ByteArrayOutputStream Создаёт буфер в памяти. Все данные, посылаемые в этот поток, размещаются в созданном буфере DataOutputStream Выходной поток, включающий методы для записи стандартных типов данных Java FileOutputStream Отправка данных в файл на диске. Реализация класса OutputStream ObjectOutputStream Выходной поток для объектов PipedOutputStream Реализует понятие выходного канала. FilterOutputStream Абстрактный класс, предоставляющий интерфейс для классов-надстроек, которые добавляют к существующим потокам полезные свойства.

  • int close() — закрывает выходной поток. Следующие попытки записи передадут исключение IOException
  • void flush() — финализирует выходное состояние, очищая все буферы вывода
  • abstract void write (int oneByte) — записывает единственный байт в выходной поток
  • void write (byte[] buffer) — записывает полный массив байтов в выходной поток
  • void write (byte[] buffer, int offset, int count) — записывает диапазон из count байт из массива, начиная с смещения offset

BufferedOutputStream

Класс BufferedOutputStream не сильно отличается от класса OutputStream, за исключением дополнительного метода flush(), используемого для обеспечения записи данных в буферизируемый поток. Буферы вывода нужно для повышения производительности.

ByteArrayOutputStream

Класс ByteArrayOutputStream использует байтовый массив в выходном потоке. Метод close() можно не вызывать.

DataOutputStream

Класс DataOutputStream позволяет писать элементарные данные в поток через интерфейс DataOutput, который определяет методы, преобразующие элементарные значения в форму последовательности байтов. Такие потоки облегчают сохранение в файле двоичных данных.

Класс DataOutputStream расширяет класс FilterOutputStream, который в свою очередь, расширяет класс OutputStream.

Методы интерфейса DataOutput:

  • writeDouble(double value)
  • writeBoolean(boolean value)
  • writeInt(int value)

FileOutputStream

Класс FileOutputStream создаёт объект класса OutputStream, который можно использовать для записи байтов в файл. Создание нового объекта не зависит от того, существует ли заданный файл, так как он создаёт его перед открытием. В случае попытки открытия файла, доступного только для чтения, будет передано исключение.

Классы символьных потоков

Символьные потоки имеют два основных абстрактных класса Reader и Writer, управляющие потоками символов Unicode.

Reader

Методы класса Reader:

  • abstract void close() — закрывает входной поток. Последующие попытки чтения передадут исключение IOException
  • void mark(int readLimit) — помещает метку в текущую позицию во входном потоке
  • boolean markSupported() — возвращает true, если поток поддерживает методы mark() и reset()
  • int read() — возвращает целочисленное представление следующего доступного символа вызывающего входного потока. При достижении конца файла возвращает значение -1. Есть и другие перегруженные версии метода
  • boolean ready() — возвращает значение true, если следующий запрос не будет ожидать.
  • void reset() — сбрасывает указатель ввода в ранее установленную позицию метки
  • logn skip(long charCount) — пропускает указанное число символов ввода, возвращая количество действительно пропущенных символов

Класс BufferedReader

Класс BufferedReader увеличивает производительность за счёт буферизации ввода.

Класс CharArrayReader

Класс CharArrayReader использует символьный массив в качестве источника.

Класс FileReader

Класс FileReader, производный от класса Reader, можно использовать для чтения содержимого файла. В конструкторе класса нужно указать либо путь к файлу, либо объект типа File.

Writer

Класс Writer — абстрактный класс, определяющий символьный потоковый вывод. В случае ошибок все методы класса передают исключение IOException.

  • Writer append(char c) — добавляет символ в конец вызывающего выходного потока. Возвращает ссылку на вызывающий поток
  • Writer append(CharSequence csq) — добавляет символы в конец вызывающего выходного потока. Возвращает ссылку на вызывающий поток
  • Writer append(CharSequence csq, int start, int end) — добавляет диапазон символов в конец вызывающего выходного потока. Возвращает ссылку на вызывающий поток
  • abstract void close() — закрывает вызывающий поток
  • abstract void flush() — финализирует выходное состояние так, что все буферы очищаются
  • void write(int oneChar) — записывает единственный символ в вызывающий выходной поток. Есть и другие перегруженные версии метода

Класс BufferedWriter

Класс BufferedWriter — это класс, производный от класса Writer,который буферизует вывод. С его помощью можно повысить производительность за счёт снижения количества операций физической записи в выходное устройство.

Класс CharArrayWriter

Класс CharArrayWriter использует массив для выходного потока.

Класс FileWriter

Класс FileWriter создаёт объект класса, производного от класса Writer, который вы можете применять для записи файла. Есть конструкторы, которые позволяют добавить вывод в конец файла. Создание объекта не зависит от наличия файла, он будет создан в случае необходимости. Если файл существует и он доступен только для чтения, то передаётся исключение IOException.

Класс File

В отличие от большинства классов ввода/вывода, класс File работает не с потоками, а непосредственно с файлами. Данный класс позволяет получить информацию о файле: права доступа, время и дата создания, путь к каталогу. А также осуществлять навигацию по иерархиям подкаталогов.

Подробнее о классе java.io.File

Чтение и запись файлов

Существует множество классов и методов для чтения и записи файлов. Наиболее распространённые из них — классы FileInputStream и FileOutputStream, которые создают байтовые потоки, связанные с файлами. Чтобы открыть файл, нужно создать объект одного из этих файлов, указав имя файла в качестве аргумента конструктора.

 FileInputStream(String filename) throws FileNotFoundException FileOutputStream(String filename) throws FileNotFoundException 

В filename нужно указать имя файла, который вы хотите открыть. Если при создании входного потока файл не существует, передаётся исключение FileNotFoundException. Аналогично для выходных потоков, если файл не может быть открыт или создан, также передаётся исключение. Сам класс исключения происходит от класса IOException. Когда выходной файл открыт, любой ранее существовавший файл с тем же именем уничтожается.

После завершения работы с файлом, его необходимо закрыть с помощью метода close() для освобождения системных ресурсов. Незакрытый файл приводит к утечке памяти.

В JDK 7 метод close() определяется интерфейсом AutoCloseable и можно явно не закрывать файл, а использовать новый оператор try-с-ресурсами, что для Android пока не слишком актуально.

Чтобы читать файл, нужно вызвать метод read(). Когда вызывается этот метод, он читает единственный байт из файла и возвращает его как целое число. Когда будет достигнут конец файла, то метод вернёт значение -1. Примеры использования методов есть в различных статьях на сайте.

Иногда используют вариант, когда метод close() помещается в блок finally. При таком подходе все методы, которые получают доступ к файлу, содержатся в пределах блока try, а блок finally используется для закрытия файла. Таким образом, независимо от того, как закончится блок try, файл будет закрыт.

Так как исключение FileNotFoundException является подклассом IOException, то не обязательно обрабатывать два исключения отдельно, а оставить только IOException, если вам не нужно отдельно обрабатывать разные причины неудачного открытия файла. Например, если пользователь вводит вручную имя файла, то более конкретное исключение будет к месту.

Для записи в файл используется метод write().

 void write(int value) throws IOException 

Метод пишет в файл байт, переданный параметром value. Хотя параметр объявлена как целочисленный, в файл записываются только младшие восемь бит. При ошибке записи передаётся исключение.

В JDK 7 есть способ автоматического управления ресурсами:

 try (спецификация_ресурса) < // использование ресурса >

Когда в Android будет полноценная поддержка JDK 7, то дополним материал.

Буферизированное чтение из файла — BufferedReader

Чтобы открыть файл для посимвольного чтения, используется класс FileInputReader; имя файла задаётся в виде строки (String) или объекта File. Ускорить процесс чтения помогает буферизация ввода, для этого полученная ссылка передаётся в конструктор класса BufferedReader. Так как в интерфейсе класса имеется метод readLine(), все необходимое для чтения имеется в вашем распоряжении. При достижении конца файла метод readLine() возвращает ссылку null.

Вывод в файл — FileWriter

Объект FileWriter записывает данные в файл. При вводе/выводе практически всегда применяется буферизация, поэтому используется BufferedWriter.

Когда данные входного потока исчерпываются, метод readLine() возвращает null. Для потока явно вызывается метод close(); если не вызвать его для всех выходных файловых потоков, в буферах могут остаться данные, и файл получится неполным.

Сохранение и восстановление данных — PrintWriter

PrintWriter форматирует данные так, чтобы их мог прочитать человек. Однако для вывода информации, предназначенной для другого потока, следует использовать классы DataOutputStream для записи данных и DataInputStream для чтения данных.

Единственным надежным способом записать в поток DataOutputStream строку так, чтобы ее можно было потом правильно считать потоком DataInputStream, является кодирование UTF-8, реализуемое методами readUTF() и writeUTF(). Эти методы позволяют смешивать строки и другие типы данных, записываемые потоком DataOutputStream, так как вы знаете, что строки будут правильно сохранены в Юникоде и их будет просто воспроизвести потоком DataInputStream.

Метод writeDouble() записывает число double в поток, а соответствующий ему метод readDouble() затем восстанавливает его (для других типов также существуют подобные методы).

RandomAccessFile — Чтение/запись файлов с произвольным доступом

Работа с классом RandomAccessFile напоминает использование совмещенных в одном классе потоков DataInputStream и DataOutputStream (они реализуют те же интерфейсы DataInput и DataOutput). Кроме того, метод seek() позволяет переместиться к определенной позиции и изменить хранящееся там значение.

При использовании RandomAccessFile необходимо знать структуру файла. Класс RandomAccessFile содержит методы для чтения и записи примитивов и строк UTF-8.

RandomAccessFile может открываться в режиме чтения («r») или чтения/записи («rw»). Также есть режим «rws», когда файл открывается для операций чтения-записи и каждое изменение данных файла немедленно записывается на физическое устройство.

Исключения ввода/вывода

В большинстве случаев у классов ввода/вывода используется исключение IOException. Второе исключение FileNotFoundException передаётся в тех случаях, когад файл не может быть открыт. Данное исключение происходит от IOException, поэтому оба исключения можно обрабатывать в одном блоке catch, если у вас нет нужды обрабатывать их по отдельности.

Дополнительное чтение

Используем AsyncTask для загрузки текстового файла из сети — используются BufferedReader, InputStreamReader, InputStream.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *