Java -практика использования

         

Буферизованный ввод/вывод



Буферизованный ввод/вывод

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

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

Классы файлового ввода/вывода не занимаются буферизацией. Для этой цели есть четыре специальных класса BufferedXxx, перечисленных выше. Они присоединяются к потокам ввода/вывода как "переходное кольцо", например:

BufferedReader br = new BufferedReader(isr);

BufferedWriter bw = new BufferedWriter(fw);

Потоки isr и fw определены выше.



Программа листинга 18.3 читает текстовый файл, написанный в кодировке СР866, и записывает его содержимое в файл в кодировке KOI8_R. При чтении и записи применяется буферизация. Имя исходного файла задается в командной строке параметром args[0], имя копии — параметром argstl].



Данные передаваемые между подпроцессами



Рисунок 18.6. Данные, передаваемые между  подпроцессами















Файловый ввод/вывод



Файловый ввод/вывод

Поскольку файлы в большинстве современных операционных систем понимаются как последовательность байтов, для файлового ввода/вывода создаются байтовые потоки с помощью классов Fiieinputstream и FiieOutputstream. Это особенно удобно для бинарных файлов, хранящих байт-коды, архивы, изображения, звук.

Но очень много файлов содержат тексты, составленные из символов. Несмотря на то, что символы могут храниться в кодировке Unicode, эти тексты чаще всего записаны в байтовых кодировках. Поэтому и для текстовых файлов можно использовать байтовые потоки. В таком случае со стороны программы придется организовать преобразование байтов в символы и обратно.

Чтобы облегчить это преобразование, в пакет java.io введены классы FineReader и FileWriter. Они организуют преобразование потока: со стороны программы потоки символьные, со стороны файла — байтовые. Это происходит потому, что данные классы расширяют классы InputStreamReader и OutputstreamWriter, соответственно, значит, содержат "переходное кольцо" внутри себя.

Несмотря на различие потоков, использование классов файлового ввода/вывода очень похоже.

В конструкторах всех четырех файловых потоков задается имя файла в виде строки типа string или ссылка на объект класса File. Конструкторы не только создают объект, но и отыскивают файл и открывают его. Например:

Fileinputstream fis = new FilelnputStreamC'PrWr.Java"); 

FileReader fr = new FileReader("D:\\jdkl.3\\src\\PrWr.Java");

При неудаче выбрасывается исключение класса FileNotFoundException, но конструктор класса FileWriter выбрасывает более общее исключение IOException.

После открытия выходного потока типа FileWriter или FileQutputStEeam содержимое файла, если он был не пуст, стирается. Для того чтобы можно было делать запись в конец файла, и в том и в другом классе предусмотрен конструктор с двумя аргументами. Если второй аргумент равен true, то происходит дозапись в конец файла, если false, то файл заполняется новой информацией. Например:

FileWriter fw = new FileWriter("ch!8.txt", true);

FileOutputstream fos = new FileOutputstream("D:\\samples\\newfile.txt");

Внимание

Содержимое файла, открытого на запись конструктором с одним аргументом, стирается.

Сразу после выполнения конструктора можно читать файл:

fis.read(); fr.read();

или записывать в него:

fos.write((char)с); fw.write((char)с);

По окончании работы с файлом поток следует закрыть методом close ().

Преобразование потоков в классах FileReader и FileWriter выполняется по кодовым таблицам установленной на компьютере локали. Для правильного ввода кирилицы надо применять FileReader, a нe FileInputStream. Если файл содержит текст в кодировке, отличной от локальной кодировки, то придется вставлять "переходное кольцо" вручную, как это делалось для консоли, например:

InputStreamReader isr = new InputStreamReader(fis, "KOI8_R"));

Байтовый поток fis определен выше.




Иерархия символьных потоков



Рисунок 18.1. Иерархия символьных потоков












Каналы обмена информацией



Каналы обмена информацией

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

В одном подпроцессе — источнике информации — создается объект класса PipedWriter+ или PipedOutputstream, в который записывается информация методами write () этих классов.

В другом .подпроцессе —приемнике информации — формируется объект класса PipedReader или Pipedinputstream. Он связывается с объектом-источником с помощью конструктора или специальным методом connect (), и читает информацию методами read ().

Источник и приемник можно создать и связать в обратном порядке.

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

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

В листинге 18.5 метод run о класса source генерирует информацию, для простоты просто целые числа k, и передает £е в канал методом pw. write (k). Метод run() класса Target читает информацию из канала методом pr.read(). Концы канала связываются с помощью конструктора класса Target. На Рисунок 18.6 видна последовательность записи и чтения информации.



Классы байтовых потоков



Рисунок 18.2. Классы байтовых потоков


Все классы пакета java.io можно разделить на две группы: классы, создающие поток (data sink), и классы, управляющие потоком (data processing).

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

классы, создающие потоки, связанные с файлами:

FileReader        FilelnputStream 

FileWriterFile    Outputstream

                  RandomAccessFile

классы, создающие потоки, связанные с массивами:

CharArrayReader   ByteArraylnputStream 

CharArrayWriter   ByteArrayOutputStream

классы, создающие каналы обмена информацией между подпроцессами:

PipedReader     PipedlnputStream 

PipedWriter     PipedOutputStream

классы, создающие символьные потоки, связанные со строкой:

StringReader    

StringWriter

классы, создающие байтовые потоки из объектов Java:

                         ObjectlnputStream 

                        ObjectOutputStream

Слева перечислены классы символьных потоков, справа — классы байтовых потоков.

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

Четыре класса созданы специально для преобразования потоков:

FilterReader        FilterlnputStream 

FilterWriter        FilterOutputStream

Сами по себе эти классы бесполезны — они выполняют тождественное преобразование. Их следует расширять, переопределяя методы ввода/вывода. Но для байтовых фильтров есть полезные расширения, которым соответствуют некоторые символьные классы. Перечислим их.

Четыре класса выполняют буферизованный ввод/вывод:

BufferedReader         BufferedlnputStream 

BufferedWriter         BufferedOutputStream

Два класса преобразуют поток байтов, образующих восемь простых типов Java, в эти самые типы:

DatalnputStream        DataOutputStream

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

