Next Previous Contents

Unix Review Column 24 -- Постоянные данные

Randal Schwartz

Январь 1999

Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>

Каждый запуск программы на Perl начинается с чистого мира, в котором создаются и обрабатываются новые данные. Однако в настоящем мире некоторые вези должны сохраняться. Так, как сохранить данные, чтобы позже получить к ним доступ, возможно при другом запуске той же самой программы, или даже другой программы?

Одним из самых простых способов является передача массива данных в текстовый файл по ``одной записи на строку''. Например, в начале вашей программы вы можете загружать массив данных:

    open DB, "<file" or die "open: $!";
    @data = <DB>;
    close DB;

и затем использовать @data как список строк7 Вы можете вставлять и удалять строки или добавлять новые записи так, как вы сами захотите. Когда вы закончите работать, то вы просто сохраняете массив обратно в файл:

    open DB, ">file" or die "create: $!";
    print DB @data;
    close DB;

Одним из ограничений этого метода является то, что он может достаточно просто сохранять в заданный файл только один массив. Также необходимо, чтобы данный были соответственно организованы: они не должны содержать символов новых строк, и должны разделяться одиночным символом новой строки.

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

    open DB, "<file" or die "open: $!";
    %mydata = map /(.*)\t(.*)/, <DB>;
    close DB;

Здесь мы использовали операцию map для выделения ключа и соответствующего ему значения для каждой из строк нашего хранилища. В конце программы мы просто выполняем обратный процесс:

    open DB, ">file" or die "create: $!";
    print DB map "$_\t$mydata{$_}\n",
        keys %mydata;
    close DB;

Еще раз операция map облегчает нам работу. Мы все еще страдаем от ограничения накладываемые на данные: внутри ключей или значений не должно находится символов табуляции или новой строки.

Для практически любых значений и ключей мы можем перешагнуть на следующий уровень утонченности: DBM. DBM является небольшой базой данных создаваемой пакетами DBM поставляемыми с почти каждой версией Unix начиная с середины 70 гг. База данных состоит из пары ключ-значение произвольных двоичных данных, ограниченных размером примерно 1K для каждой пары ключ-значение.

Perl производит доступ к DBM используя хэш DBM, создаваемый примерно так:

    dbmopen %DB, "my_database", 0644
        or die "dbmopen: $!";

Из программы, любой доступ к хэшу %DB, будет автоматически преобразовываться в соответствующее получение или сохранение данных в базе данных. База данных хранится в файлах my_database.dir и my_database.pag находящихся в текущем каталоге, которые создаются с правами доступа 644 (в восьмеричном виде), если они до этого не существовали. Например, давайте поместим в базу данных некоторые произвольные данные:

    for (1..10) {
        $DB{"item $_"} = rand(1000);
    }

Каждая запись в %DB немедленно отражается в файлах базы данных. Позднее, мы сможем заново открыть базу данных в следующий раз запуска программу (иди даже запуская другую программу):

    for (keys %DB) {
        print "$_ => $DB{$_}\n";
    }

и вы увидите теже произвольные данные, которые были сохранены раньше. Почти все что работает со стандартным хэшем, также будет работать с хэшем DBM. Однако ключи и значения ограничены длиной записи, и значение undef не сохраняется (оно возвращается как пустая строка).

