Пакет java.io. Байтовые потоки ввода-вывода При создании приложений всегда возникает необходимость прочитать информацию из какого-либо источника и/или.

Презентация:



Advertisements
Похожие презентации
Saint Petersburg, 2012 Java Lecture #04 Part I - IO.
Advertisements

Практическое занятие 6. Функции. Большинство языков программирования используют понятия функции и процедуры. C++ формально не поддерживает понятие процедуры,
Работа с файлами Сазонов Д.О. ПМиЭММ Часть 2. Тема занятия: Работа с файлами через потоки Для реализации файлового ввода/вывода, необходимо включить в.
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Файловый тип данных Turbo Pascal Операции для работы с файлами 11 класс.
©Павловская Т.А. (СПбГУ ИТМО) Курс «С#. Программирование на языке высокого уровня» Павловская Т.А.
Основы ООП и C# Работа с объектами и классами. Классы Класс специальный тип данных для описания объектов. Он определяет данные и поведение типа. Определение.
1 Java 9. ПОТОКИ ВВОДА/ВЫВОДА. 2 Класс File Класс File может представлять как имя определенного файла, так, имена группы файлов, находящихся в каталоге.
Система ввода/вывода Потоки данных (stream) Сериализация объектов.
Синтаксис языка Java. Символы и синтаксис Перевод строчки эквивалентен пробелу Регистр в именах различается.
Классы Math, system, Data на языке Java Назарова К
Файловый тип данных Файл – это область памяти на внешнем носителе, в которой хранится некоторая информация. В языке Паскаль файл представляет собой последовательность.
1 Классы в Java Ключевое слово class означает: Я говорю тебе, как выглядит новый тип объекта. Класс является базовым элементом объектно-ориентированного.
Сериализация и RMI Java Advanced. 2Georgiy KorneevJava Advanced / Сериализация и RMI Содержание Сериализация Концепции RMI Применение RMI Заключение.
Полиморфизм. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Вперёд ЯЗЫКИ ПРОГРАММИРОВАНИЯ ЦЕЛИ: ЦЕЛИ: 1. Средство для задания действий, которые должны быть выполнены машиной.(Машинный язык) 1. Средство для задания.
Ввод-вывод Java Advanced. 2Georgiy KorneevJava Advanced / Ввод-вывод Содержание 1.Потоки ввода-вывода 2.Файловый ввод-вывод и конвертация потоков 3.Фильтрующие.
Работа с файлами.. Процедура Assign(var f; name : String); Связывает внешний файл с именем name и переменную файлового типа f. Все дальнейшие операции.
Java. Part 2. Спецификаторы доступа public private protected не указан – доступ в пределах пакета Могут использоваться перед классами, методами, полями.
Множества значений или переменных с одним общим именем называются структурированными типами. По способу организации и типу компонентов выделяют: 1. Массивы.
Транксрипт:

Пакет java.io. Байтовые потоки ввода-вывода При создании приложений всегда возникает необходимость прочитать информацию из какого-либо источника и/или сохранить результат. Самые первые классы ввода/вывода связаны с передачей последовательностей байтов в разных направлениях. Потоки ввода последовательностей байтов являются подклассами абстрактного класса InputStream, потоки вывода – подклассами абстрактного класса OutputStream. Эти два класса являются суперклассами для всех остальных средств ввода/вывода. Некоторые классы ввода-вывода работают вовсе не с внешними устройствами, а с массивами, последовательностями символов, сетевыми соединениями. При работе с содержимым файлов используются подклассы этих классов (соответственно – FileInputStream и FileOutputStream), конструкторы которых открывают поток и связывают его с соответствующим физическим файлом. Основой всех операций ввода являются абстрактные методы класса InputStream read( ), read( byte[ ] b) и read(byte[ ] b, int off, int len). Эти методы возвращают -1 (тип int а не byte – наследие С), если оказался достигнут конец потока данных. При взаимодействии с информационными потоками возможны различные исключительные ситуации, поэтому обработка исключений вида try-catch при использовании методов чтения (и записи) является обязательной. В конкретных классах потоков ввода эти методы реализованы в соответствии с предназначением класса.