PushbackReader         PushbacklnputStream

Два класса связаны с выводом на строчные устройства — экран дисплея, принтер:

PrintWriter             PrintStream

Два класса связывают байтовый и символьный потоки:

inputstreamReader — преобразует входной байтовый поток в символьный поток; Outputstreamwriter — преобразует выходной символьный поток в байтовый поток.

Класс streamTokenizer позволяет разобрать входной символьный поток на отдельные элементы (tokens) подобно тому, как класс stringTokenizer, рассмотренный нами в главе 5, разбирал строку.

Из управляющих классов выделяется класс sequenceinputstream, сливающий несколько потоков, заданных в конструкторе, в один поток, и класс

LineNumberReader, "умеющий" читать выходной символьный поток построчно. Строки в потоке разделяются символами '\n' и/или '\г'.

Этот обзор классов ввода/вывода немного проясняет положение, но не объясняет, как их использовать. Перейдем к рассмотрению реальных ситуаций.



в байтовой кодировке вызывает трудности



Кодировка UTF-8

Запись потока в байтовой кодировке вызывает трудности с использованием национальных символов, запись потока в Unicode увеличивает длину потока в два раза. Кодировка UTF-8 (Universal Transfer Format) является компромиссом. Символ в этой кодировке записывается одним, двумя или тремя байтами.

Символы Unicode из диапазона '\u0000' —'\u007F', в котором лежит английский алфавит, записываются одним байтом, старший байт просто отбрасывается.

Символы Unicode из диапазона '\u0080' —'\u07FF', в котором лежат наиболее распространенные символы национальных алфавитов, записываются двумя байтами следующим образом: символ Unicode с кодировкой 00000хххххуууууу записывается как 110ххххх10уууууу.

Остальные символы Unicode из диапазона '\u0800' —'\UFFFF' записываются тремя байтами по следующему правилу: символ Unicode с кодировкой xxxxyyyyyyzzzzzz записывается как 1110xxxx10yyyyyy10zzzzzz.

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

Так вот, метод writeUTF( string s) сначала записывает в поток в первые два байта потока длину строки s в кодировке UTF-8, а затем символы строки в этой кодировке. Читать эту запись потом следует парным методом readUTF() класса DatalnputStream.

Класс DatalnputStream преобразует входной поток байтов типа InputStream, составляющих данные простых типов Java, в данные этого типа. Такой поток, как правило, создается методами класса DataOutputstream. Данные из этого потока можно прочитать методами readBoolean(), readByte(), readShort(), readChar(), readlnt(), readLong(), readFloat(), readDouble(), возвращающими данные соответствующего типа.

Кроме того, методы readUnsignedByteO H readUnsignedShort () возвращают целое типа int, в котором старшие три или два байта нулевые, а младшие один или два байта заполнены байтами из входного потока.

Метод readUTF(), двойственный методу writeUTF(), возвращает строку типа string, полученную из потока, записанного методом writeUTF ().

Еще один, статический, метод readUTF(Datainput in) делает то же самое со входным потоком in, записанным в кодировке UTF-8. Этот метод можно применять, не создавая объект класса DatalnputStream.

Программа в листинге 18.4 записывает в файл fib.txt числа Фибоначчи, а затем читает этот файл и выводит его содержимое на консоль. Для контроля записываемые в файл числа тоже выводятся на консоль. На Рисунок 1S.5 рока-зан вывод этой программы.




Консольный ввод/вывод



Консольный ввод/вывод

Для вывода на консоль мы всегда использовали метод printino класса Pnntstream, никогда не определяя экземпляры этого класса. Мы просто использовали статическое поле out класса system, которое является объектом класса PrintStream. Исполняющая система Java связывает это поле с консолью.

Кстати говоря, если вам надоело писать system.out.printino, то вы можете определить новую ссылку на system, out, например:

PrintStream pr - System.out;

и писать просто pr. printin ().

Консоль является байтовым устройством, и символы Unicode перед выводом на консоль должны быть преобразованы в байты. Для символов Latin 1 с кодами '\u0000' — '\u00FF' при этом просто откидывается нулевой старший байт и выводятся байты '0х00' —'0xFF'. Для кодов кириллицы, которые лежат в диапазоне '\u0400 1 —'\u04FF 1 кодировки Unicode, и других национальных алфавитов производится преобразование по кодовой таблице, соответствующей установленной на компьютере л окал и. Мы обсуждали это в главе 5.

Трудности с отображением кириллицы возникают, если вывод на консоль производится в кодировке, отличной от локали. Именно так происходит в русифицированных версиях MS Windows NT/2000. Обычно в них устанавливается локаль с кодовой страницей СР1251, а вывод на консоль происходит в кодировке СР866.

В этом случае надо заменить Printstream, который не может работать с сим- , вольным потоком, на Printwriter и "вставить переходное кольцо" между потоком символов Unicode и потоком байтов system, out, выводимых на консоль, в виде объекта класса OutputstreamWriter. В конструкторе этого объекта следует указать нужную кодировку, в данном случае, СР866. Все это можно сделать одним оператором:

PrintWriter pw = new PrintWriter(

new OutputstreamWriter(System.out, "Cp866"), true);

Класс Printstream буферизует выходной поток. Второй аргумент true его конструктора вызывает принудительный сброс содержимого буфера в выходной поток после каждого выполнения метода printin(). Но после print() буфер не сбрасывается! Для сброса буфера после каждого print() надо писать flush(), как это сделано в листинге 18.2.

Замечание

Методы класса PrintWriter по умолчанию не очищают буфер, а метод print () не очищает его в любом случае. Для очистки буфера используйте метод flush().

После этого можно выводить любой текст методами класса PrintWriter, которые просто дублируют методы класса Printstream, и писать, например,

pw.println("Это русский текст");

как показано в листинге 18.1 и на Рисунок 18.3.

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

Ввод с консоли производится методами read о класса inputstream с помощью статического поля in класса system. С консоли идет поток байтов, полученных из scan-кодов клавиатуры. Эти байты должны быть преобразованы в символы Unicode такими же кодовыми таблицами, как и при выводе на консоль. Преобразование идет по той же схеме — для правильного ввода кириллицы удобнее всего определить экземпляр класса BufferedReader, используя в качестве "переходного кольца" объект класса inputstreamReader:

