Глава 6 - Кафедра анализа данных и исследования операций

advertisement
Ульман Дж. Базы
(глава 6)
ББК
УДК
У51
данных на
32.973.2-01
У51
[[681.3.016: 519.682] +681.3-181.4])-
Паскале
03 == 20
Ульман Дж.
Базы данных на Паскале/Пер, с англ. М. В. Сергиевского, А. В. Шалашова; Под ред. Ю. И. Топчеева.-М.:
Машиностроение, 1990.-368.: ил.
ISBN 5-217-00628-5
В книге английского автора процесс создания баз данных впервые описывается с
позиций инженера-программиста. Многочисленные примеры структур данных и запросов
дают возможность читателю быстро овладеть мощными языковыми средствами и могут
быть легко обобщены на ситуациях, возникающих в технических областях. Основное
внимание автор уделяет широко распространенным реляционным базам данных,
реализуемых на мини- и микроЭВМ, в частности на персональных компьютерах.
Для инженеров-разработчиков и пользователей баз данных во всех областях
техники.
Оригинал книги опубликован на английском языке
издательством Оксфорд Юниверсити Пресс, г. Оксфорд, Англия
ПРОИЗВОДСТВЕННОЕ ИЗДАНИЕ
ДЖУЛИАН УЛЬМАН
БАЗЫ ДАННЫХ
НА ПАСКАЛЕ
Ордена Трудового Красного Знамени издательство <Машиностроение>
107076, Москва, Стромынский пер., 4
Типография № 6 ордена Трудового Красного Знамени
издательства <Машиностроение> при Государственном комитете СССР по печати.
193144, Ленинград, ул. Моисеенко, 10.
ISBN 5-217-00628-5 (СССР)
@ Julian Ullmann, 1985
ISBN 0-19-859642-1 (Велико- @ Перевод на русский язык и
британия)
ответы к упражнениям,
М. В. Сергиевский, А. В. Шалашов, 1990
ОГЛАВЛЕНИЕ
Глава
6. ВВЕДЕНИЕ В ОРГАНИЗАЦИЮ
ФАЙЛОВ
6.1. Организация файлов и доступ к ним
6.2. Сортировка
6,3. Простые последовательные файлы
6.3.1. Доступ к записям в простых последовательных файлах
6.3.2. Групповая обработка
6.3.3. Буфер файлового блока
6.3.4. Блоки, связанные в цепь
6.4. Коэффициент активности файла и эффективности доступа
6.5, Определение адреса
6.5.1. Прямая адресация
6.5.2. Методы хэширования
6.5.3. Непригодность файлов с кэш-адресацией к групповой
обработке
6.5.4. Упражнения
6.6. .Индексно-последовательные файлы
6.6.1. Введение в индексно-последовательную организацию
6.6.2. Включения и удаления (переполнение отсутствует)
6.6.3. Переполнение
6.6,4. Иерархические индексы для индексно-последовательных файлов
6.6.5. Упражнения
6.7. Сопровождение файлов
6.8. В-деревья
6.8.1. Основная терминология
6.8.2. Поиски в В-деревьях
6.8.3. Основные свойства В-деревьев
6.8.4. Высота В-дерева
6.8.5. Включение записи в В-дерево
6.8.6. Удаление записи из В-дерева
6.8.7. Объявление В-деревьев
6.8.8 Процедуры, предназначенные для работы с В-деревьями
6.8.9. Упражнения
ГЛАВА 6
ВВЕДЕНИЕ
В ОРГАНИЗАЦИЮ ФАЙЛОВ
6.1. ОРГАНИЗАЦИЯ ФАЙЛОВ И ДОСТУП
К НИМ
В первых трех главах файлы рассматривались как наборы записей,
сохраняющиеся во внешней памяти после завершения работы программы. Вопросы,
связанные со структурой файлов и размещением файлов в памяти, в тех главах не
затрагивались. В этой главе описываются традиционные методы организации файлов,
обеспечивающие эффективный доступ к файлам.
6.2. СОРТИРОВКА
В разных приложениях
часто встречаются наборы записей, порядок
расположения которых не случаен. В качестве первого шага к введению в методы
организации файлов остановимся на терминологии, описывающей порядок сортировки.
Предположим, что имеется файл, состоящий из восьми записей; каждая запись
делится на четыре поля Fl, F2, F3 и F4:
F1
ас
an
bd
ck
F2
А
А
С
А
F3
62
74
13
21
F4
J
J
J
К
de
dw
dx
ea
N
Р
В
D
93
18
22
26
К
К
J
J
Файл отсортирован по F1, если записи файла расположены
возрастания значения поля Fl. Например, следующий
файл:
F1
F2
F3
de
N
93
an
А
74
ас
А
62
ea
D
26
dx
В
22
ck
А
21
dw
Р
18
bd
С
13
упорядочен
в порядке
F4
К
J
J
J
J
К
К
J
по убыванию F3.
Поле или несколько полей, которые полностью определяют последовательность
расположения записей, называются ключом сортировки. В предыдущих примерах ключ
сортировки состоял из одного поля; в приводимом ниже примере два поля F1 и F2
составляют ключ сортировки:
F1
2
2
2
3
4
4
5
6
F2
A
G
N
В
G
H
P
X
F3
62
74
62
62
21
21
74
62
F4
J
J
J
K
K
K
J
J
Этот файл отсортирован по F2 внутри F1. Записи файла расположены в порядке
возрастания значения поля F1 а если значение этого поля у нескольких записей
одинаково, то они расположены в порядке возрастания значения поля F2, Таким
образом, F2 меняется быстрее, чем F1. В этом случае F1 называется главным
ключом сортировки, а F2 дополнительным ключом сортировки. Если вспомнить файл
НЕОПЛАЧЕННЫЕ_СЧЕТА, то для него СРОК_ОПЛАТЫ и ДАТА_СЧЕТА соответственно главный
и дополнительный ключи сортировки. Файл НЕОПЛАЧЕННЫЕ_СЧЕТА отсортирован по
полю ДАТА_СЧЕТА внутри поля НОМЕР_ПОСТАВЩИКА и по полю НОМЕР_ПОСТАВЩИКА внутри
поля СРОК_ОПЛАТЫ, т.е., записи, имеющие одинаковые значения полей СРОК_ОПЛАТЫ
и НОМЕР_ПОСТАВЩИКА, отсортированы по полю ДАТА_СЧЕТА.
6.3. ПРОСТЫЕ ПОСЛЕДОВАТЕЛЬНЫЕ ФАЙЛЫ
6.3. L ДОСТУП К ЗАПИСЯМ В ПРОСТЫХ ПОСЛЕДОВАТЕЛЬНЫХ
ФАЙЛАХ
Материал о последовательных файлах может быть положен в основу разговора
о методах организации файлов. Стандартные файлы Паскаля - это простые
последовательные файлы. Для того чтобы сохранить данные после завершения работы
программы на Паскале, необходимо сформировать стандартный файл Паскаля и
записать в него эти данные. Программа, которая запускается после этого, может
использовать сохраненные данные, считывая их из файла.
Записи в простом последовательном файле доступны только последовательно
одна за другой. Например, можно обратиться к N-ой записи только после обращения
к 1, 2, N-1 записям. Для того чтобы удалить запись из файла, необходимо
создать копию файла, в которой эта запись отсутствует; для того чтобы поместить
запись в файл, также необходимо создать копию файла, в которую эта новая запись
включена. И наконец, чтобы изменить хотя бы одно поле в записи, необходимо
создать копию файла, содержащую модифицированную запись. В гл. 4 более подробно
даются методы
работы с файлами на языке Паскаль.
Один из недостатков, свойственных последовательным файлам, заключается в
том, что для доступа к N-й записи обязательно <пробраться> через (N-1)
предшествующих ей записей. Но, с другой стороны, возможность пользоваться для
работы последовательными файлами с теми же инструкциями READ
и
WRITЕ, что на для ввода и вывода на терминальное устройство, является их
несомненным преимуществом. В заключение стоит заметить, что если файл
располагается на магнитной ленте, то никакая другая организация, кроме
последовательной, невозможна.
6.3.2. ГРУППОВАЯ ОБРАБОТКА
Линейный поиск - это метод, заключающийся в просмотре одна за другой
последовательности значений до тех пор, пока искомое
значение не будет
найдено. Если метод линейного поиска используется для поиска записи с заданным
значением первичного ключа в последовательном файле, состоящем из N записей, то
для того, чтобы обнаружить ее, необходимо просмотреть в среднем N/2 записей. Это
можно объяснить следующим образом. Предположим, что проводится достаточно много
испытаний, связанных с поиском в последовательном файле случайно выбранных
значений ключа. Причем каждый раз поиск начинается с первой записи файла. Тогда
для обнаружения искомой записи надо просмотреть в среднем N/2 записей.
Если необходимо организовать доступ к М записям последовательного файла,
состоящего из N записей, то обычно стараются не прибегать к методу линейного
поиска. Ведь тогда придется прибегать к этому методу М раз, начиная каждый раз
просмотр файла с первой записи. Всего потребуется проанализировать MN/2
записей; очевидно, что при большом N на это будет затрачено значительное время.
Более эффективный метод позволяет просмотреть файл только 1 раз. Для того
чтобы использовать этот метод, необходимо все значения первичных ключей,
которые ищутся, включить в новый файл. Этот файл обычно называют файлом
сообщений, а тот файл, в котором производится поиск, главным файлом. Главный
файл и файл сообщений должны быть упорядочены по одному и тому же ключу.
Теперь перейдем к описанию метода. Сначала из файла сообщений считывается
первое значение первичного класса и по методу линейного поиска в главном файле
ищется соответствующая ему запись. Затем считывается второе значение первичного
ключа, и поиск в главном файле продолжается, начиная с той записи, на которой он
был остановлен. Этот процесс последовательно повторяется для всех значений
первичного ключа из файла сообщений.
. . .
Групповая обработка - это процедура поиска в главном
файле записей,
определенных с помощью файла сообщений, в котором записи упорядочены так же, как
и в главном файле. Только что рассмотренная программа как раз и дает пример
групповой обработки, заключающейся в просмотре записей главного файла с целью
получения необходимой информации. Групповая обработка может быть также
использована для модификации М записей в главном файле, включающем N записей,
для удаления М записей из файла и для включения М новых записей в главный файл.
Программа слияния двух файлов из разд. 2.6 относится к программам групповой
обработки. Она осуществляет включение записей из файла сообщений в главный файл,
формируя в результате новый главный файл. Примеры групповой обработки также
могут быть взяты из упражнений 6 и 7 разд. 2.7.
6.3.3. БУФЕР ФАЙЛОВОГО
БЛОКА
Программисту, работающему на Паскале, не обязательно знать, как данные,
составляющие стандартный последовательный файл, размещаются на устройствах
внешней памяти, таких как магнитные диски. Кроме того, он может не знать и все
те шаги, которые составляют процесс передачи данных между внешней и оперативной
памятью. Читателям этой книги предстоит спуститься на более низкий уровень, чем
тот, на котором работает программист, и познакомиться с процессом передачи
данных.
Сначала объясним, чем вызвана необходимость передачи данных. Одна из
причин заключается в том, что значения обычных (не сохраняющих свои значения)
переменных Паскаля хранятся в оперативной памяти и после завершения работы
программы теряются. Это дает возможность использовать ту же оперативную
память для хранения значений переменных другой программы. Если программист,
работающий на Паскале, захочет сохранить значения переменных после завершения
выполнения программы, он должен будет скопировать их во внешнюю память. В
стандартном Паскале такое копирование во внешнюю память выполняется
с помощью процедуры записи в последовательный файл.
Кроме того, внешняя память обычно дешевле оперативной (за показатель
стоимости может быть принята цена одного бита или байта). Если программа
обрабатывает достаточно много данных, можно хранить часть этих данных во внешней
памяти и при необходимости осуществлять передачу данных между оперативной и
внешней памятью. Это позволит уменьшить стоимость занимаемой данными памяти.
Обычно передача данных между оперативной и внешней памятью
осуществляется так быстро, что обычный центральный процессор не успевает
управлять ею на уровне символов. Вместо центрального процессора функции
управления на этом уровне берет на себя специальное управляющее устройство.
Центральный процессор просто сообщает этому устройству, сколько символов (или
байтов) необходимо передать, где взять эти символы и куда поместить.
Передаваемая таким путем последовательность символов (или байтов) называется
блоком. Блок может содержать, например, 512 символов. Блок считается наименьшей
совокупностью данных, которая может передаваться между оперативной и внешней
памятью. Важно понять, что передача данных между оперативной и внешней
памятью производится блоками, т. е. либо передается один блок, либо
последовательно один за другим передаются несколько блоков.
В некоторых случаях блок может содержать точно одну запись; еще реже
запись разбивается на части, хранящиеся в разных блоках. Обычно длина записи
значительно короче длины блока, и поэтому в блок можно поместить сразу
несколько записей. В этом случае записи называют сблокированными.
Число символов (или байтов) в блоке либо жестко фиксировано, либо может
быть установлено программистом. Для памяти на магнитных дисках первый вариант
проще в реализации и поэтому предпочтительнее. Для памяти на магнитных лентах
чаще используется второй вариант. Преимущество второго варианта перед первым
заключается в том, что позволяет избежать незаполненных данными пространств в
блоках. Для этого программист должен выбрать длину блока кратной длине записи.
Существует еще одна возможность организации блоков, сочетающая в себе
преимущества как первого, так и второго вариантов, хотя в этом случае длина
блока является постоянной, программисту разрешается объединять два, четыре или
восемь блоков, образуя как бы один большой блок. Такой блок называется
бакетом. Если запись слишком велика и не помещается в блоке, ее можно поместить
в бакет. В этом случае можно рассматривать бакет как минимальную
совокупность
данных, которая может передаваться между внешней и оперативной памятью. Далее
бакет будет считаться просто большим блоком и термин бакет использоваться не
будет. Кроме того, предполагается, что записи, составляющие стандартный файл
Паскаля, размещаются в блоках одинакового размера.
Когда с помощью программы, написанной на Паскале, осуществляется запись
данных в файл, передача данных между устройствами производится блоками, а не
записями. Процедура WRITE формирует из записей блоки; этот процесс называется
блокированием. В свою очередь, процедура READ должна извлечь записи из блоков;
этот процесс называется деблокированием.
Обновление файла приводит к созданию буфера файлового блока, который
необходим при выполнении операций блокирования и деблокирования. Отметим, что
программист с этим буфером не работает и может вообще не знать о его
существовании. Буфер и файлового блока - это область оперативной памяти,
размер 1 которой совладает с размерами блока этого файла. Его не следует
путать с файловым буфером (см. разд. 4.6), который предназначен для хранения
одной записи файла. С файловым буфером программист может работать, а с буфером
файлового блока - нет. Для каждого файла, объявленного в программе, создается
его собственный файловый буфер и его собственный буфер файлового блока.
Пользоваться одним и тем же буфером два файла не могут.
. . .
Специальные программы, выполняющие блокирование и деблокирование,
работают, считая, что блоки записываются во внешнюю память и считываются из
внешней памяти в определенной последовательности. Если устройством внешней
памяти является магнитная лента, то следующие друг за другом блоки файла будут
последовательно расположенными на магнитной ленте физическими блоками.
6.3.4. БЛОКИ, СВЯЗАННЫЕ В ЦЕПОЧКУ
Если файл хранится на диске, следующие друг за другом блоки могут быть
расположены так, как показано на рис. 6.1. На этом рисунке изображена одна
дорожка, разделенная на восемь блоков; блоки, принадлежащие файлу, обозначены
цифрами 1, ..., 6, а оставшиеся на дорожке два свободных блока заштрихованы.
Блоки, расположенные таким образом, называются смежными. Это означает, что
следующие друг за другом блоки файла физически расположены рядом. Если в файле
так много блоков, что они не помещаются на одной дорожке, часть блоков можно
расположить на следующей дорожке (желательно того же самого цилиндра) так,
чтобы они оказались смежными. Организация файла, при которой блоки расположены
так, как было описано выше, называется смежной. На практике часто бывает, что
до того, как блоки связываются с конкретным файлом, часть дорожек диска уже
занята блоками других файлов. На рис. 6.2 заполненные данными блоки дорожек
заштрихованы. Отметим, что попытки разместить файл так, чтобы он удовлетворял
условию смежности, часто бывают неудачными, поскольку не всегда имеется
достаточное число свободных смежных
блоков. Обычно блоки последовательных
файлов расположены
в памяти в случайном порядке, хотя чаще всего в пределах
одного цилиндра. Когда для файла отводится память, на диске ищутся свободные
блоки и связываются с файлом. Связывание достигается включением в каждый блок
адреса следующего за ним блока. Адрес дает возможность ЭВМ найти место на
диске, где находится блок. Адрес включает номер цилиндра, номер поверхности и
номер сектора. Операционная система запоминает адрес первого из
последовательности блоков. Все блоки соединены в цепь, т. е. каждый блок
содержит адрес, указывающий на следующий за ним блок. Блоки стандартного
файла Паскаля могут быть расположены так, как показано на рис. 6.2, б.
Адреса связи на этом рисунке заменены стрелками.
Если требуется, чтобы файл размещался в смежных блоках, то необходимо
знать, сколько блоков этот файл займет, поскольку надо заранее найти на диске
достаточный участок свободной памяти. Если же блоки файла должны быть связаны
в цепь, то не обязательно знать, сколько всего блоков требуется для размещения
этого файла, поскольку новые блоки просто присоединяются к концу файла.
Легкость присоединения новых блоков является достоинством файла, состоящего из
блоков, связанных в цепочку.
6.4. КОЭФФИЦИЕНТ АКТИВНОСТИ ФАЙЛА
И ЭФФЕКТИВНОСТИ
ДОСТУПА
Когда программа находит в файле искомое значение ключа, считается, что
получен ответ. В процессе, групповой обработки одной программе требуется найти
несколько различных значений ключа, т. е. получить как бы несколько ответов.
Для программ групповой обработки определим коэффициент активности.
Коэффициент
активности = (число
ответов)/(число записей в файле).
Групповая обработка, проводимая с главным файлом, включающим N записей, и
файлом сообщений, состоящим из М записей, имеет коэффициент активности M/N до
тех пор, пока не найдена ни одна из требуемых записей главного файла. Типичным
примером, в котором коэффициент активности близок к единице, является
вычисление зарплаты при обработке платежных ведомостей: каждая (или почти
каждая) запись в файле EMPLOYEES (СЛУЖАЩИЕ) обрабатывается.
Будем считать, что имеет место произвольный доступ, когда программе нужно
найти только одну запись файла.
В случае произвольного доступа коэффициент активности близок к нулю.
Произвольный доступ возникает, например, тогда, когда фирма <Типико> принимает
заказы клиентов по телефону и поодиночке в произвольном порядке просматривает
элементы файла товарных запасов. Это делается для того, чтобы проверить,
согласуется ли заданный клиентом шифр товара с шифром и описанием этого товара в
файле товарных запасов. Если существуют какие-то расхождения, фирма предпочитает
немедленно получить дополнительную информацию от клиента и только после этого
перейти к следующему элементу.
Эффективность произвольного доступа обратно пропорциональна общему
времени, затрачиваемому на доступ к произвольно взятой записи, хранящейся во
внешней памяти. Производя оценку факторов, вносящих вклад в общее время
доступа к записи, необходимо отметить, что время, затрачиваемое на передачу
блоков, является преобладающим. Время передачи складывается из времени поиска,
задержки н времени копирования найденного блока. Все эти величины определяются
скоростью выполнения электромеханических операций. Известно, что скорость
выполнения электромеханических операций ниже, чем скорость выполнения команд
центральным процессором. Таким образом эффективность доступа к записи,
хранящейся во внешней памяти, обратно пропорциональна числу обращений к
блокам, которые потребуются в процессе реализации доступа к записи.
Произвольный доступ к последовательному файлу, как правило, неэффективен,
поскольку требует просмотра файла с самого начала, и передача следующих друг за
другом блоков продолжается до тех пор, пока не будет найден блок, содержащий
искомую запись. В следующих разделах будут введены другие методы организации
файлов, которые позволяют повысить эффективность произвольного доступа. Их
использование даст возможность организовать доступ к блоку без передачи в
оперативную память всех предшествующих ему блоков. Отметим, что все отличные от
последовательного методы организации в стандартном Паскале не предусмотрены.
6.5. ОПРЕДЕЛЕНИЕ
АДРЕСА
6.5.1. ПРЯМАЯ АДРЕСАЦИЯ
Файл с прямой адресацией напоминает массив записей, где роль индекса
записи играет функция, аргументом которой является значение первичного ключа
записи файла. Например, значение функции может быть равно значению первичного
ключа. В разд. 5.3 в примере, описывающем упрощенную таблицу товарных запасов,
массив записей рассматривался как файл с прямой адресацией, в котором значением
первичного ключа являлся индекс массива. Преимущество такого подхода по
сравнению со стандартной последовательной организацией файла в Паскале
заключается в том, что удается получить запись по заданному значению ключа (в
данном случае ключом является ITEMREFNO) без предварительного просмотра всех
предшествующих ей записей файла.
Рассмотрим, что происходит в этом случае на физическом уровне.
Предположим, что записи массива объединены в смежные блоки и задан начальный
адрес размещения блоков в памяти. Тогда, зная значение поля ITEMREFNO, ЭВМ
может сразу определить адрес блока, который содержит запись с этим значением.
Естественно, что в этом случае для того, чтобы обратиться к записи, потребуется
передать только один блок. Для определенности предположим, что массив хранится в
смежных блоках с номерами от 0 до 10 и что в каждом блоке содержится по четыре
записи. Тогда для того, чтобы организовать доступ к записи с ITEMREFNO = 135,
ЭВМ
необходимо обратиться к блоку, номер которого равен (135-100) div 4-8.
Передав блок в оперативную память, ЭВМ может не просматривать по очереди все
входящие в него записи; ей достаточно прямо обратиться к четвертой записи
блока. Объясним, почему это так. Значение поля ITEMREFNO
первой записи
блока с номером восемь равно 132, второй - 133, третьей - 134, четвертой - 135.
Функция, с помощью которой определяется порядковый номер записи в блоке,
имеет вид
(ITEMREFNO-100) mod 4 + 1
В разд. 6.3.4 было показано, что для размещения файла не всегда удается
найти достаточное число свободных смежных блоков. Чаще свободные блоки
располагаются на дорожках вперемешку с блоками, занятыми данными. Поэтому
обычно файлы с прямой адресацией хранятся в несмежных блоках. Как и раньше,
пронумеруем эти блоки 0, 1,2. ... . Но теперь для того, чтобы определить
физический адрес блока, понадобится специальная таблица или индекс. Для файла
товарных запасов этот индекс будет иметь вид таблицы.
Номер блока
0
1
2
3
4
5
6
7
8
9
10
цилиндр
3
3
2
2
2
2
2
2
2
3
3
Физический адрес
поверхность
сектор
2
4
1
3
1
7
1
3
1
4
1
5
2
3
2
6
2
7
1
7
2
1
Например, пользуясь индексом, можно установить, что блок с номером восемь
имеет следующий
физический
адрес: номер цилиндра = 2, номер поверхности =
2, номер сектора = 7. Анализируя содержимое индекса, можно легко установить,
что не все блоки являются смежными (впрочем, есть смежные блоки, например 3,
4 и 5). Сам индекс обычно хранится по крайней мере в одном блоке. Тогда для
организации доступа по заданному ключу требуется не менее двух обращений к
блокам: одно -для получения индекса, второе – для получения блока, содержащего
искомую запись. Таким образом, введение индекса, с одной стороны, позволило
избавиться от необходимости искать свободные смежные блоки, но, с другой
стороны, уменьшило общую эффективность доступа.
Удалить запись из последовательного файла можно, только сформировав новую
версию этого файла, которая не содержит удаляемой записи. Важно
отметить, что
удаление записи из файла с прямой адресацией не приводит к созданию новой версии
всего файла. Файл с прямой адресацией строится так, что память резервируется
для всех возможных записей, даже для тех, которые либо должны быть удалены, либо
не имеют конкретных значений.
В разд. 5.3 был описан способ, позволяющий отделить истинные записи от,
во-первых, тех записей, которые подлежат удалению, и, во-вторых, тех записей,
полям которых не присвоены значения. Этот метод заключается в создании
специального поля со значением булевого типа. В разд. 5.3 это поле имело имя
NORECORD
и принимало значение FALSE только тогда, когда запись существовала
и была нужна. Для удаления записи достаточно было просто присвоить NORECORD
значение TRUE. Таким образом, если в файле с прямой адресацией имеется всего
несколько записей, то большая часть памяти, отведенная для размещения этого
файла, тратится впустую. В этом случае часто бывает целесообразным использовать
при организации файла методы хэширования.
6.5.2. МЕТОДЫ ХЭШИРОВАНИЯ
На практике, как правило, число возможных значений первичного ключа
намного превосходит число реально присутствующих в любой момент значений этого
ключа. В этом случае прямая адресация неудобна, поскольку слишком много памяти
отводится для записей, которых мет и никогда не будет в файле.
В качестве примера снова рассмотрим сильно упрощенный
файл товарных
запасов TOYINVEN, состоящий из записей типа
record
ITEMREFNO:
STRING4;
QUANTITYINSTOCK:
0..99999
end.
Для этого файла диапазон возможных значений первичного ключа ITEMREFNO
очень велик: от ААОО до ZZ99, т.е. 26*36*100 значений. Если файл состоит из 72
записей, значения первичных ключей которых лежат в интервале ААОО, ..., ZZ99,
то при использовании прямой адресации память, отведенная для 22*26*100-72
записей, будут расходоваться впустую.
Метод хэширования позволит избежать этого и в то же время во многом
сохранить эффективность, присущую прямой адресации.
При использовании хэширования запись помещается в первый свободный участок
блока, номер которого является функцией значения первичного ключа записи. Эта
функция называется функцией хэширования. Наличие свободных участков
устанавливается с помощью булевой переменной, являющейся обязательным полем
каждой записи. Если в блоке нет свободных участков, запись помещается в
специальный блок, называемый блоком переполнения.
Рассмотрим пример. Пусть функция хэширования имеет следующий вид:
h (ITEMREFNO)
= (последние две цифры ITEMREFNO)
mod
13
Например,
h (АА39) - 39 mod 13 = 0;
h (AA62) – 62 mod 13 = 10.
Поле ITEMREFNO здесь используется в качестве первичного ключа для
записей файла TOYINVEN. Функция хэширования позволяет получить значения в
диапазоне от О до 12, идентифицируя тем самым 13 блоков. В каждый блок можно
поместить восемь записей. Тогда, если файл содержит 72 записи, пространство,
выделенное для его хранения, будет на две трети заполнено данными. Пометим все
13 блоков номерами от 0 до 12.
Ниже показано состояние блоков после помещения в файл 72 записей.
Читатель может самостоятельно убедиться, что номера блоков совпадают со
значениями функции хэширования для тех записей, которые в них находятся.
0:
АА91
РК39
AS65
АА39
FS52
00350
00041
00184
00061
00114
3:
ХВОЗ
LX94
DD68
НА16
TL81
МС55
00315
00608
01642
00579
00034
00082
6:
АН84
ВА32
ММ71
АМ19
SX19
RT06
SC84
00621
00002
00008
00034
00060
00061
00112
9:
YH61
AC22
MQ09
AJ09
00070
00020
00014
00000
12:
AF77
HL12
CE64
00011
00400
00087
1.
AQOI
AZ27
АН01
00247
00065
00322
4:
АС56
YD30
АВ82
00664
00000
00196
7:
AL72
ММ72
WG33
ВС59
00007
00006
00016
00031
10:
DT62
AA62
DD62
AL62
SY75
VF62
GB75
VK89
00095
00008
00315
00085
00008
00315
00338
00456
V:
XX44
PY05
04000
00073
2:
FD80
HS41
FN02
AS80
FX80
00731
00059
00317
00001
00051
5:
AV18
AD37
AB83
AC05
AP83
AE31
FS44
RT05
01075
00050
00000
01006
00914
07154
00321
00108
8:
FH08
SY73
VR34
RB99
00629
00094
00788
00775
11:
FA37
AP76
DD63
AP50
MT50
РАН
HH50
00000
00015
00579
00034
00078
00314
00002
WN77
PC25
00019
00080
Блок переполнения помечается буквой V. В нем содержатся записи,
для
которых
значение функции хэширования paвно 5 и которые не попали в блок 5
потому, что он к TOMV времени был уже заполнен.
Для того чтобы по заданному значению ITEMREFNO
найти QUANTITYINSTOCK,
необходимо вычислить h (ITEMREFNO) и затем организовать линейный поиск в блоке,
номер которого равен h (ITEMREFNO). Этот поиск будет продолжаться до тех
пор, пока не будет найдена запись, значение первичного ключа которой совпадает с
заданным.
Например, пусть требуется определить QUANTITYINSTOCK записи со значением
ITEMREFNO,
равным MT50. Сначала вычисляем значение функции хэширования: оно
равно 1. Затем, используя метод линейного поиска, ищем в блоке 11 запись со
значением ключа MT50. Таким образом, требуемое значение поля QUANTITYINSTOCK
может быть найдено с помощью обращения к записям только одного блока. В
результате удалось сохранить быстроту метода прямой адресации и избежать
свойственных этому методу больших затрат памяти. Следует отметить, что
применение методов хэширования возможно только тогда, когда в состав записей
включены значения первичного ключа. В отличие от методов хэширования при
использовании методов прямой адресации, в которых первичный ключ используется
так же, как индекс массива, не обязательно включать в состав записей значения
первичного ключа. Важно, что в файлах с хэш-адресацией записи внутри блоков не
упорядочены.
Чтобы включить в файл новую запись, нужно с помощью метода линейного
поиска в блоке, помер которого определяется значением h(ITEMREFNO),
найти
запись с NORECORD = TRUE. После этого на место найденной записи надо поместить
новую. Например, новая запись PQ56 00012 помещается в первое свободное
пространство блока, номер которого равен h(PQ56)=4. Теперь блок 4 содержит
следующие четыре записи:
АС56
YD30
АВ82
PQ55
00664
00000
00196
00012
В том случае, если при попытке поместить новую запись в файл
обнаруживается, что в найденном блоке нет записи с NORECORD = TRUE, говорят,
что блок переполнен. Несколько позже будет описана реакция на переполнение.
Для удаления записи из файла надо, используя метод линейного поиска,
попытаться обнаружить ее в блоке, номер которого равен h (значение первичного
ключа). Если запись будет найдена, то надо установить NORECORD=TRUE; если нет,
то очевидно, что блок был переполнен к необходимо продолжить поиск (об этом
будет идти разговор ниже). Отмерим, что перед тем как заполнить файл записями,
необходимо установить NORECORD= TRUE для всех записей всех блоков.
Если требуется модифицировать запись, например изменить значение ее поля
QUANTITYINSTOCK,
то надо найти эту запись и заменить старое значение поля
QUANTITYINSTOCK
на новое. Никаких других изменений не требуется.
Переполнение. Для борьбы с переполнением могут быть использованы три
метода: блоков, связанных в цепь; записей, связанных в цепь и прогрессирующего
переполнения.
Метод блоков, связанных в цепь, состоит в следующем. Когда блок
переполняется, в нем размещается указатель, ссылающийся на вновь созданный блок,
предназначенный исключительно для записей, не поместившихся в первый блок.
Желательно этот вновь созданный блок разместить в том же цилиндре. Если же
вновь созданный блок, в свою очередь, переполнен, в нем размещается указатель,
ссылающийся на еще одни вновь созданный блок, и т. д. В результате будет
сформирован связанный список блоков, для всех записей которого значение функции
хэширования одинаково.
Основной недостаток метода блоков, связанных в цепь, заключается в том,
что блоки переполнения часто оказываются заполненными только несколькими
записями, а оставшаяся в них память тратится впустую. Для устранения этого
недостатка можно использовать один блок переполнения для всех записей,
не обращая внимание на то, в какой блок они не попали. Когда блок переполнения,
в свою очередь, переполняется, используется еще один блок переполнения, и т. д.
Записи, имеющие одинаковые значения функции хэширования и попавшие в блоки
переполнения, объединяются в связанный список. Тот блок, который изначально
предназначен для хранения записей с одинаковыми значениями функции хэширования,
в случае переполнения должен содержать указатель, ссылающийся на начало
связанного списка записей, находящихся в блоках переполнения. Для поиска записи
в файле с такой организацией необходимо сначала произвести линейный поиск в
блоке, номер которого определяется значением функции хэширования для этой
записи; если искомая запись не обнаружена, необходимо, воспользовавшись
указателем, находящимся
в блоке, просмотреть связанный список записей
в блоках переполнения.
Если необходимо включить в файл новую запись, а в блоке, предназначенном
для этого, места нет, эта запись помещается в первый блок переполнения, в
котором есть свободное место. После этого надо еще включить эту запись в
связанный список записей с тем же самым значением функции хэширования. Такой
метод получил название метода записей, связанных в цепь.
Хотя во втором из указанных методов часто используется меньше блоков
переполнения, чем в первом, поиск записи, попавшей в область переполнения,
может потребовать для второго метода больше времени. Предположим, например, что
каждый блок предназначен для размещения восьми записей и что требуется
найти запись, 'которая является седьмой в связанном списке записей, попавших в
область переполнения. При использовании первого метода потребуется организовать
доступ к двум блокам: во-первых, к основному блоку, номер которого
определяется значением функции хэширования и, во-вторых, к блоку переполнения,
который и содержит искомую запись. Если же использовать второй метод, то
необходимо сначала организовать доступ к основному блоку, определенному с
помощью функции хэширования, а затем просмотреть связанный список записей,
попавших в область переполнения. Так как эти последние записи, возможно,
принадлежат разным блокам, то весьма вероятно, что седьмая запись связанного
списка находится не в первом блоке переполнения. Тогда необходимо будет, следуя
по связанному списку, организовать доступ к блокам до тех пор, пока не будет
найден блок, содержащий искомую запись. Этот пример позволяет понять, почему
для второго метода часто надо организовывать доступ к большему числу блоков, чем
для первого метода. Отметим еще раз, что в первом методе записи в блоках не
объединяются в списки.
Метод прогрессирующего переполнения во многом похож на второй метод.
Единственное отличие состоит в том, что записи в блоках переполнения не
объединяются в связанные списки. В том случае, если в блоке, номер которого
определяется значением функции хэширования, больше нет места, запись помещается
в первое свободное пространство блока переполнения. В этот блок помещаются
записи с различными значениями функции хэширования.
Для поиска записи надо сначала организовать доступ к основному блоку,
номер которого определяется значением функции хэширования. Если искомой записи
там нет, необходимо, используя метод линейного поиска, просматривать блоки
переполнения до тех пор, пока запись не будет обнаружена. В этом случае будут
просматриваться даже те записи, которые имеют другие значения функции
хэширования. Отметим, что второй метод обладает более высоким
быстродействием, чем третий, поскольку ограничивает поиск только теми записями,
которые имеют то же значение функции хэширования, что и искомая. Но третий
метод требует памяти меньше, чем второй, поскольку нет необходимости хра
нить указатели. Кроме того, третий метод проще в реализации.
Функции хэширования. При прямой адресации по заданному значению
первичного ключа вычисляется номер блока. Число возможных значений первичного
ключа, которые будут преобразовываться в один и тот же номер блока, равно
максимальному числу записей, которые могут быть помещены в блок. При
использовании
хэширования вычисляются номер блока для заданного
значения первичного ключа, но число возможных значений первичного ключа, которые
соответствуют этому же блоку, теперь превышает максимальное число записей,
которые можно поместить в блок. Для файла TOYINVEN и выбранной ранее функции
хэширования число возможных значений ключа, преобразуемых в один и тот же
номер блока, равно 26*26*(100 div 13 + 1).
Коэффициент
26*26 появляется из-за того, что в первичном ключе первые
два символа могут быть произвольными буквами латинского алфавита, которые не
принимаются во внимание при вычислении функции хэширования.
Выбор функции хэширования осуществляется в некотором смысле
произвольно. Покажем, как для любого заданного файла выбрать <хорошую> функцию
хэширования. Для этого необходимо знать, во-первых, примерное число записей в
файле и, во-вторых, сколько записей можно помещать в блок. Эта информация будет
использоваться для подсчета числа блоков в файле, на основании которого можно
определить диапазон изменения и значений функции хэширования. Если для файла
будет отведено слишком мало блоков, то эффективность доступа будет уменьшаться
по мере переполнения этих блоков. Если, наоборот, для размещения файла будет
предусмотрено много блоков, то переполнение блоков будет происходить редко и,
следовательно, доступ будет осуществляться быстро. Значительная часть памяти
в этом случае останется не занятой данными. Будем стремиться к золотой середине,
т. е. считать, что примерно две трети блока должны быть заняты данными. Именно
этим объясняется, почему, зная, что в файле TOYINVEN 72 записи и что каждый блок
может вместить восемь записей, для файла выделено 13 блоков. В 13 таких блоках
можно разместить 13*8 = 104 записи, а 72 приблизительно составляют две трети от
104.
Функция хэширования должна быть выбрана таким образом, чтобы максимально
удовлетворять следующим условиям:
- каждое ее значение должно быть целым из интервала O..число_блоков-1;
- она должна быть удобной для вычисления, т. е. Определение значения функции
хэширования должно происходить быстро;
- для множества реально появляющихся значений первичного ключа все
значения функции хэширования должны быть равновероятны.
Если h ((значение ключа> полностью удовлетворяет последнему условию, то
говорят, что h перемешивает значения ключа. Перемешивание позволяет добиться
того, чтобы все блоки содержали примерно одинаковое число записей. Если условие
перемешивания не выполняется, то часть блоков может переполниться, в то время
как другие блоки будут содержать лишь небольшое число записей, и в результате
эффективность доступа может оказаться ниже, чем при использовании перемешивания.
При вычислении функции хэширования буквы, входящие в состав ключа,
удобнее интерпретировать как целые числа в интервале 0... 25, т.е. А = О, В = 1,
С = 2 и т.д.
Например, числовое значение СВ53 тогда будет равно
(2 * 26 * 26 + 1*26) * 100 + 53
Используя аналогичные методы, любому ключу можно сопоставить численное
значение. Остановимся на некоторых из наиболее часто используемых методов выбора
функций хэширования.
Деление на простое число. Число блоков выбирается таким образом, чтобы
оно было простым. Используется следующая функция хэширования:
h ((значение ключа)) = (значение ключа) mod (число блоков).
Выбор простого числа в качестве делителя позволяет лучше организовать
перемешивание. Именно по этой причине для размещения файла TOYINVEN
было
выбрано 13 блоков.
Использование нескольких цифр ключа. Можно в качестве значения функции
хэширования использовать несколько цифр из значения ключа. Например, если для
этой цели выбрать вторую и пятую цифры шестизначного ключа, то h (932148) = 34
и h (716559) = 15.
Сложение. Можно брать из значения ключа два (или более) подмножества
цифр, формировать из цифр каждого подмножества число и использовать сумму этих
чисел в качестве значения функции хэширования. В этом качестве можно также
использовать последние несколько цифр суммы. Если ключ состоит из шести цифр,
то имеет право на существование такая функция хэширования:
h (<значение ключа>) = последние две цифры числа (<пятая цифра> +
<вторая цифра> * 7 + <третья цифра> + <четвертая цифра>).
Функция хэширования такого типа сложнее для вычисления, чем функция,
построения которой используется несколько цифр ключа, но в тоже время она
для
обеспечивает лучшее перемешивание. Кроме того, значения этой последней функции
могут быть вычислены быстрее, чем значения функции, для построения которой
используется деление на простое число.
Реализация с использованием несмежных блоков. Во время обсуждения
методов хэширования внимание в основном концентрировалось на определении номера
блока. Не были рассмотрены вопросы, связанные с поиском места нахождения блока
во внешней памяти. Эта задача решается точно так же, как и в случае
использования прямой адресации. Если файл размещен в смежных блоках и известен
адрес первого из этих блоков, то, зная номер блока, легко определить его адрес.
Если же для размещения файла нет достаточного количества смежных блоков, можно
для поиска адреса использовать индекс, как это было сделано в разд. 6.5.1.
Поскольку индекс хранится по крайней мере в одном блоке, для организации
доступа к файлу с помощью индекса необходимо по меньшей мере на одно обращение
к блокам больше, чем в случае, когда индекс не используется.
Напомним, что когда в файл о хэш-адресацией либо включается, не вызывая
переполнения, новая запись, либо удаляется или модифицируется запись, не
находящаяся в области переполнения, то сначала определяется адрес блока,
содержащего эту запись, затем блок из внешней памяти передается в оперативную,
где выполняется вставка, удаление или модификация, и, наконец, блок помещается
точно на то же место во внешней памяти, откуда он был взят.
В случае переполнения для включения, удаления или модификации записи
необходимо сначала (для того чтобы определить, что произошло переполнение)
передать в оперативную память блок, причем не тот, содержимое которого будет
впоследствии изменено, поскольку невозможно сразу определить адрес искомого
блока.
6.5.3. НЕПРИГОДНОСТЬ ФАЙЛОВ С ХЭШ-АДРЕСАЦИЕЙ
К
ГРУППОВОЙ
ОБРАБОТКЕ
При использовании файла с хэш-адресацией для того, чтобы найти запись,
нужно организовать доступ к меньшему числу блоков, чем при линейном поиске в
последовательном файле. Так как хэширование удобно для организации
произвольного доступа, файл с -кэш-адресацией иногда называют файлом
произвольного доступа или файлом прямого доступа. Эти же термины применяются и к
файлам с прямой адресацией. По мнению автора, прямую адресацию можно считать
частным случаем хэш-адресации.
При групповой обработке, для которой коэффициент активности близок к
единице, использовать последовательный файл часто бывает выгоднее, чем файл с
хэш-адресацией. Если требуется организовать доступ к записям, находящимся в
каждом из следующих друг за другом блоках последовательного файла, то
удобнее работать с этими блоками по очереди, не тратя времени на вычисление их
адресов (возможно, с помощью блоков индексов и блоков переполнения).
Для того чтобы правильно выбрать организацию файла, необходимо хотя бы
приблизительно знать коэффициент активности этого файла. Если коэффициент
активности близок к нулю, следует выбрать файл с хэш-адресацией (или с прямой
адресацией), если же коэффициент активности близок к единице, предпочтительнее
последовательный файл.
Есть и другой фактор, который может повлиять на выбор способа
организации файла. Важно знать, нужно или нет выводить содержимое файла в таком
виде, когда его записи упорядочены по какому-нибудь ключу. Это может
понадобиться для того, чтобы получить твердую копию справочного документа,
такого, например, как телефонная книга. Если получать содержимое файла в
упорядоченном
виде надо часто, то предпочтительнее использовать
последовательную организацию, поскольку записи в файле с хэш-адресацней обычно
не упорядочены. Если же записи в упорядоченном виде требуются лишь
эпизодически, то на выбор организации файла это не повлияет, поскольку в этом
случае для сортировки записей можно использовать специальную программу.
6.5.4. УПРАЖНЕНИЯ
1. В файле, в котором размещены данные об автомобилях и их владельцах, в
качестве ключа используется REGISTRATIONNUMBER (РЕГИСТРАЦИОННЫЙ_НОМЕР). Каждый
регистрационный номер состоит из буквы, трех цифр и еще трех букв. Выберите
функцию хэширования для этого файла, считая, что число записей в файле никогда
не превысит 3000 и что в каждый блок можно поместить не более 10 записей.
Аргументируйте свой выбор.
2. Записи файла, содержащего данные о горных вершинах и их высотах, относятся
к следующему типу:
record
MOUTAINNAME:
packed array 1..12 of CHAR;
COUNTRY:
packed array 1 ..10 of CHAR;
HEIGHT:
REAL
end.
Два поля -- MOUNTAINNAME
и COUNTRY – составляют первичный ключ.
Выберите функцию хэширования для этого файла, считая, что число записей в
файле никогда не превысит 10 000 и что каждый блок может содержать 32 записи.
Аргументируйте свой выбор.
6.6. ИНДЕКСНО-ПОСЛЕДОВАТЕЛЬНЫЕ
ФАЙЛЫ
6.6.1. ВВЕДЕНИЕ В ИНДЕКСНО-ПОСЛЕДОВАТЕЛЬНУЮ ОРГАНИЗАЦИЮ
Ранее было показано, что применение простой последовательной организации
оправданно, если коэффициент активности достаточно высок; если же коэффициент
активности невелик, то предпочтительнее использовать файлы с хэш-адресацией. В
том случае, когда коэффициент активности в зависимости от приложения становится
то высоким, то низким, ни хэш-адресация, ни последовательная организация не
обеспечивают эффективный доступ. Очевидно, что для таких случаев необходима
другая организация хранения файлов. Она получила название индекснопоследовательной. При использовании индексно-последовательной организации
записи хранятся в отсортированном виде и к ним возможен произвольный доступ,
который обычно осуществляется быстрее, чем для простых последовательных файлов,
и медленнее, чем для файлов с хэш-адресацней.
Попытаемся основную идею, лежащую
в основе индексно-последовательной
организации, пояснить с помощью примера. Для этого снова обратимся к файлу
TOYINVEN. Будем считать, что файл содержит 72 записи. В блок может быть
помещено восемь записей. Сначала поместим в каждый блок только по шесть записей
(для чего это сделано, будет объяснено далее). После этого содержимое блоков
примет следующий вид:
1:
АА39
АА62
АА91
АB82
АВ83
АС05
00061
00008
00350
00196
00000
01006
2:
AC22
AC56
AD31
AE31
AF77
AH01
00020
00664
00059
07154
00011
00322
3:
AH84
AJ09
AL62
AL72
AM19
AP76
00621
00000
00085
00007
00034
00015
4:
AP83
AQOI
AS63
AS80
AT50
AV18
00914
00247
00184
00001
00034
01075
5:
AZ27
BA32
BC59
CE64
DD62
DD63
00065
00002
00031
00087
00315
00579
6:
DD68
DT62
FA37
FD80
FH08
FN02
01642
00095
00000
00731
00629
00317
7:
FS44
FS52
FS80
GB75
HA16
HL12
00321
00114
00051
00338
00579
00400
8:
HS41
HH50
LE31
LX94
MC55
ММ71
00059
00002
00050
00608
00082
00008
9:
MM72
MQ09
MT50
РА11
PC25
PK39
00006
00014
00078
00314
00080
00041
10:
PY05
RB99
RT05
RT06
SC84
SX19
00073
00775
00108
00061
00112
00060
11:
SY73
SY75
TL81
VF62
VK89
VR34
12:
WC33
WN77
XB03
XX44
YD30
YH61
00094
00008
00034
00315
00458
00788
00016
00019
00315
04000
00000
00070
Напомним, что записи в файле отсортированы. К файлу добавляется индекс,
содержащий минимальные значения ключей для каждого блока и соответствующие этим
блокам физические адреса. Индекс сам может быть простым последовательным файлом,
размещенным в нескольких блоках. Например
Первый блок индекса
Второй блок индекса
АС05
AHO1
AP76
AV18
DD63
FN02
HL12
ММ71
PK39
SX19
VR34
YH61
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
1-го блока
2-го блока
3-го блока
4-го блока
5-го блока
6-го блока
7-го блока
8-го блока
9-го блока
10-го блока
11-го блока
12-го блока
Тогда максимальное значение ключа для первого блока файла - АС05, для
второго блока файла - AHO1. Адрес n-го блока - это реальный физический адрес,
включающий номер цилиндра, номер поверхности и номер сектора.
Предположим, необходимо выяснить, сколько имеется в наличии товара с
шифром FH08. Для этого надо выполнить следующую
последовательность действий:
1. Организовать в индексе линейный поиск первого ключа, значение которого
больше или равно искомому значению ключа FH08. Выбрать соответствующий
найденному значению ключа физический адрес блока, в котором находится запись с
искомым значением ключа. Искомая запись не может находиться в блоках
1-5, так как FH08 больше соответственно АС05, AHO1, AP76, AV18 и DD63. Она
должна быть в блоке 6, поскольку FH08 <= FN02.
2. Передать содержимое выбранного блока из внешней памяти в оперативную.
Организовать в нем линейный поиск записи с искомым значением ключа.
В этом примере искомая запись будет найдена в результате обращения только
к двум блокам: первому блоку индекса и шестому блоку файла данных. Это,
очевидно, значительно быстрее линейного поиска в последовательном файле,
поскольку в последнем случае пришлось бы просматривать шесть блоков. Индекс
помогает ускорить доступ, когда коэффициент активности очень низок. В том
случае, когда коэффициент активности высок, можно не пользоваться индексом, а
проводить групповую обработку записей точно так же, как с обычным
последовательным файлом. По сравнению с хэш-адресацией и с простой
последовательной организацией индексно-последовательная организация имеет преимущество, заключающееся в том, что она позволяет эффективно проводить как
произвольный доступ, так и групповую обработку.
6.6.2. ВКЛЮЧЕНИЯ И
УДАЛЕНИЯ (ПЕРЕПОЛНЕНИЕ
ОТСУТСТВУЕТ)
Для включения новой записи в файл с хэш-адресацией необходимо записать во
внешнюю
память измененную версию только одного блока файла, содержащего эту
новую запись. Если нет переполнения, организовывать доступ к другим блокам файла
не нужно; правда, возможно, понадобится еще обращение к индексу. Так как
индексно-последовательный файл предназначен и для произвольного доступа,
включение в него новой записи связано с помещением во внешнюю память измененной
версии только одного блока. Исключения связаны с обработкой переполнений
и с редко происходящими изменениями индекса. Нет смысла останавливаться на
деталях какого-нибудь одного метода индексно-последовательной организации. Лучше
поговорить о чертах, присущих всем методам этого семейства.
Во время создания индексно-последовательного файла часть выделенной для
него памяти была оставлена свободной. Позже, когда в файл помешается новая
запись, ее следует расположить так, чтобы не нарушить упорядоченность. Для того
чтобы это стало возможным, необходимо передвинуть все записи блока, которые
должны следовать за новой записью. Такой сдвиг возможен только в том случае,
если в блоке есть свободная зона. Например, после включения в файл TOYINVEN
новой записи AS69 00200 блок 4 примет следующий вид:
AP83
AQO1
AS65
AS69
AS80
AT50
AV18
00914
00247
00184
00200
00001
00034
01075
Записи с ключами AS80, AT50 и AV18 передвинутся так, что содержимое блока
останется упорядоченным.
Вносить изменения в индекс не потребуется, поскольку AV18 останется
наибольшим значением ключа для записей, содержащихся в блоке 4.
Когда из файл удаляется запись, следующие за ней записи передвигаются так,
чтобы заполнить образовавшееся свободное пространство. Например, после удаления
записи TL81 00034 блок 11 примет следующий вид:
SY73
SY75
VF62
VK89
VR34
00094
00008
00315
00458
00788
Индекс останется неизменным, поскольку не изменится максимальное для
блока значение ключа UR34. Если же из файла удалить запись UR34 00788, то
изменяется запись индекса, относящаяся к блоку 11:
UK89 адрес
11-го блока.
Для всех блоков, кроме последнего, справедливо следующее: только удаление
может привести к изменению индекса. Например, новая запись SY72 00350 после
включения ее в файл окажется первой в блоке 11, а не последней в блоке 12. Если
бы она стала последней, это бы привело к изменению индекса. Запись, значение
ключа которой больше, чем значение ключа последней записи в последнем блоке,
будет помещена в последний блок, и соответствующим образом будет изменена
запись индекса, относящаяся к последнему блоку.
6.6.3. ПЕРЕПОЛНЕНИЕ
Можно предложить очевидный способ включения записи в блок, в котором
нет свободного места. Он заключается в переписывании последней записи этого
блока в следующий за ним блок, последней записи этого второго блока в третий и
т. д. до тех пор, пока в каком-нибудь блоке не обнаружится свободное место. Но,
естественно, такой способ требует значительных затрат времени, и от него следует
отказаться. Нужны какие-то другие методы борьбы с переполнением. Таких методов
довольно много, но здесь будет рассмотрен только один. Этот довольно известный
метод напоминает второй метод борьбы с переполнением, описанным в разд. 6.5.2.
К каждому блоку прикрепляется один (возможно пустой) связанный список
записей, не поместившихся в этот блок. Указатель, ссылающийся на начало этого
списка, хранится в индексе вместе с физическим адресом блока. Индекс
представляет собой файл, состоящий из записей, имеющих следующие поля- МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ КЛЮЧА В БЛОКЕ ДАННЫХ
- ФИЗИЧЕСКИЙ АДРЕС
БЛОКА ДАННЫХ
- МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ КЛЮЧА ДЛЯ ЗАПИСЕЙ, НЕ ПОМЕСТИВШИХСЯ В БЛОК
- УКАЗАТЕЛЬ К ПЕРВОЙ
ЗАПИСИ
В СВЯЗАННОМ СПИСКЕ
ЗАПИСЕЙ,
НЕ
ПОМЕСТИВШИХСЯ В БЛОК
Покажем на примере работу по этому методу. Для этого воспользуемся файлом
TOYINVEN из разд. 6.6.2. Будем считать, что все блоки, кроме 9 и II, содержат по
шесть записей (как и было в разд. 6.6.2) и два выделенных блока по восемь.
Записи, входящие в состав блоков 9 и II, приведены ниже:
9:
ММ72
MQ09
MS33
МТ50
MW45
РА11
РС25
РК39
00006
00014
00556
00078
00004
00314
00080
00041
11:
SY71
SY73
SY75
TL81
VF62
VK89
VP16
VR34
00060
00094
00008
00034
00315
00458
00082
00788
Таким образом, пока переполнение не произошло. Второй блок индекса
содержит следующие данные:
МАКСИМАЛЬНОЕ
ЗНАЧЕНИЕ КЛЮЧА
В БЛОКЕ ДАННЫХ
АДРЕС БЛОКА
ДАННЫХ
МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ
КЛЮЧА ДЛЯ ЗАПИСЕЙ,
ПЕРЕПОЛНИВШИХ БЛОК
УКАЗАТЕЛЬ
СПИСКА
РК39
SX19
VR34
YH61
9
10
II
12
РК39
SX19
VR34
YH61
nil
nil
nil
nil
Заметим, что в каждой из этих записей значения двух полей МАКСИМАЛЬНОЕ
ЗНАЧЕНИЕ КЛЮЧА
одинаковы Но это только до тех пор, пока нет переполнения.
Перейдем к включению записи MS34 00065 в блок 9 и записи VR33 00006 в
блок 1.
Включение должно быть произведено так, чтобы сохранилась упорядоченность
файла. В связи с этим последние записи выталкиваются из блоков 9 и II и попадают
в область переполнения. Блоки 9 и II теперь состоят из следующих записей:
ММ72
MQ09
MS33
MS34
MT50
MW45
РА11
РС25
9:
00006
00014
0356
00065
00078
00004
00314
00080
11:
SY71 00060
SY73 00094
SY75 00008
TL81 00034
VFG2 00315
VK89 00458
VP16 00082
VR33 00006
В области переполнения также
КР39
VR34
00041
00788
имеются две записи:
nil
nil
Каждая из этих двух записей содержит указатель, ссылающийся на остальные
записи, не поместившиеся в блок. В данный момент значением указателей является
nil, поскольку других записей, относящихся к блокам 9 и 11,в области
переполнения нет. Изменится и второй блок индекса. Он примет следующий вид:
МАКСИМАЛЬНОЕ
ЗНАЧЕНИЕ КЛЮЧА
В БЛОКЕ ДАННЫХ
АДРЕС БЛОКА
ДАННЫХ
МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ
КЛЮЧА ДЛЯ ЗАПИСЕЙ,
ПЕРЕПОЛНИВШИХ БЛОК
УКАЗАТЕЛЬ
СПИСКА
РС25
SX19
VR33
YH61
9
10
II
12
РК39
SX19
VR34
YH61
к РК39
nil
к VR34
nil
Проведем теперь включение в индексно-последовательный файл одну за другой
следующих шести записей:
SY08
VQ62
PD95
VR31
MS36
VA70
00416
00057
00006
00016
00060
00011
В результате изменится содержимое только двух блоков файла 9 и II:
9:
ММ72
MQ09
MS33
MS34
MS36
MT50
MW45
РА11
00006
00014
00556
00065
00060
00037
00004
00314
11:
SY08
SY71
SY73
SY75
TL81
VA70
VF62
VK89
00416
00060
00094
00008
00034
00011
00315
00458
Новые записи были размещены в этих блоках так, чтобы сохранилась
упорядоченность записей внутри блоков. Это привело к тому, что несколько записей
было удалено из блоков и попало в область переполнения. Если в блок включается
запись, значение ключа которой меньше максимального значения ключа для списка
записей переполнения этого блока, но больше максимального значения ключа для
записей этого же блока, то эта запись должна быть помещена в список записей
переполнения все того же блока. Ниже изображена область переполнения (записи,
входящие в один и тот же список, связываются стрелками).
РК39
VR34
VR33
VQ62
PD95
VR31
РС25
VP16
O0041
00788
00006
00057
00006
00016
00080
00082
nil
nil
к VR34
к VR31
к РК39
к VR33
к PD95
к VQ62
Когда запись попадает в область переполнения, она помещается на первое
свободное место и с помощью указателей так связывается с другими записями
списка, чтобы не нарушить их упорядоченность. Во втором блоке индекса теперь
содержится следующая информация:
а) МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ КЛЮЧА В БЛОКЕ ДАННЫХ;
б) АДРЕС БЛОКА ДАННЫХ;
в) МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ КЛЮЧА ДЛЯ ЗАПИСЕЙ, ПЕРЕПОЛНИВШИХ БЛОК;
г) УКАЗАТЕЛЬ СПИСКА.
(а)
РА11
SX19
VK89
YH61
(б)
9
10
II
12
(в)
РК39
SX19
VR34
YH61
(г)
к РС25
nil
к VPI6
nil
В блоке 9 записи находятся в отсортированном виде. Анализ индекса
показывает, что список записей переполнения для блока 9 начинается с записи,
значением ключа которой является РС25. Отметим, что записи переполнения в этом
списке упорядочены и должны были бы находиться в блоке 9, если бы в нем было
место. Для блока список записей переполнения начинается с записи с ключом VP16.
Легко проверить, что и в этом списке записи упорядочены. Последней в списке
переполнения стоит запись, которая ранее была последней в блоке 11.
Для включения записи в файл необходимо, используя метод линейного поиска,
найти в индексе первое МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ КЛЮЧА ДЛЯ ЗАПИСЕЙ, ПЕРЕПОЛНИВШИХ
БЛОК, которое больше ключа новой записи. Для этого же блока необходимо
проверить, не больше ли МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ КЛЮЧА БЛОКА ДАННЫХ ключа новой
записи. Если больше, то запись помещается непосредственно в блок данных;
это может привести к переполнению блока и, следовательно, к изменению индекса.
Если же ключ новой записи превышает МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ КЛЮЧА В БЛОКЕ
ДАННЫХ, то запись помещается в список записей переполнения этого блока.
При этом индекс модифицируется только в том случае, если новая запись займет
первое место в списке.
Поиск
значения
QUANTITYINSTOCK по
заданному ITEMREFNO
начинается
с просмотра индекса. Просмотр продолжается до тех пор, пока не будет обнаружен
блок, МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ
КЛЮЧА
ДЛЯ ЗАПИСЕЙ
ПЕРЕПОЛНЕНИЯ которого
больше или равно искомому значению ключа. Для этого блока проверяется, больше
или равно МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ КЛЮЧА В БЛОКЕ ДАННЫХ искомому значению КЛЮЧА.
Если да, то организуется линейный поиск в блоке данных; если нет просматривается список переполнения. Отметим, что никогда поиск не проводится и
в блоке данных и в списке переполнения одновременно, поскольку, проанализировав
максимальные значения ключей блока, можно точно определить, где - в блоке или в
списке переполнения - находится запись.
Процесс удаления записи напоминает процесс поиска записи. Если запись
обнаружена в списке переполнения, то ее удаление сводится к модификации
указателя, принадлежащего предшествующей в списке записи, и к изменению
индекса, если удаленная запись была первой или последней записью списка. Если
запись обнаружена в блоке данных, то она удаляется и производится перемещение
следующих за ней записей к началу блока (см. разд. 6.6.2). Если для этого
блока существует список переполнения, то первая запись перемещается из него в
блок данных и соответственно модифицируется индекс.
Покажем
на примере файла TOYINVEN,
как производится удаление записей.
Рассмотрим удаление записи VR33 00006 из списка переполнения блока 11 и записи
MQ09 00014 из блока 9.
В результате удаления записи из блока 9 первая запись списка переполнения
этого блока РС25 00080 перемещается обратно в блок 9. Содержимое блока 9 после
этого примет следующий вид:
9:
ММ72
MS33
MS34
MS36
MT50
MW45
РАН
РС25
00006
00556
00065
00060
00037
00004
00314
00080
Ниже
приведена запись индекса, соответствующая блоку 9:
МАКСИМАЛЬНОЕ
ЗНАЧЕНИЕ КЛЮЧА
В БЛОКЕ ДАННЫХ
АДРЕС БЛОКА
ДАННЫХ
МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ
КЛЮЧА ДЛЯ ЗАПИСЕЙ,
ПЕРЕПОЛНИВШИХ БЛОК
УКАЗАТЕЛЬ
СПИСКА
РС25
9
РК39
к РD95К39
Область переполнения теперь выглядит так:
РК39
VR34
VQ62
PD95
VR31
VP16
O0041
00788
00057
00006
00016
00082
nil
nil
к VR31
к РК39
к VR34
к VQ62
Когда запись удаляется из блока данных файла с хэш-адресацией, ни одна из
записей области переполнения не передается обратно в блок данных. Когда запись
удаляется из блока данных индексно-последовательного файла, запись из области
переполнения передается в этот блок. Это различие возникает из-за того,
что когда из файла с хэш-адресацией удаляется запись, поле NORECORD
этой
записи принимает значение TRUE.
Следовательно, когда в блок помещается новая
запись, она попадает на место записи с NORECORD = TRUE или, если такой записи
нет, - в область переполнения. Главное здесь заключается в том, что новая запись
помещается на место первой записи с NORECORD = TRUE, поскольку нет
необходимости сохранять упорядоченность внутри блоков файла с хэш-адресацней.
Это приводит к тому, что блоки данных файлов с хэш-адресацией обычно хорошо
заполнены.
Из-за того, что индексно-последовательный файл предназначен для групповой
обработки, упорядоченность записей в нем постоянно поддерживается. Не
разрешается, не сортируя, помещать новые записи в свободные места блоков. Если
бы записи не передавались обратно в блоки данных из области переполнения,
то удаление записей приводило бы к образованию свободных мест в блоках и новые
записи попадали бы в списки переполнения, а не в блоки данных. А, как известно,
поиск в списке переполнения требует в среднем больше времени, чем поиск
непосредственно в блоке данных. В общем запись, находящаяся в блоке данных,
может быть обнаружена без доступа к другим блокам, а поиск записи из области
переполнения может повлечь за собой доступ к нескольким блокам, поскольку
элементы списка переполнения обычно размещаются в разных блоках. Поэтому блоки
индексно-последовательного файла необходимо заполнять записями как можно
плотнее и стараться уменьшить длину списков переполнения.
Выше был описан метод организации индексно-последовательных файлов,
предусматривающий хранение записей переполнения для разных блоков данных в одном
и том же блоке переполнения. Это позволяет полнее использовать пространство
блоков переполнения. Если возможно, следует размещать весь файл, включая
индекс, данные и блоки переполнения, в пределах одного цилиндра. В этом случае
обращение после просмотра индекса к блоку данных или блокам переполнения не
приводит к перемещению головок записи-считывания.
6.6.4. ИЕРАРХИЧЕСКИЕ ИНДЕКСЫ ДЛЯ
ИНДЕКСНО-ПОСЛЕДОВАТЕЛЬНЫХ ФАЙЛОВ
До сих пор индексы представляли собой простые последовательные файлы.
Повысить скорость работы с индексом можно, преобразовав его в индекснопоследовательный файл. Для больших файлов, занимающих несколько цилиндров, это
имеет принципиальное значение. Доступ к такому файлу можно начать с линейного
поиска в индексе, что позволит определить максимальные значения ключей для всех
цилиндров. Затем организуется доступ к выбранному цилиндру и анализируется
индекс блоков, находящихся в этом цилиндре. Индекс блоков представляет собой тот
же индекс, который был использован в разд. 6.6.2 и 6.6.3, и содержит
максимальные значения ключей для блоков данных. Линейный поиск в индексе
позволит найти блок данных, который содержит искомую запись. Для простоты не
будем учитывать переполнение и допустим, что каждый цилиндр содержит по четыре
блока. На рис. 6.3 изображена структура файла с двухуровневым индексом.
Использование индекса цилиндров позволяет в случае произвольного доступа к
записи в файле большого объема значительно уменьшить общее число блоков, к
которым необходимо организовать доступ.
Основная идея, лежащая в основе иерархического индекса, заключается в
том, что можно добиться повышения эффективности, используя индекс, позволяющий
перейти к более детальному индексу, который, в свою очередь, позволяет перейти к
еще более детальному индексу и т.д. до тех пор, пока не будут достигнуты
реальные данные. Такого рода иерархические структуры будут описаны в разд. 6.8.
Индекс цилиндров
Максимальное
значение
ключа для
цилиндра 1
Максимальное
значение
ключа для
цилиндра 2
Максимальное
значение
ключа для
цилиндра 3