Пакет java.io. Иерархия классов байтовых потоков ввода

Пакет java.io. Байтовые потоки ввода Поток ввода связывается с источником данных, в качестве которого могут быть использованы массив байтов, строка, файл, «pipe»-канал, сетевые соединения: StringBufferInputStream – устарел, использовать его не рекомендуется; SequenceInputStream – реализует конкатенацию двух или более входных потоков (аргументы конструктора); ByteArrayInputStream – в качестве источника байтового потока используется байтовый массив (аргумент конструктора); PipedInputStream – в качестве источника используется аналогичный, но противонаправленный поток вывода PipedOutputStream в многопоточной или многозадачной реализации приложения; ObjectInputStream – основа механизма сериализации/ десериализации объектов; в качестве источника используется результат выполненной ранее работы класса ObjectOutputStream; FileInputStream – в качестве источника данных используется файл на внешнем носителе; FilterInputStream – абстрактный класс, используемый как шаблон для настройки классов ввода, наследуемых от класса InputStream. DataInputStream – позволяет читать данные примитивных типов из любого InputStream; этот класс частично устарел, перекрывающий набор методов содержится в ObjectInputStream.

Пакет java.io. Байтовые потоки ввода Наследники класса FilterInputStream Классы, производные от FilterInputStream, выполняют две различные миссии. Класс DataInputStream позволяет читать из потока различные типы простейших данных и строки. Практически все методы этого класса (за единственным исключением skipBytes( )) начинаются с префикса read например, readByte( ), readFloat( ) и т. п. Этот класс, вместе с «парным» классом DataOutputStream, предоставляет возможность направленной передачи примитивных данных посредством потоков. Другие классы-наследники изменяют внутренние механизмы входного потока: применение буферизации, подсчет количества прочитанных строк (что позволяет запросить или задать номер строки), возможность возврата в поток только что прочитанных символов. BufferedInputStream – добавляет внутренний буфер для предотвращения лишних обращений к операционной системе, которая, как правило, тоже осуществляет буферизацию. LineNumberInputStream – обеспечивает подсчет количества считанных текстовых строк, возможность получения их номеров и перехода к чтению строки с заданным номером. PushBackInputStream – обеспечивает возможность возврата одного символа в поток. Обычное действие при лексическом анализе текста. Последние два класса, вероятно, создавались в основном для построения компиляторов (то есть их добавили в библиотеку в процессе написания компилятора Java). Используются они довольно редко.

Пакет java.io. Иерархия классов байтовых потоков вывода

Пакет java.io. Потоки вывода Потоки вывода связываются с получателями данных, в качестве которых могут быть массивы байтов, файлы, «pipe»-каналы, сетевые соединения. ByteArrayOutputStream – данные, отправляемые в поток, размещаются в байтовом массиве (в качестве аргумента конструктора можно указать ожидаемый размер), который затем можно получить или преобразовать в строку; PipedOutputStream – в качестве получателя данных используется аналогичный, но противонаправленный поток вывода PipedInputStream в другом приложении; ObjectOutputStream – используется для сериализации (интерфейс ObjectOutput)/ десериализации объектов; при вызове метода writeObject( ) значения полей экземпляра объекта превращаются в последовательность байтов, отправляемых в поток; все ссылочные поля обрабатываются рекурсивно аналогичным образом; FileOutputStream – получателем данных является файл на внешнем носителе; FilterOutputStream – абстрактный класс, используемый как шаблон для настройки классов вывода: DataOutputStream – обеспечивает возможность передачи в выходной поток байтового представления всех примитивных типов для дальнейшего хранения; PrintStream – предназначен для вывода значений в формате, удобном для человека; его недостатки: плохо поддерживает локализацию и инкапсулирует обработку ошибок при работе с устройством (есть метод checkError( ), но его надо не забывать вызывать); более удобен класс PrintWriter; BufferedOutputStream – обеспечивает дополнительную буферизацию потока байтов.

