Классы Java для работы с потоками
Базовые классы для работы с файлами и потоками
Базовые классы для работы с файлами и потокамиКоличество классов, созданных для работы с файлами, достаточно велико, чтобы привести начинающего программиста в растерянность. Прежде чем мы займемся конкретными классами и приведем примеры приложений, работающих с потоками и файлами, рассмотрим иерархию классов, предназначенных для орагнизации ввода и вывода.
Все основные классы, интересующие нас в этой главе, произошли от класса Object (Рисунок 1).
Добавление буферизации
Добавление буферизацииА что, если нам нужен не простой выходной поток, а буферизованный?
Здесь нам может помочь класс BufferedOutputStream. Вот два конструктора, предусмотренных в этом классе:
public BufferedOutputStream( OutputStream out); public BufferedOutputStream( OutputStream out, int size);
Первый из них создает буферизованный выходной поток на базе потока класса OutputStream, а второй делает то же самое, но дополнительно позволяет указать размер буфера в байтах.
Если вам нужно создать выходной буферизованный поток для записи форматированных данных, создание потока выполняется в три приема:
Вот фрагмент исходного текста программы, который создает выходной буферизованный поток для записи форматированных данных в файл с именем output.txt:
DataOutputStream OutStream; OutStream = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("output.txt")));
Аналогичным образом создается входной буферизованный поток для чтения форматированных данных из того же файла:
DataInputStream InStream; InStream = new DataInputStream( new BufferedInputStream( new FileInputStream("output.txt")));
Исходный текст приложения DirectFile
Исходный текст приложения DirectFile представлен в листинге 2.Исходный текст приложения
Исходный текст приложенияИсходный текст приложения StreamToken представлен в листинге 1.
Исключения при создании потоков
Исключения при создании потоковПри создании потоков на базе классов FileOutputStream и FileInputStream могут возникать исключения FileNotFoundException, SecurityException, IOException.
Исключение FileNotFoundException возникает при попытке открыть входной поток данных для несуществующего файла, то есть когда файл не найден.
Исключение SecurityException возникает при попытке открыть файл, для которого запрещен доступ. Например, если файл можно только читать, а он открывается для записи, возникнет исключение SecurityException.
Если файл не может быть открыт для записи по каким-либо другим причинам, возникает исключение IOException.
Класс BufferedInputStream
Класс BufferedInputStreamБуферизация операций ввода и вывода в большинстве случаев значительно ускоряет работу приложений, так как при ее использовании сокращается количество обращений к системе для обмена данными с внешними устройствами.
Класс BufferedInputStream может быть использован приложениями Java для организации буферизованных потоков ввода. Заметим, что конструкторы этого класса в качестве параметра получают ссылку на объект класса InputStream. Таким образом, вы не можете просто создать объект класса BufferedInputStream, не создав перед этим объекта класса InputStream. Подробности мы обсудим позже.
Класс BufferedOutputStream
Класс BufferedOutputStreamКласс BufferedOutputStream предназначен для создания буферизованных потоков вывода. Как мы уже говорили, буферизация ускоряет работу приложений с потоками.
Класс ByteArrayInputStream
Класс ByteArrayInputStreamПри необходимости вы можете создать в приложениях Java входной поток данных не на базе локального или удаленного файла, а на базе массива, расположенного в оперативной памяти. Класс ByteArrayInputStream предназначен именно для этого - вы передаете конструктору класса ссылку на массив, и получаете входной поток данных, связанный с этим массивом.
Потоки в оперативной памяти могут быть использованы для временного хранения данных. Заметим, что так как аплеты Java не могут обращаться к локальным файлам, для создания временных файлов можно использовать потоки в оперативной памяти на базе класса ByteArrayInputStream. Другую возможность предоставляет класс StringBufferInputStream, рассмотренный ниже.
Класс ByteArrayInputStream
С помощью класса ByteArrayInputStream вы можете создать входной поток на базе массива байт, расположенного в оперативной памяти. В этом классе определено два конструктора:
public ByteArrayInputStream(byte buf[]); public ByteArrayInputStream( byte buf[], int offset, int length);
Первый конструктор получает через единственный параметр ссылку на массив, который будет использован для создания входного потока. Второй позволяет дополнительно указать смещение offset и размер области памяти length, которая будет использована для создания потока.
Вот несколько методов, определенных в классе ByteArrayInputStream:
public int available(); public int read(); public int read(byte b[],int off, int len); public void reset(); public long skip(long n);
Наиболее интересен из них метод available, с помощью которого можно определить, сколько байт имеется во входном потоке для чтения.
Обычно класс ByteArrayInputStream используется вместе с классом DataInputStream, что позволяет организовать форматный ввод данных.
Класс ByteArrayOutputStream
Класс ByteArrayOutputStreamС помощью класса ByteArrayOutputStream можно создать поток вывода в оперативной памяти.
Класс ByteArrayOutputStream
Класс ByteArrayOutputStream создан на базе класса OutputStream. В нем имеется два конструктора, прототипы которых представлены ниже:
public ByteArrayOutputStream(); public ByteArrayOutputStream( int size);
Первый из этих конструкторов создает выходной поток в оперативной памяти с начальным размером буфера, равным 32 байта. Второй позволяет указать необходимый размер буфера.
В классе ByteArrayOutputStream определено несколько достаточно полезных методов. Вот некоторые из них:
public void reset(); public int size(); public byte[] toByteArray(); public void writeTo(OutputStream out);
Метод reset сбрасывает счетчик байт, записанных в выходной поток. Если данные, записанные в поток вам больше не нужны, вы можете вызвать этот метод и использовать память, выделенную для потока, для записи других данных.
С помощью метода size можно определить количество байт данных, записанных в поток.
Метод toByteArray позволяет скопировать данные, записанные в поток, в массив байт. Этот метод возвращает адрес созданного для этой цели массива.
С помощью метода writeTo вы можете скопировать содержимое данного потока в другой выходной поток, ссылка на который передается методу через параметр.
Для выполнения форматированного вывода в поток, вы должны создать поток на базе класса DataOutputStream, передав соответствующему конструктору ссылку на поток класса ByteArrayOutputStream.
Класс DataInputStream
Класс DataInputStreamСоставляя программы на языке программирования С, вы были вынуждены работать с потоками на уровне байт или, в лучшем случае, на уровне текстовых строк. Однако часто возникает необходимость записывать в потоки данных и читать оттуда объекты других типов, например, целые числа и числа типа double, числа в формате с плавающей десятичной точкой, массивы байт и символов и так далее.
Класс DataInputStream содержит методы, позволяющие извлекать из входного потока данные в перечисленных выше форматах или, как говорят, выполнять форматированный ввод данных. Он также реализует интерфейс DataInput, служащий для этой же цели. Поэтому класс DataInputStream очень удобен и часто применяется в приложениях для работы с потоками ввода.
Так же как и конструктор класса BufferedInputStream, конструктор класса DataInputStream должен получить через свои параметр ссылку на объект класса InputStream.
Класс DataOutputStream
Класс DataOutputStreamС помощью класса DataOutputStream приложения Java могут выполнять форматированный вывод данных. Для ввода форматированных данных вы должны создать входной поток с использованием класса DataInputStream, о котором мы уже говорили. Класс DataOutputStream реализует интерфейс DataOutput.
Класс File
Класс FileКласс File предназначен для работы с оглавлениями каталогов. С помощью этого класса можно получить список файлов и каталогов, расположенных в заданном каталоге, создать или удалить каталог, переименовать файл или каталог, а также выполнить некоторые другие операции.
Класс FileDescriptor
Класс FileDescriptorC помощью класса FileDescriptor вы можете проверить идентификатор открытого файла.
Класс FileInputStream
Класс FileInputStreamЭтот класс позволяет создать поток ввода на базе класса File или FileDescriptor.
Класс FileOutputStream
Класс FileOutputStreamЭтот класс позволяет создать поток вывода на базе класса File или FileDescriptor.
Класс FilterInputStream
Класс FilterInputStreamКласс FilterInputStream, который происходит непосредственно от класса InputStream, является абстрактным классом, на базе которого созданы классы BufferedInputStream, DataInputStream, LineNumberInputStream и PushBackInputStream. Непосредственно класс FilterInputStream не используется в приложениях Java, так как, во-первых, он является абстрактным и предназначен для переопределения методов базового класса InputStream, а во-вторых, наиболее полезные методы для работы с потоками ввода имеются в классах, созданных на базе класса FilterInputStream.
Класс FilterOutputStream
Класс FilterOutputStreamАбстрактный класс FilterOutputStream служит прослойкой между классом OutputStream и классами BufferedOutputStream, DataOutputStream, а также PrintStream. Он выполняет роль, аналогичную роли рассмотренного ранее класса FilterIntputStream.
Класс InputStream
Класс InputStreamКласс InputStream является базовым для большого количества классов, на основе которых создаются потоки ввода. Именно производные классы применяются программистами, так как в них имеются намного более мощные методы, чем в классе InputStream. Эти методы позволяют работать с потоком ввода не на уровне отдельных байт, а на уровне объектов различных классов, например, класса String и других.
Класс LineNumberInputStream
Класс LineNumberInputStreamС помощью класса LineNumberInputStream вы можете работать с текстовыми потоками, состоящими из отдельных строк, разделенных символами возврата каретки \r и перехода на следующую строку \n. Методы этого класса позволяют следить за нумерацией строк в таких потоках.
Класс OutputStream
Класс OutputStreamАналогично, класс OutputStream служит в качестве базового для различных классов, имеющих отношение к потокам вывода.
Класс PipedInputStream
Класс PipedInputStreamС помощью классов PipedInputStream и PipedOutputStream можно организовать двухстороннюю передачу данных между двумя одновременно работающими задачами мультизадачного аплета.
Класс PipedOutputStream
Класс PipedOutputStreamКак мы уже говорили, классы PipedInputStream и PipedOutputStream предназначены для организации двухсторонней передачи данных между двумя одновременно работающими задачами мультизадачного аплета.
Класс PrintStream
Класс PrintStreamПотоки, созданные с использованием класса PrintStream, предназначены для форматного вывода данных различных типов с целью их визуального представления в виде текстовой строки. Аналогичная операция в языке программирования С выполнялась функцией printf.
Класс PushBackInputStream
Класс PushBackInputStreamКласс PushBackInputStream позволяет возвратить в поток ввода только что прочитанный оттуда символ, с тем чтобы после этого данный символ можно было прочитать снова.
Класс RandomAccesFile
Класс RandomAccesFileС помощью класса RandomAccesFile можно организовать работу с файлами в режиме прямого доступа, когда программа указывает смещение и размер блока данных, над которым выполняется операция ввода или вывода. Заметим, кстати, что классы InputStream и OutputStream также можно использовать для обращения к файлам в режиме прямого доступа.
Класс SequenceInputStream
Класс SequenceInputStreamКласс SequenceInputStream позволяет объединить несколько входных потоков в один поток. Если в процессе чтения будет достигнут конец первого потока такого объединения, в дальнейшем чтение будет выполняться из второго потока и так далее.
Класс SimpleDBMS
Класс SimpleDBMSРассмотрим теперь класс SimpleDBMS.
В этом классе определено три поля с именами idx, dat и idxFilePointer, а также три метода.
Класс StreamTokenizer для разбора входных потоков
Класс StreamTokenizer для разбора входных потоковЕсли вы создаете приложение, предназначенное для обработки текстов (например, транслятор или просто разборщик файла конфигурации, содержащего значения различных параметров), вам может пригодиться класс StreamTokenizer. Создав объект этого класса для входного потока, вы можете легко решить задачу выделения из этого потока отдельных слов, символов, чисел и строк комментариев.
Класс StreamTokenizer
Класс StreamTokenizerОчень удобен класс StreamTokenizer. Он позволяет организовать выделение из входного потока данных элементов, отделенных друг от друга заданными разделителями, такими, например, как запятая, пробел, символы возврата каретки и перевода строки.
Класс StringBufferInputStream
Класс StringBufferInputStreamКласс StringBufferInputStream позволяет создавать потоки ввода на базе строк класса String, используя при этом только младшие байты хранящихся в такой строке символов. Этот класс может служить дополнением для класса ByteArrayInputStream, который также предназначен для создания потоков на базе данных из оперативной памяти.
Класс StringBufferInputStream
Класс StringBufferInputStream предназначен для создания входного потока на базе текстовой строки класса String. Ссылка на эту строку передается конструктору класса StringBufferInputStream через параметр:
public StringBufferInputStream(String s);
В классе StringBufferInputStream определены те же методы, что и в только что рассмотренном классе ByteArrayInputStream. Для более удобной работы вы, вероятно, создадите на базе потока класса StringBufferInputStream поток класса DataInputStream.
Класс StringTokenizer
Класс StringTokenizerРассказывая о классе StreamTokenizer, нельзя не упомянуть о другом классе с похожим названием и назначением, а именно о классе StringTokenizer.
Определение этого класса достаточно компактно.
public StringTokenizer(String str); public StringTokenizer(String str, String delim); public StringTokenizer(String str, String delim, boolean returnTokens);
public String nextToken(); public String nextToken(String delim); public int countTokenss(); public boolean hasMoreElements(); public boolean hasMoreTokenss(); public Object nextElement(); }
Класс StringTokenizer не имеет никакого отношения к потокам, так как предназначен для выделения отдельных элементов из строк типа String.
Конструкторы класса получают в качетсве первого параметра str ссылку на разбираемую строку. Второй параметр delim, если он есть, задает разделители, с испльзованием которых в строке будут выделяться элементы. Параметр returnTokens определяет, надо ли вовзвращать обнаруженные разделители как элементы разбираемой строки.
Рассмотрим кратко методы класса StringTokenizer.
Для разбора строки приложение должно организовать цикл, вызывая в нем метод nextToken. Условием завершения цикла может быть либо возникновение исключения NoSuchElementException, либо возврат значения false методами hasMoreElements или hasMoreTokens.
Метод countTokens позволяет определить, сколько раз был вызван метод nextToken перед возникновением исключения NoSuchElementException.
Классы Java для работы с потоками
Классы Java для работы с потокамиПрограммист, создающий автономное приложение Java, может работать с потоками нескольких типов:
Рассмотрим кратко классы, связанные с потоками.
Конструктор класса SimpleDBMS
Конструктор класса SimpleDBMSКонструктор класса SimpleDBMS выглядит достаточно просто. Все, что он делает, - это создает два объекта класса RandomAccessFile, соответственно, для индекса и данных:
idx = new RandomAccessFile(IndexFile, "rw"); dat = new RandomAccessFile(DataFile, "rw");
Так как в качестве второго параметра конструктору класа RandomAccessFile передается строка "rw", файлы открываются и для чтения, и для записи.
Конструктор класса StreamTokenizer
Конструктор класса StreamTokenizerДля создание объектов класса StreamTokenizer предусмотрен всего один конструктор:
public StreamTokenizer(InputStream istream);
В качестве параметра этому конструктору необходимо передать ссылку на заранее созданный входной поток.
new DataOutputStream( new BufferedOutputStream( new
Листинг 1. Файл StreamToken.java
import java.io.*;
public class StreamToken { public static void main(String args[]) { DataOutputStream OutStream; DataInputStream InStream;
byte bKbdInput[] = new byte[256]; String sOut;
try { System.out.println( "Enter string to parse...");
System.in.read(bKbdInput);
sOut = new String(bKbdInput, 0);
OutStream = new DataOutputStream( new BufferedOutputStream( new FileOutputStream( "output.txt")));
OutStream.writeBytes(sOut);
OutStream.close();
InStream = new DataInputStream( new BufferedInputStream( new FileInputStream( "output.txt")));
TokenizerOfStream tos = new TokenizerOfStream();
tos.TokenizeIt(InStream);
InStream.close();
System.out.println( "Press
to terminate...");
System.in.read(bKbdInput);
} catch(Exception ioe) { System.out.println(ioe.toString());
} } }
class TokenizerOfStream { public void TokenizeIt(InputStream is) { StreamTokenizer stok; String str;
try { stok = new StreamTokenizer(is);
stok.slashSlashComments(true);
stok.ordinaryChar('.');
while(stok.nextToken() != StreamTokenizer.TT_EOF) { switch(stok.ttype) { case StreamTokenizer.TT_WORD: { str = new String( "\nTT_WORD >
" + stok.sval);
break; }
case StreamTokenizer.TT_NUMBER: { str = "\nTT_NUMBER >
" + Double.toString(stok.nval);
break; }
case StreamTokenizer.TT_EOL: { str = new String(">
End of line");
break; }
default: { if((char)stok.ttype == '"') { str = new String( "\nTT_WORD >
" + stok.sval);
}
else str = ">
" + String.valueOf( (char)stok.ttype);
} }
System.out.println(str);
} } catch(Exception ioe) { System.out.println(ioe.toString());
} } }
MenuBar mbMainMenuBar; Menu mnFile; Menu
Листинг 2. Файл DirectFile.java
import java.awt.*; import java.io.*; import java.util.*;
public class DirectFile { public static void main(String args[]) { MainFrameWnd frame = new MainFrameWnd("MenuApp");
frame.setSize( frame.getInsets().left + frame.getInsets().right + 320, frame.getInsets().top + frame.getInsets().bottom + 240);
frame.show();
} }
class MainFrameWnd extends Frame { MenuBar mbMainMenuBar; Menu mnFile; Menu mnHelp; boolean fDBEmpty = true;
public MainFrameWnd(String sTitle) { super(sTitle);
setSize(400, 200);
setBackground(Color.yellow);
setForeground(Color.black);
setLayout(new FlowLayout());
mbMainMenuBar = new MenuBar();
mnFile = new Menu("File");
mnFile.add("New...");
mnFile.add("View records...");
mnFile.add("-");
mnFile.add("Exit");
mnHelp = new Menu("Help");
mnHelp.add("Content");
mnHelp.add("-");
mnHelp.add("About");
mbMainMenuBar.add(mnFile);
mbMainMenuBar.add(mnHelp);
setMenuBar(mbMainMenuBar);
}
public void paint(Graphics g) { g.setFont(new Font("Helvetica", Font.PLAIN, 12));
g.drawString("Frame window", 10, 70);
super.paint(g);
}
public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { setVisible(false);
System.exit(0);
return true; } else return super.handleEvent(evt);
}
public boolean action(Event evt, Object obj) { MenuItem mnItem; if(evt.target instanceof MenuItem) { mnItem = (MenuItem)evt.target;
if(obj.equals("Exit")) { System.exit(0);
}
else if(obj.equals("New...")) { if(fDBEmpty) { SimpleDBMS db = new SimpleDBMS( "dbtest.idx", "dbtest.dat");
db.AddRecord("Ivanov", 1000);
db.AddRecord("Petrov", 2000);
db.AddRecord("Sidoroff", 3000);
db.close();
fDBEmpty = false;
MessageBox mbox; mbox = new MessageBox( "Database created", this, "Information", true);
mbox.show();
} }
else if(obj.equals("View records...")) { SimpleDBMS db = new SimpleDBMS( "dbtest.idx", "dbtest.dat");
String szRecords;
szRecords = db.GetRecordByNumber(0) + db.GetRecordByNumber(1) + db.GetRecordByNumber(2);
db.close();
MessageBox mbox; mbox = new MessageBox(szRecords, this, "Database records", true);
mbox.show();
}
else if(obj.equals("Content")) { MessageBox mbox; mbox = new MessageBox( "Item Content selected", this, "Dialog from Frame", true);
mbox.show();
}
else if(obj.equals("About")) { MessageBox mbox; mbox = new MessageBox( "Item About selected", this, "Dialog from Frame", true);
mbox.show();
} else return false; return true; } return false; } }
class MessageBox extends Dialog { Label lbMsg; Button btnOK;
public MessageBox(String sMsg, Frame parent, String sTitle, boolean modal) { super(parent, sTitle, modal);
resize(300, 100);
setLayout(new GridLayout(2, 1));
lbMsg = new Label(sMsg, Label.CENTER);
add(lbMsg);
btnOK = new Button("OK");
add(btnOK);
}
public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { dispose();
return true; } else return super.handleEvent(evt);
}
public boolean action(Event evt, Object obj) { Button btn; if(evt.target instanceof Button) { btn = (Button)evt.target;
if(evt.target.equals(btnOK)) { dispose();
} else return false; return true; } return false; } }
class SimpleDBMS { RandomAccessFile idx; RandomAccessFile dat;
long idxFilePointer = 0;
public SimpleDBMS(String IndexFile, String DataFile) { try { idx = new RandomAccessFile( IndexFile, "rw");
dat = new RandomAccessFile( DataFile, "rw");
} catch(Exception ioe) { System.out.println(ioe.toString());
} }
public void close() { try { idx.close();
dat.close();
} catch(Exception ioe) { System.out.println(ioe.toString());
} }
public void AddRecord(String name, int account) { try { idx.seek(idx.length());
dat.seek(dat.length());
idxFilePointer = dat.getFilePointer();
idx.writeLong(idxFilePointer);
dat.writeBytes(name+ "\r\n");
dat.writeInt(account);
} catch(Exception ioe) { System.out.println(ioe.toString());
} }
public String GetRecordByNumber(long nRec) { String sRecord = "
";
try { Integer account; String str = null;
idx.seek(nRec * 8);
idxFilePointer = idx.readLong();
dat.seek(idxFilePointer);
str = dat.readLine();
account = new Integer(dat.readInt());
sRecord = new String(">
" + account + ", " + str);
} catch(Exception ioe) { System.out.println(ioe.toString());
} return sRecord; } }
Метод AddRecord
Метод AddRecordМетод AddRecord добавляет новую запись в конец файла данных, а смещение этой записи - в конец файла индекса. Поэтому перед началом своей работы текущая позиция обоих указанных файлов устанавливается на конец файла.
Для установки мы применили метод seek из класса RandomAccessFile, передав ему в качестве параметра значение длины файла в байтах, определенное при помощи метода length из того же класса:
idx.seek(idx.length()); dat.seek(dat.length());
Перед тем как добавлять новую запись в файл данных, метод AddRecord определяет текущую позицию в файле данных (в данном случае это позиция конца файла) и записывает эту позицию в файл индекса:
idxFilePointer = dat.getFilePointer(); idx.writeLong(idxFilePointer);
Далее метод AddRecord выполняет сохранение полей записи в файле данных. Для записи строки вызывается метод writeBytes, а для записи численного значения типа int - метод writeInt:
dat.writeBytes(name + "\r\n"); dat.writeInt(account);
Обратите внимение, что к строке мы добавляем символы возврата каретки и перевода строки. Это сделано исключительно для того чтобы обозначить конец строки текстового поля.
Метод close
Метод closeМетод close закрывает файлы индекса и данных, вызывая метод close из класса RandomAccessFile:
idx.close(); dat.close();
Метод GetRecordByNumber
Метод GetRecordByNumberМетод GetRecordByNumber позволяет извлечь произвольную запись из файла данных по ее порядковому номеру.
Напомним, что смещения всех записей хранятся в файле индексов и имеют одинаковую длину 8 байт. Пользуясь этим, метод GetRecordByNumber вычисляет смещение в файле индекса простым умножением порядкового номера записи на длину переменной типа long, то есть на 8 байт, а затем выполняет позиционирование:
idx.seek(nRec * 8);
После этого метод GetRecordByNumber извлекает из файла индексов смещение нужной записи в файле данных, вызывая для этого метод readLong, а затем выполняет позиционирование в файле данных:
idxFilePointer = idx.readLong(); dat.seek(idxFilePointer);
Поля записи читаются из файла данных в два приема. Вначале читается строка текстового поля, а затем - численное значение, для чего вызываются, соответственно, методы readLine и readInt:
str = dat.readLine(); account = new Integer(dat.readInt());
Полученные значения полей объединяются в текстовой строке и записываются в переменную sRecord:
sRecord = new String("> " + account + ", " + str);
Содержимое этой переменной метод GetRecordByNumber возвращает в качестве извлеченной строки записи базы данных.
Методы для чтения и записи форматированных данных
Методы для чтения и записи форматированных данныхВместо того чтобы записывать в потоки и читать оттуда отдельные байты или массивы байт, программисты обычно предпочитают пользоваться намного более удобными методами классов DataOutputStream и DataInputStream, допускающими форматированный ввод и вывод данных.
Вот, например, какой набор методов можно использовать для записи форматированных данных в поток класса DataOutputStream:
public final void writeBoolean(boolean v); public final void writeByte(int v); public final void writeBytes(String s); public final void writeChar(int v); public final void writeChars(String s); public final void writeDouble(double v); public final void writeFloat(float v); public final void writeInt(int v); public final void writeLong(long v); public final void writeShort(int v); public final void writeUTF(String s);
Хотя имена методов говорят сами за себя, сделаем замечания относительно применения некоторых из них.
Метод writeByte записывает в поток один байт. Это младший байт слова, которое передается методу через параметр v. В отличие от метода writeByte, метод writeChar записывает в поток двухбайтовое символьное значение (напомним, что в Java символы хранятся с использованием кодировки Unicode и занимают два байта).
Если вам нужно записать в выходной поток текстовую строку, то это можно сделать с помощью методов writeBytes, writeChars или writeUTF. Первый из этих методов записывает в выходной поток только младшие байты символов, а второй - двухбайтовые символы в кодировке Unicode. Метод writeUTF предназначен для записи строки в машинно-независимой кодировке UTF-8.
Все перечисленные выше методы в случае возникновения ошибки создают исключение IOException, которое вы должны обработать.
В классе DataInputStream определены следующие методы, предназначенные для чтения форматированных данных из входного потока:
public final boolean readBoolean(); public final byte readByte(); public final char readChar(); public final double readDouble(); public final float readFloat(); public final void readFully(byte b[]); public final void readFully(byte b[], int off, int len); public final int readInt(); public final String readLine(); public final long readLong(); public final short readShort(); public final int readUnsignedByte(); public final int readUnsignedShort(); public final String readUTF(); public final static String readUTF( DataInput in); public final int skipBytes(int n);
Обратите внимание, что среди этих методов нет тех, что специально предназначены для четния данных, записанных из строк методами writeBytes и writeChars класса DataOutputStream.
Тем не менее, если входной поток состоит из отдельных строк, разделенных символами возврата каретки и перевода строки, то такие строки можно получить методом readLine. Вы также можете воспользоваться методом readFully, который заполняет прочитанными данными массив байт. Этот массив потом будет нетрудно преобразовать в строку типа String, так как в классе String предусмотрен соответствующий конструктор.
Для чтения строк, записанных методом writeUTF вы должны обязательно пользоваться методом readUTF.
Метод skipBytes позволяет пропустить из входного потока заданное количество байт.
Методы класса DataInputStream, предназначенные для чтения данных, могут создавать исключения IOException и EOFException. Первое из них возникает в случае ошибки, а второе - при достижении конца входного потока в процессе чтения.
Методы для настройки параметров разборщика
Методы для настройки параметров разборщикаНиже мы привели прототипы методов, предназначенных для настройки параметров разборщика:
public void commentChar(int ch); public void slashSlashComments(boolean flag); public void slashStarComments(boolean flag); public void quoteChar(int ch);
public void eolIsSignificant(boolean flag); public void lowerCaseMode(boolean fl);
public void ordinaryChar(int ch); public void ordinaryChars(int low,int hi); public void resetSyntax();
public void parseNumbers(); public void whitespaceChars(int low, int hi); public void wordChars(int low, int hi);
Несколько методов определяют, будет ли разборщик выделять во входном потоке строки комментария и если будет, то какми образом.
С помощью метода commentChar вы можете указать символ комментария. Если в строке входного потока попадется такой символ, то он и все следующие за ним до конца текущей строки символы будут проигнорированы.
Методы SlashSlashComments и slashStarComments позволяют указать, что для входного текста используются разделители комментариев в виде двойного символа '/' и '/* … */', соответственно. Это соответствует способу указания комментариев в программах, составленных на языках программирования С++ и С. Для включения режима выделения комментариев обоим методам в качетстве параметра необходимо передать значение true, а для отключения - false.
Метод quoteChar позволяет задать символ, который будет использован в качестве кавычек. Когда при разборе потока встречаются слова, взятые в кавычки, они возвращаются программе разбора без кавычек.
Если передать методу eolIsSignificant значение true, разделители строк будут интерпретироваться как отдельные элементы. Если же этому методу передать значение false, разделители строк будут использоваться аналогично пробелам для разделения элементов входного потока.
Метод lowerCaseMode позволяет включить режим, при котором все выделенные элементы будут перекодированы в строчные символы.
Методы ordinaryChar и ordinaryChars позволяют указать символы, которые должны интерпретироваться как обычные, из которых составляются слова или цифры. Например, если передать методу ordinaryChar символ '.', то слово java.io будет восприниматься как один элемент. Если же этого не сделать, то разборщик выделит из него три элемента - слово java, точку '.' и слово io. Метод ordinaryChars позволяет указать диапазон значений символов, которые должны интерпретироваться как обычные.
С помощью метода resetSyntax вы можете указать, что все символы будут рассматриваться, как обычные.
Метод parseNumbers включает режим разбора чисел, при котором распознаются и преобразуются числа в формате с плавающей десятичной точкой.
Метод whitespaceChars задает диапазон значений для символов-разделителей отдельных слов в потоке.
Метод wordChars позволяет указать символы, которые являются составными частями слов.
Методы для разбора входного потока
Методы для разбора входного потокаПосле того как вы создали разборщик входного потока на базе класса StreamTokenizer и установили его параметры с помощью описанных выше методов, можно приступать собственно к разборке потока. Обычно для этого организуется цикл, в котором вызывается метод nextToken:
public int nextToken();
Этот метод может вернуть одно из следующих значений:
| Значение | Описание |
| TT_WORDTT_WORD | Из потока было извлечено слово |
| TT_NUMBERTT_NUMBER | Из потока было извлечено численное значение |
| TT_EOLTT_EOL | Обнаружен конец строки. Это значение возвращается только в том случае, если при настройке параметров разборщика был вызван метод eolIsSignficant |
| TT_EOFTT_EOF | Обнаружен конец файла |
Если метод nextToken вернул значение TT_EOF, следует завершить цикл разбора входного потока.
Как извлечь считанные элементы потока?
В классе StreamTokenizer определено три поля:
public String sval; public double nval; public int ttype;
Если метод nextToken вернул значение TT_WORD, в поле sval содержится извлеченный элемент в виде текстовой строки. В том случае, когда из входного потока было извлечено числовое значение, оно будет храниться в поле nval типа double. Обычные символы записываются в поле ttype.
Заметим, что если в потоке обнаружены слова, взятые в кавычки, то символ кавычки записывается в поле ttype, а слова - в поле sval. По умолчанию используется символ кавычек '"', однако с помощью метода quoteChar вы можете задать любой другой символ.
При необходимости в процессе разбора вы можете определить номер текущей строки, вызвав для этого метод lineno:
public int lineno();
После вызова метода pushBack следующий вызов метода nextToken приведет к тому, что в поле ttype будет записано текущее значение, а содержимое полей sval и nval не изменится. Прототип метода pushBack приведен ниже:
public void pushBack();
Метод toString возвращает текстовую строку, представляющую текущий элемент, выделенный из потока:
public String toString();
Методы класса StreamTokenizer
Методы класса StreamTokenizerДля настройки параметров разборщика StreamTokenizer и получения отдельных элементов входного потока вы должны пользоваться методами, определенными в классе StreamTokenizer. Рассмотрим самые важние из них.
Описание исходного текста приложения DirectFile
Для работы с базой данных мы создали класс SimpleDBMS, определив в нем конструктор, методы для добавления записей, извлечения записей по их порядковому номеру, а также метод для закрытия базы данных.Описание исходного текста приложения
Описание исходного текста приложенияПосле ввода строки с клавиатуры и записи ее в файл через поток наше приложение создает входной буферизованный поток, как это показано ниже:
InStream = new DataInputStream( new BufferedInputStream( new FileInputStream("output.txt")));
Далее для этого потока создается разборщик, который оформлен в отдельном классе TokenizerOfStream, определенном в нашем приложении:
TokenizerOfStream tos = new TokenizerOfStream();
Вслед за этим мы вызываем метод TokenizeIt, определенный в классе TokenizerOfStream, передавая ему в качестве параметра ссылку на входной поток:
tos.TokenizeIt(InStream);
Метод TokenizeIt выполняет разбор входного потока, отображая результаты разбора на консоли. После выполнения разбора входной поток закрывается методом close:
InStream.close();
Самое интересное в нашем приложении связано, очевидно, с классом TokenizerOfStream, поэтому перейдем к его описанию.
В этом классе определен только один метод TokenizeIt:
public void TokenizeIt(InputStream is) { . . . }
Получая в качестве параметра ссылку на входной поток, он прежде всего создает для него разборщик класса StreamTokenizer:
StreamTokenizer stok; stok = new StreamTokenizer(is);
Настройка параметров разборщика очень проста и сводится к вызовам всего двух методов:
stok.slashSlashComments(true); stok.ordinaryChar('.');
Метод slashSlashComments включает режим распознавания комментариев в стиле языка программирования С++, а метод ordinaryChar объявляет символ '.' обычным символом.
После настройки запускается цикл разбора входного потока, причем условием завершения цикла является достижение конца этого потока:
while(stok.nextToken() != StreamTokenizer.TT_EOF) { . . . }
В цикле анализируется содержимое поля ttype, которое зависит от типа элемента, обнаруженного во входном потоке:
switch(stok.ttype) { case StreamTokenizer.TT_WORD: { str = new String("\nTT_WORD >" + stok.sval); break; } case StreamTokenizer.TT_NUMBER: { str = "\nTT_NUMBER >" + Double.toString(stok.nval); break; } case StreamTokenizer.TT_EOL: { str = new String("> End of line"); break; } default: { if((char)stok.ttype == '"') str = new String( "\nTT_WORD >" + stok.sval); else str = "> " + String.valueOf( (char)stok.ttype); } }
На слова и численные значения мы реагируем очень просто - записываем их текстовое представление в рабочую переменную str типа String. При обнаружении конца строки в эту переменную записывается строка End of line.
Если же обнаружен обычный символ, мы сравниваем его с символом кавычки. При совпадении в переменную str записывается содержимое поля sval, в котором находятся слова, обнаруженные внутри кавычек. Если же обнаруженный символ не является символом кавычки, он преобразуется в строку и записывается в переменную str.
В заключении метод выводит строку str в стандартный поток вывода, отображая на консоли выделенный элемент потока:
System.out.println(str);
Определение атрибутов файлов и каталогов
Определение атрибутов файлов и каталоговПосле того как вы создали объект класса File, нетрудно определить атрибуты этого объекта, воспользовавшись соответствующими методами класса File.
Определение длины файла в байтах
Определение длины файла в байтахДлину файла в байтах можно определить с помощью метода length:
public long length();
Определение пути к файлу или каталогу
Определение пути к файлу или каталогуМетод getPath позволяет определить машинно-независимый путь файла или каталога:
public String getPath();
Определение родительского каталога
Определение родительского каталогаЕсли вам нужно определить родительский каталог для объекта класса File, то это можно сделать методом getParent:
public String getParent();
Определение типа объекта - файл или каталог
Определение типа объекта - файл или каталогС помощью методов isDirectory и isFile вы можете проверить, чему соответствует созданный объект класса File - каталогу или файлу:
public boolean isDirectory(); public boolean isFile();
Определение типа указанного пути - абсолютный или относительный
Определение типа указанного пути - абсолютный или относительныйС помощью метода isAbsolute вы можете определить, соответствует ли данный объект класса File файлу или каталогу, заданному абсолютным (полным) путем, либо относительным путем:
public boolean isAbsolute();
Определение времени последней модификации файла или каталога
Определение времени последней модификации файла или каталогаДля определения времени последней модификации файла или каталога вы можете вызвать метод lastModified:
public long lastModified();
Заметим, однако, что этот метод возвращает время в относительных единицах с момента запуска системы, поэтому его удобно использовать только для относительных сравнений.
Переименование файлов и каталогов
Переименование файлов и каталоговДля переименования файла или каталога вы должны создать два объекта класса File, один из которых соответствует старому имени, а второй - новому. Затем для перовго из этих объектов нужно вызвать метод renameTo, указав ему в качестве параметра ссылку на второй объект:
public boolean renameTo(File dest);
В случае успеха метод возвращает значение true, при возникновении ошибки - false. Может также возникать исключение SecurityException.
Поля класса SimpleDBMS
Поля класса SimpleDBMSПоля idx и dat являются объектами класса RandomAccessFile и представляют собой, соответственно, ссылки на файл индекса и файл данных. Поле idxFilePointer типа long используется как рабочее и хранит текущее смещение в файле.
Получение абсолютного пути к каталогу
Получение абсолютного пути к каталогуМетод getAbsolutePath возвращает абсолютный путь к файлу или каталогу, который может быть машинно-зависимым:
public String getAbsolutePath();
Получение имени файла или каталога
Получение имени файла или каталогаМетод getName возвращает имя файла или каталога для заданного объекта класса File (имя выделяется из пути):
public String getName();
Получение списка содержимого каталога
Получение списка содержимого каталогаС помощью метода list вы можете получить список содержимого каталога, соответствующего данному объекту класса File. В классе File предусмотрено два варианта этого метода - без параметра и с параметром:
public String[] list(); public String[] list(FilenameFilter filter);
Первый из этих методв возвращает массив строк с именами содержимого каталога, не включая текущий каталог и родительский каталог. Второй позволяет получить список не всех объектов, хранящихся в каталоге, а только тех, что удовлетворяют условиям, определенным в фильтре filter класса FilenameFilter.
Получение текстового представления объекта
Получение текстового представления объектаМетод toString возвращает текстовую строку, представляющую объект класса File:
public String toString();
Получение значения хэш-кода
Получение значения хэш-кодаМетод hashCode возвращает значение хэш-кода, соответствующего объекту File:
public int hashCode();
Потоки в оперативной памяти
Потоки в оперативной памятиОперационные системы Windows 95 и Windows NT предоставляют возможность для программиста работать с оперативной памятью как с файлом. Это очень удобно во многих случаях. В частности, файлы, отображаемые на память, можно использовать для передачи данных между одновременно работающими задачами и процессами.
При создании приложений и аплетов Java вы также можете работать с объектами оперативной памяти, как с файлами, а точнее говоря, как с потоками. Так как аплетам запрещено обращаться к файлам, расположенным на локальном диске компьютера, при небходимости создания временных потоков ввода или вывода последние могут быть размещены в оперативной памяти.
Ранее мы отмечали, что в библиотеке классов Java есть три класса, специально предназначенных для создания потоков в оперативной памяти. Это классы ByteArrayOutputStream, ByteArrayInputStream и StringBufferInputStream.
Приложение DirectFile
Приложение DirectFileДля иллюстрации способов работы с классом RandomAccessFile мы подготовили приложение DirectFile, в котором создается небольшая база данных. Эта база данных состоит из двух файлов: файла данных и файла индекса.
В файле данных хранятся записи, сосотящие из двух полей - текстового и числового. Текстовое поле с названием name хранит строки, закрытые смиволами конца строки "\r\n", а числовое с названием account - значения типа int.
В меню File нашего приложения есть строки New и View records (Рисунок 5).
Приложение StreamToken
Приложение StreamTokenВ приложении StreamToken мы демонстрируем использование класса StreamTokenizer для разбора входного потока.
Вначале приложение запрашивает у пользователя строку для разбора, записывая ее в файл. Затем этот файл открывается для чтения буферизованным потоком и разбирается на составные элементы. Каждый такой элемент выводится в отдельной строке, как это показано на Рисунок 4.
Принудительный сброс буферов
Принудительный сброс буферовЕще один важный момент связан с буферизованными потоками. Как мы уже говорили, буферизация ускоряет работу приложений с потоками, так как при ее использовании сокращается количество обращений к системе ввода/вывода. Вы можете постепенно в течении дня добавлять в поток данные по одному байту, и только к вечеру эти данные будут физически записаны в файл на диске.
Во многих случаях, однако, приложение должно, не отказываясь совсем от буферизации, выполнять принудительную запись буферов в файл. Это можно сделать с помощью метода flush.
Производные от класса InputStream
Производные от класса InputStreamОт класса InputStream производится много других классов, как это показано на Рисунок 2.
Производные от класса OutputStream
Производные от класса OutputStreamКласс OutputStream предназначен для создания потоков вывода. Приложения, как правило, непосредственно не используют этот класс для операций вывода, так же как и класс InputStream для операций ввода. Вместо этого применяются классы, иерархия которых показана на Рисунок 3.
Произвольный доступ к файлам
Произвольный доступ к файламВ ряде случаев, например, при создании системы управления базой данных, требуется обеспечить произвольный доступ к файлу. Рассмотренные нами ранее потоки ввода и вывода пригодны лишь для последовательного доступа, так как в соответствующих классах нет средств позиционирования внутри файла.
Между тем библиотека классов Java содержит класс RandomAccessFile, который предназначен специально для организации прямого доступа к файлам как для чтения, так и для записи.
В классе RandomAccessFile определено два конструктора, прототипы которых показаны ниже:
public RandomAccessFile( String name, String mode); public RandomAccessFile( File file, String mode);
Первый из них позволяет указывать имя файла, и режим mode, в котором открывается файл. Второй конструктор вместо имени предполагает использование объекта класса File.
Если файл открывается только для чтения, вы должны передать конструктору текстовую строку режима "r". Если же файл открывается и для чтения, и для записи, конструктору передается строка "rw".
Позиционирование внутри файла обеспечивается методом seek, в качестве параметра pos которому передается абсолютное смещение файла:
public void seek(long pos);
После вызова этого метода текущая позиция в файле устанавливается в соответствии со значением параметра pos.
В любой момент времени вы можете определить текущую позицию внутри файла, вызвав метод getFilePointer:
public long getFilePointer();
Еще один метод, который имеет отношение к позиционированию, называется skipBytes:
public int skipBytes(int n);
Он работает так же, как и одноименный метод для потоков - продвигает текущую позицию в файле на заданное количество байт.
С помощью метода close вы должны закрывать файл, после того как работа с им завершена:
public void close();
Метод getFD позволяет получить дескриптор файла:
public final FileDescriptor getFD();
С помощью метода length вы можете определить текущую длину файла:
public long length();
Ряд методов предназначен для выполнения как обычного, так и форматированного ввода из файла. Этот набор аналогичен методам, определенным для потоков:
public int read(); public int read(byte b[]); public int read(byte b[],int off,int len); public final boolean readBoolean(); public final byte readByte(); public final char readChar(); public final double readDouble(); public final float readFloat(); public final void readFully(byte b[]); public final void readFully(byte b[], int off, int len); public final int readInt(); public final String readLine(); public final long readLong(); public final short readShort(); public final int readUnsignedBytee(); public final int readUnsignedShort(); public final String readUTF();
Существуют также методы, позволяющие выполнять обычную или форматированную запись в файл с прямым доступом:
public void write(byte b[]); public void write(byte b[],int off,int len); public void write(int b); public final void writeBoolean(boolean v); public final void writeBytee(int v); public final void writeBytes(String s); public final void writeChar(int v); public final void writeChars(String s); public final void writeDouble(double v); public final void writeFloat(float v); public final void writeInt(int v); public final void writeLong(long v); public final void writeShort(int v); public final void writeUTF(String str);
Имена приведенных методов говорят сами за себя, поэтому мы не будем их описывать.
Просмотр записей базы данных
Просмотр записей базы данныхПри выборе строки View records из меню File приложение открывает файл базы данных:
SimpleDBMS db = new SimpleDBMS( "dbtest.idx", "dbtest.dat");
Затем оно извлекает три записи с номерами 0, 1 и 2, вызывая для этого метод GetRecordByNumber, также определенный в классе SimpleDBMS:
String szRecords;
szRecords = db.GetRecordByNumber(0) + db.GetRecordByNumber(1) + db.GetRecordByNumber(2);
Записи объединяются и сохраняются в переменной szRecords типа String. После этого база данных закрывается:
db.close();
Для отображения содержимого записей мы создаем диалоговую панель на базе определенного нами класса MessageBox:
MessageBox mbox; mbox = new MessageBox(szRecords, this, "Database records", true); mbox.show();
Простейшие методы
Простейшие методыСоздав выходной поток на базе класса FileOutputStream, вы можете использовать для записи в него данных три разновидности метода write, прототипы которых представлены ниже:
public void write(byte b[]); public void write(byte b[], int off, int len); public void write(int b);
Первый из этих методов записывает в поток содержимое массива, ссылка на который передается через параметр, начиная с текущей позиции в потоке. После выполнения записи текущая позиция продвигается вперед на число записанных байт, которое при успешном завершении операции равно длине массива (b.length).
Второй метод позволяет дополнительно указать начальное смещение off записываемого блока данных в массиве и количество записываемых байт len.
Третий метод просто записывает в поток один байт данных.
Если в процессе записи происходит ошибка, возникает исключение IOException.
Для входного потока, созданного на базе класса FileInputStream, определены три разновидности метода read, выполняющего чтение данных:
public int read(); public int read(byte b[]); public int read(byte b[], int off, int len);
Первая разновидность просто читает из потока один байт данных. Если достигнут конец файла, возвращается значение -1.
Вторая разновидность метода read читает данные в массив, причем количество прочитанных данных определяется размером массива. Метод возвращает количество прочитанных байт данных или значение -1, если в процессе чтения был достигнут конец файла.
И, наконец, третий метод позволяет прочитать данные в область массива, заданную своим смещением и длиной.
Если при чтении происходит ошибка, возникает исключение IOException.
Проверка существования файла или каталога
Проверка существования файла или каталогаС помощью метода exists вы можете проверить существование файла или катлога, для которого был создан объект класса File:
public boolean exists();
Этот метод можно применять перед созданием потока на базе класса FileOutputStream, если вам нужно избежать случайной перезаписи существующего файла. В этом случае перед созданием выходного потока класса FileOutputStream следует создать объект класса File, указав конструктору путь к файлу, а затем проверить сущестование файла методом exists.
Проверка возможности чтения и записи
Проверка возможности чтения и записиМетоды canRead и canWrite позволяют проверить возможность чтения из файла и записи в файл, соответственно:
public boolean canRead(); public boolean canWrite();
Их полезно применять перед созданием соответствующих потоков, если нужно избежать возникновение исключений, связанных с попыткой выполнения доступа неразрешенного типа. Если доступ разрешен, эти методы возвращают значение true, а если запрещен - false.
Работа с файлами
Работа с файламиБиблиотека классов языка программирования Java содержит многочисленные средства, предназначенные для работы с файлами. И хотя аплеты не имеют доступа к локальным файлам, расположенным на компьютере пользователя, они могут обращаться к файлам, которые находятся в каталоге сервера Web. Автономные приложения Java могут работать как с локальными, так и с удаленными файлами (через сеть Internet или Intranet).
В любом случае, будете ли вы создавать автономные приложения Java или аплеты, взаимодействующие с сервером Web через сеть, вы должны познакомиться с классами, предназначенными для организации ввода и вывода.
Работа с файлами и каталогами при помощи класса File
Работа с файлами и каталогами при помощи класса FileВ предыдущих разделах мы рассмотрели классы, предназначенные для чтения и записи потоков. Однако часто возникает необходимость выполнения и таких операций, как определение атрибутов файла, создание или удаление каталогов, удаление файлов, получение списка всех файлов в каталоге и так далее. Для выполнения всех этих операций в приложениях Java используется класс с именем File.
Работа со стандартными потоками
Работа со стандартными потокамиПриложению Java доступны три стандратных потока, которые всегда открыты: стандартный поток ввода, стандартный поток вывода и стандартный поток вывода сообщений об ошибках.
Все перечисленные выше потоки определены в классе System как статические поля с именами, соответственно, in, out и err:
public static PrintStream err; public static InputStream in; public static PrintStream out;
Заметим, что стандратные потоки, как правило, не используются аплетами, так как браузеры общаются с пользователем через окно аплета и извещения от мыши и клавиатуры, а не через консоль.
Основные классы для работы с файлами и потоками
Рисунок 1. Основные классы для работы с файлами и потоками
Классы, производные от класса InputStream
Рисунок 2. Классы, производные от класса InputStream
Классы, производные от класса OutputtStream
Рисунок 3. Классы, производные от класса OutputtStream
Рассмотрим кратко назначение этих классов.
Разбор входного потока в приложении StreamToken
Рисунок 4. Разбор входного потока в приложении StreamToken
Обратите внимание, что в процессе разбора значение 3.14 было воспринято как числовое, а 3,14 - нет. Это потому, что при настройке разборщика мы указали, что символ '.' является обычным.
Строки меню File
Рисунок 5. Строки меню File
С помощью строки New вы можете создать базу данных, состоящую из трех записей. Если выбрать из меню File строку View records, на экране появится диалоговая панель с содержимым этих записей (Рисунок 6).
Содержимое трех первых полей базы данных
Рисунок 6. Содержимое трех первых полей базы данных
Вместо символа перевода строки в диалоговой панели отображается маленький квадратик.
Дамп создаваемого файла данных приведен на Рисунок 7.
Дамп файла данных
Рисунок 7. Дамп файла данных
Из этого дампа видно, что после первого запуска приложения в файле данных имеются следующие записи:
| Номер записи | Смещение в файле данных | Поле name | Поле account |
| 0 | 0 | Ivanov | 1000 |
| 1 | 12 | Petrov | 2000 |
| 2 | 24 | Sidoroff | 3000 |
При последующих запусках каждый раз в файл данных будут добавляться приведенные выше записи.
Так как поле name имеет переменную длину, для обеспечения возможности прямого доступа к записи по ее номеру необходимо где-то хранить смещения всех записей. Мы это делаем в файле индексов, дамп которого представлен на Рисунок 8.
Дамп файла индекса
Рисунок 8. Дамп файла индекса
Файл индексов хранит 8-байтовые смещения записей файла данных в формате long. Зная номер записи, можнор легко вычислить смещение в файле индексов, по которому хранится смещение нужной записи в файле данных. Если извлечь это смещение, то можно выполнить позиционирование в файле данных с целью чтения нужной записи, что и делает наше приложение.
Создание базы данных
Создание базы данныхКогда пользователь выбирает из меню File строку New, соответствующий обработчик события создает базу данных, передавая конструктору имена файла индекса dbtest.idx и файла данных dbtest.dat:
SimpleDBMS db = new SimpleDBMS( "dbtest.idx", "dbtest.dat");
После этого с помощью метода AddRecord, определенного в классе SimpleDBMS, в базу добавляются три записи, состоящие из текстового и числового полей:
db.AddRecord("Ivanov", 1000); db.AddRecord("Petrov", 2000); db.AddRecord("Sidoroff", 3000);
После завершения работы с базой данных она закрывается методом close из класса SimpleDBMS:
db.close();
Создание каталогов
Создание каталоговС помощью методов mkdir и mkdirs можно создавать новые каталоги:
public boolean mkdir(); public boolean mkdirs();
Первый из этих методов создает один каталог, второй - все подкаталоги, ведущие к создаваемому каталогу (то есть полный путь).
Создание объекта класса File
Создание объекта класса FileУ вас есть три возможности создать объект класса File, вызвав для этого один из трех конструкторов:
public File(String path); public File(File dir, String name); public File(String path, String name);
Первый из этих конструкторов имеет единственный параметр - ссылку на строку пути к файлу или каталогу. С помощью второго конструктора вы можете указать отдельно каталог dir и имя файла, для которого создается объект в текущем каталоге. И, наконец, третий конструктор позволяет указать полный путь к каталогу и имя файла.
Если первому из перечисленных конструкторов передать ссылку со значением null, возникнет исключение NullPointerException.
Пользоваться конструкторам очень просто. Вот, например, как создать объект класса File для файла c:\autoexec.bat и каталога d:\winnt:
f1 = new File("c:\\autoexec.bat"); f2 = new File("d:\\winnt");
Создание потока для форматированного обмена данными
Создание потока для форматированного обмена даннымиОказывается, создание потоков, связанных с файлами и предназначенных для форматированного ввода или вывода, необходимо выполнять в несколько приемов. При этом вначале необходимо создать потоки на базе класса FileOutputStream или FileInputStream, а затем передать ссылку на созданный поток констркутору класса DataOutputStream или DataInputStream.
В классах FileOutputStream и FileInputStream предусмотрены конструкторы, которым в качестве параметра передается либо ссылка на объект класса File, либо ссылка на объект класса FileDescriptor, либо, наконец, текстовая строка пути к файлу:
public FileOutputStream(File file); public FileOutputStream( FileDescriptor fdObj); public FileOutputStream(String name);
Таким образом, если вам нужен выходной поток для записи форматированных данных, вначале вы создаете поток как объект класса FileOutputStream. Затем ссылку на этот объект следует передать конструктору класса DataOutputStream. Полученный таким образом объект класса DataOutputStream можно использовать как выходной поток, записывая в него форматированные данные.
Создание потоков, связанных с файлами
Создание потоков, связанных с файламиЕсли вам нужно создать входной или выходной поток, связанный с локальным файлом, следует воспользоваться классами из библиотеки Java, созданными на базе классов InputStream и OutputStream. Мы уже кратко рассказывали об этих классах в разделе "". Однако методика использования перечисленных в этом разделе классов может показаться довольно странной.
В чем эта странность?
Говоря кратко, странность заключается в том, что для создания потока вам необходимо воспользоваться сразу несколькими классами, а не одним, наиболее подходящим для решения поставленной задачи, как это можно было бы предположить.
Поясним сказанное на примере.
Пусть, например, нам нужен выходной поток для записи форматированных данных (скажем, текстовых строк класса String). Казалось бы, достаточно создать объект класса DataOutputStream, - и дело сделано. Однако не все так просто.
В классе DataOutputStream предусмотрен только один конструктор, которому в качестве параметра необходимо передать ссылку на объект класса OutputStream:
public DataOutputStream(OutputStream out);
Что же касается конструктора класса OutputStream, то он выглядит следующим образом:
public OutputStream();
Так как ни в том, ни в другом конструкторе не предусмотрено никаких ссылок на файлы, то непонятно, как с использованием только одних классов OutputStream и DataOutputStream можно создать выходной поток, связанный с файлом.
Что же делать?
Сравнение объектов класса File
Сравнение объектов класса FileДля сравнения объектов класса File вы должны использовать метод equals:
public boolean equals(Object obj);
Заметим, что этот метод сравнивает пути к файлам и каталогам, но не сами файли или каталоги.
Стандартные потоки
Стандартные потокиДля работы со стандартными потоками в классе System имеется три статических объекта: System.in, System.out и System.err. По своему назначению эти потоки больше всего напоминают стандартные потоки ввода, вывода и вывода сообщений об ошибках операционной системы MS-DOS.
Поток System.in связан с клавиатурой, поток System.out и System.err - с консолью приложения Java.
Стандартный поток ввода
Стандартный поток вводаСтандартный поток ввода in определен как статический объект класса InputStream, который содержит только простейшие методы для ввода данных. Нужнее всего вам будет метод read:
public int read(byte b[]);
Этот метод читает данные из потока в массив, ссылка на который передается через единственный параметр. Количество считанных данных определяется размером массива, то есть значением b.length.
Метод read возвращает количество прочитанных байт данных или -1, если достигнут конец потока. При возникновении ошибок создается исключение IOException, обработку которого необходимо предусмотреть.
Стандартный поток вывода
Стандартный поток выводаСтандартный поток вывода out создан на базе класса PrintStream, предназначенного, как мы это отмечали раньше, для форматированного вывода данных различного типа с целью их визуального отображения в виде текстовой строки.
Для работы со стандартным потоком вывода вы будете использовать главным образом методы print и println, хотя метод write также доступен.
В классе PrintStream определено несколько реализаций метода print с параметрами различных типов:
public void print(boolean b); public void print(char c); public void print(char s[]); public void print(double d); public void print(float f); public void print(int i); public void print(long l); public void print(Object obj); public void print(String s);
Как видите, вы можете записать в стандартный поток вывода текстовое представление данных различного типа, в том числе и класса Object.
Метод println аналогичен методу print, отличаясь лишь тем, что он добавляет к записываемой в поток строке символ перехода на следующую строку:
public void println(); public void println(boolean b); public void println(char c); public void println(char s[]); public void println(double d); public void println(float f); public void println(int i); public void println(long l); public void println(Object obj); public void println(String s);
Реализация метода println без параметров записывает только символ перехода на следующую строку.
Стандртный поток вывода сообщений об ошибках
Стандртный поток вывода сообщений об ошибкахСтандртный поток вывода сообщений об ошибках err так же, как и стадартный поток вывода out, создан на базе класса PrintStream. Поэтому для записи сообщений об ошибках вы можете использовать только что описанные методы print и println.
Удаление файлов и каталогов
Удаление файлов и каталоговДля удаления ненужного файла или каталога вы должны создать соответствующий объект File и затем вызвать метод delete:
public boolean delete();
Закрывание потоков
Закрывание потоковРаботая с файлами в среде MS-DOS или Windows средствами языка программирования С вы должны были закрывать ненужные более файлы. Так как в системе интерпертации приложений Java есть процесс сборки мусора, возникает вопрос - выполняет ли он автоматическое закрывание потоков, с которыми приложение завершило работу?
Оказывается, процесс сборки мусора не делает ничего подобного!
Сборка мусора выполняется только для объектов, размещенных в оперативной памяти. Потоки вы должны закрывать явным образом, вызывая для этого метод close.
Запись данных в поток и чтение данных из потока
Запись данных в поток и чтение данных из потокаДля обмена данными с потоками можно использовать как простейшие методы write и read, так и методы, допускающие ввод или вывод форматированных данных. В зависимости от того, на базе какого класса создан поток, зависит набор доступных методов, предназначенных для чтения или записи данных.
Классы Java для работы с потоками
Адрес IP и класс InetAddress
Адрес IP и класс InetAddressПрежде чем начинать создание сетевых приложений для Internet, вы должны разобраться с адресацией компьютеров в сети с протоколом TCP/IP, на базе которого построена сеть Internet. Здесь мы приведем самые необходимые для этого сведения.
Все компьютеры, подключенные к сети TCP/IP, называются узлами (в оригинальной терминологии узел - это host). Каждый узел имеет в сети свой адрес IP, состоящий из четырех десятичных цифр в диапазоне от 0 до 255, разделенных символом "точка ", например:
193.120.54.200
Фактически адрес IP является 32-разрядным двоичным числом. Упомянутые числа представляют собой отдельные байты адеса IP.
Так как работать с цифрами удобно лишь компьютеру, была придумана система доменных имен. При использовании этой системы адресам IP ставится в соответсвие так называемый доменный адрес, такой как, например, www.sun.com.
В сети Internet имеется распределенная по всему миру база доменных имен, в которой установлено соответствие между доменными именами и адресами IP в виде четырех чисел.
Для работы с адресами IP в библиотеке классов Java имеется класс InetAddress, определение наиболее интересных методов которого приведено ниже:
public static InetAddress getLocalHost(); public static InetAddress getByName(String host); public static InetAddress[] getAllByName(String host); public byte[] getAddress(); public String toString(); public String getHostName(); public boolean equals(Object obj);
Рассмотрим применение этих методов.
Чтобы работать с адресами IP, прежде всего вы должны создать объект класса InetAddress. Эта процедура выполняется не с помощью оператора new, а с применением статических методов getLocalHost, getByName и getAllByName.
Аплет Form
Аплет FormНа примере аплета Form мы покажем, как приложения Java могут взаимодействовать с расширениями сервера Web, такими как программы CGI или приложения ISAPI.
В окне нашего аплета находится форма, содержащая два однострочных поля редактирования, кнопку и многострочное поле редактирования (Рисунок 5).
Аплет ShowChart
Аплет ShowChartПопробуем теперь на практике применить технологию передачи файлов из каталога сервера Web в аплет для локальной обработки. Наше следующее приложение с названием ShowChart получает небольшой текстовый файл с исходными данными для построения круговой диаграммы, содержимое которого представлено ниже:
10,20,5,35,11,10,3,6,80,10,20,5,35,11,10,3,6,80
В этом файле находятся численные значения углов для отдельных секторов диаграммы, причем сумма этих значений равна 360 градусам. Наш аплет принимает этот файл через сеть и рисует круговую диаграмму, показанную на Рисунок 2.
Инициализация клиента
Инициализация клиентаПроцесс инициализации клиентского приложения выглядит весьма просто. Клиент должен просто создать сокет как объект класса Socket, указав адрес IP серверного приложения и номер порта, используемого сервером:
Socket s; s = new Socket("localhost",9999);
Здесь в качестве адреса IP мы указали специальный адрес localhost, предназначенный для тестирования сетевых приложений, а в качестве номера порта - ззначение 9999, использованное сервером.
Теперь можно создавать входной и выходной потоки. На стороне клиента эта операция выполняется точно также, как и на стороне сервера:
InputStream is; OutputStream os; is = s.getInputStream(); os = s.getOutputStream();
Инициализация сервера
Инициализация сервераВначале мы рассмотрим действия приложения, которое на момент инициализации является сервером.
Первое, что должно сделать серверное приложение, это создать объект класса ServerSocket, указав конструктору этого класса номер используемого порта:
ServerSocket ss; ss = new ServerSocket(9999);
Заметим, что объект класса ServerSocket вовсе не является сокетом. Он предназначен всего лишь для установки канала связи с клиентским приложением, после чего создается сокет класса Socket, пригодный для передачи данных.
Установка канала связи с клиентским приложением выполняется при помощи метода accept, определенного в классе ServerSocket:
Socket s; s = ss.accept();
Метод accept приостанавливает работу вызвавшего потока до тех пор, пока клиентское приложение не установит канал связи с сервером. Если ваше приложение однопоточное, его работа будет блокирована до момента установки канала связи. Избежать полной блокировки приложения можно, если выполнять создание канала передачи данных в отдельном потоке.
Как только канал будет создан, вы можете использовать сокет сервера для образования входного и выходного потока класса InputStream и OutputStream, соответственно:
InputStream is; OutputStream os; is = s.getInputStream(); os = s.getOutputStream();
Эти потоки можно использовать таким же образом, что и потоки, связанные с файлами.
Обратите также внимание на то, что при создании серверного сокета мы не указали адрес IP и тип сокета, ограничившись только номером порта.
Что касается адреса IP, то он, очевидно, равен адресу IP узла, на котором запущено приложение сервера. В классе ServerSocket определен метод getInetAddress, позволяющий определить этот адрес:
public InetAddress getInetAddress();
Тип сокета указывать не нужно, так как для работы с датаграммными сокетами предназначен класс DatagramSocket, который мы рассмотрим позже.
Исходные тексты аплета Form
Исходные тексты аплета Form представлены в листинге 5.Исходные тексты аплета ShowChart
Исходный текст приложения ShowChart приведен в листинге 1.Исходный текст клиентского приложения SocketClient
Исходный текст клиентского приложения SocketClientИсходный текст клиентского приложения SocketClient приведен в листинге4.
Исходный текст программы CGI store.exe
Исходный текст программы CGI store.exe очень прост и показан в листинге 7.Исходный текст серверного приложения SocketServ
Исходный текст серверного приложения SocketServИсходный текст серверного приложения SocketServ приведен в листинге3.
Использование датаграммных сокетов
Использование датаграммных сокетовКак мы уже говорили, датаграммные сокеты не гарантируют доставку пакетов данных. Тем не менее, они работают быстрее потоковых и обеспечивают возможность широковещательной расслыки пакетов данных одновременно всем узлам сети. Последняя возможность используется не очень широко в сети Internet, однако в корпоративной сети Intranet вы вполне можете ей воспользоваться.
Для работы с датаграммными сокетами приложение должно создать сокет на базе класса DatagramSocket, а также подготовить объект класса DatagramPacket, в который будет записан принятый от партнера по сети блок данных.
Канал, а также входные и выходные потоки создавать не нужно. Данные передаются и принимаются методами send и receive, определенными в классе DatagramSocket.
Класс DatagramPacket
Класс DatagramPacketПеред тем как принимать или передавать данные с использованием методов receive и send вы должны подготовить объекты класса DatagramPacket. Метод receive запишет в такой объект принятые данные, а метод send - перешлет данные из объекта класса DatagramPacket узлу, адрес которого указан в пакете.
Подготовка объекта класса DatagramPacket для приема пакетов выполняется с помощью следующего конструктора:
public DatagramPacket(byte ibuf[], int ilength);
Этому конструктору передается ссылка на массив ibuf, в который нужно будет записать данные, и размер этого массива ilength.
Если вам нужно подготовить пакет для передачи, воспользуйтесь конструктором, который дополнительно позволяет задать адрес IP iaddr и номер порта iport узла назначения:
public DatagramPacket(byte ibuf[], int ilength, InetAddress iaddr, int iport);
Таким образом, информация о том, в какой узел и на какой порт необходимо доставить пакет данных, хранится не в сокете, а в пакете, то есть в объекте класса DatagramPacket.
Помимо только что описанных конструкторов, в классе DatagramPacket определены четыре метода, позволяющие получить данные и информацию об адресе узла, из которого пришел пакет, или для которого предназначен пакет.
Метод getData возвращает ссылку на массив данных пакета:
public byte[] getData();
Размер пакета, данные из которого хранятся в этом массиве, легко определить с помощью метода getLength:
public int getLength();
Методы getAddress и getPort позволяют определить адрес и номер порта узла, откуда пришел пакет, или узла, для которого предназначен пакет:
public InetAddress getAddress(); public int getPort();
Если вы создаете клиент-серверную систему, в которой сервер имеет заранее известный адрес и номер порта, а клиенты - произвольные адреса и различные номера портов, то после получения пакета от клиента сервер может определить с помощью методов getAddress и getPort адрес клиента для установления с ним связи.
Если же адрес сервера неизвестен, клиент может посылать широковещательные пакеты, указав в объекте класса DatagramPacket адрес сети. Такая методика обычно используется в локальных сетях.
Как указать адрес сети?
Напомним, что адрес IP состоит из двух частей - адреса сети и адреса узла. Для разделения компонент 32-разрядного адреса IP используется 32-разрядная маска, в которой битам адреса сети соответствуют единицы, а битам адреса узла - нули.
Например, адрес узла может быть указан как 193.24.111.2. Исходя из значения старшего байта адреса, это сеть класса С, для которой по умолчанию используется маска 255.255.255.0. Следовательно, адрес сети будет такой: 193.24.111.0.
Класс DatagramSocket
Класс DatagramSocketРассмотрим конструкторы и методы класса DatagramSocket, предназначенного для создания и использования датаграммных сокетов.
В классе DatagramSocket определены два конструктора, прототипы которых представлены ниже:
public DatagramSocket(int port); public DatagramSocket();
Первый из этих конструкторов позволяет определить порт для сокета, второй предполагает использование любого свободного порта.
Обычно серверные приложения работают с использованием какого-то заранее определенного порта, номер которого известен клиентским приложениям. Поэтому для серверных приложений больше подходит первый из приведенных выше конструкторов.
Клиентские приложения, напротив, часто применяют любые свободные на локальном узле порты, поэтому для них годится конструктор без параметров.
Кстати, с помощью метода getLocalPort приложение всегда может узнать номер порта, закрепленного за данным сокетом:
public int getLocalPort();
Прием и передача данных на датаграммном сокете выполняется с помощью методов receive и send, соответственно:
public void receive(DatagramPacket p); public void send(DatagramPacket p);
В качестве параметра этим методам передается ссылка на пакет данных (соответственно, принимаемый и передаваемый), определенный как объект класса DatagramPacket. Этот класс будет рассмотрен позже.
Еще один метод в классе DatagramSocket, которым вы будете пользоваться, это метод close, предназначенный для закрытия сокета:
public void close();
Напомним, что сборка мусора в Java выполняется только для объектов, находящихся в оперативной памяти. Такие объекты, как потоки и сокеты, вы должны закрывать после использования самостоятельно.
Класс Socket
Класс SocketПосле краткого введения в сокеты приведем описание наиболее интересных конструкторов и методов класса Socket.
Класс URL в библиотеке классов Java
Класс URL в библиотеке классов JavaДля работы с ресурсами, заданными своими адресами URL, в библиотеке классов Java имеется очень удобный и мощный класс с названием URL. Простота создания сетевых приложений с использованием этого класса в значительной степени опровергает общераспространенное убеждение в сложности сетевого программирования. Инкапсулируя в себе достаточно сложные процедуры, класс URL предоставляет в распоряжение программиста небольшой набор простых в использовании конструкторов и методов.
Класс URLConnection
Класс URLConnectionНапомним, что в классе URL, рассмотренном нами в начале этой главы, мы привели прототип метода openConnection, возвращающий для заданного объекта класса URL ссылку на объект URLConnection:
public URLConnection openConnection();
Что мы можем получить, имея ссылку на этот объект?
Прежде всего, пользуясь этой ссылкой, мы можем получить содержимое объекта, адресуемое соответствующим объектом URL, методом getContent:
public Object getContent();
Заметим, что метод с таким же названием есть и в классе URL. Поэтому если все, что вы хотите сделать, это получение содержимое файла, адресуемое объектом класса URL, то нет никакой необходимости обращаться к классу URLConnection.
Метод getInputStream позволяет открыть входной поток данных, с помощью которого можно считать файл или получить данные от расширения сервера Web:
public InputStream getInputStream();
В классе URLConnection определен также метод getOutputStream, позволяющий открыть выходной поток данных:
public OutputStream getOutputStream();
Не следует думать, что этот поток можно использовать для записи файлов в каталоги сервера Web. Однако для этого потока есть лучшее применение - с его помощью можно передать данные расширению сервера Web.
Рассмотрим еще несколько полезных методов, определенных в классе URLConnection.
Метод connect предназначен для установки соединения с объектом, на который ссылается объект класса URL:
public abstract void connect();
Перед установкой соединения приложение может установить различные параметры соединения. Некоторые из методов, предназначенных для этого, приведены ниже:
Включение или отключение кэширования по умолчанию
public void setDefaultUseCaches( boolean defaultusecaches);
Включение или отключение кэширования
public void setUseCaches(boolean usecaches);
Возможность использования потока для ввода
public void setDoInput(boolean doinput);
Возможность использования потока для вывода
public void setDoOutput(boolean dooutput);
Установка даты модификации документа
public void setIfModifiedSince( long ifmodifiedsince);
В классе URLConnection есть методы, позволяющие определить значения параметров, установленных только что описанными методами:
public boolean getDefaultUseCaches(); public boolean getUseCaches(); public boolean getDoInput(); public boolean getDoOutput(); public long getIfModifiedSince();
Определенный интерес могут представлять методы, предназначенные для извлечения информации из заголовка протокола HTTP:
Метод возвращает содержимое заголовка content-encoding (кодировка ресурса, на который ссылается URL)
public String getContentEncoding();
Метод возвращает содержимое заголовка content-length (размер документа)
public int getContentLength();
Метод возвращает содержимое заголовка content-type (тип содержимого)
public String getContentType();
Метод возвращает содержимое заголовка date (дата посылки ресурса в секундах с 1 января 1970 года)
public long getDate();
Метод возвращает содержимое заголовка last-modified (дата изменения ресурса в секундах с 1 января 1970 года)
public long getLastModified();
Метод возвращает содержимое заголовка expires (дата устаревания ресурса в секундах с 1 января 1970 года)
public long getExpiration();
Другие методы, определенные в классе URLConnection, позволяют получить все заголовки или заголовки с заданным номером, а также другую информацию о соединении. При необходимости вы найдете описание этих методов в справочной системе Java WorkShop.
Конструкторы класса Socket
Конструкторы класса SocketЧаще всего для создания сокетов в клиентских приложениях вы будете использовать один из двух конструкторов, прототипы которых приведены ниже:
public Socket(String host,int port); public Socket(InetAddress address,int port);
Первый из этих конструкторов позволяет указывать адрес серверного узла в виде текстовой строки, второй - в виде ссылки на объект класса InetAddress. Вторым параметром задается номер порта, с использованием которого будут передаваться данные.
В классе Socket определена еще одна пара конструкторов, которая, однако не рекомендуется для использования:
public Socket(String host, int port, boolean stream); public Socket(InetAddress address, int port, boolean stream);
В этих конструкторах последний параметр определяет тип сокета. Если этот параметр равен true, создается потоковый сокет, а если false - датаграммный. Заметим, что для работы с датаграммными сокетами следует использовать класс DatagramSocket.
Конструкторы класса URL
Конструкторы класса URLСначала о конструкторах. Их в классе URL имеется четыре штуки.
public URL(String spec);
Первый из них создает объект URL для сетевого ресурса, адрес URL которого передается конструктору в виде текстовой строки через единственный параметр spec:
public URL(String spec);
В процессе создания объекта проверяется заданный адрес URL, а также наличие указанного в нем ресурса. Если адрес указан неверно или заданный в нем ресурс отсутствует, возникает исключение MalformedURLException. Это же исключение возникает при попытке использовать протокол, с которым данная система не может работать.
Второй вариант конструктора класса URL допускает раздельное указание протокола, адреса узла, номера порта, а также имя файла:
public URL(String protocol, String host, int port, String file);
Третий вариант предполагает использование номера порта, принятого по умолчанию:
public URL(String protocol, String host, String file);
Для протокола HTTP это порт с номером 80.
И, наконец, четвертый вариант конструктора допускает указание контекста адреса URL и строки адреса URL:
public URL(URL context, String spec);
Строка контекста позволяет указывать компоненты адреса URL, отсустсвующие в строке spec, такие как протокол, имя узла, файла или номер порта.
public class ShowChart extends Applet
Листинг 1. Файл ShowChart.java
import java.applet.*; import java.awt.*; import java.net.*; import java.io.*; import java.util.*;
public class ShowChart extends Applet { URL SrcURL; Object URLContent; int errno = 0; String str; byte buf[] = new byte[200];
public String getAppletInfo() { return "Name: ShowChart"; }
public void init() { try { SrcURL = new URL( "http://frolov/chart.txt");
try { InputStream is = SrcURL.openStream();
is.read(buf);
str = new String(buf, 0);
} catch (IOException ioe) { showStatus("read exception");
errno = 1; } } catch (MalformedURLException uex) { showStatus( "MalformedURLException exception");
errno = 2; } }
public void paint(Graphics g) { Integer AngleFromChart = new Integer(0);
int PrevAngle = 0; int rColor, gColor, bColor; Dimension dimAppWndDimension = getSize();
g.setColor(Color.yellow);
g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
g.setColor(Color.black);
g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
showStatus(str);
StringTokenizer st = new StringTokenizer(str, ",\r\n");
while(st.hasMoreElements()) { rColor = (int)(255 * Math.random());
gColor = (int)(255 * Math.random());
bColor = (int)(255 * Math.random());
g.setColor(new Color(rColor, gColor, bColor));
String angle = (String)st.nextElement();
AngleFromChart = new Integer(angle) ; g.fillArc(0, 0, 200, 200, PrevAngle, AngleFromChart.intValue());
PrevAngle += AngleFromChart.intValue();
} } }
Исходный текст документа HTML, созданного автоматически для нашего аплета, представлен в листинге 2.
enabled browser, you would see
Листинг 2. Файл ShowChart.tmp.html
ServerSocket ss; Socket s; InputStream
Листинг 3. Файл SocketServ.java
import java.io.*; import java.net.*; import java.util.*;
public class SocketServ { public static void main(String args[]) { byte bKbdInput[] = new byte[256]; ServerSocket ss; Socket s; InputStream is; OutputStream os;
try { System.out.println( "Socket Server Application");
} catch(Exception ioe) { System.out.println(ioe.toString());
}
try { ss = new ServerSocket(9999);
s = ss.accept();
is = s.getInputStream();
os = s.getOutputStream();
byte buf[] = new byte[512]; int lenght;
while(true) { lenght = is.read(buf);
if(lenght == -1) break;
String str = new String(buf, 0);
StringTokenizer st; st = new StringTokenizer( str, "\r\n");
str = new String( (String)st.nextElement());
System.out.println(">
" + str);
os.write(buf, 0, lenght);
os.flush();
}
is.close();
os.close();
s.close();
ss.close();
} catch(Exception ioe) { System.out.println(ioe.toString());
}
try { System.out.println( "Press
to terminate application...");
System.in.read(bKbdInput);
} catch(Exception ioe) { System.out.println(ioe.toString());
} } }
Socket s; InputStream is; OutputStream
Листинг 4. Файл SocketClient.java
import java.io.*; import java.net.*; import java.util.*;
public class SocketClient { public static void main(String args[]) { byte bKbdInput[] = new byte[256]; Socket s; InputStream is; OutputStream os;
try { System.out.println( "Socket Client Application" + "\nEnter any string or" + " 'quit' to exit...");
} catch(Exception ioe) { System.out.println(ioe.toString());
}
try { s = new Socket("localhost",9999);
is = s.getInputStream();
os = s.getOutputStream();
byte buf[] = new byte[512]; int length; String str;
while(true) { length = System.in.read(bKbdInput);
if(length != 1) { str = new String(bKbdInput, 0);
StringTokenizer st; st = new StringTokenizer( str, "\r\n");
str = new String( (String)st.nextElement());
System.out.println(">
" + str);
os.write(bKbdInput, 0, length);
os.flush();
length = is.read(buf);
if(length == -1) break;
str = new String(buf, 0);
st = new StringTokenizer( str, "\r\n");
str = new String( (String)st.nextElement());
System.out.println(">
>
" + str);
if(str.equals("quit")) break; } } is.close();
os.close();
s.close();
} catch(Exception ioe) { System.out.println(ioe.toString());
}
try { System.out.println( "Press
to " + "terminate application...");
System.in.read(bKbdInput);
} catch(Exception ioe) { System.out.println(ioe.toString());
} } }
public class Form extends Applet
Листинг 5. Файл Form.java
import java.applet.*; import java.awt.*; import java.net.*; import java.io.*; import java.util.*;
public class Form extends Applet implements Runnable { private Thread m_store = null;
TextField txtName; TextField txtEMail; TextArea txta; Button btnGetText;
public void init() { Label lbName; Label lbEMail; Label lbPress;
lbName = new Label("Enter your name:");
lbEMail = new Label( "Enter your E-Mail address:");
add(lbName);
txtName = new TextField("Your name", 40);
add(txtName);
add(lbEMail);
txtEMail = new TextField("your@email", 40);
add(txtEMail);
btnGetText = new Button("Send!");
add(btnGetText);
txta = new TextArea(8, 65);
add(txta);
setBackground(Color.yellow);
}
public void paint(Graphics g) { setBackground(Color.yellow);
Dimension dimAppWndDimension = getSize();
g.setColor(Color.black);
g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
}
public boolean action(Event evt, Object obj) { Button btn;
if(evt.target instanceof Button) { btn = (Button)evt.target;
if(evt.target.equals(btnGetText)) { startTransaction();
} else return false; return true; } return false; }
void startTransaction() { m_store = new Thread(this);
m_store.start();
}
public void stop() { if (m_store != null) { m_store.stop();
m_store = null; } }
public void run() { URL u; URLConnection c; PrintStream ps; DataInputStream is;
try { String szSourceStr = txtName.getText() + ", " + txtEMail.getText();
String szReceived; String szURL = "http://frolov/scripts/store.exe";
u = new URL(szURL);
c = u.openConnection();
ps = new PrintStream( c.getOutputStream());
ps.println(szSourceStr);
ps.close();
is = new DataInputStream( c.getInputStream());
szReceived = is.readLine();
is.close();
txta.appendText(szReceived + "\r\n");
repaint();
} catch (Exception ioe) { showStatus(ioe.toString());
stop();
} } }
Исходный текст документа HTML, который был подготовлен для нас системой Java Workshop, мы немного отредактировали, изменив параметр CODEBASE (листинг 6).
enabled browser, you would see
Листинг 6. Файл Form.tmp.html
В этом параметре следует указать путь к каталогу, в котором располагается байт-код аплета.
Этот текст подготовлен для работы
Листинг 7. Файл store.c
#include
#include
#include
#include
#include
#include
void main(int argc, char *argv[]) { int nInDatasize; char * szMethod; char szBuf[2000];
FILE *fDatabase; CRITICAL_SECTION csAddRecord;
szMethod = getenv("REQUEST_METHOD");
if(!strcmp(szMethod, "POST"));
{ nInDatasize = atoi( getenv("CONTENT_LENGTH"));
fread(szBuf, nInDatasize, 1, stdin);
szBuf[nInDatasize] = '\0';
InitializeCriticalSection(&csAddRecord);
EnterCriticalSection(&csAddRecord);
fDatabase = fopen("c:\\EMAIL.DAT", "a+");
if(fDatabase != NULL) { fputs(szBuf, fDatabase);
fclose(fDatabase);
}
LeaveCriticalSection(&csAddRecord);
DeleteCriticalSection(&csAddRecord);
printf( "Content-type: text/plain\r\n\r\n");
printf("Stored information: %s", szBuf);
} }
Этот текст подготовлен для работы в среде Windows 95 или Windows NT, так как для синхронизации доступа к файлу мы использовали специфические для этих операционных систем функции работы с критическими секциями.
Свою работу программа CGI начинает с анализа переменной среды REQUEST_METHOD. Убедившись, что при запуске программы ей передали данные методом POST, программа определяет размер этих данных исходя из содержимого переменной среды CONTENT_LENGTH.
Далее программа считывает соответствующее количество байт данных из стандартного потока ввода, записывает их в файл. Затем, после добавления заголовка "Stored information:", программа CGI записывает полученную строку в стандартный выходной поток, передавая ее таким образом аплету Form.
Так как при реальной работе в сети Internet вашу программу CGI могут одновременно запустить несколько пользователей, для синхронизации обновления файла базы данных мы применили критическую секцию. В результате с файлом может работать в любой момент времени только одна копия программы CGI.
Еще одно замечание касается пути к файлу, который в нашем случае создается в корневом каталоге диска C:. При установке программы CGI на сервер вам необходимо обеспечить доступ на запись к каталогу, в котором располагается файл, для удаленных пользователей. О том, как это сделать, вы можете узнать из документации на ваш сервер Web.
Метод equals
Метод equalsВы можете использовать метод equals для определения идентичности адресов URL, заданных двумя объектами класса URL:
public boolean equals(Object obj);
Если адреса URL идентичны, метод equals возвращает значение true, если нет - значение false.
Метод getContent
Метод getContentОчень интересен метод getConten. Этот метод определяет и получает содержимое сетевого ресурса, для которого создан объект URL:
public final Object getContent();
Практически вы можете использовать метод getContent для получения текстовых файлов, расположенных в сетевых каталогах.
К сожалению, данный метод непригоден для получения документов HTML, так как для данного ресурса не определен обработчик соедржимого, предназначенный для создания объекта. Метод getContent не способен создать объект ни из чего другого, кроме текстового файла.
Данная проблема, тем не менее, решается очень просто - достаточно вместо метода getContent использовать описанную выше комбинацию методов openStream из класса URL и read из класса InputStream.
Метод getFile
Метод getFileМетод getFile позволяет получить информацию о файле, связанном с данным объектом URL:
public String getFile();
Метод getHost
Метод getHostС помощью метода getHost вы можете определить имя узла, соответствующего данному объекту URL:
public String getHost();
Метод getPort
Метод getPortМетод getPortt предназначен для определения номера порта, на котором выполняется связь для объекта URL:
public int getPort();
Метод getProtocol
Метод getProtocolС помощью метода getProtocol вы можете определить протокол, с использованием которого установлено соединение с ресурсом, заданным объектом URL:
public String getProtocol();
Метод getRef
Метод getRefМетод getRef возвращает текстовую строку ссылки на ресурс, соответствующий данному объекту URL:
public String getRef();
Метод hashCode
Метод hashCodeМетод hashCode возвращает хэш-код объекта URL:
public int hashCode();
Метод init
Метод initВо время инициализации метод init создает объект класса URL для файла исходных данных:
SrcURL = new URL("http://frolov/chart.txt");
Здесь для упрощения исходного текста мы указали адрес URL файла данных непосредственно в программе, однако вы можете передать этот адрес аплету через параметр в документе HTML.
Далее для нашего объекта URL мы создаем поток ввода и получаем содержимое файла (то есть исходные данные для построения диаграммы):
InputStream is = SrcURL.openStream(); is.read(buf);
Принятые данные записываются в буфер buf и затем преобразуются к типу String с помощью соответствующего конструктора:
str = new String(buf, 0);
Если при создании объекта класса URL возникло исключение, метод init записывает в поле errno код ошибки, равный 2, записывая при этом в строку состояния браузера сообщение "MalformedURLException exception".
В том случае, когда объект класса URL создан успешно, а исключение возникло в процессе чтения содержимого файла, в поле errno записывается значение 1, а в строку состояния браузера - сообщение "read exception".
Метод openConnection
Метод openConnectionМетод openConnection предназначен для создания канала между приложением и сетевым ресурсом, представленным объектом класса URL:
public URLConnection openConnection();
Если вы создаете приложение, которое позволяет читать из каталогов сервера Web текстовые или двоичные файлы, можно создать поток методом openStream или получить содержимое текстового ресурса методом getContent.
Однако есть и другая возможность. Вначале вы можете создать канал, как объект класса URLConnection, вызвав метод openConnection, а затем создать для этого канала входной поток, воспользовавшись методом getInputStream, определенным в классе URLConnection. Такая методика позволяет определить или установить перед созданием потока некоторые характеристики канала, например, задать кэширование.
Однако самая интересная возможность, которую предоставляет этот метод, заключается в организации взаимодействия приложения Java и сервера Web.
Метод openStream
Метод openStreamМетод openStream позволяет создать входной поток для чтения файла ресурса, связанного с созданным объектом класса URL:
public final InputStream openStream();
Для выполнения операции чтения из созданного таким образом потока вы можете использовать метод read, определенный в классе InputStream (любую из его разновидностей).
Данную пару методов (openStream из класса URL и read из класса InputStream) можно применить для решения задачи получения содержимого двоичного или текстового файла, хранящегося в одном из каталогов сервера Web. Сделав это, обычное приложение Java или аплет может выполнить локальную обработку полученного файла на компьютере удаленного пользователя.
Метод paint
Метод paintПосле раскрашивания фона окна аплета и рисования вокруг него рамки метод paint приступает к построению круговой диаграммы. Принятые данные отображаются в строке состояния браузера:
showStatus(sChart);
Далее создается разборщик строки исходных данных:
StringTokenizer st = new StringTokenizer(sChart, ",\r\n");
В качестве разделителей для этого разборщика указывается запятая, символ возврата каретки и перевода строки.
Рисование секторов диаграммы выполняется в цикле, условием выхода из которого является завершение разбора строки исходных данных:
while(st.hasMoreElements()) { . . . }
Для того чтобы секторы диаграммы не сливались, они должны иметь разный цвет. Цвет сектора можно было бы передавать вместе со значением угла через файл исходных данных, однако мы применили более простой способ раскаршивания секторов - в случайные цвета. Мы получаем случайные компоненты цвета сектора, а затем выбираем цвет в контекст отображения:
rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random()); g.setColor(new Color(rColor, gColor, bColor));
С помощью метода nextElement мы получаем очередное значение угла сектора и сохраняем его в переменной angle:
String angle = (String)st.nextElement();
Далее с помощью конструктора класса Integer это значение преобразуется в численное:
AngleFromChart = new Integer(angle);
Рисование сектора круговой диаграммы выполняется с помощью метода fillArc:
g.fillArc(0, 0, 200, 200, PrevAngle, AngleFromChart.intValue());
В качестве начального значения угла сектора используется значение из переменной PrevAngle. Сразу после инициализации в эту переменную записывается нулевое значение.
Конечный угол сектора задается как AngleFromChart.intValue(), то есть указывается значение, полученное из принятого по сети файла исходных данных.
После завершения рисования очередного сектора круговой диаграммы начальное значение PrevAngle увеличивается на величину угла нарисованного сектора:
PrevAngle += AngleFromChart.intValue();
Метод sameFile
Метод sameFileС помощью метода sameFile вы можете определить, ссылаются ли два объекта класса URL на один и тот же ресурс, или нет:
public boolean sameFile(URL other);
Если объекты ссылаются на один и тот же ресурс, метод sameFile возвращает значение true, если нет - false.
Метод toExternalForm
Метод toExternalFormМетод toExternalForm возвращает текстовую строку внешнего представления адреса URL, определенного данным объектом класса URL:
public String toExternalForm();
Метод toString
Метод toStringМетод toString возвращает текстовую строку, представляющую данный объект класса URL:
public String toString();
Методы класса Socket
Методы класса SocketПеречислим наиболее интересные, на наш взгляд, методы класса Socket.
Прежде всего, это методы getInputStream и getOutputStream, предназначенные для создания входного и выходного потока, соответственно:
public InputStream getInputStream(); public OutputStream getOutputStream();
Эти потоки связаны с сокетом и должны быть использованы для передачи данных по каналу связи.
Методы getInetAddress и getPort позволяют определить адрес IP и номер порта, связанные с данным сокетом (для удаленного узла):
public InetAddress getInetAddress(); public int getPort();
Метод getLocalPort возвращает для данного сокета номер локального порта:
public int getLocalPort();
После того как работа с сокетом завершена, его необходимо закрыть методом close:
public void close();
И, наконец, метод toString возвращает текстовую строку, представляющую сокет:
public String toString();
Методы класса URL
Методы класса URLРассмотрим самые интересные методы, определенные в классе URL.
Описание исходного текста аплета ShowChart
Описание исходного текста аплета ShowChartАплет ShowChart получает содержимое файла исходных данных для построения круговой диаграммы с помощью класса URL. Как вы увидите, для получения содержимого этого файла оно создает поток ввода явным образом.
Описание исходного текста клиентского приложения SocketClient
Описание исходного текста клиентского приложения SocketClientВнутри метода main клиентского приложения SocketClient определены переменные для ввода строки с клавиатуры (массив bKbdInput), сокет s класса Socket для работы с сервером SocketServ, входной поток is и выходной поток os, которые связаны с сокетом s.
После вывода на консоль приглашающей строки клиентское приложение создает сокет, вызывая конструктор класса Socket:
s = new Socket("localhost",9999);
В процессе отладки мы запускали сервер и клиент на одном и том же узле, поэтому в качестве адреса сервера указана строка "localhost". Номер порта сервера SocketServ равен 9999, поэтому мы и передали конструктору это значение.
После создания сокета наше клиентское приложение создает входной и выходной потоки, связанные с этим сокетом:
is = s.getInputStream(); os = s.getOutputStream();
Теперь клиентское приложение готово обмениваться данными с сервером.
Этот обмен выполняется в цикле, условием завершения которого является ввод пользователем строки "quit".
Внутри цикла приложение читает строку с клавиатуры, записывая ее в массив bKbdInput:
length = System.in.read(bKbdInput);
Количество введенных символов сохраняется в переменной length.
Далее если пользователь ввел строку, а не просто нажал на клавишу
os.write(bKbdInput, 0, length); os.flush();
Сразу после передачи сбрасывается буфер выходного потока.
Далее приложение читает ответ, посылаемый сервером, в буфер buf:
length = is.read(buf);
Напомним, что наш сервер посылает клиенту принятую строку в неизменном виде.
Если сервер закрыл канал, то метод read возвращает значение -1. В этом случае мы прерываем цикл ввода и передачи строк:
if(length == -1) break;
Если же ответ сервера принят успешно, принятые данные записываются в строку str, которая отображается на консоли клиента:
System.out.println(">> " + str);
Перед завершением своей работы клиент закрывает входной и выходной потоки, а также сокет, на котором выполнялась передача данных:
is.close(); os.close(); s.close();
Описание исходного текста серверного приложения SocketServ
Описание исходного текста серверного приложения SocketServВ методе main, получающем управление сразу после запуска приложения, мы определили несколько переменных.
Массив bKbdInput размером 256 байт предназначен для хранения строк, введенных при помощи клавиатуры.
В переменную ss класса ServerSocket будет записана ссылка на объект, предназначенный для установления канала связи через потоковый сокет (но не ссылка на сам сокет):
ServerSocket ss;
Ссылка на сокет, с использованием которого будет происходить передача данных, хранится в переменной с именем s класса Socket:
Socket s;
Кроме того, мы определили переменные is и os, соответственно, классов InputStream и OutputStream:
InputStream is; OutputStream os;
В эти переменные будут записаны ссылки на входной и выходной поток данных, которые связаны с сокетом.
После отображения на консоли строки названия приложения, метод main создает объект класса ServerSocket, указывая конструктору номер порта 9999:
ss = new ServerSocket(9999);
Конструктор возвращает ссылку на объект, с использованием которого можно установить канал передачи данных с клиентом.
Канал устанавливается методом accept:
s = ss.accept();
Этот метод переводит приложение в состояние ожидания до тех пор, пока не будет установлен канал передачи данных.
Метод accept в случае успешного создания канала передачи данных возвращает ссылку на сокет, с применением которого нужно принимать и передавать данные.
На следующем этапе сервер создает входной и выходной потоки, вызывая для этого методы getInputStream и getOutputStream, соответственно:
is = s.getInputStream(); os = s.getOutputStream();
Далее приложение подготавливает буфер buf для приема данных и определяет переменную length, в которую будет записываться размер принятого блока данных:
byte buf[] = new byte[512]; int lenght;
Теперь все готово для запуска цикла приема и обработки строк от клиентского приложения.
Для чтения строки мы вызываем метод read применительно ко входному потоку:
lenght = is.read(buf);
Этот метод возвращает управление только после того, как все данные будут прочитаны, блокируя приложение на время своей работы. Если такая блокировка нежелательна, вам следует выполнять обмен данными через сокет в отдельной задаче.
Метод read возвращает размер принятого блока данных или -1, если поток исчерпан. Мы воспользовались этим обстоятельством для завершения цикла приема данных:
if(lenght == -1) break;
После завершения приема блока данных мы преобразуем массив в текстовую строку str класса String, удаляя из нее символ перевода строки, и отображаем результат на консоли сервера:
System.out.println("> " + str);
Затем полученная строка отправляется обратно клиентскому приложению, для чего вызывается метод write:
os.write(buf, 0, lenght);
Методу write передается ссылка на массив, смещение начала данных в этом массиве, равное нулю, и размер принятого блока данных.
Для исключения задержек в передаче данных из-за накопления данных в буфере (при использовании буферизованных потоков) необходимо принудительно сбрасывать содержимое буфреа метдом flush:
os.flush();
И хотя в нашем случае мы не пользуемся буферизованными потоками, мы включили вызов этого метода для примера.
Теперь о завершающих действиях после прерывания цикла получения, отображения и передачи строк.
Наше приложение явням образом закрывает входной и выходной потоки данных, сокет, а также объект класса ServerSocket, с использованием которого был создан канал передачи данных:
is.close(); os.close(); s.close(); ss.close();
Описание исходныех текстов аплета Form
При инициализации метод init создает все необходимые органы управления и добавляет их в окно аплета.Когда пользователь заполняет форму и нажимает кнопку Send, обработчик соответствующего события вызывает метод startTransaction, запускающий процесс обмена данными с расширением сервера Web:
if(evt.target.equals(btnGetText)) { startTransaction(); }
Метод startTransaction, определенный в нашем приложении, создает и запускает на выполнение поток, который и будет взаимодействовать с программой CGI:
void startTransaction() { m_store = new Thread(this); m_store.start(); }
При этом в качестве отдельного потока, работающего одновременно с кодом аплета, выступает метод run. Именно в нем сосредоточена вся логика обмена данными с сервером Web.
Так как в процессе взаимодействия могут возникать различные исключения, мы предусмотрели их обработку при помощи блока try-catch:
URL u; URLConnection c; PrintStream ps; DataInputStream is;
try { . . . } catch (Exception ioe) { showStatus(ioe.toString()); stop(); }
Название возникшего исключения будет отображено в строке состояния браузера.
Теперь о том, что делает метод run после получения управления.
Первым делом он извлекает из однострочных текстовых полей имя и электронный адрес, объединяя их и записывая полученную текстовую строку в поле szSourceStr:
String szSourceStr = txtName.getText() + ", " + txtEMail.getText();
В строке szURL находится адрес URL программы CGI:
String szURL = "http://frolov/scripts/store.exe";
В реальном приложении этот адрес необходимо передавать аплету через параметр. Мы использовали непосредственное кодирование только для упрощения исходного текста.
На следующем этапе метод run создает для программы CGI объект класса URL и открывает с ним соединение:
u = new URL(szURL); c = u.openConnection();
Пользуясь этим соединением, метод run создает форматированный поток вывода, записывает в него строку имени и электронного адреса, а затем закрывает поток:
ps = new PrintStream(c.getOutputStream()); ps.println(szSourceStr); ps.close();
Переданные таким образом данные попадут в стандартный поток ввода программы CGI, откуда она их и прочитает.
Сделав это, программа CGI запишет в стандартный выходной поток строку ответа, которую необходимо прочитать в методе run нашего аплета. Для этого мы открываем входной поток, создаем на его основе форматированный входной поток данных, читаем одну строку текста и закрываем входной поток:
is = new DataInputStream(c.getInputStream()); String szReceived; szReceived = is.readLine(); is.close();
Сразу после этого программа CGI завершит свою работу и будет готова к обработке новых запросов на добавление записей. Что же касается метода run, то он добавит полученную от расширения сервера текстовую строку в многострочное окно редактирования, как это показано ниже, а затем инициирует перерисовку окна аплета:
txta.appendText(szReceived + "\r\n"); repaint();
Заметим, что использованный нами способ передачи данных подходит только для латинских символов. Если вам нужно передавать символы кириллицы, следует преобразовывать их из кодировки UNICODE, например, в гексадецимальную кодировку, а в программе CGI выполнять обратное преобразование. Аналогичную методику можно применять и для передачи произвольных двоичных данных.
Определение адреса IP
Определение адреса IPМетод getAddress возвращает массив из чеырех байт адреса IP объекта. Байт с нулевым индексом этого массива содержит старший байт адреса IP.
Метод toString возвращает текстовую строку, которая содержит имя узла, разделитель '/' и адрес IP в виде четырех десятичных чисел, разделенных точками.
Определение имени узла
Определение имени узлаС помощью метода getHostName вы можете определить имя узла, для которого был создан объект класса InetAddress.
Передача данных между клиентом и сервером
Передача данных между клиентом и серверомПосле того как серверное и клиентское приложения создали потоки для приема и передачи данных, оба этих приложения могут читать и писать в канал данных, вызывая методы read и write, определенные в классах InputStream и OutputStream.
Ниже мы представили фрагмент кода, в котором приложение вначале читает данные из входного потока в буфер buf, а затем записывает прочитанные данные в выходной поток:
byte buf[] = new byte[512]; int lenght; lenght = is.read(buf); os.write(buf, 0, lenght); os.flush();
На базе потоков класса InputStream и OutputStream вы можете создать буферизованные потоки и потоки для передачи форматированных данных, о которых мы рассказывали раньше.
Передача данных с использованием сокетов
Передача данных с использованием сокетовВ библиотеке классов Java есть очень удобное средство, с помощью которых можно организовать взаимодействие между приложениями Java и аплетами, работающими как на одном и том же, так и на разных узлах сети TCP/IP. Это средство, родившееся в мире операционной системы UNIX, - так называемые сокеты (sockets).
Что такое сокеты?
Вы можете представить себе сокеты в виде двух розеток, в которые включен кабель, предназначенный для передачи данных через сеть. Переходя к компьютерной терминологии, скажем, что сокеты - это программный интерфейс, предназначенный для передачи данных между приложениями.
Прежде чем приложение сможет выполнять передачу аили прием данных, оно должно создать сокет, указав при этом адрес узла IP, номер порта, через который будут передаваться данные, и тип сокета.
С адресом узла IP вы уже сталкивались. Номер порта служит для идентификации приложения. Заметим, что существуют так называемые "хорошо известные" (well known) номера портов, зарезервированные для различных приложений. Например, порт с номером 80 зарезервирован для использования серверами Web при обмене данными через протокол HTTP.
Что же касается типов сокетов, то их два - потоковые и датаграммные.
С помощью потоковых сокетов вы можете создавать каналы передачи данных между двумя приложениями Java в виде потоков, которые мы уже рассматривали во второй главе. Потоки могут быть входными или выходными, обычными или форматированными, с использованием или без использования буферизации. Скоро вы убедитесь, что организовать обмен данными между приложениями Java с использованием потоковых сокетов не труднее, чем работать через потоки с обычными файлами.
Заметим, что потоковые сокеты позволяют передавать данные только между двумя приложениями, так как они предполагают создание канала между этими приложениями. Однако иногда нужно обеспечить взаимодействие нескольких клиентских приложений с одним серверным или нескольких клиентских приложений с несколькими серверными приложениями. В этом случае вы можете либо создавать в серверном приложении отдельные задачи и отдельные каналы для каждого клиентского приложения, либо воспользоваться датаграммными сокетами. Последние позволяют передавать данные сразу всем узлам сети, хотя такая возможность редко используется и часто блокируется администраторами сети.
Для передачи данных через датаграммные сокеты вам не нужно создавать канал - данные посылаются непосредственно тому приложению, для которого они предназначены с использованием адреса этого приложения в виде сокета и номера порта. При этом одно клиентское приложение может обмениваться данными с несколькими серверными приложениями или наоборот, одно серверное приложение - с несколькими клиентскими.
К сожалению, датаграммные сокеты не гарантируют доставку передаваемых пакетов данных. Даже если пакеты данных, передаваемые через такие сокеты, дошли до адресата, не гарантируется, что они будут получены в той же самой последовательности, в которой были переданы. Потоковые сокеты, напротив, гарантируют доставку пакетов данных, причем в правильной последовательности.
Причина отстутствия гарантии доставки данных при использовании датаграммных сокетов заключается в использовании такими сокетами протокола UDP, который, в свою очередь, основан на протоколе с негарантированной доставкой IP. Потоковые сокеты работают через протокол гарантированной доставки TCP.
Поля класса ShowChart
Поля класса ShowChartВ классе ShowChart определены пять полей.
URL SrcURL; Object URLContent; int errno = 0; String str; byte buf[] = new byte[200];
Поле SrcURL класса URL хранит адрес URL файла исходных данных для круговой диаграммы. В поле URLContent типа Object будет переписано содержимое этого файла. В поле errno хранится текущий код ошибки, если она возникла, или нулевое значение, если все операции были выполнены без ошибок.
Поле str хранит принятую строку, которая предварительно записывается во временный буфер buf.
Приложения SocketServ и SocketClient
Приложения SocketServ и SocketClientВ качестве примера мы приведем исходные тексты двух приложений Java, работающих с потоковыми сокетами. Одно из этих приложений называется SocketServ и выполняет роль сервера, второе называется SocketClient и служит клиентом.
Приложение SocketServ выводит на консоль строку "Socket Server Application" и затем переходит в состояние ожидания соединения с клиентским приложением SocketClient.
Приложение SocketClient устанавливает соединение с сервером SocketServ, используя потоковый сокет с номером 9999 (этот номер выбран нами произвольно). Далее клиентское приложение выводит на свою консоль приглашение для ввода строк. Введенные строки отображаются на консоли и передаются серверному приложению. Сервер, получив строку, отображает ее в своем окне и посылает обратно клиенту. Клиент выводит полученную от сервера строку на консоли.
Когда пользователь вводит строку "quit", цикл ввода и передачи строк завершается.
Весь процесс показан на Рисунок 3 и 4.
Работа с потоковыми сокетами
Работа с потоковыми сокетамиКак мы уже говорили, интерфейс сокетов позволяет передавать данные между двумя приложениями, работающими на одном или разных узлах сети. В процессе создания канала передачи данных одно из этих приложений выполняет роль сервера, а другое - роль клиента. После того как канал будет создан, приложения становятся равноправными - они могут передавать друг другу данные симметричным образом.
Рассмотрим этот процесс в деталях.
Взаимодействие приложения Java с расширением сервера Web на базе интерфейса CGI
Рисунок 1. Взаимодействие приложения Java с расширением сервера Web на базе интерфейса CGI
Расширения ISAPI работают аналогично, однако они получают данные не из стандратного входного потока, а с помощью вызова специально предназначенной для этого функции интерфейса ISAPI. Вместо стандартного потока вывода также применяется специальная функция.
Круговая диаграмма, построенная на базе исходных данных, полученных через сеть
Рисунок 2. Круговая диаграмма, построенная на базе исходных данных, полученных через сеть
Файл исходных данных занимает всего 49 байт, поэтому он передается по сети очень быстро. Если бы мы передавали графическое изображение этой диаграммы, статическое или динамическое, подготовленное, например, расширением сервера CGI или ISAPI, объем передаваемых по сети данных был бы намного больше.
Окно серверного приложения
Рисунок 4. Окно серверного приложения
Здесь в окне клиентского приложения мы ввели несколько строк, причем последняя строка была строкой "quit", завершившая работу приложений.
Окно аплета Form
Рисунок 5. Окно аплета Form
Эта форма предназначена для добавления записей в базу данных, содержащую электронные почтовые адреса. Заполнив поля имени и адреса E-Mail, пользователь должен нажать кнопку Send. При этом введенная информация будет передана расширению сервера CGI, который запишет ее в базу данных, а затем отправит обратно аплету. Сохраненные записи, полученные от программы CGI, аплет FORM отобразит в многострочном поле редактирования, как это показано на Рисунок 5.
Создание объекта класса InetAddress для локального узла
Создание объекта класса InetAddress для локального узлаМетод getLocalHost создает объект класса InetAddress для локального узла, то есть для той рабочей станции, на которой выполняется приложение Java. Так как этот метод статический, вы можете вызывать его, ссылаясь на имя класса InetAddress:
InetAddress iaLocal; iaLocal = InetAddress.getLocalHost();
Создание объекта класса InetAddress для удаленного узла
Создание объекта класса InetAddress для удаленного узлаВ том случае, если вас интересует удаленный узел сети Internet или корпоративной сети Intranet, вы можете создать для него объект класса InetAddress с помощью методов getByName или getAllByName. Первый из них возвращает адрес узла, а второй - массив всех адресов IP, связанных с данным узлом. Если узел с указанным именем не существует, при выполнении методов getByName и getAllByName возникает исключение UnknownHostException.
Заметим, что методам getByName и getAllByName можно передавать не только имя узла, такое как, например, "sun.com", но и строку адреса IP в виде четырех десятичных чисел, разделенных точками.
После создания объекта класса InetAddress для локального или удаленного узла вы можете использовать другие методы этого класса.
Создание сетевых приложений
Создание сетевых приложенийКогда мы начинали разговор про язык программирования Java, то отмечали, что он специально ориентирован на глобальные сети, такие как Internet. В этой главе мы начнем знакомство с конкретными классами Java, разработанными для сетевого программирования. На примере наших приложений вы сможете убедиться, что классы Java действительно очень удобны для создания сетевых приложений.
В этой главе мы рассмотрим два аспекта сетевого программирования. Первый из них касается доступа из приложений Java к файлам, расположенным на сервере Web, второй - создания серверных и клиентских приложений с использованием сокетов.
Напомним, что из соображений безопасности алпетам полностью запрещен доступ к локальным файлам рабочей станции, подключенной к сети. Тем не менее, аплет может работать с файлами, расположенными на серверах Web. При этом можно использовать входные и выходные потоки, описанные нами в предыдущей главе.
Для чего аплетам обращаться к файлам сервера Web?
Таким аплетам можно найти множество применений.
Представьте себе, например, что вам нужно отображать у пользователя диаграмму, исходные данные для построения которой находятся на сервере Web. Эту задачу можно решить, грубо говоря, двумя способами.
Первый заключается в том, что вы создаете расширение сервера Web в виде приложения CGI или ISAPI, которое на основании исходных данных динамически формирует графическое изображение диаграммы в виде файла GIF и посылает его пользователю.
Однако на пути решения задачи с помощью расширения сервера Web вас поджидают две неприятности. Во-первых, создать из программы красивый цветной графический файл в стандарте GIF не так-то просто - вы должны разобраться с форматом этого файла и создать все необходимые заголовки. Во-вторых, графический файл занимает много места и передается по каналам Internet достаточно медленно - средняя скорость передачи данных в Internet составляет 1 Кбайт в секунду.
В то же время файл с исходными данными может быть очень компактным. Возникает вопрос - нельзя ли передавать через Internet только исходные данные, а построение графической диаграммы выполнять на рабочей станции пользователя?
В этом заключается второй способ, который предполагает применение аплетов. Ваше приложение может, например, получать через сеть файл исходных данных, а затем на основании содержимого этого файла рисовать в своем окне цветную круговую диаграмму. Объем передаваемых данных при этом по сравнению с использованием расширения сервера Web сокращается в десятки и сотни раз.
Помимо работы с файлами, расположенными на сервере Web, мы расскажем о создании каналов между приложениями Java, работающими на различных компьютерах в сети, с использованием сокетов.
Сокеты позволяют организовать тесное взаимодействие аплетов и полноценных приложений Java, при котором аплеты могут предавать друг другу данные через сеть Internet. Это открывает широкие возможности для обработки информации по схеме клиент-сервер, причем в роли серверов здесь может выступать любой компьютер, подключенный к сети, а не только сервер Web. Каждая рабочая станция может выступать одновременно и в роли сервера, и в роли клиента.
Сравнение адресов IP
Сравнение адресов IPИ, наконец, метод equals предназначен для сравнения адресов IP как объектов класса InetAddress.
Связь приложений Java с расширениями сервера Web
Связь приложений Java с расширениями сервера WebИтак, мы расказали вам, как приложения Java могут получать с сервера Web для обработки произвольные файлы, а также как они могут передавать данные друг другу с применением потоковых или датаграммных сокетов.
Однако наиболее впечатляющие возможности открываются, если организовать взаимодействие между приложением Java и расширением сервера Web, таким как CGI или ISAPI. В этом случае приложения или аплеты Java могли бы посылать произвольные данные расширению сервера Web для обработки, а затем получать результат этой обработки в виде файла.
Универсальный адрес ресурсов URL
Универсальный адрес ресурсов URLАдрес IP позволяет идентифицировать узел, однако его недостаточно для идентификации ресурсов, имеющихся на этом узле, таких как работающие приложения или файлы. Причина очевидна - на узле, имеющем один адрес IP, может существовать много различных ресурсов.
Для ссылки на ресурсы сети Internet применяется так называемый универсальный адрес ресуросв URL (Universal Resource Locator). В общем виде этот адрес выглядит следующим образом:
[protocol]://host[:port][path]
Строка адреса начинаетс с протокола protocol, который должен быть использован для доступа к ресурсу. Документы HTML, например, передаются из сервера Web удаленным пользователям с помощью протокола HTTP. Файловые серверы в сети Internet работают с протоколом FTP.
Для ссылки на сетевые ресурсы через протокол HTTP используется следующая форма универсального адреса ресурсов URL:
http://host[:port][path]
Параметр host обязательный. Он должен быть указан как доменный адрес или как адрес IP (в виде четырех десятичных чисел). Например:
http://www.sun.com http://157.23.12.101
Необязательный параметр port задает номер порта для работы с сервером. По умолчанию для протокола HTTP используется порт с номером 80, однако для специализированных серверов Web это может быть и не так.
Номер порта идентифицирует программу, работающую в узле сети TCP/IP и взаимодействующую с другими программами, расположенными на том же или на другом узле сети. Если вы разрабатываете программу, передающую данные через сеть TCP/IP с использованием, например, интерфейса сокетов Windows Sockets, то при создании канала связи с уделенным компьютером вы должны указать не только адрес IP, но и номер порта, который будет использован для передачи данных.
Ниже мы показали, как нужно указывать в адресе URL номер порта:
http://www.myspecial.srv/:82
Теперь займемся параметром path, определяющем путь к объекту.
Обычно любой сервер Web или FTP имеет корневой каталог, в котором расположены подкаталоги. Как в корневом каталоге, так и в подкаталогах сервера Web могут находиться документы HTML, двоичные файлы, файлы с графическими изображениями, звуковые и видео-файлы, расширения сервера в виде программ CGI или библиотек динамической компоновки, дополняющих возможности сервера.
Если в качестве адреса URL указать навигатору только доменное имя сервера, сервер перешлет навигатору свою главную страницу. Имя файла этой страницы зависит от сервера. Большинство серверов на базе операционной системы UNIX посылают по умолчанию файл документа с именем index.html. Другие серверы Web могут использовать для этой цели имя default.htm или какое-нибудь еще, определенное при установке сервера, например, home.html или home.htm.
Для ссылки на конкретный документ HTML или на файл любого другого объекта необходимо указать в адресе URL его путь, включающий имя файла, например:
http://www.glasnet.ru/~frolov/index.html http://www.dials.ccas.ru/frolov/home.htm
Корневой каталог сервера Web обозначается символом /. В спецификации протокола HTTP сказано, что если путь не задан, то используется корневой каталог.
Взаимодействие приложения Java и расширения сервера Web
Взаимодействие приложения Java и расширения сервера WebМетодика организации взаимодействия приложений Java и расширений сервера Web основана на применении классов URL и URLConnection.
Приложение Java, желающее работать с расширением сервера Web, создает объект класса URL для программы расширения (то есть для исполняемого модуля расширения CGI или библиотеки динамической компоновки DLL расширения ISAPI).
Далее приложение получает ссылку на канал передачи данных с этим расширением как объекта класса URLConnection. Затем, пользуясь методами getOutputStream и getInputStream из класса URLConnection, приложение создает с расширением сервера Web выходной и входной канал передачи данных.
Когда данные передаются приложением в выходной канал, созданный подобным образом, он попадает в стандартный поток ввода приложения CGI, как будто бы данные пришли методом POST из формы, определенной в документе HTML.
Обработав полученные данные, расширение CGI записывает их в свой стандартный выходной поток, после чего эти данные становятся доступны приложению Java через входной поток, открытый методом getInputStream класса URLConnection.
На Рисунок 1 показаны потоки данных для описанной выше схемы взаимодействия приложения Java и расширения сервреа Web с интерфейсом CGI.
Завершение работы сервера и клиента
Завершение работы сервера и клиентаПосле завершения передачи данных вы должны закрыть потоки, вызвав метод close:
is.close(); os.close();
Когда канал передачи данных больше не нужен, сервер и клиент должны закрыть сокет, вызвав метод close, определенный в классе Socket:
s.close();
Серверное приложение, кроме того, должно закрыть соединение, вызвав метод close для объекта класса ServerSocket:
ss.close();
Классы Java для работы с потоками
Аплет CDRotation
Аплет CDRotationВ этом разделе мы расскажем об аплете CDRotation, в окне которого вращается компакт-диск.
В левом верхнем углу каждого кадра отображается его порядковый номер (Рисунок 1). Этот номер не нарисован в файлах кадров, а надписывается приложением после рисования очередного кадра. Такое невозможно, если располагать в документе HTML файл AVI или многосекционный файл GIF.
Биты флагов для параметра infoflags метода imageUpdate
Биты флагов для параметра infoflags метода imageUpdatepublic final static int ABORT; public final static int ALLBITS; public final static int ERROR; public final static int FRAMEBITS; public final static int HEIGHT; public final static int PROPERTIES; public final static int SOMEBITS; public final static int WIDTH;
Добавление изображений в объект класса MediaTracker
Добавление изображений в объект класса MediaTrackerДалее метод init должен создать все необходимые объекты класса Image и добавить их в объект MediaTracker методом addImage. Ниже мы показали фрагмент кода, в котором выполняется добавление трех изображений:
Image img1; Image img2; Image img3; img1 = getImage(getCodeBase(), "pic1.gif"); img2 = getImage(getCodeBase(), "pic2.gif"); img3 = getImage(getCodeBase(), "pic3.gif"); mt.addImage(img1 , 0); mt.addImage(img2 , 0); mt.addImage(img3 , 0);
В качестве первого параметра методу addImage передается ссылка на изображение, загрузку которого необходимо отслеживать, а в качестве второго - идентификатор, который можно будет использовать в процессе отслеживания. Если все, что вам нужно, это дождаться окончания загрузки изображений, то для второго параметра вы можете указать нулевое значение.
Другие методы класса MediaTracker
Другие методы класса MediaTrackerКакие другие полезные методы, кроме методов addImage и waitForAll есть в классе MediaTracker?
public boolean waitForAll(long ms);
Метод waitForAll с параметром ms позволяет выполнять ожидание в течение заданного времени. Время ожидания задается в миллисекундах. При этом если за указанное время все изображения были успешно загружены, метод waitForAll возвращает значение true, если нет - false.
Вариант метода checkAll с параметром load позволяет проверить, завершилась ли загрузка отслеживаемых изображений:
public boolean checkAll(boolean load);
Если значение параметра load равно true, метод инициирует загрузку изображений.
Если при добавлении изображений методом addImage вы использовали второй параметр этого метода для присваивания разным группам изображений различные идентификаторы, то с помощью метода checkID можно дождаться завершения загрузки отдельной группы изображений:
public boolean checkID(int id);
Есть также вариант этого метода, позволяющий проверить загрузку группы изображений с заданным идентификатором:
public boolean checkID(int id, boolean load);
Метод waitForID с параметрами id и ms позволяет выполнять ожидание загрузки группы изображений с заданным идентификатором в течении указанного периода времени:
public boolean waitForID(int id, long ms);
Класс MediaTracker предоставляет также возможность прослеживать сам процесс загрузки всех добавленных в него изображений или отдельных групп изображений с помощью методов statusAll и statusID:
public int statusAll(boolean load); public int statusID(int id, boolean load);
В зависимости от значения параметра load эти методы могут инициировать загрузку изображений. Если параметр равен true, загрузка изображений инициируется, если false - выполняется только проверка текущего состояния загрузки.
Методы statusAll и statusID возвращают значение, составленное из отдельных битов состояния при помощи логической операции ИЛИ. Ниже мы перечислили эти биты состояния и привели их краткое описание.
| Биты состояния | Описание |
| MediaTracker.LOADING | Один или несколько отслеживаемых файлов продолжают загружаться |
| MediaTracker.ABORTED | Загрузка одного или нескольких файлов была прервана |
| MediatTracker.ERRORED | При загрузке одного или нескольких файлов произошла ошибка |
| MediaTracker.COMPLETE | Загрузка всех отслеживаемых файлов произошла полностью и успешно |
public boolean isErrorAny(); public boolean isErrorID(int id); public Object[] getErrorsAny(); public Object[] getErrorsID(int id);
Методы isErrorAny и isErrorID позволяют проверить, возникла ли ошибка при загрузке, соответственно, любого из отслеживаемых изображений или изображений из заданной группы. Если ошибка произошла, возвращается значение true, если нет - значение false.
Методы getErrorsAny и getErrorsID возвращают массив объектов, при ожидании загрузки которых произошла ошибка. Первый из этих методов возвращает массив для всех отслеживаемых объектов, второй - только объектов из заданной группы.
Исходные тексты приложения
Исходные тексты приложенияГлавный файл исходных текстов приложения CDRotation представлен в листинге 1.
Класс Image
Класс ImageПроцесс рисования растрового изображения в окне аплета предельно прост - вам достаточно загрузить изображение методом getImage и затем нарисовать его методом drawImage.
Но не забывайте, что метод getImage в действительности только создает объект класса Image, но не загружает его. Давайте посмотрим на класс Image.
В этом классе имеется единственный конструктор без параметров:
public Image();
Вы, однако, скорее всего будете создавать объекты класса Image при помощи метода getImage.
Методы getHeight и getWidth, определенные в классе Image, позволяют определить, соответственно, высоту и ширину изображения:
public abstract int getHeight( ImageObserver observer); public abstract int getWidth( ImageObserver observer);
Так как при вызове этих методов изображение еще может быть не загружено, в качестве параметров методам передается ссылка на объект ImageObserver. Этот объект получит извещение, когда будет доступна высота или ширина изображения.
Метод getGraphics позволяет получить так называемый внеэкранный контекст отображения для рисования изображения не в окне аплета, а в оперативной памяти:
public abstract Graphics getGraphics();
Эта техника используется для того, чтобы вначале подготовить изображение в памяти, а затем за один прием отобразить его на экране.
Еще один метод класса Image, который мы рассмотрим, называется flush:
public abstract void flush();
Он освобождает ресурсы, занятые изображением.
public class CDRotation extends Applet
Листинг 1. Файл CDRotation.java
import java.applet.*; import java.awt.*;
public class CDRotation extends Applet implements Runnable { Thread m_CDRotation = null; private Graphics m_Graphics; private Image m_Images[]; private int m_nCurrImage; private int m_nImgWidth = 0; private int m_nImgHeight = 0; private boolean m_fAllLoaded = false; private final int NUM_IMAGES = 11;
public String getAppletInfo() { return "Name: CDRotation"; }
private void displayImage(Graphics g) { if (!m_fAllLoaded) return;
g.drawImage(m_Images[m_nCurrImage], (size().width - m_nImgWidth) / 2, (size().height - m_nImgHeight) / 2, null);
g.drawString( (new Integer(m_nCurrImage)).toString(), (size().width - m_nImgWidth) /2, ((size().height - m_nImgHeight)/2)+ 10);
}
public void paint(Graphics g) { Dimension dimAppWndDimension = size();
g.setColor(Color.white);
g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
g.setColor(Color.black);
g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
if (m_fAllLoaded) { displayImage(g);
}
else g.drawString("Please, wait...", 10, dimAppWndDimension.height / 2);
}
public void start() { if (m_CDRotation == null) { m_CDRotation = new Thread(this);
m_CDRotation.start();
} }
public void stop() { if (m_CDRotation != null) { m_CDRotation.stop();
m_CDRotation = null; } }
public void run() { m_nCurrImage = 0;
if (!m_fAllLoaded) { repaint();
m_Graphics = getGraphics();
m_Images = new Image[NUM_IMAGES];
MediaTracker tracker = new MediaTracker(this);
String strImage;
for (int i = 0; i < NUM_IMAGES; i++) { strImage = "images/cdimg0" + ((i < 10) ? "0" : "") + i + ".gif";
m_Images[i] = getImage( getDocumentBase(), strImage);
tracker.addImage(m_Images[i], 0);
}
try { tracker.waitForAll();
m_fAllLoaded = !tracker.isErrorAny();
} catch (InterruptedException e) { }
if (!m_fAllLoaded) { stop();
m_Graphics.drawString( "Load error", 10, size().height / 2);
return; }
m_nImgWidth = m_Images[0].getWidth(this);
m_nImgHeight = m_Images[0].getHeight(this);
}
repaint();
while (true) { try { displayImage(m_Graphics);
m_nCurrImage++;
if(m_nCurrImage == NUM_IMAGES) m_nCurrImage = 0;
Thread.sleep(30);
}
catch (InterruptedException e) { stop();
} } } }
Листинг 2 содержит исходный текст документа HTML, созданного для аплета CDRotation.
enabled browser, you would see
Листинг 2. Файл CDRotation.tmp.html
Метод displayImage
Метод displayImageМетод displayImage вызывается из двух мест - из метода paint при перерисовке окна аплета и из метода run (периодически).
Если кадры видеофильма не загружены, содержимое флага m_fAllLoaded равно false и метод displayImage просто возвращает управление, ничего не делая:
if(!m_fAllLoaded) return;
Если же загрузка изображений завершена, этот метод рисует в центре окна текущий кадр видеофильма, вызывая для этого знакомый вам метод drawImage:
g.drawImage(m_Images[m_nCurrImage], (size().width - m_nImgWidth) / 2, (size().height - m_nImgHeight) / 2, null);
После того как кадр нарисован, мы надписываем на нем его порядковый номер, вызывая для этого метод drawString:
g.drawString((new Integer( m_nCurrImage)).toString(), (size().width - m_nImgWidth) / 2, ((size().height - m_nImgHeight) / 2) + 10);
Метод imageUpdate
Метод imageUpdatepublic abstract boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height);
Как видите, в интерфейсе ImageObserver определен единственный метод imageUpdate и набор битовых флагов для этого метода.
Класс Component, от которого происходит класс Applet, реализует интерфейс ImageObserver:
public abstract class java.awt.Component extends java.lang.Object implements java.awt.image.ImageObserver { . . . }
Этот интерфейс используется для отслеживания процесса загрузки и перерисовки изображений и других компонент, расположенных внутри компонента. В частности, он используется для отслеживания загрузки и рисования растровых изображений в окне аплета, чем мы и воспользуемся.
В процессе загрузки вызывается метод imageUpdate, поэтому чтобы отслеживать загрузку изображений, наш аплет должен переопределить этот метод.
Процедура ожидания загрузки изображений достаточно проста.
Прежде всего, аплет должен передать в последнем параметре методу drawImage ссылку на интерфейс ImageObserver, который будет применяться для отслеживания процесса загрузки:
g.drawImage(Img, x, y, width, height, this);
Здесь в качестве ссылки на интерфейс ImageObserver мы передали значение this. При этом будет применен интерфейс нашего аплета. Соответственно, нам нужно определить в классе аплета метод imageUpdate, который будет вызываться в процессе загрузки изображений.
Ниже мы привели возможный вариант реализации этого метода:
public boolean imageUpdate( Image img, int flags, int x, int y, int w, int h) { // Проверяем, все ли // биты изображения загружены fAllLoaded = ((flags & ALLBITS) != 0);
// Если все, перерисовываем окно if(fAllLoaded) repaint();
// Если все биты загружены, // дальнейшие вызовы // метода imageUpdate не нужны return !fAllLoaded; }
Через первый параметр img методу imageUpdate передается ссылка на изображение, загрузка которого отслеживается.
Параметр flags отражает состояние процесса загрузки.
Через остальные параметры x, y, w и h передаются, соответственно, координаты и размеры изображения.
Основное, что должен делать метод imageUpdate для отслеживания процесса загрузки - это проверять флаги flags, дожидаясь установки нужных флагов.
Флаги определены следующим образом:
public final static int WIDTH; public final static int HEIGHT = 2; public final static int PROPERTIES = 4; public final static int SOMEBITS = 8; public final static int FRAMEBITS = 16; public final static int ALLBITS = 32; public final static int ERROR = 64; public final static int ABORT = 128;
Ниже мы привели краткое описание перечисленных флагов.
| Флаг | Описание |
| WIDTH | Изображение загружено настолько, что стала доступна его ширина. Значение ширины изображения можно получить из параметра w метода imageUpdate |
| HEIGHT | Аналогично предыдущему, но для высоты изображения. Высоту изображения можно получить из параметра h метода imageUpdateimageUpdate |
| PROPERTIES | Стали доступны свойства изображения, которые можно получить методом getProperty класса Image. В нашей книге мы опустили описание этого метода |
| SOMEBITS | Стали доступны биты изображения для рисования в масштабе. Через параметры x, y, h и w передаются координаты и размеры прямоугольной области, которая ограничивает загруженную часть изображения |
| FRAMEBITS | Загружен очередной фрейм изображения, состоящего из нескольких фреймов. Параметры x, y, h и w следует игнорировать |
| ALLBITS | Изображение загружено полностью. Параметры x, y, h и w следует игнорировать |
| ERROR | При загрузке произошла ошибка |
| ABORT | Загрузка изображения была прервана или отменена |
Если вам нужно только дождаться завершения процесса загрузки, достаточно использовать флаг ALLBITS. Для проверки ошибок воспользуйтесь флагами ERROR и ABORT.
Метод paint
Метод paintСразу после получения управления, метод paint закрашивает окно аплета белым цветом и рисует вокруг него черную рамку.
Затем метод проверяет содержимое флага m_fAllLoaded. Этот флаг установлен в значение true, когда все кадры видеофильма загружены и сброшен в значение false, когда загрузка кадров еще не завершена. Последняя ситуация возникает всегда при первом вызове метода paint.
Если все изображения загружены, метод paint вызывает метод displayImage, определенный в нашем приложении:
if(m_fAllLoaded) { displayImage(g); }
Этот метод, о котором мы еще расскажем подробнее, отображает в окне аплета текущий кадр видеофильма.
Если же кадры видеофильма еще не загружены, в окне аплета отображается соответствующее сообщение:
else g.drawString("Please, wait...", 10, dimAppWndDimension.height / 2);
Метод run
Метод runМетод run работает в рамках отдельного потока. Он занимается последовательным рисованием кадров нашего видеофильма.
Прежде всего метод run записывает нулевое значение в поле m_nCurrImage, хранящее номер текущего отображаемого кадра:
m_nCurrImage = 0;
Далее выполняется проверка, загружены ли все кадры видеофильма, для чего анализируется содержимое флага m_fAllLoaded.
Если изображения не загружены (а в самом начале так оно и есть) метод run перерисовывает окно аплета и получает контекст отображения для этого окна. Затем создается массив объектов Image для хранения кадров видеофильма:
m_Images = new Image[NUM_IMAGES];
Метод run создает также объект класса MediaTracker для ожидания загрузки всех кадров видеофильма:
MediaTracker tracker = new MediaTracker(this);
Далее метод run в цикле загружает изображения и добавляет их в объект класса MediaTracker для того чтобы можно было дождаться загрузки всех кадров:
for (int i = 0; i < NUM_IMAGES; i++) { strImage = "images/cdimg0" + ((i < 10) ? "0" : "") + i + ".gif"; m_Images[i] = getImage( getDocumentBase(), strImage); tracker.addImage(m_Images[i], 0); }
Здесь предполагается, что файлы изображений находятся в каталоге images, который, в свою очередь, размещен там же, где и двоичный файл аплета.
Имена файлов, составляющих отдельные кадры, начинаются с префикса cdimg0, вслед за которым идет номер кадра (00, 01, 02, и так далее), и расширение имени .gif.
Ожидание загрузки кадров выполняется с помощью метода waitForAll, о котором мы вам уже рассказывали:
try { tracker.waitForAll(); m_fAllLoaded = !tracker.isErrorAny(); } catch (InterruptedException e) { }
После окончания ожидания флаг завершения загрузки устанавливается только в том случае, если метод isErrorAny вернул значение false, то есть если не было никаких ошибок.
Если же произошла ошибка, в окне аплета отображается соответствующее сообщение, после чего работа метода run (и, следовательно, работа созданного для него потока) заканчивается:
if(!m_fAllLoaded) { stop(); m_Graphics.drawString( "Load error", 10, size().height / 2); return; }
В случае удачной загрузки всех кадров метод run получает ширину и высоту первого кадра видеофильма и сохраняет эти значения в переменных m_nImgWidth и m_nImgHeight:
m_nImgWidth = m_Images[0].getWidth(this); m_nImgHeight = m_Images[0].getHeight(this);
Далее окно аплета перерисовывается:
repaint();
При этом метод paint отображает в окне аплета первый кадр видеофильма.
На следующем этапе работы метода run запускается цикл отображения кадров фильма:
while (true) { try { displayImage(m_Graphics); m_nCurrImage++; if(m_nCurrImage == NUM_IMAGES) m_nCurrImage = 0; Thread.sleep(30); } catch (InterruptedException e) { stop(); } }
В этом бесконечном цикле вызывается метод displayImage, рисующий текущий кадр видеофильма, после чего номер текущего кадра увеличивается на единицу. Если показаны все кадры, номер текущего кадра становится равным нулю, а затем процесс продолжается.
Между отображением кадров выполняется задержка величиной 30 миллисекунд.
Метод start
Метод startВ задачу метода start, который получает управление при отображении окна аплета, входит создание и запуск потока, отображающего кадры видеофильма с изображением вращающегося компакт-диска:
if (m_CDRotation == null) { m_CDRotation = new Thread(this); m_CDRotation.start(); }
Поток создается как объект класса Thread, причем конструктору передается ссылка на главный класс аплета. Поэтому при запуске потока управление получит метод run, определенный в классе аплета.
Метод stop
Метод stopМетод stop останавливает работу потока, когда окно аплета исчезает с экрана:
if(m_CDRotation != null) { m_CDRotation.stop(); m_CDRotation = null; }
Для остановки вызывается метод stop.
Описание исходных текстов
Описание исходных текстовРассмотрим наиболее важные методы нашего аплета.
Ожидание загрузки добавленных изображений
Ожидание загрузки добавленных изображенийДля того чтобы убедиться, что все изображения загружены, вы можете воспользоваться методом waitForAll. Этот метод инициирует загрузку изображений, а также задержит выполнение вызвавшего потока до момента полной загрузки всех изображений, добавленных в объект класса MediaTracker:
try { mt.waitForAll(); } catch (InterruptedException ex) { }
Обратите внимание, что метод waitForAll может создавать исключение InterruptedException. Это исключение возникает, если по какой-либо причине процесс ожидания прерывается.
Чаще всего рисование выполняется в отдельном потоке, поэтому метод waitForAll должен вызываться в начале соответствующего метода run. Ниже мы привели исходные тексты приложения ImageDrawWait, в котором такое ожидание выполняется в методе paint, что приводит, однако, к блокировке работы аплета до момента загрузки всех изображений. В данном случае это не критично, так как кроме рисования изображений наш аплет ничего не делает, однако более предпочтительным является выполнение длительных процессов в отдельном потоке.
Ожидание загрузки изображений
Ожидание загрузки изображенийКак мы уже говорили в наших предыдущих статьях, загрузка изображений из сети Internet - длительный процесс, который в среднем идет со скоростью 1 Кбайт в секунду. Поэтому изображения загружаются навигатором в отдельной задаче. При этом метод getImage только создает объект класса Image, а метод drawImage инициирует загрузку изображения и рисует его. Причем если файл изображения имеет большую длину, он будет появляться в окне аплета постепенно по мере загрузки.
Однако в некоторых случаях было бы удобно вначале загрузить изображение полностью, а лишь затем выполнять рисование, чтобы изображение появилось на экране сразу. Кроме того, аплет может рисовать сразу несколько изображений в разных местах своего окна или показывать их по очереди в одном и том же месте для достижения эффекта анимации.
Есть ли способ определить, когда изображение будет загружено полностью?
Есть, и причем целых два. Один из них связан с использованием класса MediaTracker, специально предназначенного для этой цели и достаточно удобного в использовании, другой основан на переопределении одного из методов интерфейса ImageObserver.
Применение интерфейса ImageObserver
Применение интерфейса ImageObserverВторой способ ожидания завершения процесса загрузки изображений связан с интерфейсом ImageObserver:
Применение класса MediaTracker
Применение класса MediaTrackerДля того чтобы выполнить ожидание загрузки нескольких изображений, проще воспользоваться классом MediaTracker, а не интерфейсом ImageObserver.
Как это сделать?
Обычно метод init аплета создает объект класса MediaTracker с помощью конструктора и добавляет в него все изображения, загрузки которых необходимо дождаться.
Растровые изображения и анимация
Растровые изображения и анимацияОдно из наиболее распространенный применений аплетов связано с рисованием простых или анимированных растровых изображений. На серверах Web изображения обычно хранятся в форматах GIF или JPEG. Оба эти формата обеспечивают сжатие изображения, что весьма актуально из-за невысокой скорости передачи данных в сети Internet.
Рисование растровых изображений в приложениях для операционной системы Windows, составленных на языке программирования С - непростая задача. В классическом программном интерфейсе этой операционной системы отсутствуют функции, с помощью которых можно было бы непосредственно рисовать содержимое файлов с растровыми изображениями. Программист вынужден работать с заголовками таких файлов, выделять таблицу цветов и биты изображений, создавать и реализовывать палитру, заниматься восстановлением сжатых данных и так далее.
Разработчик приложений Java находится в намного лучшем положении, так как библиотеки классов Java содержат простые в использовании и мощные средства, предназначенные для работы с растровыми изображениями. В этой статье мы научим вас рисовать в окне аплета содержимое файлов GIF и JPEG, выполняя при необходимости масштабирование, а также создавать на базе таких файлов анимационные изображения, показывая по очереди отдельные кадры небольшого видеофильма.
Изображение вращающегося компакт-диска в окне аплета CDRotation
Рисунок 1. Изображение вращающегося компакт-диска в окне аплета CDRotation
Создание объекта класса MediaTracker
Создание объекта класса MediaTrackerОбъект класса MediaTracker создается следующим образом:
MediaTracker mt; mt = new MediaTracker(this);
Конструктору класса MediaTracker передается ссылка на компонент, для которого необходимо отслеживать загрузку изображений. В данном случае это наш аплет, поэтому мы передаем конструктору значение this.
Видео в окне аплета
Видео в окне аплетаНаиболее динамичные страницы сервера Web содержат анимационные изображения в виде небольших видеофильмов. Вы можете подготовить видеофильм как файл AVI или как многосекционный файл GIF.
Файл AVI представляет собой многопоточный файл, содержащий видео и звук. Файлы AVI можно создавать при помощи специального видеоадаптера, который способен оцифровывать сигнал с видеокамеры или видеомагнитофона, а также из отдельных изображений, составляющих кадры видеофильма.
Заметим, однако, что озвученный видеофильм в формате AVI продолжительностью в 1 минуту занимает мегабайты дискового пространства. При существующих на сегодняшний день скоростях передачи данных через Internet не имеет никакого смысла размещать на страницах сервера Web файлы такого размера.
Многосекционные файлы GIF не содержат звуковой информации и состоят обычно из одного-двух десятков кадров. Для каждого такого кадра вы можете задавать время отображения и координаты, где этот кадр будет отображаться. Можно также добиться зацикленного отображения видеофильма, созданного как многосекционный файл GIF.
Аплеты Java предоставляют вам еще одну возможность отображения небольших видеофильмов на страницах сервера Web.
Для реализации этой возможности вы должны подготовить и разместить в одном из каталогов сервера Web файлы отдельных кадров видеофильма в формате GIF или JPEG.
Аплет Java должен загрузить эти изображения, дождавшись окончания процесса загрузки, что можно сделать либо при помощи рассмотренного в этой главе класса MediaTracker либо при помощи интерфейса ImageObserver.
Как только все изображения будут полностью загружены, аплет может начинать их поочередное отображение в цикле. Этот цикл должен выполняться в отдельной задаче.
Так как аплет полностью контролирует отображение кадров фильма, он может реализовывать эффекты, недостижимые при использовании файлов AVI или многосекционных файлов GIF. Например, аплет может накладывать или смешивать кадры различных фильмов, рисовать поверх кадров произвольные изображения или делать надписи, масштабировать отдельные фрагменты кадров или весь кадр и так далее. Здесь все ограничивается главным образом вашей фантазией.
Так как мы уже научились выполнять все необходимые для показа видеофильма операции, перейдем сразу к исходным текстам приложения CDRotation.
Загрузка и рисование растрового изображения
Загрузка и рисование растрового изображенияЗагрузка растрового изображения из файла выполняется очень просто - с помощью метода getImage, определенного в классе Applet:
public Image getImage(URL url); public Image getImage(URL url, String name);
Первый вариант метода предполагает использование только одного параметра - адреса URL файла графического изображения. Второй позволяет дополнительно указать относительное расположение файла изображения относительно адреса URL, например:
Image img; img = getImage( "http://www.glasnet.ru/~frolov/pic","cd.gif");
Если аплет желает загрузить изображение, расположенное в том же каталоге, что и он сам, это можно сделать следующим образом:
img = getImage(getCodeBase(), "cd.gif");
Метод getCodeBase, определенный в классе Applet, возвращает адрес URL аплета. Вместо него можно использовать метод getDocumentBase, который также определен в классе Applet и возвращает адрес URL документа HTML, содержащего аплет:
img = getImage(getDocumentBase(), "cd.gif");
В любом случае метод getImage создает объект класса Image.
Заметим, что на самом деле метод getImage вовсе не загружает изображение через сеть, как это можно было бы подумать. Он только создает объект класса Image. Реальная загрузка файла растрового изображения будет выполняться методом рисования drawImage, который определен в классе Graphics:
public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer); public abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer); public abstract boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer); public abstract boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer);
Как видите, существует четыре варианта этого метода.
В качестве первого параметра любому варианту метода передается ссылка на объект класса Image, полученный ранее с помощью метода getImage.
Параметры x и y задают координаты верхнего левого угла прямоугольной области, внутри которой будет нарисовано изображение. Эти параметры также задаются для любого варианта метода drawImage.
Параметр bgcolor задает цвет фона, на котором будет нарисовано изображение. Как вы, вероятно, знаете, изображения GIF могут быть прозрачными. В этом случае цвет фона может иметь большое значение.
Если для рисования выбраны варианты метода drawImage с параметрами width и height, изображение будет нарисовано с масштабированием. При этом указанные параметры будут определять, соответственно, ширину и высоту изображения.
Параметр observer представляет собой ссылку на объект класса ImageObserver, который получит извещение при загрузке изображения. Обычно в качестве такого объекта используется сам аплет, поэтому данный параметр указывается как this.
Вот два примера использования метода drawImage:
g.drawImage(FloppyDiskImg, 25, 3, this); g.drawImage(FloppyDiskImg, 25, 42, 200, 200, this);
В первой строке изображение FloppyDiskImg рисуется в точке с координатами (25, 3) без масштабирования, во второй - в точке с координатами (25, 42), причем высота и ширина нарисованного изображения будет равна 200 пикселам.
Метод drawImage запускает процесс загрузки и рисования изображения, а затем, не дожидаясь его завершения, возвращает управление. Так как загрузка файла изображения по сети может отнять немало времени, она выполняется асинхронно в отдельной задаче.
Классы Java для работы с потоками
Аплет PlayClip
Аплет PlayClipАплет PlayClip демонстрирует использование интерфейса AudioClip. В его окне (Рисунок 1) имеются три кнопки с названиями Play, Loop и Stop.
Исходные тексты приложения
Исходные тексты приложенияОсновной файл исходных текстов приложения приведен в листинге 1.
public class PlayClip extends Applet
Листинг 1. Файл PlayClip.java
import java.applet.*; import java.awt.*;
public class PlayClip extends Applet { private String m_ClipName = "kaas.au"; private final String PARAM_ClipName = "ClipName"; AudioClip auClip; Button btPlay; Button btLoop; Button btStop; boolean fLoopPlay = false;
public String getAppletInfo() { return "Name: PlayClip"; }
public String[][] getParameterInfo() { String[][] info = { { PARAM_ClipName, "String", "Audioclip filename" }, }; return info; }
public void init() { String param; param = getParameter(PARAM_ClipName);
if (param != null) m_ClipName = param;
btPlay = new Button("Play");
btLoop = new Button("Loop");
btStop = new Button("Stop");
btStop.disable();
add(btPlay);
add(btLoop);
add(btStop);
auClip = this.getAudioClip(getCodeBase(), m_ClipName);
}
public boolean action(Event evt, Object obj) { Button btn;
if(evt.target instanceof Button) { btn = (Button)evt.target;
if(evt.target.equals(btPlay)) { auClip.play();
btStop.enable();
}
else if(evt.target.equals(btLoop)) { auClip.loop();
fLoopPlay = true; btStop.enable();
}
else if(evt.target.equals(btStop)) { auClip.stop();
fLoopPlay = false; btStop.disable();
}
else { return false; }
return true; }
return false; }
public void paint(Graphics g) { Dimension dimAppWndDimension = size();
g.setColor(Color.yellow);
g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
g.setColor(Color.black);
g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
}
public void start() { if(fLoopPlay) auClip.loop();
}
public void stop() { if(fLoopPlay) auClip.stop();
} }
В листинге 2 вы найдете исходный текст документа HTML, созданного автоматически для нашего приложения системой Java WorkShop.
enabled browser, you would see
Листинг 2. Файл PlayClip.tmp.html
Метод action
Метод actionМетод action получает управление, когда пользователь нажимает на одну из кнопок, расположенных в окне аплета. В зависимости от того, какая именно кнопка была нажата, выполняются различные действия.
Если пользователь нажал кнопку Play, вызывается метод play для запуска однократного проигрывания звукового файла:
auClip.play(); btStop.enable();
Сразу после того как проигрывание будет запущено, приложение разблокирует кнопку Stop, предоставляя пользователю возможность прервать звучание.
В том случае, когда пользователь нажал кнопку Loop, вызывается метод loop, запусчкающий проигрывание звукового файла в цикле:
auClip.loop(); fLoopPlay = true; btStop.enable();
После запуска устанавливается флаг fLoopPlay и разблокируется кнопка Stop.
И, наконец, если пользователь нажимает кнопку Stop, выполняется остановка проигрывания методом stop:
auClip.stop(); fLoopPlay = false; btStop.disable();
Флаг fLoopPlay сбрасывается, после чего кнопка Stop блокируется.
Метод getParameterInfo
Метод getParameterInfoМетод getParameterInfo возвращает описание единственного параметра нашего аплета, через который передается имя звукового файла.
Метод init
Метод initСразу после запуска аплета метод init получает значение параметра - имя звукового файла, и если этот параметр задан в документе HTML, записывает полученное имя в поле m_ClipName:
param = getParameter(PARAM_ClipName); if(param != null) m_ClipName = param;
Далее создаются три кнопки, управляющие звучанием аплета:
btPlay = new Button("Play"); btLoop = new Button("Loop"); btStop = new Button("Stop");
Кнопка Stop блокируется, так как на данный момент проигрывание еще не запущено:
btStop.disable();
Для блокирования вызывается метод disable, определенный в классе Button.
Подготовленные таким образом кнопки добавляются в окно аплета:
add(btPlay); add(btLoop); add(btStop);
Последнее, что делает метод init перед тем как возвратить управление, это получение ссылки на интерфейс AudioClip:
auClip = this.getAudioClip( getCodeBase(),m_ClipName);
Адрес URL каталога, в котором расположен аплет, определяется с помощью метода getCodeBase, о котором мы говорили в предыдущей главе.
Метод start
Метод startМетод start получает управление при первом запуска аплета, а также когда страница документа появляется вновь после того как пользователь временно переходил к просмотру другой страницы.
Наша реализация метода start возобновляет циклическое проигрывание, если оно выполнялось, когда пользователь покинул страницу с аплетом:
if(fLoopPlay) auClip.loop();
Метод stop
Метод stopЕсли пользователь запустил проигрывание звукового файла в цикле, а затем перешел к просмотру другой страницы, метод stop останавливает циклическое проигрывание:
if(fLoopPlay) auClip.stop();
Когда пользователь вернется к просмотру нашей страницы, метод start, описанный выше, возобновит проигрывание звукового файла.
Описание исходного текста
Описание исходного текстаВ главном классе аплета определено несколько полей и методов. Рассмотрим эти поля и наиболее важные методы.
Поля классаPlayClip
Поля класса PlayClipВ поле m_ClipName хранится имя звукового файла, которое передается через параметр ClipName из документа HTML. По умолчанию для этого параметра используется значение kaas.au.
Строка PARAM_ClipName хранит имя указанного выше параметра.
Ссылка на интерфейс AudioClip хранится в поле auClip:
AudioClip auClip;
Следующие три поля хранят ссылки на кнопки, предназначенные для управления проигрыванием звукового файла:
Button btPlay; Button btLoop; Button btStop;
Поле fLoopPlay типа boolean используется для флага, которым отмечается режим проигрывания звукового файла в цикле.
Окно аплета PlayClip
Рисунок 1. Окно аплета PlayClip
Сразу после запуска аплета кнопка Stop находится в заблокированном состоянии. Если нажать кнопку Play или Loop, начнется, соответственно, однократное проигрывание или проигрывание в цикле файла с именем kaas.au, распложенного в том же каталоге, что и двоичный файл аплета PlayClip.
Когда начинается проигрывание звукового файла, кнопка Stop разблокируется, что позволяет остановить проигрывание.
Загрузка и проигрывание звуковых файлов
Загрузка и проигрывание звуковых файловРабота со звуковыми файлами во многом напоминает работу с растровыми графическими файлами. Вначале вы должны получить ссылку на интерфейс AudioClip, а затем, пользуясь его методами, вы сможете выполнять проигрывание содержимого этого файла.
Для получения интерфейса AudioClip вы должны воспользоваться одним из двух вариантов метода getAudioClip, определенных в классе Applet:
public AudioClip getAudioClip(URL url): public AudioClip getAudioClip(URL url, String name);
Первый вариант метода предполагает указание адреса URL звукового файла через единственный параметр, второй допускает раздельное указание адреса URL каталога, содержащего файл, и имени файла.
В документации на метод getAudioClip сказано, что этот метод фактически не выполняет загрузку звуковых данных, а только возвращает ссылку на интерфейс AudioClip и немедленно возвращает управление. Загрузка звуковых данных выполняется методами, предназначенными для проигрывания файла.
Однако в книге "The Java Tutorial. Object-Oriented Programming for the Internet", подготовленной специалистами группы JavaSoft, утверждается, что текущие реализации Java работают по другому: метод getAudioClip возвращает управление только после завершения загрузки звукового файла. Очевидно, вам не стоит полагаться на то, что так будет всегда. В тех случаях, когда нежелательно блокирование работы аплета на время загрузки звукового файла, загрузку и проигрывание следует выполнять в отдельном потоке.
Интерфейс AudioClip определен следующим образом:
public interface java.applet.AudioClip { public abstract void play(); public abstract void loop(); public abstract void stop(); }
Метод play запускает однократное проигрывание звукового файла, которое выполняется от начала файла и до его конца.
Метод loop запускает проигрывание звукового файла в цикле, которое будет продолжаться до тех пор, пока вы не остановите его, вызвав метод stop.
Метод stop, как нетрудно догадаться из его названия, останавливает проигрывание звукового файла, как однократное, так и выполняемое в цикле.
Звук в аплетах Java
Звук в аплетах JavaНельзя сказать, что звуковые возможности аплетов Java чрезмерно велики. Скорее наоборот, они минимальны. Тем не менее, аплеты могут проигрывать звуковые клипы, записанные в файлах формата AU, который пришел из мира компьютеров фирмы Sun.
Сказанное, однако, не означает, что если у вас нет рабочей станции Sun, то вы не сможете озвучить свои аплеты. Во-первых, в сети Internet можно найти много готовых звуковых файлов AU, а во-вторых, там же есть программы для преобразования форматов звуковых файлов. Одну из таких условно-бесплатных программ, которая называется GoldWave, вы можете загрузить с сервера ftp.winsite.com.
Бизнес: Предпринимательство - Малый бизнес - Управление
- Бизнес
- Разновидности бизнеса
- Планирование бизнеса
- Управление бизнесом
- Предпринимательство
- Русское предпринимательство
- Управление и предпринимательство
- Малый бизнес
- Виды малого бизнеса
- Русский малый бизнес
- Управление малым бизнесом
- Posix для малого бизнеса
- Телефония как малый бизнес
- Телефония на Java для малого бизнеса