Индексы блоков
Максимальное
Максимальное
Максимальное
Максимальное
Максимальное
Максимальное
Максимальное
Максимальное
Максимальное
Максимальное
Максимальное
Максимальное
Блоки данных
значение
значение
значение
значение
значение
значение
значение
значение
значение
значение
значение
значение
ключа
ключа
ключа
ключа
ключа
ключа
ключа
ключа
ключа
ключа
ключа
ключа
для
для
для
для
для
для
для
для
для
для
для
для
блока
блока
блока
блока
блока
блока
блока
блока
блока
блока
блока
блока
1
2
3
4
5
6
7
8
9
10
11
12












Рис. 6.3. Индексно-последовательная организация с двумя уровнями индексации
6.7. СОПРОВОЖДЕНИЕ ФАЙЛОВ
Произвольный доступ к файлу с хэш-адресацией производится тем быстрее,
чем меньше записей находится в облает переполнения. Если записей в области
переполнения много, то можно следующим образом изменить структуру файла.
Сначала все записи файла перепишем в простой последовательный файл.
Затем уничтожим файл с хэш-адресацией и создадим новый файл, содержащий блоков
больше, чем его предшественник. Естественно, что в этом случае надо изменить и
функцию хэширования. После этого будем по очереди считывать записи из
последовательного файла и помещать их в новый файл с хэш-адресацией, используя
для определения номера блока, в который будет помещаться запись, функцию
хэширования. В результате всей этой процедуры будет получен файл с хэшадресацией, произвольный доступ к которому будет осуществляться быстрее, чем к
старой версии этого файла. Такого рода операции входят в комплекс работ по
сопровождению файла.
Если обнаружено, что блоки файла с хэш-адресацией содержат всего по
несколько записей и большая часть блоков остается незанятой, то можно создать
новый файл, содержащий меньшее число блоков, и соответствующим образом изменить
функцию хэширования. Эта операция - другой пример организации работ по
сопровождению файла.
Реконструировать индексно-последовательный файл (например, такой, как в
разд. 6.6.3) имеет смысл, когда списки переполнения становятся длинными и
принадлежащие им записи размещаются в нескольких блоках, в особенности, если
эти блоки не находятся в одном цилиндре. Для этого надо сначала переписать
записи из индексно-последовательного файла в простой последовательный файл,
сохранив при этом их упорядоченность. Затем, уничтожив индексно-последовательный
файл, создать новый, содержащий возможно другое число блоков файл и
предусмотреть в каждом блоке файла значительное свободное пространство. И,
наконец, переписать все записи из последовательного файла в индекснопоследовательный. Это еще один пример операции по сопровождению файла,
показывающий, как можно увеличить эффективность доступа.
В следующем разделе будут введены В-деревья. В-деревья являются, по сути
дела, индексно-последовательными файлами, имеющими иерархические индексы.
Отметим, что В-деревья не нуждаются в специальных средствах сопровождения.
6.8. В-ДЕРЕВЬЯ
6.8.1, ОСНОВНАЯ ТЕРМИНОЛОГИЯ
Перед описанием В-деревьев введем несколько определений, относящихся к
деревьям любых типов.
Рис. 6.5. Примеры сбалансированного и
несбалансированного деревьев
На рис. 6.5, а показано дерево. Дерево состоит из двух множеств: множества
вершин и множества ориентированных дуг. На рис. 6.5, а вершинам соответствуют
прямоугольники, а дугам - стрелки. Корневой называется вершина, не имеющая
входящих в нее дуг.
Дерево всегда имеет только один корень. На рис. 6.5, а вершины названы
Nl, N2, N3 и т.д.; N1 - корневая вершина. Листом называется вершина, которая не
имеет выходящих дуг. На рис. 6.5, a N5, N6, ..., N13 - листья. Рассмотрим
произвольную вершину. Вершины, в которые входят дуги, начинающиеся
в этой первой вершине, называются дочерними, а сама первая вершина родительской.
Например, N8, N9 и NIO-дочерние по отношению к N3 вершины. У
листьев нет дочерних вершин. Важным отличительным свойством дерева является то,
что каждая его вершина кроме корня имеет только одну родительскую вершину.
Предположим, что для двух вершин NJ и NK справедливо следующее: NK
является дочерней по отношению к вершине, которая, в свою очередь, является
дочерней по отношению к вершине, которая и т.д. является дочерней по отношению к
NJ. В этом случае NJ - предок NK, a NK - потомок NJ. Например, на рис. 6.5, a
N6 является потомком Nl, a NI является предком N8. N3 также является предком N8,
так как отношение родительский-дочерний является частным случаем отношения
предок-потомок. Дерево называют сбалансированным тогда и только тогда когда
каждый его лист имеет одинаковое число предков. На рис. 6.5, а изображено
сбалансированное дерево, а на рис. 6.5, б - несбалансированное.
6.8.2. ПОИСКИ В В-ДЕРЕВЬЯХ
Введем понятие В-дерева с помощью примера, в котором используются записи,
содержащие только по одному полю. Это единственное воле является ключом и
состоит из двух букв, но все последующие рассуждения, относящиеся к этим
записям, будут справедливы и для записей, состоящих из нескольких полей.
Рис. 6.6. Пример В-дерева
На рис. 6.6 показано В-дерево, в котором Dl, D2, ..., D7 являются блоками
данных, содержащими записи, отсортированные по первичному ключу. Если запись
отсутствует, т. е. NORECORD = TRUE, то ее обозначают "-"; если используется
указатель, равный "nil", то его обозначают "-". I11, 112 и 113 – это индексные
блоки, которые ссылаются к блокам данных.
К этим индексным блокам имеются ссылки из индексного блока более высокого
уровня I21. Индексный блок I21 играет роль корневого блока В-дерева.
В разд. 6.6.3 в индексе хранились максимальные для каждого блока
значения ключей. Для В-деревьев более удобен индекс, содержащий минимальные
для каждого блока значения ключей. Эту разницу важно запомнить. Для первого
блока, на который есть ссылка из индекса, бессмысленно хранить минимальное
значение ключа, поскольку каждая запись со значением ключа, меньшим первого
хранимого значения ключа для второго блока, должна быть помещена в первый блок.
Именно поэтому каждый индексный блок В-дерева, изображенного на рис. 6.6,
начинается с указателя, с которым не связано никакое значение ключа. Например,
первым в индексном блоке I12 стоит указатель к блоку данных D3, а
соответствующего ему значения ключа в индексном блоке I12 нет. Вторая запись в
I12 ссылается к D4 и, кроме того, сообщает о том, что минимально возможное
значение ключа в блоке D4 равно ЕЕ. Аналогично, третья запись в I12 ссылается к
D5 и содержит минимально возможное для блока D5 значение ключа - GG. Первая
запись корневого блока I21 состоит из указателя, ссылающегося к I11, и не
имеет связанного значения ключа. Вторая запись блока I21 ссылается к I12 и
содержит минимально возможное для блока I12 значение ключа - ВН. Аналогично,
третья запись блока I12 ссылается к I13 и содержит минимально возможное для
блока I13 значение ключа JB.
Чтобы найти в В-дереве запись с заданным значением ключа, необходимо
начать с линейного поиска в корневом блоке первого значения ключа, которое
больше искомого. Затем надо воспользоваться указателем, который принадлежит
записи индекса, предшествующей той, которая была обнаружена ранее. Если в
корневом блоке нет значения ключа, которое больше искомого, то надо
воспользоваться указателем последней записи блока. Если указатель выдается, в
свою очередь, к еще одному индексному блоку, то поиск необходимо продолжать
точно так же, как и раньше, и в этом индексном блоке. Когда, наконец, будет
достигнут блок данных, нужно будет опять воспользоваться методом линейного
поиска для обнаружения записи с искомым значением ключа.
Предположим, например, что искомым значением ключа является FT. Сначала
сравним это значение ключа с значением ключа из из второй записи корневого блока
I21. Так как FT > ВН, то необходимо сравнить искомое значение ключа с JB.
Поскольку FT < JB, поиск в блоке I21 закончен. Теперь перейдем к блоку 112,
следуя за указателем, идущим от I21. Поиск в блоке 112 продолжается до тех пор,
пока значение ключа какой-нибудь записи этого блока не станет больше, чем FT.
Таким образом, значением ключа является GG. Далее следуем за вторым указателем,
ведущим от I12 к D4. Применяя метод линейного поиска, находим в блоке D4
искомую запись со значением ключа FT.
Рассмотрим еще один примеру Предположим, что надо найти запись со
значением ключа, равным BG. Поскольку BG < ВН, переходим от блока I21 к блоку
I11. Поиск в блоке I11 не дает результата. Поэтому продолжаем поиск в блоке,
на который ссылается последний указатель из I11. Этим блоком является D2. В
нем и удается обнаружить> BG.
Для В-дерева на рис. 6.6 произвольный доступ организован так, что всегда
необходимо просматривать два индексных блока. Но естественно описанный метод
поиска является более общим и пригоден для В-деревьев со многими уровнями
индексации. На рис. 6.7 изображено В-дерево с тремя уровнями индексации.
В-деревья относятся к семейству индексно-последовательных структур.
Записи, расположенные внутри блоков данных В-деревьев, всегда упорядочены. В
связи с этим В-деревья удобны для групповой обработки. Иерархический индекс
нужен для произвольного доступа и не обязателен для простого последовательного доступа. Таким образом, В-деревья удобны как для произвольного, так и для
последовательного доступа.
Рис. 6.7. В-дерево с тремя уровнями индексации
6.8.3. ОСНОВНЫЕ СВОЙСТВА
B-ДЕРЕВЬЕВ
Хотя не было дано формального определения В-дерева, основные свойства Вдеревьев должны быть перечислены.
1. Каждый блок рассчитан на хранение максимально возможного нечетного
числа записей (в блоке всегда больше двух записей). В индексных блоках
учитываются и первые записи, не содержащие значений ключа.
Все индексные блоки предназначены для хранения одного и того же числа
записей. Все блоки данных также имеют одинаковую емкость. Но блоки данных и
индексные блоки могут содержать разное число записей.
2. Нет такого блока (за исключением корневого), который может быть более
чем наполовину пустым.
3. В-деревья всегда сбалансированы.
6.8.4. ВЫСОТА В-ДЕРЕВА
Предположим, что в В-дереве хранится D блоков данных. Для простоты
допустим, что каждый индексный блок, за исключением корневого, содержит ровно i
записей. Предположим также, что L - число уровней индекса; например, для Вдерева, изображенного на рис. 6.6, L = 2, L- это высота В-дерева; блоки данных
при определении L во внимание не принимаются. Высота - важная характеристика Вдерева, поскольку именно она определяет количество индексных блоков, которые
надо просмотреть, чтобы найти искомые данные, т. е. высота - важный фактор,
влияющий на эффективность произвольного доступа.
Поскольку каждый индексный блок содержит ровно i записей, число
индексных блоков, расположенных на уровне, предшествующем блокам с данными,
равно D/i. Число индексных блоков на третьем снизу уровне равно D/(i*i), на
четвертом – D/(i*i*i) и т. д. Число индексных блоков на уровне корневой
вершины, естественно, равно единице. Поэтому D/iL = 1, следовательно,
L =
logi D.
Если индексные блоки содержат больше i записей, то высота дерева будет
меньше, чем logi D.
Предположим, например, что D = 100000, i= 10. Тогда для организации
произвольного доступа необходимо будет обращаться не больше, чем к log10 100000
= 5 индексным блокам.
На первом уровне будет не более 100 000/i = 10 000 индексных блоков. Если
воспользоваться методом линейного поиска для обнаружения одного индексного
блока из этих 10 000, то в среднем надо будет организовать доступ к 10 000/2 =
5000 индексным блокам. Используя В-дерево с i = 10 вместо одноуровневого
индексно-последовательного файла с i записями в каждом индексном блоке, можно
значительно уменьшить число обращений к индексным блокам. В результате общее
время, затрачиваемое на доступ, будет сокращено примерно в 1000 раз.
Этот пример показывает, насколько эффективно применение В-деревьев.
Использование хэширования позволяет найти искомую запись, организовав
доступ к меньшему числу блоков, чем при хранении записей в В-дереве. Но
преимуществом В-деревьев является то, что они сохраняют упорядоченность
записей. Во время создания файла с хэш-адресацией надо хотя бы приблизительно
знать общее число записей. Как будет видно в дальнейшем при организации Вдерева, не обязательно знать общее число записей в файле. Это также является
одним из преимуществ В-деревьев.
6.8.5. ВКЛЮЧЕНИЕ ЗАПИСИ
В В-ДЕРЕВО
Для включения записи в В-дерево необходимо сначала найти соответствующий
блок данных, т. е. провести ту же операцию, что и в случае поиска записи. Если в
блоке есть свободное место, включение производится так же, как описано в разд.
6.6.2: упорядоченность записей сохраняется и, если нужно, следующие за
включаемой записи передвигаются. Если же в блоке нет свободного места, то
необходимо создать новый блок. После этого некоторые из записей старого блока
передаются в новый блок. Причем число передаваемых записей должно быть таким,
чтобы старый и новый блоки содержали равное число записей. После включения
новой записи в блок данных соответствующим образом должны быть изменены записи
индекса.
Например, предположим, что необходимо включить в В-дерево, изображенное на
рис. 6.6, запись ВА. В результате блок данных D2 имеет вид
AW BA BG
Теперь поместим в это же В-дерево запись AY. Для этого потребуется новый
блок, поскольку в блоке D2 больше места нет. На новый блок D8 должен ссылаться
указатель из индексного блока I11. Та часть В-дерева, которая изменилась после
включения, изображена на рис. 6.8.
Отметим, что старый блок D2 и новый блок D8 содержат одинаковое количество
записей.
Опишем теперь процесс включения в В-дерево записей СЕ и DT. Поскольку в
D3 места нет, создадим новый блок D9:
Блок D3:
Блок D9:
BH CE –DA DT –-
В индексном блоке I12 нет места для указателя, ссылающегося на D9. Поэтому
необходим новый индексный блок I14. Поскольку в I21 также нет свободного места
для указателя к I14, требуется новый индексный блок I22.
В дереве должна быть только одна корневая вершина. В связи с этим
генерируется новая корневая вершина I31, от которой идут указатели к I21 и I22.
После описанного выше ввода новых блоков B-дерево существенно изменит свой вид
(см. рис. 6.7).
6.8.6. УДАЛЕНИЕ ЗАПИСИ ИЗ
В-ДЕРЕВА
Для
обнаружения записи, подлежащей
удалению, можно использовать ту
же процедуру, что и для поиска. После удаления записи необходимо заполнить
образовавшееся свободное пространство. Для этого записи, находящиеся за
удаленной, должны быть передвинуты так же, как описано в разд. 6.6.2. Если
теперь блок оказался более чем наполовину пустым, необходимо найти соседний с
ним блок, имеющий ту же самую родительскую вершину. Причем этот блок должен
содержать более минимально необходимого числа записей. Если такой блок
существует, то записи из него передаются в блок, из которого произведено
удаление, в таком количестве, чтобы сделать число записей в этих двух блоках
примерно одинаковым. Естественно, что все проводимые операции требуют
соответствующей модификации индекса.
Если блок, из которого удалена запись, оказывается более чем наполовину
пустым и если не существует соседнего с ним блока, имеющего ту же самую
родительскую вершину и содержавшего более минимально необходимого числа
записей, то первый блок сливается с соседним с ним блоком, имеющим ту же самую
родительскую вершину. Затем производится уничтожение той записи индекса,
указатель которой ссылается на удаленный блок.
Если в результате этого индексный блок оказывается заполненным менее чем
наполовину, повторяем описанную выше процедуру и делаем так до тех пор, пока
дальнейших изменений индекса не потребуется.
Рассмотрим несколько примеров удаления записей из В-дерева, изображенного
на рис. 6.7.
После удаления записи AW блок D2 становится более чем нaпoлoвину
пустым. Поэтому производится слияние блоков D2 и D8 и соответствующим образом
изменяется индексный блок I11. На рис. 6.9 показана та часть В-дерева, которая
претерпела изменения.
Отметим, что вместо слияния блоков D2 и D8 можно было бы объединить блоки
D1 и D2.
Будем считать, что В-дерево, изображенное на рис. 6.7, не изменилось.
Проведем удаление из него записи ВН. Следствием удаления является объединение
блоков D3 и D9 в блок D3 и уничтожение блока D9. Тогда I12 окажется более чем
наполовину пустым. Соседний с ним блок I11 содержит более минимально
необходимого числа записей; поэтому передадим запись из блока I11 в блок I12.
Заметим, что изменения коснутся и блока I21. Следовательно, возможно, что
удаление записи повлечет за собой изменение предка родительской вершины этой
записи. На рис. 6.10 показана измененная часть В-дерева.
Удаление записи JB приводит к объединению блоков D6 и D7, что в свою
очередь вызывает слияние I13 и I14. Затем происходит объединение блоков I21 и
I22 и уничтожение блока I31, который после слияния I21 и I22 становится
ненужным. В результате дерево существенно изменит свою структуру (рис. 6.11).
Как было отмечено выше, важным преимуществом В-деревьев является то, что
они не нуждаются в сопровождении. И действительно, в процедуре удаления записи
предусмотрены средства для уничтожения ненужных блоков, а процедура включено
записи снабжена средствами генерации новых блоков.
Рис. 6.10. Модифицированная часть В-дерева
Рис. 6.11. В-дерево, сформировавшееся в результате всех удалений
Download