Пакет java.io. Символьные потоки ввода-вывода Байтовая библиотека ввода/вывода поддерживает только 8-битовые символы и зачастую неверно работает с 16-битовыми символами Юникода. Именно благодаря символам Юникода возможна интернационализация программ (простейший тип Java char (символ) также основан на Юникоде). Начиная с версии 1.2 пакет java.io подвергся значительным изменениям. Появились новые классы, которые производят скоростную обработку потоков символов, хотя и не полностью перекрывают возможности классов предыдущей версии. Классы символьных потоков обеспечивают корректную обработку символов Юникода в операциях ввода/вывода и работают быстрее "старых" классов. Для обработки символьных потоков в формате Unicode применяется отдельная иерархия подклассов абстрактных классов Reader и Writer, которые почти полностью повторяют функциональность байтовых потоков, но являются более адекватными при передаче текстовой информации. Например, аналогом класса FileInputStream является класс FileReader. В некоторых приложениях используются как «байтовые», так и «символьные» классы. Для легкого конвертирования разных видов потоков друг в друга в библиотеке ввода-вывода появились классы-адаптеры: InputStreamReader конвертирует InputStream в Reader, a OutputStreamWriter трансформирует OutputStream в Writer. Назначение классов символьных потоков можно определить по их названиям.

Пакет java.io. Иерархия классов символьных потоков ввода-вывода

Пакет java.io. Примеры использования (1) // Использование буферизованных классов: // файл BufferedInputFile.java читает сам себя и выводит текст на консоль import java.io.*; public class BufferedInputFile {// выбрасывает исключения на консоль public static String read( String filename ) throws IOException { // создание символьного потока для чтения BufferedReader in = new BufferedReader( new FileReader( filename ) ); String s; StringBuilder sb = new StringBuilder( ); // чтение открытого файла построчно: while( ( s = in.readLine( ) ) != null ) sb.append(s + "\n"); in.close( ); return sb.toString( ); } public static void main( String[ ] args ) throws IOException { System.out.print( read( "BufferedInputFile.java" ) ); }

Пакет java.io. Примеры использования (2) // В этом примере результат - строка вызова метода BufferedInputFile.read( ) // используется для создания потока StringReader. // Затем символы последовательно читаются методом read(), // и каждый следующий символ посылается на консоль. // файл MemoryInput.java import java.io.*; public class MemoryInput { public static void main( String[ ] args ) throws IOException { StringReader in = new StringReader( BufferedInputFile.read( "MemoryInput.java" )); int symbol; while( (symbol = in.read( )) != -1) System.out.print( ( char ) symbol ); }

Пакет java.io. Примеры использования (3) Для передачи данных между приложениями (или потоками одного приложения) с использованием средств ввода/вывода следует применять классы DataOutputStream (для записи данных) и DataInputStream (для чтения данных) основанные, соответственно на OutputStream и InputStream. Природа этих потоков данных может быть любой. В этом примере открывается файл, буферизованный как для чтения, так и для записи. //: файл StoringAndRecoveringData.java import java.io.*; public class StoringAndRecoveringData { public static void main( String[ ] args ) throws IOException { DataOutputStream out = new DataOutputStream( new BufferedOutputStream( new FileOutputStream( "Data.txt" ) ) );

