GrabDuck

Приложение DirectFile

:

Оглавление

Работа с файлами
Классы потоков
Стандартные потоки
Потоки и файлы
Запись и чтение потоков
Закрывание потоков
Сброс буферов
Потоки в памяти
Stream Tokenizer
String Tokenizer
Класс File
Произвольный доступ
Приложение StreamToken
Приложение DirectFile

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

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

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

pic05.gif (2516 bytes)

Рис. 5. Строки меню File

С помощью строки New вы можете создать базу данных, состоящую из трех записей. Если выбрать из меню File строку View records, на экране появится диалоговая панель с содержимым этих записей (рис. 6).

pic06.gif (2365 bytes)

Рис. 6. Содержимое трех первых полей базы данных

Вместо символа перевода строки в диалоговой панели отображается маленький квадратик.

Дамп создаваемого файла данных приведен на рис. 7.

pic07.gif (1956 bytes)

Рис. 7. Дамп файла данных

Из этого дампа видно, что после первого запуска приложения в файле данных имеются следующие записи:

Номер записи Смещение в файле данных Поле name Поле account
0 0 Ivanov 1000
1 12 Petrov 2000
2 24 Sidoroff 3000

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

Так как поле name имеет переменную длину, для обеспечения возможности прямого доступа к записи по ее номеру необходимо где-то хранить смещения всех записей. Мы это делаем в файле индексов, дамп которого представлен на рис. 8.

pic08.gif (1575 bytes)

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