Другим, более серьезным ограничением является то, что значения хэша DBM являются строками, и таким образом не могут быть другими типами данных, например ссылками. Для обхода это ограничения, нам надо быть более изощренными. Одним из простых подходов к решению этой проблемы является расширение возможностей DBM используя пакет MLDBM, который можно найти на CPAN (хранимом на http://www.cpan.org и других местах по всему миру.

Пакте MLDBM использует технологию DBM, но автоматически преобразует (serializes) каждое значение, так что, если даже данные являются ссылкой на структуру данных, то все данные правильно сохраняются в DBM. Это выглядит примерно так:

    use MLDBM; # do this once
    use Fcntl; # also once

    tie %DB, MLDBM, "my_database",
        O_RDWR | O_CREAT, 0644;

Это похоже на вызов dbmopen, показанный выше, но теперь значения (с некоторыми ограничениями) могут быть почти произвольным сочетанием ссылок на списки и хэши структур данных.

Однако мы все еще себе заносчиво ведем с объектами, размер ключей и значений составляет меньше 1К. Так, что с помощью относительно большего молотка мы можем забить большой гвоздь. А нашем случае большим молотком является модуль Data::Dumper (который также можно найти на CPAN). Этот модуль может получить ссылку на структуру данных и создать код на Perl, который точно воспроизведет структуру данных, даже если данные являются сложными.

Вот как он работает. Сначала в начале программы загрузим модуль:

    use Data::Dumper;

Затем для сохранения данных, на которые указывает $data, откроем текстовый файл и запишем в него результаты выдачи Data::Dumper для наших данных:

    open DB, ">file" or die "create: $!";
    print Data::Dumper->
        Dump([$data], [qw($data)]);
    close DB;

Текст в файле file является настоящим кодом Perl, который может быть выполнен для восстановления значения переменной $data, которое она имела в момента сохранения данных. Он также является хорошо читаемым (и с правильно сделанными отступами), так что вы можете использовать этот модуль для отладки ваших сложных структур данных:

Для загрузки $data при следующих запусках, просто выполните этот файл: выполните его как код Perl:

    do 'file';

и значение $data будет восстановлено. Вы можете поместить в один файл несколько структур данных. Для получения детального описания смотрите документацию по модулю.

Одним из недостатков Data::Dumper заключается в том, что он ен может сохранять ссылки на код или точно сохранять числа с плавающей точкой, поскольку весь его вывод должен быть в виже строки. Другим недостатком является то, что создание удобного для чтения человеком кода, просто для того, чтобы его потом обрабатывать с помощью Perl, иногда становится слишком накладным.

Существует более мощный и быстрый способ сохранения произвольных структур данных используя модуль Storable (который также можно найти на CPAN). Этот модуль также организует данные как серии байтов, но вместо создания понятного для человека текста, он сохраняет данные в формате понятном только тому, кто прочитал исходный код данного модуля. В общем, это не является спорным вопросом, поскольку мы будем использовать Storable tи для сохранения и восстановления значений.

Использование модуля иногда является более легким, чем при использовании Data::Dumper:

    use Storable;
    store $data, "file";

Мне даже не надо было открывать файл, поскольку модуль делает это за меня. А теперь как получить сохраненные данные...

    $data = retrieve "file";

Ок. Выполнив немного работы, я даже смогу сделать, чтобы данные сохранялись автоматически в конце программы, создав небольшой объект с методом DESTROY:

    package Persist;
    use Storable;
    sub TIESCALAR {
        my $self = bless {}, shift;
        my $file = shift;
        $self->{File} = $file;
        $self->{Value} = -e $file ?
            retrieve $file : undef;
        $self;
    }
    sub FETCH {
        shift->{Value};
    }
    sub STORE {
        my $self = shift;
        $self->{Value} = shift;
    }
    sub DESTROY {
        my $self = shift;
        store $self->{Value}, $self->{File};
    }

    # ... ниже находится основной код ...

    package main;
    tie $data, Persist, 'my_persistent_file';
    $data->{"Fred" . time} = "barney $$";
    $data->{"Wilma" . time} =
         ['pebbles', 'bamm-bamm', time, $$];
    ## для просмотра содержимого структуры (не для сохранения):
    use Data::Dumper;
    print Data::Dumper->Dump([$data], [qw($data)]);

И когда программа заканчивает работу, она автоматически сохраняет $data в файл, так что, когда вы перезапустите программу, то содержимое $data будет автоматически восстановлено! Это работает, потому что $data был привязан к пакету Persist так, что все сохранение и получения значений автоматически вызывают нижележащие методы. Мы получим уведомление о неминуемой гибели в конце программы (или когда переменная выйдет из области видимости) благодаря методу DESTROY.

Этот пример является только скелетом структуры. Вы вероятно захотите добавить в него обработку ошибок. Вы даже можете поместить обработчик в подпрограмму TIESCALAR для того, чтобы автоматически вызывать функцию flock если файл существует, для того, чтобы быть уверенными, что только одна программа обновляет данные, а затем снимать блокировку при разрушении переменной. Великолепно.

Я надеюсь, что вам понравилась эта небольшая экскурсия в мир постоянных данных Perl. Иногда вы можете взять их с собой.


Next Previous Contents