Пакет java.io. Примеры использования (4) out.writeDouble( Math.PI ); out.writeUTF( "Это число pi" ); out.writeDouble( Math.sqrt( Math.PI ) ); out.writeUTF( "А это квадратный корень из него" ); out.close( ); DataInputStream in = new DataInputStream( new BufferedInputStream( new FileInputStream( "Data.txt" ) ) ); System.out.println( in.readDouble( ) ); // Только метод readUTF() нормально читает // строки в кодировке UTF для Java: System.out.println( in.readUTF( ) ); System.out.println( in.readDouble( ) ); System.out.println( in.readUTF( ) ); } Это число pi А это квадратный корень из него

Пакет java.io. Примеры использования (5) В файле Data.txt окажется записано вот что ( в текстовом представлении Р­СРѕ Сисло pi?ц FЗd® 9Рђ СЌСРѕ квадраСРЅСРкорень РёР· него Язык Java гарантирует, что если данные записываются в выходной поток DataOutputStream, то они в исходном виде будут восстановлены входным потоком DataInputStream – невзирая на платформу, на которой производится запись или чтение (если последовательность вызовов методов read*( ) в точности совпадает с последовательностью вызовов методов write*( )). Это очень важно с точки зрения переносимости программ. Единственным надежным способом записать в поток DataOutputStream строку (String) так, чтобы ее можно было потом правильно считать потоком DataInputStream, является кодирование UTF-8, реализуемое методами readUTF() и writeUTF(). UTF-8 это разновидность кодировки Юникод, UTF-8 кодирует символы ASCII одним байтом, а символы из других кодировок записывает двумя или тремя байтами. В первых двух байтах строки хранится ее длина. Методы readUTF() и writeUTF() используют специальную модификацию UTF-8 для Java (она описана в документации на JDK), и для правильного считывания строки программой, написанной не на Java придется добавить в нее специальный код, обрабатывающий длину строки. Методы readUTF() и writeUTF() позволяют смешивать строки и другие типы данных, записываемые в поток DataOutputStream.

Пакет java.io. Класс RandomAccessFile Класс RandomAccessFile предназначен для работы с дисковыми файлами, содержащими записи известного приложению размера, между которыми можно перемещаться методом seek(), а также выполнять операции чтения и модификации. Записи не обязаны иметь фиксированную длину; просто нужно уметь определить их размер и место, где они располагаются в файле. Класс RandomAccessFile не является представителем иерархии потоков ввода/вывода на основе классов InputStream и OutputStream, он напрямую наследует от корневого класса Object. Никаких связей с потоковыми классами у него нет, кроме того, что он реализует интерфейсы DataInput и DataOutput (также реализуемые классами DataInputStream и DataOutputStream). Он не использует функциональность существующих классов из иерархии InputStream и OutputStream – это полностью независимый класс, написанный «с чистого листа», со своими собственными методами. Класс RandomAccessFile позволяет свободно перемещаться по файлу как в прямом, так и в обратном направлении, что для других типов ввода/вывода (потоков) невозможно. По сути, класс RandomAccessFile похож на пару совмещенных в одном классе потоков DataInputStream и DataOutputStream, к которым на всем «протяжении» потока применимы: метод getFilePointer(), показывающий, где вы «находитесь» в данный момент; метод seek(), позволяющий перемещаться на заданную позицию файла; метод length(), возвращающий максимальный размер файла.

Пакет java.io. Пример использования класса RandomAccessFile (1) //: UsingRandomAccessFile.java import java.io.*; public class UsingRandomAccessFile { static String fileName = "raFileTest.dat"; static void display( ) throws IOException {// этот метод //выводит на консоль содержимое файла RandomAccessFile raFile = new RandomAccessFile( fileName, "r"); for( int i = 0; i < 7; i++ ) System.out.println( "Value " + i + ": " + raFile.readDouble( ) ); System.out.println( raFile.readUTF( ) ); raFile.close( ); }

Пакет java.io. Пример использования класса RandomAccessFile (2) public static void main( String[ ] args ) throws IOException { RandomAccessFile raFile = new RandomAccessFile( fileName, "rw" ); for( int i = 0; i < 7; i++ ) raFile.writeDouble( i*1.414 ); raFile.writeUTF( "Конец файла" ); raFile.close( ); display( ); raFile = new RandomAccessFile( fileName, "rw" ); raFile.seek( 5*8 ); raFile.writeDouble( ); raFile.close(); display(); } Value 0: 0.0 Value 1: Value 2: Value 3: Value 4: Value 5: Value 6: Конец файла Value 0: 0.0 Value 1: Value 2: Value 3: Value 4: Value 5: Value 6: Конец файла

Пакет java.nio. Введение Целью создания библиотеки «нового ввода/вывода» Java, появившейся в JDK-1.4 в пакетах java.nio.*, было увеличение производительности и для файловых и для сетевых операций. При этом существующая библиотека ввода/вывода была в очередной раз переработана, в нее было включены средства конвертирования потоков в "новые" классы. Новая библиотека содержит классы, близкие к средствам ввода/вывода операционной системы: каналы (channels) и буферы (buffers). Приложения работают с буферами, занося в них данные и передавая их в каналы при выводе или получая буферы от каналов и извлекая из них данные при вводе. Основой всей иерархии является класс ByteBuffer (являющийся наследником класса Buffer). Этот абстрактный класс содержит набор методов для пересылки данных в виде последовательности байтов или в виде примитивов. Для каждого примитивного типа данных (кроме boolean) существует свое представление байтового буфера. Кроме того, есть вариант представления буфера, позволяющий работать с очень большими файлами, отображенными на память. Перейти от работы с потоками к работе с каналами можно путем вызова метода getChannel( ) (возвращающего объект типа FileChannel), добавленного в классы FileInputStream, FileOutputStream и RandomAccessFile "старой" библиотеки. Классы для символьных данных Reader и Writer не образуют каналов. Однако в пакете java.nio есть вспомогательный класс java.nio.channels.Channels, имеющий набор статических методов, "преобразующих" объекты InputStrem, OutputStrim, Reader и Writer в каналы и обратно.

Пакет java.nio

Пакет java.nio. Пример работы с буферами и каналами (1) import java.nio.*; import java.nio.channels.*; import java.io.*; public class GetChannel {// Операции с каналами из потоков и // c разными буферами private static final int BUFFER_SIZE = 1024; public static void main( String[ ] args ) throws Exception { FileChannel fChannel = new FileOutputStream( "data.txt" ).getChannel( ); char[ ] data = "Это русский текст".toCharArray( ); ByteBuffer bb = ByteBuffer.allocate( data.length * 2 );// это не UTF-8 CharBuffer cb = bb.asCharBuffer( );// получить символьный буфер cb.put( data );// занести в него данные fChannel.write( bb );// и записать их в канал fChannel.close( );// закрыть и переоткрыть файл fChannel = new RandomAccessFile( "data.txt", "rw" ).getChannel( ); fChannel.position( fChannel.size( ) ); // Переместиться в конец файла

Пакет java.nio. Пример работы с буферами и каналами (2) data = " This is english text".toCharArray( ); bb = ByteBuffer.allocate( data.length * 2 );// образовать новый буфер cb = bb.asCharBuffer(); // создать символьное представление cb.put(data); fChannel.write( bb );// и записать буфер в файл fChannel.close( ); fChannel = new FileInputStream( "data.txt" ).getChannel( ); ByteBuffer buffer = ByteBuffer.allocate( (int)fChannel.size( ) );// BUFFER_SIZE fChannel.read( buffer ); buffer.flip( );// сделать буфер доступным для выборки cb = buffer.asCharBuffer( );// создать символьное представление while( cb.hasRemaining( ) ) System.out.print( cb.get( ) ); } Вот результат работы программы: А вот что находится в файле data.txt: Это русский текст This is english text

Пакет java.nio Взаимодействие буферов t

Пакет java.nio. Пример работы с буферами (1) import java.nio.*; public class BuffOperations{// операции c разными представлениями буфера private static final int BUFFER_SIZE = 1024; public static void main( String[ ] args ) throws Exception { ByteBuffer bBuffer = ByteBuffer.allocate( BUFFER_SIZE ); bBuffer.rewind(); // Запись данных разных примитивных типов в буфер bBuffer.asCharBuffer( ).put( "Cтарт!" ); bBuffer.asShortBuffer( ).put( 6,(short)12345 ); bBuffer.asIntBuffer( ).put( 4, ); bBuffer.asLongBuffer( ).put( 3, L ); bBuffer.asFloatBuffer( ).put( 8,(float)Math.E ); bBuffer.asDoubleBuffer( ).put( 5, Math.PI ); bBuffer.rewind( ); // выборка данных разных примитивных типов из буфера

Пакет java.nio. Пример работы с буферами (2) System.out.print( bBuffer.position() + ": " ); char curChar; while ( (curChar = bBuffer.getChar( ) ) != '!') System.out.print( curChar + " " ); System.out.println(curChar); System.out.println(bBuffer.position() + ": " + bBuffer.getShort());// 6 bBuffer.getShort();// выравнивание до int System.out.println(bBuffer.position() + ": " + bBuffer.getInt()); // 4 bBuffer.getInt();// выравнивание до long System.out.println(bBuffer.position() + ": " + bBuffer.getLong()); // 3 System.out.println(bBuffer.position() + ": " + bBuffer.getFloat()); // 8 bBuffer.getFloat();// выравнивание до double System.out.println(bBuffer.position() + ": " + bBuffer.getDouble()); // 5 } 0: C т а р т ! 12: : : : :

Пакет java.nio Еще один механизм работы с символьными данными: import java.nio.charset.CharsetEncoder; CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); ByteBuffer byteBuffer = encoder.encode( CharBuffer.wrap( "Моя строка" ) ); // теперь byteBuffer можно писать в канал // а после чтения из канала: CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); CharBuffer charBuffer = decoder.decode(ByteBuffer byteBuffer ); Декодированный поток символов

Пакет java.nio. О порядке байтов Различные операционные системы хранят данные с различным порядком следования байтов. Прямой порядок big_endian располагает старший байт по младшему адресу памяти, а для обратного порядка little_endian старший байт помещается по высшему адресу памяти. При хранении значения, занимающего более одного байта, такого как число int, float и т. п., приходится учитывать различные варианты следования байтов в памяти. Буфер ByteBuffer укладывает данные в порядке big_endian, такой же способ всегда используется для данных, пересылаемых по сети. Порядок байтов в буфере при выборке примитивных значений можно изменить методом order(), передав ему аргумент ByteOrder.BIG_ENDIAN или ByteOrder. LITTLE_ENDIAN. Пусть два очередные байта буфера содержат двоичные числа Если прочитать эти данные как тип short ( byteBuffer.asShortBuffer().get( ) ), то будет получено число 97. Но если перед этим вызвать метод buffer.order(ByteOrder.LITTLE_ENDIAN), то (при другом порядке следования байтов) вызов этого же метода вернет число ( ). На следующем слайде показан пример работы с порядком следования байтов в примитивных типах.

Пакет java.nio. О порядке байтов. Пример import java.io.*; import java.nio.*; import java.nio.channels.*; public class Main { public static void main( String[ ] args ) throws IOException { short[ ] shArray = { 97, }; FileChannel fChannel = new FileOutputStream( "data.dat" ).getChannel( ); ByteBuffer bBuffer = ByteBuffer.allocate( shArray.length * 2 ); ShortBuffer shBuffer = bBuffer.asShortBuffer( ); shBuffer.put( shArray ); fChannel.write( bBuffer ); fChannel.close( ); fChannel = new FileInputStream( "data.txt" ).getChannel( ); ByteBuffer byteBuf = ByteBuffer.allocate( (int)fChannel.size( ) ); fChannel.read( byteBuf ); fChannel.close( ); byteBuf.order( ByteOrder.LITTLE_ENDIAN ); byteBuf.flip( ); shBuffer = byteBuf.asShortBuffer( ); System.out.println( shBuffer.get( ) + " " + shBuffer.get( ) ); }

Пакет java.nio. Файлы, отображаемые в память Механизм отображения файлов в память позволяет создавать и изменять файлы, размер которых слишком велик для полного размещения данных в памяти. В таком случае вы считаете, что файл целиком находится в памяти, и работаете с ним как с очень большим массивом. Такой подход значительно упрощает код изменения файла. В примере на следующем слайде имитируется работа с файлом, размер которого равен 128 Mb. Чтобы одновременно выполнять чтение и запись, создается объект RandomAccessFile, из него формируется канал, а затем методом mар() получается буфер (экземпляр класса) MappedByteBuffer, который представляет собой разновидность буфера прямого доступа. Необходимо указать начальную точку и длину участка файла, который будет проецироваться на буфер. Таким образом, есть возможность отображать маленькие участки больших файлов. Класс MappedByteBuffer унаследован от буфера ByteBuffer, поэтому он содержит все методы последнего. Здесь представлены только простейшие вызовы методов put() и get(), но можно также использовать все остальные возможности, такие как как метод asCharBuffer()... В итоге создается впечатление, что весь файл доступен сразу, поскольку только часть его подгружается в память, в то время как остальные части выгружены. Таким образом можно работать с очень большими (размером до 2 Гбайт) файлами. Для достижения максимальной производительности используются низкоуровневые механизмы отображения файлов используемой операционной системы.

Пакет java.nio. Файлы, отображаемые в память Пример //: LargeMappedFiles.java // Создание очень большого файла, отображаемого в память. import java.nio.*; import java.nio.channels.*; import java.io.*; public class LargeMappedFiles { static int length = 0x8FFFFFF; // 128 MB public static void main( String[ ] args ) throws Exception { MappedByteBuffer out = new RandomAccessFile( "test.dat", "rw" ).getChannel().map( FileChannel.MapMode.READ_WRITE, 0, length ); for( int i = 0; i < length; i++ ) out.put( (byte)( 'x' + i ) ); System.out.println("Finished writing"); for( int i = length/2; i < length/2 + 6; i++ ) System.out.print( (char)out.get( i ) ); } Finished writing wxyz{|

Пакет java.io. Сериализация объектов (1) Любой объект, реализующий интерфейс Serializable, можно превратить в последовательность байтов, из которой впоследствии можно полностью восстановить исходный объект. Это справедливо и для сетевых соединений, поэтому механизм сериализации позволяет легко преодолевать барьеры между разными операционными системами. Для этого, например, на машине с ОС Windows можно создать объект, превратить его в последовательность байтов, а затем переслать их по сети на машину с ОС *NIX, где объект будет корректно воссоздан. При этом не требуетсязаботиться о различных форматах представления данных, порядке следования байтов или других деталях. Сама по себе сериализация объектов интересна потому, что с ее помощью можно осуществить легковесное долговременное хранение (lightweight persistence). Это означает, что время жизни объекта определяется не только временем выполнения программы – объект существует и между запусками программы. Можно записать представление объекта в файл на диске, а при другом запуске программы восстановить его в первоначальном виде. Термин «легковесное» понимается так: объект нельзя определить как «постоянный» при помощи некоторого ключевого слова, то есть долговременное хранение напрямую не поддерживается языком (хотя вероятно, такая возможность появится в будущем). Система выполнения не заботится о деталях сериализации – программисту приходится собственноручно сериализовывать и восстанавливать объекты вашей программы. Если необходим более серьезный механизм сериализации, можно применить библиотеку Java JDO или инструмент, подобный библиотеке Hibernate.

Пакет java.io. Сериализация объектов (2) Механизм сериализации объектов был добавлен в язык для поддержки двух расширенных возможностей. Удаленный вызов методов Java (Remote Method Invocation – RMI) позволяет работать с объектами, находящимися на других компьютерах, точно так же, как и с теми, что существуют на вашей машине. При посылке сообщений удаленным объектам необходимо транспортировать аргументы и возвращаемые значения, как раз для этого используется сериализация объектов. Сериализация объектов также необходима визуальным компонентам JavaBean. Информация о состоянии визуальных компонентов обычно изменяется во время разработки. Эту информацию о состоянии необходимо сохранить, а затем, при запуске программы, восстановить; и данную задачу решает сериализация объектов. Возможность сериализация нарушает принципы безопасности, поскольку приложение может сериализовать объект в байтовый массив и получить доступ к полям, объявленным как private (аналогичную возможность предоставляет механизм рефлексии). Поэтому сериализации можно подвергнуть не каждый объект, а только тот, который реализует интерфейс Serializable (это интерфейс- маркер, в нем нет ни одного метода).

Пакет java.io. Сериализация объектов (3) Чтобы сериализовать объект, требуется создать выходной поток OutputStream, который нужно вложить в объект ObjectOutputStream. Вызов его метода writeObject( ) осуществляет преобразование указанного в качестве аргумента объекта в последовательность байтов, отправляемых в выходной поток данных OutputStream. Преобразованию и сохранению не подлежат статические поля (поскольку они принадлежат не объекту, а его классу) и поля, помеченные модификатором transient. Соблюдается последовательность объявления полей в тексте класса. При сериализации любых объектов эффективно обрабатываются все имеющиеся ссылки – все объекты, на которые имеются ссылки из сериализуемого объекта, также (рекурсивно) сериализуются. Гарантируется, что любой объект будет сохранен в точности один раз, даже если на него есть несколько ссылок. Если какой-либо объект, попавший в процесс сериализации, не является экземпляром интерфейса Serializable, то процесс прекращается с выбрасыванием исключения NotSerializableException. Для восстановления объекта необходимо надстроить ObjectInputStream над входным потоком InputStream, а затем вызвать метод readObject(), который вернет ссылку на объект обобщенного типа Object. Поэтому следует выполнить нисходящее преобразование для получения ссылки на объект нужного типа. Восстанавливаются все сохраненные поля, ни один конструктор не вызывается. Если восстанавливаемый объект содержал ссылки на другие объекты, то все они тоже будут восстановлены.

Пакет java.io. Сериализация объектов (3) В каждый класс, реализующий интерфейс Serializable, на стадии компиляции неявно добавляется уникальный идентификатор версии класса – поле private static final long serialVersionUID. Оно вычисляется по содержимому класса – полям, их порядку объявления, методам, их порядку объявления. Это единственноесериализуемое static-поле. При десериализации значение этого поля сравнивается с вычисляемым для имеющегося у виртуальной машины в настоящий момент класса. Если значения не совпадают, инициируется исключение java.io.InvalidClassException. При любом изменении в классе это поле поменяет свое значение, а следовательно исчезнет возможность десериализации ранее сохраненных объектов. Иногда желательно реализовать другое поведение: жестко зафиксировать набор полей класса и последовательность их определения, но иметь возможность изменения методов. В этом случае фактически десериализации ничего не угрожает, однако стандартный механизм не позволит десериализовать старые объекты. Для того, чтобы обойти это затруднение, можно в классе явно определить это поле например так: private static final long serialVersionUID = 1L; Вместо реализации интерфейса Serializable можно реализовать интерфейс Externalizable, который содержит два метода: void writeExternal( ObjectOutput out ); void readExternal( ObjectInput in ); При использовании этого интерфейса автоматически сериализуется только идентификация класса. Сохранить и восстановить все свои поля (и поля всех суперклассов) должен сам класс методами этого интерфейса.