WebClub - Всероссийский Клуб Веб-разработчиков
WebClub.RU » Архив » Ввод-вывод на Java.

Ввод-вывод на Java.


Дата публикации: 17-03-2013

ВВОД-ВЫВОД

Для обслуживания ввода-вывода в Java применяются ряд классов, большей частью расположенных в пакете java.io.

Класс File.

Объект этого класса представляет файл как физический объект. Он не отражает ни внутренней структуры файла, ни его содержимого, а служит только для именования файла и манипуляций с ним, как с целостным физическим объектом. Можно создать экземпляр класса File как для уже существующего (физически) файла, так и для еще несуществующего. В первом случае мы можем узнать действительное наличие файла, длину файла, его тип (директория это или файл), уничтожить (стереть), переименовать и т.п. - эти функции похожи на те, которые доступны нам в приложениях управления файлами типа File Manager, Проводник или Norton Commander. Класс File не предоставляет методов ни открывать, ни закрывать файл, ни манипулировать данными внутри файла. (Если создан экземпляр класса File для еще физически несуществующего файла, то манипуляции, которые мы можем провести над файлом, отчасти зависят от того, открыт ли поток ввода/вывода, записывающего в этот файл, или закрыт.)
Конструкторы класса File:
public File(String name) - создает объект класса File, ассоциированный с физическим файлом имеющим путь и имя, заданное строкой name;
public File(String path, String name) - создает объект класса File, ассоциированный с физическим файлом имеющим путь path и имя name;
public File(File dir, String name) - создает объект класса File, ассоциированный с физическим файлом имеющим имя name и расположенным в директории, заданной объектом dir;
Основные методы класса File:
public boolean exists() - проверяет физическое наличие файла;
public boolean isFile() - проверяет, является ли файл "обычным" файлом (а не директорией);
public boolean isDirectory() - проверяет, является ли файл директорией;
public long length() - возвращает длину файла в байтах;
public boolean mkdir() - создает из данного объекта директорию;
public boolean mkdirs() - также создает из данного объекта директорию, а также все родительские директории, перечисленные в объекте;
public boolean renameTo(File newname) - переименовывает файл в newname;
public String[] list() - возвращает список файлов в директории, заданной объектом (объект должен при этом представлять директорию);
public String[] list(FilenameFilter filter) - возвращает список файлов в директории по маске, заданной filter;
public boolean delete() - удаляет файл (если файл является директорией, то она должна быть пуста);
Переменная класса public static final char separatorChar - символ, служащий разделителем в полном имени файла. Например, для Windows это будет '\', а для UNIX - '/'.

Класс RandomAccessFile

Этот класс предоставляет возможность читать из файла и писать в файл в режиме "прямого доступа", т.е. в любой позиции файла. Для этого существует специальный "указатель" на текущую позицию, который может произвольно перемещаться по файлу.
Конструкторы класса:
public RandomAccessFile(File file, String mode) - создает поток ввода/вывода (иначе говоря, открывает) файл прямого доступа заданный экземпляром file класса File и с режимом доступа, заданным строкой mode, которая может быть или "r" (для файла "только для чтения") или "rw" (для файла "для чтения/записи");
public RandomAccessFile(String name, String mode) - открывает файл прямого доступа c с именем, заданным строкой name и с режимом доступа, заданным строкой mode. Это конструктор - пример открытия потока ввода/вывода без использования экземпляра класса File, так сказать, "напрямую". Такого рода конструкторы есть и у некоторых других потоков ввода/вывода.
Основные методы класса RandomAccessFile:
public native long length() - возвращает длину файла в байтах;
public native void seek(long pos) - перемещает указатель файла на заданную позицию (первая позиция файла = 0);
public native long getFilePointer() - возвращает позицию, на которой находится в данный момент указатель;
public native void close() - закрывает файл;
public int skipBytes(int n) - перемещает указатель файла на позицию, на n больше текущей;
Остальные методы представляют из себя разнообразные методы read и write, предназначенные для чтения из файла и записи в файл примитивных типов - числовых, булевых, строковых и байтовых массивов.