BufferedReader br = new BufferedReader(

new InputstreamReader(System.an, "Cp866"));

Класс BufferedReader переопределяет три метода read о своего суперкласса Reader. Кроме того, он содержит метод readLine ().

Метод readLine о возвращает строку типа string, содержащую символы входного потока, начиная с текущего, и заканчивая символом '\п' и/или '\r'. Эти символы-разделители не входят в возвращаемую строку. Если во входном потоке нет символов, то возвращается null.

В листинге 18.1 приведена программа, иллюстрирующая перечисленные методы консольного ввода/вывода. На Рисунок 18.3 показан вывод этой программы.



Консольный ввод/вывод



Рисунок 18.3. Консольный  ввод/вывод




 












Консольный ввод/вывод



Листинг 18.1. Консольный ввод/вывод

import j ava.io.*;

class PrWr{

public static void main(String[] args){

try{

BufferedReader br =

new BufferedReader(new InputstreamReader(System.in, "Cp866"));
 

PrintWriter pw = new PrintWriter(

new OutputstreamWriter(System.out, "Cp866"), true);
 

String s = "Это строка с русским текстом"; 

System.out.println("System.out puts: " + s);
 

pw.println("PrintWriter puts: " + s) ; 

int с = 0;

pw.println("Посимвольный ввод:");
 

while((с = br.read()) != -1)

pw.println((char)c);
 

pw.println("Построчный ввод:");
 

do{

s = br.readLine();
 

pw.println(s);
 

}while(!s.equals("q"));
 

}catch(Exception e){

System.out.println(e);
 

}

Поясним Рисунок 18.3. Первая строка выводится потоком system.out. Как видите, кириллица выводится неправильно. Следующая строка предварительно преобразована в поток байтов, записанных в кодировке СР866.

Затем, после текста "Посимвольный ввод:" с консоли вводятся символы "Россия" и нажимается клавиша <Enter>
. Каждый вводимый символ отображается на экране — операционная система работает в режиме так называемого "эха". Фактический ввод с консоли начинается только после нажатия клавиши <Enter>
, потому что клавиатурный ввод буферизуется операционной системой. Символы сразу после ввода отображаются по одному на строке. Обратите внимание на две пустые строки после буквы я. Это выведены символы '\п' и '\г', которые попали во входной поток при нажатии клавиши <Enter>
. У них нет никакого графического начертания (glyph).

Потом нажата комбинация клавиш <Ctrl>
+<Z>
. Она отображается на консоль как "^Z" и означает окончание клавиатурного ввода, завершая цикл ввода символов. Коды этих клавиш уже не попадают во входной поток.

Далее, после текста "Построчный ввод:" с клавиатуры набирается строка "Это строка" и, вслед за нажатием клавиши <Enter>
, заносится в строку s. Затем строка s выводится обратно на консоль.

Для окончания работы набираем q и нажимаем клавишу <Enter>
.



Определение свойств файла и каталога



Листинг 18.2. Определение свойств файла и каталога

import java.io.*;

class FileTest{

public static void main(String[] args) throws IOException{ 

PrintWriter pw = new PrintWriter(

new OutputStreamWriter(System.out, "Cp866"), true);
 

File f = new File("FileTest.Java");
 

pw.println();

pw.println("Файл \"" + f.getName() + "\" " + 

(f.exists()?"":"не ") + "существует");

pw.println("Вы " + (f.canRead()?"":"не ") + "можете читать файл");
 

pw.println("Вы " + (f.canWrite()?"":"нe ") +

"можете записывать в файл");
 

pw.println("Длина файла " + f.length() + " б");

pw.println() ;

File d = new File(" D:\\jdkl.3\\MyProgs ");
 

pw.println("Содержимое каталога:");
 

if (d.exists() && d.isDirectory()) { 

String[] s = d.list();
 

for (int i = 0; i < s.length; i++)

pw.println(s[i]);
 

}



Буферизованный файловый ввод/вывод



Листинг 18.3. Буферизованный файловый ввод/вывод

import java.io.*;

class DOStoUNIX{

public static void main(String[] args) throws IOException{ 

if (args.length != 2){

System.err.println("Usage: DOStoUNIX Cp866file KOI8_Rfile");
 

System.exit(0);

}

BufferedReader br = new BufferedReader( 

new InputStreamReader(

new FileInputStream(args[0]), "Cp866"));
 

BufferedWriter bw = new BufferedWriter( 

new OutputStreamWriter(

new FileOutputStreamtargs[1]), "KOI8_R"));
 

int с = 0; 

while ((c = br.readO) != -1)

bw.write((char)c);
 

br.closeO; bw.close();

System.out.println("The job's finished.");
 

}



Ввод/вывод данных



Листинг 18.4. Ввод/вывод данных

import j ava.io.*;

class DataPrWr{

public static void main(String[] args) throws IOException{

DataOutputstream dos = new DataOutputstream (

new FileOutputStream("fib.txt"));
 

int a = 1, b = 1, с = 1; 

for (int k = 0; k < 40; k++){ 

System.out.print(b + " ");
 

dos.writelnt(b);
 

a = b; b = с; с = a + b; 

}

dos.closet);

System.out.println("\n");
 

DatalnputStream dis = new DatalnputStream (

new FilelnputStream("fib.txt")) ; 

while(true) 

try{

a = dis.readlnt();
 

System.out.print(a + " ">

}catch(lOException e){ 

dis.close();

System.out.println("End of file");
 

System.exit (0);
 

}

Обратите внимание на то, что попытка чтения за концом файла выбрасывает исключение класса IOException, его обработка заключается в закрытии файла и окончании программы.



Канал обмена информацией



Листинг 18.5. Канал обмена информацией

import java.io.*;

class Target extends Thread{

private PipedReader pr; 

Target(PipedWriter pw){ 

try{

pr = new PipedReader(pw);
 

}catch(lOException e){

System.err.println("From Target(): " + e);
 

}

PipedReader getStream(){ return pr;} 

public void run(){ 

while(true) 

try{

System.out.println("Reading: " + pr.read());
 

}catch(IOException e){

System.out.println("The job's finished.");
 

System.exit(0);
 

}

class Source extends Thread{ 

private PipedWriter pw; 

Source (){

pw = new PipedWriter();
 

}

PipedWriter getStream(){ return pw;} 

public void run(){

for (int k = 0; k < 10; k++)

try{

pw.write(k);

System.out.println("Writing: " + k);
 

}catch(Exception e){

System.err.printlnf"From Source.run(): " + e) ; 

class PipedPrWr{

public static void main(String[] args){ 

Source s = new Source();
 

Target t = new Target(s.getStream());
 

s.start();
 

t.start();
 

)



Сериализация объекта



Листинг 18.6. Сериализация объекта

import java.io.*; 

import java.util.*;

class SerDatef

public static void main(String[] args) throws Exception{

GregorianCaiendar d - new GregorianCaiendar();
 

QbjectOutputStream oos = new ObjectOutputStream{

new FileOutputStream("date.ser"));
 

oos.writeObject(d);
 

oos.flush();
 

oos.close();

Thread.sleep(3000);

ObjectlnputStream ois = new ObjectlnputStream(

new FileInputStream("date.ser"));

GregorianCaiendar oldDate = (GregorianCaiendar)ois.readObject();
 

ois.close();

GregorianCaiendar newDate = new GregorianCaiendar();

System.out.println("Old time = " +

oldDate.get(Calendar.HOUR) + ":" +

oldDate.get(Calendar.MINUTE) +":" + 

oldDate.get(Calendar.SECOND) +"\nNew time = " + 

newDate.get(Calendar.HOUR) +":" + 

newDate.get(Calendar.MINUTE) +":" + 

newDate.get(Calendar.SECOND));
 

}



Печать средствами AWT



Листинг 18.7. Печать средствами AWT

import java.awt.*; 

import j ava.awt.event.*;

class PrintTest extends Frame{ 

PrintTest(String s){ 

super(s);

setSize(400, 400);
 

setVisible(true);
 

}

public void simplePrint{){ 

PrintJob pj =

getToolkitO.getPrintJob(this, "JobTitle", null);
 

if (pj != null){

Graphics pg = pj.getGraphics();
 

if (pg != null){ 

print(pg);
 

pg.dispose();

}else System.err.println("Graphics's null");
 

pj.end();

}else System.err.println("Job's null");
 

public void paint(Graphics g){

g.setFonttnew Font("Serif", Font.ITALIC, 30));
 

g.setColor(Color.black);
 

g.drawArcdOO, 100, 200, 200, 0, 360);
 

g.drawstring("Страница 1", 100, 100);
 

public static void main(String[] args){

PrintTest pt = new PrintTest(" Простой гфимер печати");

pt.simplePrint();

pt.addWindowListener(new WindowAdpter(){

public void windowClosing(WindowEvent ev){

System.exit(0);
 

}

});
 

}



Простая печать методами Java 2D



Листинг 18.8. Простая печать методами Java 2D

import java.awt.*;

import java.awt.geom.*;

import java.awt.print.*;

class Print2Test implements Printable{

public int print(Graphics g, PageFormat pf, int ind)

throws PrinterException{         // Печатаем не более 5 страниц 

if (ind >
4) return Printable.NO_SUCH_PAGE; 

Graphics2D g2 = (Graphics2D)g; 

g2.setFont(new Font("Serif", Font.ITALIC, 30));
 

g2.setColor (Color.black);

g2.drawstring("Page " + (ind + I), 100, 100);
 

g2.draw(new Ellipse2D.Double(100, 100, 200, 200));
 

return Printable.PAGE_EXISTS; 

}

public static void main(String[] args){ 

// 1. Создаем экземпляр задания 

PrinterJob pj = Printer Job.getPrinter Job();

// 2, Открываем диалоговое окно Параметры страницы 

PageFormat pf = pj.pageDialog (pj.defaultPaige() );
 

// 3. Задаем вид задания, объект класса, рисующего страницу, 

// и выбранные параметры страницы 

pj.setPrintable(new Print2Test(), pf};

// 4. Если нужно напечатать несколько копий, то: 

pj.setCopies(2);
       // По умолчанию печатается одна копия

// 5. Открываем диалоговое окно Печать (необязательно) 

if (pj.printDialog())( // Если OK... try{

pj.print();
            // Обращается к print(g, pf, ind) 

}catch(Exception e){

System.err.println(e);
 

}

// 6. Завершаем задание 

System.exit(0);
 

}



Печать текстового файла



Листинг 18.9. Печать текстового файла 

import java.awt.*; 

import java.awt.print.*; 

import java.io.* ;

public class Print2File{

public static void main(String[] args){ 

if (args.length < 1){

System.err.println("Usage: Print2File path");
 

System, exit(0);
 

}

PrinterJob pj = PrinterJob.getPrinterJob();
 

PageFormat pf = pj.pageDialog(pj.defaultPage());
 

pj.setPrintable(new FilePagePainter(args[0]), pf);

if (pj.printDialog()){ 

try{

pj.print();

}catch(PrinterException e){} 

)

System, exit(0);
 

}

class FilePagePainter implements Printable{ 

private BufferedReader br; 

private String file; 

private int page = -1; 

private boolean eof; 

private String[] line; 

private int numLines;

public FilePagePainter(String file){

this.file = file;

try{

br = new BufferedReader(new FileReader(file));

}catch(IOException e){ eof = true; } 

public int print(Graphics g, PageFormat pf, int ind)

throws PrinterException(

g.setColor(Color.black);

g.setFont(new Font("Serif", Font.PLAIN, 10));

int h = (int)pf.getlmageableHeight();

int x = (int)pf.getlmageableX() + 10;

int у = (int)pf.getlmageableY() + 12;

try{

// Если система печати запросила эту страницу первый раз 

if (ind != page){

if (eof) return Printable.NO_SUCH_PAGE;

page = ind;

line = new String[h/12];           // Массив строк на странице

numLines =0;                       // Число строк на странице

// Читаем строки из файла и формируем массив строк 

while (у + 48 < pf.getlmageableY() + h){ 

line[numLines] = br.readLine();
 

if (line[numLines] == null){ 

eof = true; break; }

numLines++; 

У += 12;

}

}

// Размещаем колонтитул 

у = (int)pf.getImageableY() + 12; 

g.drawstring("Файл: " + file + ", страница " +

(ind + 1), x, у);
 

// Оставляем две пустые строки 

у += 36;

// Размещаем строки текста текущей страницы 

for (int i = 0; i < numLines; i++){ 

g.drawString(line[i], x, y) ; 

у += 12; 

}

return Printable.PAGE_EXISTS; 

}catch(lOException e){

return Printable.NO_SUCH_PAGE; 

}



Печать страниц с разными параметрами



Листинг 18.10. Печать страниц с разными параметрами

import j ava.awt.*; 

import j ava.awt.print.*;

public class Print2Book{

public static void main(String[] 

args){

PrinterJob pj = PrinterJob.getPrinterJob();

// Для титульного листа выбирается альбомная ориентация 

PageFormat pfl = pj.defaultPage();
 

pfl.setOrientation(PageFormat.LANDSCAPE);

// Параметры других страниц задаются в диалоговом окне 

PageFormat pf2 = pj.pageDialog (new PageFormat());

Book bk = new Book();

// Первая страница — титульный лист 

bk.append(new Cover(), pfl);

// Две другие страницы 

bk.append(new Content(), pf2, 2);

// Определяется вид печати — Pageable Job 

pj.setPageable(bk);

if (pj.printDialog()){ 

try{

pj.print() ; 

}catch (Exception e){}

}

System.exit(0);

class Cover implements Printable{

public int print(Graphics g, PageFormat pf, int ind)

throws PrinterException{

g.setFont (new Font ("Helvetica-Bold", Font.PIiAIsN, 40)) ; 

g.setColor(Color.black) ;

int у = (int) (pf.getlmageableY() + 

pf.getlmageableHeigbt() /2);
 

g.drawstring("Это заголовок,", 72, у);

g.drawstring("Он печатается вдоль длинной", 72, у+60);
 

g.drawstring("стороны листа бумаги.", 72, у+120);

return Printable.PAGE_EXISTS; 

class Content implements Printable{

public int print(Graphics g, PageFormat pf, int ind)

throws PrinterException{ 

Graphics2D g2 = (Graphics2D)g; 

g2.setFont(new Font("Serif", Font.PLAIN, 12));
 

g2.setColor(Color.black);

int x = (int)pf .getlmageableXO + 30; 

int у = (int)pf.getlmageableY();

g2.drawstring("Это строки обычного текста.", х, у += 16);
 

g2.drawstring("Они печатаются с параметрами,", х, у += 16);
 

g2.drawstring("выбранными в диалоговом окне.", х, у += 16);

return Printable.PAGE_EXISTS; 

}

}



Печать файла



Печать файла

Печать текстового файла заключается в размещении его строк в графическом контексте методом drawstring (). При этом необходимо проследить за правильным размещением строк в области печати и разбиением файла на страницы.

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



Печать средствами Java 2D



Печать средствами Java 2D

Расширенная графическая система Java 2D предлагает новые интерфейсы и классы для печати, собранные в пакет java.awt.print. Эти классы полностью перекрывают все стандартные возможности печати библиотеки AWT. Более того, они удобнее в работе и предлагают дополнительные возможности. Если этот пакет установлен в вашей вычислительной системе, то, безусловно, нужно применять его, а не стандартные средства печати AWT.

Как и стандартные средства AWT, методы классов Java 2D выводят на печать содержимое графического контекста, заполненного методами класса Graphics или класса Graphics2D.

Всякий класс Java 2D, собирающийся печатать хотя бы одну страницу текста, графики или изображения называется классом, рисующим страницы (page painter). Такой класс должен реализовать интерфейс Printable. В этом интерфейсе описаны две константы и только один метод print о. Класс, рисующий страницы, должен реализовать этот метод. Метод print о возвращает целое типа int и имеет три аргумента:

print(Graphics g, PageFormat pf, int ind);

Первый аргумент g — это графический контекст, выводимый на лист бумаги, второй аргумент pf — экземпляр класса PageFormat, определяющий размер и ориентацию страницы, третий аргумент ind — порядковый номер страницы, начинающийся с нуля.

Метод print () класса, рисующего страницы, заменяет собой метод paint (), использовавшийся стандартными средствами печати AWT. Класс, рисующий страницы, не обязан расширять класс Frame и переопределять метод paint (). Все заполнение графического контекста методами класса Graphics или Graphics2D теперь выполняется в методе print ().

Когда печать страницы будет закончена, метод print () должен возвратить целое значение, заданное константой PAGE_EXISTS. Будет сделано повторное обращение к методу print () для печати следующей страницы. Аргумент ind при этом возрастет на 1. Когда ind превысит количество страниц, метод print о должен возвратить значение NO_SUCH_PAGE, что служит сигналом окончания печати.

Следует помнить, что система печати может несколько раз обратиться к методу paint () для печати одной и той же страницы. При этом аргумент ind не меняется, а метод print () должен создать тот же графический контекст.

Класс PageFormat определяет параметры страницы. На странице вводится система координат с единицей длины 1/72 дюйма, начало которой и направление осей определяется одной из трех констант:

PORTRAIT — начало координат расположено в левом верхнем углу страницы, ось Ох направлена вправо, ось Оу — вниз;   LANDSCAPE — начало координат в левом нижнем углу, ось Ох идет вверх, ось Оу — вправо;   REVERSE_LANDSCAPE — начало координат в правом верхнем углу, ось Ох идет вниз, ось Оу — влево.

Большинство принтеров не может печатать без полей, на всей странице, а осуществляет вывод только в некоторой области печати (imageable area), координаты левого верхнего угла которой возвращаются методами getimageabiex() и getlmageableY(), а ширина и высота — методами getlmageableWidth() и getlmageableHeight().

Эти значения надо учитывать при расположении элементов в графическом контексте, например, при размещении строк текста методом drawstring (), как это сделано в листинге 18.9.

В классе только один конструктор по умолчанию PageFormat о, задающий стандартные параметры страницы, определенные для принтера по умолчанию вычислительной системы.

Читатель, добравшийся до этого места книги, уже настолько поднаторел в Java, что у него возникает вопрос: "Как же тогда задать параметры страницы?" Ответ простой: "С помощью стандартного окна операционной системы".

Метод pageDiaiog(PageDiaiog pd) открывает на экране стандартное окно Параметры страницы (Page Setup) операционной системы, в котором уже заданы параметры, определенные в объекте pd. Если пользователь выбрал в этом окне кнопку Отмена, то возвращается ссылка на объект pd, если кнопку ОК, то создается и возвращается ссылка на новый объект. Объект pd в любом случае не меняется. Он обычно создается конструктором.

Можно задать параметры страницы и из программы, но тогда следует сначала определить объект класса Paper конструктором по умолчанию:

Paper р = new Paper() 

Затем методами

p.setSize(double width, double height)

p.setlmageableArea(double x, double y, double width, double height)

задать размер страницы и области печати.

Потом определить объект класса pageFormat с параметрами по умолчанию:

PageFormat pf = new PageFormat()

и задать новые параметры методом

pf.setPaper(p)

Теперь вызывать на экран окно Параметры страницы методом pageDiaiogo уже не обязательно, и мы получим молчаливый (silent) процесс печати. Так делается в тех случаях, когда печать выполняется на фоне отдельным подпроцессом.

Итак, параметры страницы определены, метод print о — тоже. Теперь надо дать задание на печать (print job) — указать количество страниц, их номера, порядок печати страниц, количество копий. Все эти сведения собираются в классе Printer Job.

Система печати Java 2D различает два вида заданий. В более простых заданиях — Printable Job — есть только один класс, рисующий страницы, поэтому у всех страниц одни и те же параметры, страницы печатаются последовательно с первой по последнюю или с последней страницы по первую, это зависит от системы печати.

Второй, более сложный вид заданий — Pageable Job — определяет для печати каждой страницы свой класс, рисующий страницы, поэтому у каждой страницы могут быть собственные параметры. Кроме того, можно печатать не все, а только выбранные страницы, выводить их в обратном порядке, печатать на обеих сторонах листа. Для осуществления этих возможностей определяется экземпляр класса Book или создается класс, реализующий интерфейс Pageable.

В классе Book, опять-таки, один конструктор, создающий пустой объект:

Book b = new Book()

После создания в данный объект добавляются классы, рисующие страницы. Для этого в классе Book есть два метода:

append (Printable p, PageFormat pf) —добавляет объект р В конец;

append(Printable p, PageFormat pf, int numPages) — добавляет numPages

экземпляров р в конец; если число страниц заранее неизвестно, то задается константа UNKNOWN_NUMBER_OF_PAGES .

При составлении задания на печать, т. е. после создания экземпляра класса PrinterJob, надо указать вид задания одним и только одним из трех методов ЭТОГО класса setPrintable(Printable pr), setPrintable(Printable pr, PageFormat pf) ИЛИ setPageble (Pageable pg). Заодно задаются один или несколько классов рг, рисующих страницы в этом задании.

Остальные параметры задания можно задать в стандартном диалоговом окне Печать (Print) операционной системы, которое открывается на экране при выполнении логического метода printoiaiog (). Указанный метод не имеет аргументов. Он возвратит true, когда пользователь щелкнет по кнопке ОК, и false после нажатия кнопки Отмена.

Остается задать число копий, если оно больше 1, методом setcopies(int n) и задание сформировано.

Еще один полезный метод defaultPage() класса PrinterJob возвращает объект класса PageFormat по умолчанию. Этот метод можно использовать вместо конструктора класса PageFormat.

Осталось сказать, как создается экземпляр класса PrinterJob. Поскольку этот класс тесно связан с системой печати компьютера, его объекты создаются не конструктором, а статическим методом getPrinterJob(), Имеющимся в том же самом классе Printer Job.

Начало печати задается методом print () класса PrinterJob. Этот метод не имеет аргументов. Он.последбватель но вызывает методы print (g, pf, ind) классов, рисующих страницы, для каждой страницы.

Соберем все это вместе в листинге 18.8. В нем средствами JavaJ2D печатается то же, что и в листинге 18.7. Обратите внимание на п. 6. Пдсле окончания печати программа не заканчивается автоматически, для ее завершения мы обращаемся к методу System.exit (0).



Печать страниц с разными параметрами



Печать страниц с разными параметрами

Печать вида Printable Job не совсем удобна — у всех страниц должны быть одинаковые параметры, нельзя задать число страниц и порядок их печати, в окне Параметры страницы не видно число страниц, выводимых на печать.

Все эти возможности предоставляет печать вида Pageable Job с помощью класса Book.

Как уже говорилось выше, сначала создается пустой объект класса Book , затем к нему добавляются разные или одинаковые классы, рисующие страницы. При этом определяются объекты класса pageFormat , задающие параметры этих страниц, и число страниц. Если число страниц заранее неизвестно, то вместо него указывается константа UNKNOWN_NUMBER_OF_PAGES . В таком случае страницы будут печататься в порядке возрастания их номеров до тех пор, пока метод print () не возвратит NO_SUCH_PAGE .

Метод setPage(int pagelndex, Printable p, PageFormat pf)

заменяет объект в позиции pagelndex на новый объект р.

В программе листинга 18.10 создаются два класса, рисующие страницы: Cover и content. Эти классы очень просты — в них только реализован метод print(). Класс cover рисует титульный лист крупным полужирным шрифтом. Текст печатается снизу вверх вдоль длинной стороны листа на его правой половине. Класс content выводит обыкновенный текст обычным образом.

Параметры титульного листа определяются в классе pfl, параметры других страниц задаются в диалоговом окне Параметры страницы и содержатся в классе рf2.

В объект bk класса Book занесены три страницы: первая страница — титульный лист, на двух других печатается один и тот же текст, записанный в методе print() класса Content.



Печать в Java



Печать в Java

Поскольку принтер — устройство графическое, вывод на печать очень похож на вывод графических объектов на экран. Поэтому в Java средства печати входят в графическую библиотеку AWT и в систему Java 2D.

В графическом компоненте кроме графического контекста — объекта класса Graphics, создается еще "печатный контекст". Это тоже объект класса Graphics, но реализующий интерфейс printGraphics и полученный из другого источника — объекта класса print job, входящего в пакет java.awt. Сам же этот объект создается с помощью класса Toolkit пакета java.awt. На практике это выглядит так:

PrintJob pj = getToolkitO .get,Print Job (this, "Job Title", null);

Graphics pg = pj.getGraphics();

Метод getPrintJob () сначала выводит на экран стандартное окно Печать (Print) операционной системы. Когда пользователь выберет в этом окне параметры печати и начнет печать кнопкой ОК, создается объект pj. Если пользователь отказывается от печати при помощи кнопки Отмена (Cancel), то метод возвращает null.

В классе Toolkit два метода getPrint Job ():

getPrintJob(Frame frame, String jobTitle, JobAttributes jobAttr,

PageAttributes pageAttr) 

getPrintJob(Frame frame, String jobTitle, Properties prop)

Аргумент frame указывает на окно верхнего уровня, управляющее печатью. Этот аргумент не может быть null. Строка jobTitle задает заголовок задания, который не печатается, и может быть равна null. Аргумент prop зависит от реализации системы печати, часто это просто null, в данном случае задаются стандартные параметры печати.

Аргумент jobAttr задает параметры печати. Класс JobAttributes, экземпляром которого является этот аргумент, устроен сложно. В нем пять подклассов, содержащих статические константы — параметры печати, которые используются в конструкторе класса. Впрочем, есть конструктор по умолчанию, задающий стандартные параметры печати.

Аргумент pageAttr задает параметры страницы. Класс pageProperties тоже содержит пять подклассов со статическими константами, которые и задают параметры страницы и используются в конструкторе класса. Если для печати достаточно стандартных параметров, то можно воспользоваться конструктором по умолчанию.

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

После того как "печатный контекст" — объект pg класса Graphics — определен, МОЖНО вызывать МеТОД print(pg) ИЛИ printAll(pg) Класса Component. Этот метод устанавливает связь с принтером по умолчанию и вызывает метод paint (pg). На печать выводится все то, что задано этим методом.

Например, чтобы распечатать текстовый файл, надо в процессе ввода разбить его текст на строки и в методе paint (pg) вывести строки методом pg.drawstring() так же, как мы выводили их на экран в главе 9. При этом следует учесть, что в "печатном контексте" нет шрифта по умолчанию, всегда надо устанавливать шрифт методом pg.setFont ().

После выполнения всех методов print о применяется метод pg. dispose(), вызывающий прогон страницы, и метод pj .endо, заканчивающий печать.

В листинге 18.7 приведен простой пример печати текста и окружности, заданных в методе paint (>. Этот метод работает два раза: первый раз вычерчивая текст и окружность на экране, второй раз, точно так же, на листе бумаги, вставленной в принтер. Все методы печати собраны в один метод simplePrint().



Получение свойств файла



Получение свойств файла

В конструкторах классов файлового ввода/вывода, описанных в предыдущем разделе, указывалось имя файла в виде строки. При этом оставалось неизвестным, существует ли файл, разрешен ли к, нему доступ, какова длина файла.

Получить такие сведения можно от предварительно созданного экземпляра класса File, содержащего сведения о файле. В конструкторе этого класса

File(String filename)

указывается путь к файлу или каталогу, записанный по правилам операционной системы. В UNIX имена каталогов разделяются наклонной чертой /, в MS Windows — обратной наклонной чертой \, в Apple Macintosh — двоеточием :. Этот символ содержится в системном свойстве file.separator (см. Рисунок 6.2). Путь к файлу предваряется префиксом. В UNIX это наклонная черта, в MS Windows — буква раздела диска, двоеточие и обратная наклонная черта. Если префикса нет, то путь считается относительным и к нему прибавляется путь к текущему каталогу, который хранится в системном свойстве user.dir.

Конструктор не проверяет, существует ли файл с таким именем, поэтому после создания объекта следует это проверить логическим методом exists ().

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

Прежде всего, логическими методами isFileO, isDirectoryO можно выяснить, является ли путь, указанный в конструкторе, путем к файлу или каталогу.

Для каталога можно получить его содержимое — список имен файлов и подкаталогов— методом list о, возвращающим массив строк stringf]. Можно получить такой же список в виде массива объектов класса File[] методом listFilest). Можно выбрать из списка только некоторые файлы, реализовав интерфейс FileNameFiiter и обратившись к методу

list(FileNameFilter filter).

Если каталог с указанным в конструкторе путем не существует, его можно создать логическим методом mkdir(). Этот метод возвращает true, если каталог удалось создать. Логический метод mkdirso создает еще и все несуществующие каталоги, указанные в пути.

Пустой каталог удаляется методом delete ().

Для файла можно получить его длину в байтах методом length (), время последней модификации в секундах с 1 января 1970 г. методом lastModifiedo. Если файл не существует, эти методы возвращают нуль.

Логические методы canRead (), canwrite () показывают права доступа к файлу.

Файл можно переименовать логическим методом renameTo(Fiie newMame) или удалить логическим методом delete о. Эти методы возвращают true, если операция прошла удачно.

Если файл с указанным в конструкторе путем не существует, его можно создать логическим методом createNewFilet), возвращающим true, если файл не существовал, и его удалось создать, и false, если файл уже существовал.

Статическими методами

createTempFile(String prefix, String suffix, File tmpDir)

createTempFile(String prefix, String suffix)

можно создать временный файл с именем prefix и расширением suffix в каталоге tmpDir или каталоге, указанном в системном свойстве java.io.tmpdir (см. Рисунок 6.2). Имя prefix должно содержать не менее трех символов. Если suffix = null, то файл получит суффикс .tmp.

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

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

В листинге 18.2 показан пример использования класса File, а на Рисунок 18.4 — начало вывода этой программы.



Поток простых типов Java



Поток простых типов Java

Класс DataOutputstream позволяет записать данные простых типов Java в выходной поток айтов методами writeBoolean (boolean b), writeBytefint b), writeShort(int h), writeChar(int c), writelnt"(int n), writeLong(long 1), writeFloat(float f), writeDouble(double d).

Кроме того, метод writeBytes(string s) записывает каждый символ строки s в один байт, отбрасывая старший байт кодировки каждого символа Unicode, а метод writecnarststring s) записывает каждый символ строки s в два байта, первый байт — старший байт кодировки Unicode, так же, как это делает метод writeChar ().

Еще один метод writeUTFtstring s) записывает строку s в выходной поток в кодировке UTF-8. Надо пояснить эту кодировку.




Прямой доступ к файлу



Прямой доступ к файлу

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

В конструкторах этого класса

RandomAccessFile(File file, String mode) 

RandomAccessFile(String fileName, String mode)

вторым аргументом mode задается режим открытия файла. Это может быть строка "r" — открытие файла только для чтения, или "rw" — открытие файла для чтения и записи.

Этот класс собрал все полезные методы работы с файлом. Он содержит все методы классов Datainputstream и DataOutputstream, кроме того, позволяет прочитать сразу целую строку методом readidne () и отыскать нужные данные в файле.

Байты файла нумеруются, начиная с 0, подобно элементам массива. Файл снабжен неявным указателем (file pointer) текущей позиции. Чтение и запись производится, начиная с текущей позиции файла. При открытии файла конструктором указатель стоит на начале файла, в позиции 0. Текущую позицию можно узнать методом getFiiePointer(). Каждое чтение или запись перемещает указатель на длину прочитанного или записанного данного. Всегда можно переместить указатель в новую позицию, роз методом seek (long pos). Метод seek(0) перемещает указатель на начало файла.

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




Сериализация объекта



Рисунок 18.7. Сериализация  объекта


Если не нужно сериализовать какое-то поле, то достаточно пометить его служебным словом transient, например:

transient MyClass me = new MyClass("abc", -12, 5.67e-5);

Метод writeObjecto не записывает в выходной поток поля, помеченные static и transient. Впрочем, это положение можно изменить, переопределив метод writeObjecto или задав список сериализуемых полей.

Вообще процесс сериализации можно полностью настроить под свои нужды, переопределив методы ввода/вывода и воспользовавшись вспомогательными классами. Можно даже взять весь процесс на себя, реализовав не интерфейс Serializable, а интерфейс Externaiizabie, но тогда придется реали-зовать методы readExternai () и writeExternai о, выполняющие ввод/вывод.

Эти действия выходят за рамки книги. Если вам необходимо полностью освоить процесс сериализации, то обратитесь к спецификации Java Object Serialization Specification, расположенной среди документации J2SDK в каталоге docs\guide\serialization\spec\. Там же есть и примеры программ, реализующих эту спецификацию.




Сериализация объектов



Сериализация объектов

Методы классов ObjectlnputStream и ObjectOutputStream позволяют прочитать из входного байтового потока или записать в выходной байтовый поток данные сложных типов — объекты, массивы, строки — подобно тому, как методы классов Datainputstream и DataOutputstream читают и записывают данные простых типов.

Сходство усиливается- тем, Что классы Objeetxxx содержат методы как для чтений, так и записи простых типов. Впрочем, эти методы предназначены не для использования в программах, а для записи/чтения полей объектов и элементов массивов.

Процесс записи объекта в выходной поток получил название сериализации (serialization), а чтения объекта из входного потока и восстановления его в оперативной памяти — десериализации (deserialization).

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

Поэтому сериализации можно подвергнуть не каждый объект, а только тот, который реализует интерфейс seriaiizabie. Этот интерфейс не содержит ни полей, ни методов. Реализовать в нем нечего. По сути дела запись

class A implements Seriaiizabie{...}

это только пометка, разрешающая сериализацию класса А.

Как всегда в Java, процесс сериализации максимально автоматизирован. Достаточно создать объект класса ObjectOutputStream, связав его с выходным потоком, и выводить в этот поток объекты методом writeObject():

MyClass me = new MyClass("abc", -12, 5.67e-5);

int[] arr = {10, 20, 30};

ObjectOutputStream oos = new ObjectOutputStream(

new FileOutputStream("myobjects.ser")) ;

oos.writeObject(me); 

oos.writeObject(arr); 

oos.writeObject("Some string"); 

oos.writeObject (new Date()); 

oos.flush();

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

Если в объекте присутствуют ссылки на другие объекты, то они тоже сериализуются, а в них могут быть ссылки на другие объекты, которые опять-таки сериализуются, и получается целое множество причудливо связанных между собой сериализуемых объектов. Метод writeObjecto распознает две ссылки на один объект и выводит его в выходной поток только один раз. К тому же, он распознает ссылки, замкнутые в кольцо, и избегает зацикливания.

Все классы объектов, входящих в такое сериализуемое множество, а также все их внутренние классы, должны реализовать интерфейс seriaiizabie, в противном случае будет выброшено исключение класса NotseriaiizabieException и процесс сериализации прервется. Многие классы J2SDK реализуют этот интерфейс. Учтите также, что все потомки таких классов наследуют реализацию. Например, класс java.awt.Component реализует интерфейс Serializable, значит, все графические компоненты можно сериализовать. Не реализуют этот интерфейс обычно классы, тесно связанные с выполнением программ, например, java.awt.Toolkit. Состояние экземпляров таких классов нет смысла сохранять или передавать по сети. Не реализуют интерфейс Serializable и классы, содержащие внутренние сведения Java "для служебного пользования".

Десериализация происходит так же просто, как и сериализация:

ObjectlnputStream ois = new ObjectInputStream(

new FilelnputStream("myobjects.ser")); 

MyClass mcl = (MyClass)ois.readObject(); 

int[] a = (int[])ois.readObject(); 

String s = (String)ois.readObject(); 

Date d = (Date)ois.readObject() ;

Нужно только соблюдать порядок чтения элементов потока. В листинге 18.6 мы создаем объект класса GregorianCaiendar с текущей датой и временем, сериализуем его в файл date.ser, через три секунды десериа-лизуем и сравниваем с текущим временем. Результат показан на Рисунок 18.7.



Свойства файла и начало вывода каталога



Рисунок 18.4. Свойства файла  и начало вывода каталога















Ввод и вывод данных



Рисунок 18.5. Ввод и вывод данных