Приложение DirectFile

Оглавление

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

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

Назад Вперед

Java | Продукты и решения | Технологии | Сервис и обучение
О компании | Партнеры | Новости | Поиск