Пример использования RandomAccessFile, для создания и просмотра примитивной базы данных. В файле база данных хранятся записи следующей структуры: день месяца (byte), месяц (byte), год (short), состояние (boolean - 1 byte), текст (100 chars = 200 bytes). Итого длина каждой записи - 205 байт. В начале файла записывается количество записей (int - 4 bytes), затем последовательно записи. Т.к. длина каж дой записи фиксирована (205 байт), то всегда можно найти позицию начала нужной записи в файле: позиция = 4+205*номер записи. Чтобы не загромождать программу, в ней не предусмотрены изменение и удаление записей (только добавление новых записей и просмотр с простейшей навигацией), а также не проверяются введённые данные. Интерфейс также предельно упрощен.

//DB (RandomAccessFile)
import java.io.*;
import java.awt.*;
import java.awt.event.*;

public class DB_RAF extends Frame
implements WindowListener, ActionListener {
TextArea tData;
TextField tDay, tMonth, tYear;
Checkbox chState;
Button bWrite, bPrev, bNext;
Panel pN, pS;
int recNbr; //кол-во записей в базе и
int pointer; //номер текущей записи
RandomAccessFile f; //файл базы данных
char buf[]; //вспомогательный массив char

public DB_RAF(String s) { //Конструктор
super(s); //Создание элементов интерфейса
tData = new TextArea(); //и регистрация Listener'ов
tDay = new TextField(2);
tMonth = new TextField(2);
tYear = new TextField(4);
chState = new Checkbox();
bWrite = new Button("Add record");
bPrev = new Button("");
pN = new Panel(); pN.setBackground(Color.lightGray);
pN.add(tDay); pN.add(tMonth); pN.add(tYear); pN.add(chState);
add(pN, "North");
pS = new Panel(); pS.setBackground(Color.lightGray);
pS.add(bWrite); pS.add(bPrev); pS.add(bNext);
add(pS, "South");
add(tData, "Center");
addWindowListener(this);
bWrite.addActionListener(this);
bNext.addActionListener(this);
bPrev.addActionListener(this);
setBounds(50,50,300,200);
setVisible(true);
}

public static void main(String arg[]) {
DB_RAF db = new DB_RAF("DB (RandomAccessFile)");
db.buf = new char[100]; // создаем временный буфер
try { //открытие файла
db.f = new RandomAccessFile("db_raf.dat", "rw");
db.recNbr = db.f.readInt(); //чтение кол-ва записей с нулевой позиции
} catch(IOException ioe) {}
db.pointer = 0;
db.showRec(db.pointer); //показываем первую запись
}

public void actionPerformed(ActionEvent ae) {
if (ae.getSource()==bWrite) { //Добавление записи в файл
try {
f.seek(4+205*recNbr); //переход конец файла
//запись содержимого поля дня как байта:
f.writeByte(Byte.parseByte(tDay.getText().trim()));
//запись содержимого поля месяца как байта:
f.writeByte(Byte.parseByte(tMonth.getText().trim()));
//запись содержимого поля года как short:
f.writeShort(Short.parseShort(tYear.getText().trim()));
//запись состояния как boolean:
f.writeBoolean(chState.getState());
StringBuffer s = new StringBuffer(tData.getText().trim());
for (int i=0; i<100; i++) //заполнение врем. буфера
buf[i] = (i//преобразование буфера в строку и запись ее в файл:
f.writeChars(new String(buf));
recNbr++; //увеличиваем счетчик записей на единицу
f.seek(0); f.writeInt(recNbr); //записываем в начало файла
} catch(IOException ioe) {}
}
//Показ след записи:
if (ae.getSource()==bNext && pointer0) {
pointer--; showRec(pointer);
}
}

public void showRec(int n) { //показ записи на экране
try {
f.seek(4+205*n); //переход на начало нужной записи
//читаем день, преобразуем в строку и заносим в поле:
tDay.setText(String.valueOf(f.readByte()));
//читаем месяц, преобразуем в строку и заносим в поле:
tMonth.setText(String.valueOf(f.readByte()));
//читаем год, преобразуем в строку и заносим в поле:
tYear.setText(String.valueOf(f.readShort()));
//читаем состояние, устанавливаем checkbox:
chState.setState(f.readBoolean());
//читаем в буфер 100 символов (200 байт):
for (int i=0; i<100; i++) buf[i] = f.readChar();
//преобразуем буфер в строку и заносим в текстовое поле:
tData.setText((new String(buf)).trim());
}
catch(IOException ioe) {}
}

//Реализация WindowListener:
public void windowClosing(WindowEvent we) {
//закрытие файла перед выходом из программы:
try {f.close();} catch(IOException ioe){}
System.exit(0);
}
public void windowClosed(WindowEvent wEvt){}
public void windowOpened(WindowEvent wEvt){}
public void windowIconified(WindowEvent wEvt){}
public void windowDeiconified(WindowEvent wEvt){}
public void windowActivated(WindowEvent wEvt){}
public void windowDeactivated(WindowEvent wEvt){}
}

Из этого примера видно, что применение RandomAccessFile имеет то преимущество, что позволяет считать в память только необходимую часть файла. (Это полезно в случае, если файл очень большой). Чтобы реализовать такое считывание, необходимо иметь возможность точно рассчитать позицию нужного участка в файле. Если в файле записаны примитивные типы, то задача упрощается, т.к. длина их известна. Но необходимость записи строк переменной длины драматически усложняет задачу. В нашем примере пришлось делать строку длиной точно в 100 символов (200 байт), когда надо удлиняя строку пробелами (тогда нерационально расходуем дисковое пространство) или обрезая её, если она длиннее 100 символов (тогда теряем информацию).

Потоки ввода-вывода (Streams and Readers/Writers)

В программировании потоки ввода-вывода подобны реальным потокам - они протекают "безвозвратно", без возможности вернуться назад. Например, поток ввода можно представить в виде трубы с заслонкой:

Tруба (поток)-¬ ---Заслонка(чтение)
¦ ¦
====================¦--
--- ()()()()()()()()()()¦()
¦ ====================¦== ()
¦

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

Иерархия потоков ввода-вывода (streams) показана на рисунке. Сплошные линии обозначают наследования, а пунктирные - какой поток на основе какого образуется. Существует также другая иерархия потоков - reader'ы и writer'ы, очень похожая на иерархию streams. Readers/writers отличаются от streams тем, что оперируют не байтами, как элементарными частицами данных, а двухбайтовыми символами Unicode.

Далее мы будем рассматривать иерархию на примере входных потоков, но выходные потоки организованы в почти также, только вместо Input везде следует читать Output, а там где мы упоминаем чтение из потока, можно подставить "запись в поток".

Базовый класс, от которого наследуются большинство остальных классов потоков - InputStream. Но реально использовать его (т.е. создавать его экземпляры) нельзя, т.к. этот класс - абстрактный, в нем не реализован один метод - метод чтения одиночного байта.
Такое положение обусловлено тем, что такая базовая функция, как чтение одиночного байта, зависит от платформы, и только реализовав её, мы можем получить универсальный метод, который используется затем во всех остальных методах. Таким "реальным воплощени ем" InputStream является, например, класс FileInputStream, в котором реализован метод read() (обратите внимание на его описание: public native int read() ).

Класс FileInputStream

Этот низкоуровневый поток ввода предназначен для чтения байтов из файла.
Конструкторы:
public FileInputStream(File file) - создает поток чтения из файла, заданного параметром file;
public FileInputStream(FileDescriptor fdObj) - создает поток чтения из файла, заданного параметром fdObj;
public FileInputStream(String name) - создает поток чтения из файла, заданного строкой name. Этот конструктор позволяет создать поток без использования переменной класса File, так сказать "напрямую".

Методов чтения у этого потока всего три:
public native int read() - возвращает очередной прочитанный одиночный байт, или -1, если достигнут конец файла;
public int read(byte b[]) - читает до b.length байтов в массив b, возвращает кол-во прочитанных байтов или -1, если достигнут конец файла;
public int read(byte b[], int off, int len) - читает до len байтов в массив b, начиная с члена массива b[off]. Возврашает кол-во прочитанных байтов или -1, если достигнут конец файла;
public native long skip(long n) - пропускает n байтов в потоке. Возвращает кол-во пропущенных байтов.

Класс FilterInputStream

Этот класс является суперклассом всех потоков-фильтров, преобразующих байты низкоуровневого потока в более сложные типы данных. В классе просто переопределены все методы чтения байтов его родителя - InputStream. Конструктор один - protected FilterInputStream(InputStream in) - создает поток на основе низкоуровневого потока in. Обычно это экземпляр FileInputStream.

Класс BufferedInputStream

Этот поток также не имеет никаких других методов чтения, кроме чтения одиночного байта и чтения массива байтов, но зато обладает важным свойством - он буферизирует чтение из нижележащего низкоуровневого потока, считывая байты блоками во внутренний буфер, так что следующий в цепочке высокоуровневый поток будет читать данные уже из этого буфера. Например, можно создать такую цепочку:
DataInputStream dis = new DataInputStream(new BufferedInputStream(new
FileInputStream("file.dat")));
или
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new
FileOutputStream("file.dat")));
Конструкторы класса:
public BufferedInputStream(InputStream in) - создает буферизированный поток на основе низкоуровневого потока in с размером внутреннего буфера по умолчанию (512 байтов).
public BufferedInputStream(InputStream in, int size) - создает буферизированный поток на основе низкоуровневого потока in с размером внутреннего буфера size байтов).

