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




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

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

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

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

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

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

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

Листинг 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); Рис. 18.6. Данные, передаваемые между подпроцессами

 

}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(); })

 

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

Методы классов 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.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)); } }

 

Рис.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\. Там же есть и примеры программ, реализующих эту спецификацию.

Печать в 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().

Листинг 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

Расширенная графическая система 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).

Листинг 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); } }

Печать файла

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

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

Листинг 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; } } }



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2020-05-09 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: