Конспект лекций по курсу Выбранные вопросы информатики (часть 2) для специальности Информатика Графика

Вид материалаКонспект
Приложение StreamToken
Описание исходного текста приложения
Приложение DirectFile
Описание исходного текста приложения DirectFile
Создание базы данных
Класс SimpleDBMS
Конструктор класса SimpleDBMS
Метод close
Метод GetRecordByNumber
Подобный материал:
1   ...   9   10   11   12   13   14   15   16   17

Приложение StreamToken

В приложении StreamToken мы демонстрируем использование класса StreamTokenizer для разбора входного потока.

Вначале приложение запрашивает у пользователя строку для разбора, записывая ее в файл. Затем этот файл открывается для чтения буферизованным потоком и разбирается на составные элементы. Каждый такой элемент выводится в отдельной строке, как это показано на рис. 4.



Рис. 4. Разбор входного потока в приложении StreamToken

Обратите внимание, что в процессе разбора значение 3.14 было воспринято как числовое, а 3,14 - нет. Это потому, что при настройке разборщика мы указали, что символ '.' является обычным.

Исходный текст приложения

Исходный текст приложения StreamToken представлен в листинге 1.

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

}

}

}

Описание исходного текста приложения

После ввода строки с клавиатуры и записи ее в файл через поток наше приложение создает входной буферизованный поток, как это показано ниже:

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


Приложение DirectFile

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

В файле данных хранятся записи, сосотящие из двух полей - текстового и числового. Текстовое поле с названием name хранит строки, закрытые смиволами конца строки "\r\n", а числовое с названием account - значения типа int.

В меню File нашего приложения есть строки New и View records (рис. 5).



Рис. 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. Зная номер записи, можнор легко вычислить смещение в файле индексов, по которому хранится смещение нужной записи в файле данных. Если извлечь это смещение, то можно выполнить позиционирование в файле данных с целью чтения нужной записи, что и делает наше приложение.

Исходный текст приложения DirectFile

Исходный текст приложения DirectFile представлен в листинге 2.

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

}

}

Описание исходного текста приложения DirectFile

Для работы с базой данных мы создали класс SimpleDBMS, определив в нем конструктор, методы для добавления записей, извлечения записей по их порядковому номеру, а также метод для закрытия базы данных.

Создание базы данных

Когда пользователь выбирает из меню 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();

Просмотр записей базы данных

При выборе строки 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();

Класс SimpleDBMS

Рассмотрим теперь класс SimpleDBMS.

В этом классе определено три поля с именами idx, dat и idxFilePointer, а также три метода.

Поля класса SimpleDBMS

Поля idx и dat являются объектами класса RandomAccessFile и представляют собой, соответственно, ссылки на файл индекса и файл данных. Поле idxFilePointer типа long используется как рабочее и хранит текущее смещение в файле.

Конструктор класса SimpleDBMS

Конструктор класса SimpleDBMS выглядит достаточно просто. Все, что он делает, - это создает два объекта класса RandomAccessFile, соответственно, для индекса и данных:

idx = new RandomAccessFile(IndexFile, "rw");

dat = new RandomAccessFile(DataFile, "rw");

Так как в качестве второго параметра конструктору класа RandomAccessFile передается строка "rw", файлы открываются и для чтения, и для записи.

Метод close

Метод close закрывает файлы индекса и данных, вызывая метод close из класса RandomAccessFile:

idx.close();

dat.close();

Метод 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);

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

Метод 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 возвращает в качестве извлеченной строки записи базы данных.