Класс DataInputStream

Потоки этого класса предназначены для чтения разнообразных типов данных - всех числовых примитивных типов, булевых, строк текста, беззнаковых байтов, беззнаковых коротких целых (short), строк в формате UTF. По своим возможностям чтения данных близок к Ran Конструктор только один - public DataInputStream(InputStream in) - создает поток на основе низкоуровневого потока in. Обычно это экземпляр класса FileInputStream. Методы чтения похожи на методы RandomAccessFile, поэтому рассмотрим подробнее только один: public final String readUTF() - читает из потока строку, записанную в поток методом writeUTF(String) класса DataOutputStream и возвращает Unicode строку;
В классе DataOutputStream есть соответствующий метод:
public final void writeUTF(String str) - записывает в поток строку в кодировке UTF-8, причем в начале строки записываются два байта (short), указывающих на количество последующих байтов (не символов!) в строке.

Пример:
Используем класс DataInputStream в программе примитивной базы данных из нашего предыдущего примера.

//DB (DataInputStream and DataOutputStream)
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;

public class DB_DIOS extends Frame implements WindowListener, ActionListener
{
TextArea tData;
TextField tDay, tMonth, tYear;
Checkbox chState;
Button bWrite, bPrev, bNext;
Panel pN, pS;
DataInputStream dis; //входной поток
DataOutputStream dos; //выходной поток
int recNbr; //кол-во записей в базе и
int pointer; //номер текущей записи
Vector v; //Vector для хранения записей
Record r; //класс - запись

public DB_DIOS(String s) //Конструктор
{
super(s); //Создание элементов интерфейса
tData = new TextArea(); //и регистрация Listener'ов
tDay = new TextField(2);
tMonth = new TextField(2);
tYear = new TextField(4);
chState = new Checkbox();
bWrite = new Button("Add record");
bPrev = new Button("");
pN = new Panel(); pN.setBackground(Color.lightGray);
pN.add(tDay); pN.add(tMonth); pN.add(tYear); pN.add(chState);
add(pN, "North");
pS = new Panel(); pS.setBackground(Color.lightGray);
pS.add(bWrite); pS.add(bPrev); pS.add(bNext);
add(pS, "South");
add(tData, "Center");
addWindowListener(this);
bWrite.addActionListener(this);
bNext.addActionListener(this);
bPrev.addActionListener(this);
setBounds(50,50,300,200);
setVisible(true);
}

public static void main(String arg[])
{
DB_DIOS db = new DB_DIOS("DB (DataInputStream and DataOutputStream)");
db.readData(); //вызываем метод чтения данных из файла
}

public void actionPerformed(ActionEvent ae)
{
if (ae.getSource()==bWrite) //добавление record в vector
{
r = new Record();
r.day = Byte.parseByte(tDay.getText().trim());
r.month = Byte.parseByte(tMonth.getText().trim());
r.year = Short.parseShort(tYear.getText().trim());
r.state = chState.getState();
r.data = tData.getText().trim();
v.addElement(r);
recNbr++;
pointer = recNbr-1;
}

if (ae.getSource()==bNext && pointer0)
//Показ предыдущей записи:
{
pointer--; showRec(pointer);
}
}

public void showRec(int n) //показ записи на экране
{
r = (Record)v.elementAt(n);
tDay.setText(String.valueOf(r.day));
tMonth.setText(String.valueOf(r.month));
tYear.setText(String.valueOf(r.year));
chState.setState(r.state);
tData.setText(r.data);
}

public void readData() //метод чтения всех данных из файла
{ //и занесения записей в вектор
v = new Vector();
recNbr = 0;
try {
//Создаем поток ввода из файла db_dios.dat
dis = new DataInputStream ( new BufferedInputStream
(new FileInputStream ("db_dios.dat")));
for (;;) {
r = new Record(); //создаем объект-запись
r.day = dis.readByte(); //читаем данные
r.month = dis.readByte(); //и заносим их
r.year = dis.readShort(); // в объект-запись
r.state = dis.readBoolean();
r.data = dis.readUTF();
v.addElement(r); //добавляем запись в вектор
recNbr++; //увеличиваем счетчик записей
}
}
catch(IOException ioe){}
//закрываем файл (в любом случае):
finally{try{dis.close();} catch(IOException ioe){}}
pointer = 0; //указатель - на первую запись
showRec(pointer); //показываем запись на экране
}

public void writeData() //метод записи данных в файл
{
try { //создаем поток вывода в файл db_dios.dat
dos = new DataOutputStream (new BufferedOutputStream
(new FileOutputStream ("db_dios.dat")));
for (int i=0; i{ //до последней
r = (Record)v.elementAt(i); //берем из вектора запись
dos.writeByte(r.day); //пишем в поток
dos.writeByte(r.month); //данные
dos.writeShort(r.year); //соответствующими
dos.writeBoolean(r.state); //методами
dos.writeUTF(r.data);
}
}
catch(IOException ioe){}
//закрываем файл (в любом случае):
finally{try{dos.close();} catch(IOException ioe){}}
}

//Реализация WindowListener:
public void windowClosing(WindowEvent we) {
//при выходе из программы записываем данные в файл
//(если есть что писать, т.е. есть вектор с записями):
if (v!=null) writeData();
System.exit(0);
}
public void windowClosed(WindowEvent wEvt){}
public void windowOpened(WindowEvent wEvt){}
public void windowIconified(WindowEvent wEvt){}
public void windowDeiconified(WindowEvent wEvt){}
public void windowActivated(WindowEvent wEvt){}
public void windowDeactivated(WindowEvent wEvt){}
}

class Record //класс-запись
{
byte day;
byte month;
short year;
boolean state;
String data;
}

Программа несколько упростилась, а главное - теперь можно записать в файл строку почти любой длины (до 32 Кбайт)- при коротких строках нет необходимости дополнять ее пробелами до нужной длины, а длинные строки не надо обрезать. Кроме того, файл данных ст ал еще компактнее, т.к. английские буквы записываются методом writeUTF() как однобайтовые символы (в RandomAccessFile они пишутся двухбайтовыми кодами Unicode, как и символы других языков). Единственный недостаток - файл данных теперь надо загружать и держать в памяти целиком, впрочем, эта проблема тоже разрешима, хотя и специальными методами.

Класс ObjectInputStream

Это как бы "расширенный" класс DataInputStream. Потоки этого класса могут читать не только такие типы данных, с какими может оперировать DataInputStream, но и целые объекты, предварительно записанные туда ObjectOutputStream. Для того, чтобы быть записанн ым в поток, объект должен обладать свойством "сериализации" - исполнять интерфейс Serializable (или Externalizable). В свою очередь, каждая переменная объекта тоже должна обладать этим свойством. Обычно явно объявлять интерфейс Serializable необходимо то лько для вновь созданных (пользовательских) классов, т.к. большинство библиотечных классов уже реализуют его. Записываются в поток и могут быть прочитаны только "глобальные" нестатические переменные экземпляра класса. Не записываются локальные переменные методов, как и информация о самих методах. Также не записываются в поток переменные, объявленные с модификатором transient.

В качестве примера изменим нашу программу так, чтобы она записывала в файл и читала из него экземпляры класса Record целиком:

//DB (ObjectInputStream and ObjectOutputStream)
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class DB_OIOS extends Frame implements WindowListener,
ActionListener {
TextArea tData;
TextField tDay, tMonth, tYear;
Checkbox chState;
Button bWrite, bPrev, bNext;
Panel pN, pS;
ObjectInputStream ois; //входной поток
ObjectOutputStream oos; //выходной поток
int recNbr; //кол-во записей в базе и
int pointer; //номер текущей записи
Vector v; //Vector для хранения записей
Record r; //класс - запись

public DB_OIOS(String s) { //Конструктор
super(s); //Создание элементов интерфейса
tData = new TextArea(); //и регистрация Listener'ов
tDay = new TextField(2);
tMonth = new TextField(2);
tYear = new TextField(4);
chState = new Checkbox();
bWrite = new Button("Add record");
bPrev = new Button("");
pN = new Panel(); pN.setBackground(Color.lightGray);
pN.add(tDay); pN.add(tMonth); pN.add(tYear); pN.add(chState);
add(pN, "North");
pS = new Panel(); pS.setBackground(Color.lightGray);
pS.add(bWrite); pS.add(bPrev); pS.add(bNext);
add(pS, "South");
add(tData, "Center");
addWindowListener(this);
bWrite.addActionListener(this);
bNext.addActionListener(this);
bPrev.addActionListener(this);
setBounds(50,50,300,200);
setVisible(true);
}

public static void main(String arg[]) {
DB_OIOS db = new DB_OIOS("DB (ObjectInputStream and ObjectOutputStream)");
db.readData(); //вызываем метод чтения данных из файла
}

public void actionPerformed(ActionEvent ae) {
if (ae.getSource()==bWrite) { //добавление record в vector
r = new Record();
r.day = Byte.parseByte(tDay.getText().trim());
r.month = Byte.parseByte(tMonth.getText().trim());
r.year = Short.parseShort(tYear.getText().trim());
r.state = chState.getState();
r.data = tData.getText().trim();
v.addElement(r);
recNbr++;
pointer = recNbr-1;
}

if (ae.getSource()==bNext && pointer0) { //Показ предыдущей записи:
pointer--; showRec(pointer);
}
}

public void showRec(int n) { //показ записи на экране
r = (Record)v.elementAt(n);
tDay.setText(String.valueOf(r.day));
tMonth.setText(String.valueOf(r.month));
tYear.setText(String.valueOf(r.year));
chState.setState(r.state);
tData.setText(r.data);
}

public void readData() //метод чтения всех данных из файла
{ //и занесения записей в вектор
v = new Vector();
recNbr = 0;
try {
//Создаем поток ввода из файла db_obj.dat
ois = new ObjectInputStream ( new BufferedInputStream
( new FileInputStream ("db_obj.dat")));
for (;;) { //читаем объекты-записи из файла и сразу добавляем
//их в вектор (без лишних конвертаций):
v.addElement(ois.readObject());
recNbr++; //увеличиваем счетчик записей
}
}
catch(IOException ioe){}
//дополнительно должны перехватывать это исключение:
catch(ClassNotFoundException cnfe) {}
//закрываем файл (в любом случае):
finally{try{ois.close();} catch(IOException ioe){}}
pointer = 0; //указатель - на первую запись
showRec(pointer); //показываем запись на экране
}

public void writeData() //метод записи данных в файл
{
try { //создаем поток вывода в файл db_obj.dat
oos = new ObjectOutputStream ( new BufferedOutputStream
( new FileOutputStream ("db_obj.dat")));
for (int i=0; i//последней берем объекты из вектора и пишем их в поток:
oos.writeObject(v.elementAt(i));
}
catch(IOException ioe){}
//закрываем файл (в любом случае):
finally{try{oos.close();} catch(IOException ioe){}}
}

public void windowClosing(WindowEvent we)
{ //при выходе из программы записываем данные в файл
//(если есть что писать, т.е. есть вектор с записями):
if (v!=null) writeData();
System.exit(0);
}
public void windowClosed(WindowEvent wEvt){}
public void windowOpened(WindowEvent wEvt){}
public void windowIconified(WindowEvent wEvt){}
public void windowDeiconified(WindowEvent wEvt){}
public void windowActivated(WindowEvent wEvt){}
public void windowDeactivated(WindowEvent wEvt){}
}

class Record implements Serializable { //класс-запись
byte day;
byte month;
short year;
boolean state;
String data;
}

Обратите внимание на класс Record - он объявлен как реализующий интерфейс Serializable. Если не сделать этого, то программа работать не будет, хотя и скомпилируется. В этом варианте программы код еще более упростился, хотя выходной файл чуть-чуть увеличился - в начале файла записывается некоторая служебная информация об именах переменных и их классах. Метод записи объектов класса ObjectOutput Stream объявлен след. образом:
public final void writeObject(Object obj) throws IOException и может также выбрасывать исключения типа InvalidClassException (потомок IOException) - при ошибках во время сериализации объекта и NotSerializableException (также потомок IOException) - если о бъект, предназначенный для записи, не реализует интерфейс Serializable.

Метод чтения объектов класса ObjectInput Stream:
public final Object readObject() throws OptionalDataException, ClassNotFoundException, IOException - возвращает объект типа Object, поэтому обычно надо делать явное приведение типа в оригинальный тип перед использованием объекта.
Этот метод требует обработки исключений сразу трех типов, но если OptionalDataException является потомком IOException и может перехватываться вместе с ним, то ClassNotFoundException - потомок Exception и должно перехватываться отдельно. OptionalDataException возникает, если в прочитанном объекте присутствуют лишние переменные, которых нет в исходном классе. ClassNotFoundException возникает в случае, если тип (класс) прочитанного объекта не соответствует никакому найти для него исходный класс. Метод также может выбросить StreamCorruptedException - при несовпадении контрольных сумм для объекта.

Классы StringBufferInputStream и StringReader/StringWriter

Объекты этих классов создают входные потоки из строк.
Конструкторы:
public StringBufferInputStream(String s) - создает входной поток из строки s
public StringReader(String s) - создает входной поток из строки s
Методы read этих классов могут читать из потока отдельные байты и массивы байтов (символы и массивы символов для SringReader)

В классе StringBufferInputStream используются только младший байт каждого символа, поэтому класс StringBufferInputStream в JDK1.1 deprecated, рекомендуется использовать вместо него StringReader.Класс StringBufferInputStream не имеет своего аналога в выходных потоках (т.е. нет StringBufferOutputStream)

Класс StringWriter для записи из выходного потока в строку символов.
Конструкторы:
public StringWriter() - создает поток с размером буфера по умолчанию;
protected StringWriter(int initialSize) - создает поток с размером буфера initialSize.
Методы write этого класса могут записывать в поток отдельные символы, строки и массивы символов.

Классы ByteArrayInputStream / ByteArrayOutputStream и CharArrayReader / CharArrayWriter

Аналогично предыдущим классам создают входные потоки из массивов байтов (массивов символов для reader/writer) и пишут из выходных потоков в массивы байтов (массивы символов для reader/writer).

Потоки System.in и System.out
В классе java.io.System есть переменные in и out, которыми мы обычно пользуемся для ввода с клавиатуры и вывода на экран. Они представляют из себя ничто иное, как потоки:
public static final InputStream in - входной поток со стандартного устройства ввода (обычно клавиатура);
public static final PrintStream out - выходной поток на стандартное устройство вывода (обычно монитор).

Пример использования System.in:
Программа позволяет создать файл и записать в него символы непосредственно с клавиатуры.

//Typing on the screen and writing to file BY BYTE
import java.io.*;

public class TypeFile {
public static void main(String arg[]) {
if (arg.length<1) {
System.out.println("Usage: java TypeFile filename\nTo end input enter Ctrl+Z");
return;
}
File f = new File(arg[0]);
BufferedOutputStream txt = null;
BufferedInputStream bis = null;
byte b;
try {
txt = new BufferedOutputStream(new FileOutputStream(f));
bis = new BufferedInputStream(System.in);
for (;;) {
b = (byte)bis.read();
if (b==-1) {txt.flush(); break;} // if Ctrl+Z - конец ввода
txt.write(b);
}
}
catch(IOException ioe){System.out.println(ioe.getMessage());}
finally { try {bis.close(); txt.close();} catch(IOException ioe){} }
}
}

Пример использования System.out:
Программа распечатывает содержимое файла на экран побайтно.

//Reading file and printing it on screen BY BYTE
import java.io.*;

public class FileType {
public static void main(String arg[]) {
if (arg.length<1) {
System.out.println("Usage: java TypeFile filename ");
return;
}
File f= new File(arg[0]);
if (f.exists()) {
long len = f.length();
System.out.println("Length of the file: "+len
+" bytes\n======= begin of the file ========");
try {
BufferedInputStream txt = new BufferedInputStream
(new FileInputStream(f));
byte buf[] = new byte[(int)len];
int n = txt.read(buf, 0, (int)len);
System.out.print(new String(buf));
txt.close();
}
catch(FileNotFoundException fnfe)
{System.out.println(fnfe.getMessage());}
catch(IOException ioe){System.out.println(ioe.getMessage());}
}
else System.out.println("No such file!");
System.out.println("\n======== end of the file =========");
}
}

Другой пример использования System.out:
Программа распечатывает содержимое файла на экран, считывая из файла не побайтно, а целыми строками символов.

//Reading file and printing it on screen BY CHAR LINES
import java.io.*;

public class FileTypeL {
public static void main(String arg[]) {
if (arg.length<1) {
System.out.println("Usage: java TypeFile filename");
return;
}

File f= new File(arg[0]);
String s = null;
if (f.exists()) {
long len = f.length();
System.out.println("Length of the file: "+len
+" bytes\n======= begin of the file ========");
try {
BufferedReader txt = new BufferedReader (new FileReader(f));
for (;;) {
s = txt.readLine();
if (s==null) break;
System.out.println(s);
}
txt.close();
}
catch(FileNotFoundException fnfe)
{System.out.println(fnfe.getMessage());}
catch(IOException ioe){System.out.println(ioe.getMessage());}
}
else System.out.println("No such file!");
System.out.println("\n======== end of the file =========");
}
}


АНОНИМНЫЕ КЛАССЫ

Для анонимных классов характерно, что они определяются там же, где и создается экземпляр этого класса - внутри метода. Применение их очень ограничено. Во-первых, поскольку экземпляр анонимного класса не имеет имени, то и доступ к нему "извне" невозможен.
Во-вторых, как всякий класс, определенный внутри метода, методам анонимного класса доступны только final переменные окружающего их метода.
Создание и определение анонимного класса выглядит следующим образом:
new Xxxx() { //тело класса - определения методов }, где Xxxx - имя интерфейса Анонимный класс не может иметь конструкторов, т.к. у него нет имени (а имя класса и есть имя конструктора).Рекомендуется применять анонимные классы в основном только для определения интерфейсов Listener'ов.

Пример применения анонимного класса для определения интерфейса ActionListener: Обратите внимание: переменная TextArea t, объявлена как final, чтобы анонимный класс имел к ней доступ.

//Anonymous classes
import java.awt.*;
import java.awt.event.*;

public class Anonym extends Frame {
public static void main(String arg[]) {
Anonym a = new Anonym();
final TextArea t = new TextArea();
Button b = new Button("Push me!");
a.add(b, "South");
a.add(t, "Center");
b.addActionListener
(
new ActionListener() {
public void actionPerformed(ActionEvent ae) {
t.append("Pushed!\n");
}
}
);
a.setBounds(50,50,200,200);
a.setVisible(true);
}
}

В другом примере анонимный класс - кнопка, при перерисовке которой, происходит изменение цвета текста у текстового поля. По-видимому, абсолютно бесполезная программа, т.к. больше ничего с этой кнопкой поделать нельзя - ни обработку событий добавить, ни т екст, ни других каких свойств изменить - она невидима для остальной программы. Впрочем, она может быть полезна в "исследовательских целях" - можно понаблюдать, когда именно происходит перерисовка кнопки (т.е. исполнение её метода paint):

import java.awt.*; import java.awt.event.*;

public class Anonym2 extends Frame {
TextArea t;
public Anonym2() //Constructor
{
t = new TextArea("Absolutely useless programm!");
t.setFont(new Font("Serif", Font.BOLD, 20));
add(new Button("Push me!") {
public void paint(Graphics g) {
t.setForeground(new Color((int)(Math.random()*255),
(int)(Math.random()*255), (int)(Math.random()*255)));
}
}, "South" );
add(t, "Center");
}

public static void main(String arg[]) {
Anonym2 a = new Anonym2();
a.setBounds(50,50,400,100); a.setVisible(true);
}
}

Здесь также обратите внимание на переменную TextArea t - теперь она не обязана быть final, т.к. не является локальной переменной какого-либо метода.

Андрей Дуглас 1998г.
Домен продается

Популярное

Не так давно в сети появился новый сервис, под названием Dead Man Zero. Этот сервис сделал...
Рынок социальных площадок уже давно стал стабильным. Несмотря на то, что время от времени...
Artisteer 4 – единственный в своем роде продукт, позволяющий автоматизировать работу над созданием...
Апрель 2024 (1)
Октябрь 2018 (14)
Февраль 2017 (3)
Январь 2017 (1)
Август 2016 (1)
Май 2016 (2)

Карта сайта: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41

Друзья сайта



Случайная цитата

Blair P. Houghton:

"По моему эгоистическому мнению, большинство программ на C должны быть отформатированы с отступами на 2 метра вниз и засыпанными землей."

Опрос

Какой текстовый редактор Вы используете?

OpenOffice
AbiWord
Notepad++
UltraEdit
PSPad
Microsoft Office
Microsoft Блокнот
Другой...