File e Archivi Ivano Mazzarotto 2010-2011 Indice generale 1 . Aspetti comuni e definizioni..............................................................................1 1.1 1.2 1.3 1.4 1.5 Introduzione: azienda, organizzazione, sistema informativo, sistema informatico...............................1 Definizione di archivio........................................................................................................................2 Differenza fra archivio e file................................................................................................................2 Record logico, chiave primaria e secondaria.......................................................................................2 Record logici e record fisici, il concetto di blocco...............................................................................3 1.5.1 Il record logico..........................................................................................................................4 1.5.2 Il record fisico............................................................................................................................4 1.5.3 Il fattore di blocco......................................................................................................................4 1.6 Organizzazione degli archivi: organizzazione fisica e logica...............................................................5 1.7 Fattori che influenzano la scelta dell'organizzazione...........................................................................5 1.8 Tipologie di organizzazione dei file ....................................................................................................6 2 . Gli archivi sequenziali.......................................................................................7 2.1 Archivi ad organizzazione sequenziale................................................................................................7 2.1.1 Operazioni: inserimento, aggiornamento, cancellazione, ordinamento, ricerca.........................7 3 . Archivi sequenziale con indice..........................................................................9 3.1 La struttura dell'archivio indice e dell'archivio principale....................................................................9 3.2 Operazioni: inserimento, aggiornamento, cancellazione, ordinamento, ricerca...................................9 3.2.1 Ricerca e scansione in struttura sequenziale con indice e archivio primario non ordinato.........9 3.2.2 Ricerca e scansione in strutture sequenziali con indice e archivio primario ordinato...............10 3.2.3 Inserimento e cancellazione in strutture sequenziali con indice e archivio primario ordinato..11 3.3 Indici multipli o a più livelli (file ISAM)...........................................................................................12 4 . Gli archivi non sequenziali..............................................................................13 4.1 Archivi ad organizzazione relative.....................................................................................................13 4.2 Archivi ad organizzazione hash.........................................................................................................13 4.2.1 Introduzione.............................................................................................................................13 4.2.2 Tabelle hash.............................................................................................................................13 4.2.3 Categorie di funzioni hash ......................................................................................................19 4.2.4 Gestione delle collisioni...........................................................................................................19 4.2.5 Chaining o metodi di concatenazione......................................................................................20 4.2.6 Open addressing o metodo di indirizzamento aperto...............................................................20 5 . Organizzazione ad indici B-Tree....................................................................23 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 Introduzione.......................................................................................................................................23 BTree.................................................................................................................................................23 Obiettivo limitare le operazioni di I/O...............................................................................................23 Perché usiamo i BTree?.....................................................................................................................25 Perché usare le varianti ottimizzate dei BTree...................................................................................25 Caratteristiche ...................................................................................................................................25 Costo delle operazioni ......................................................................................................................26 Altezza di un Btree.............................................................................................................................26 5.9 Operazioni Base.................................................................................................................................27 5.9.1 Divisione di un nodo Procedura SplitChild..............................................................................27 5.9.2 Inserimento .............................................................................................................................27 5.9.3 Fusione dei nodi Procedura Merge..........................................................................................28 5.9.4 Cancellazione..........................................................................................................................28 5.10 Codice B-Tree..................................................................................................................................30 5.10.1 B-Tree-Search(x, k)...............................................................................................................30 5.10.2 B-Tree-Create(T)...................................................................................................................30 5.10.3 B-Tree-Split-Child(x, i, y).....................................................................................................31 5.10.4 B-Tree-Insert(T, k).................................................................................................................31 5.10.5 B-Tree-Insert-Nonfull(x, k)...................................................................................................31 Indice Analitico 4 1. Aspetti comuni e definizioni Il problema dell'organizzazione dei dati in archivi deriva dalla necessità di conservare le informazioni nel tempo e di poterle reperire con facilità. Nella realtà quotidiana abbiamo spesso l’esigenza di trattare problematiche inerente alla conservazione e al reperimento di grandi quantità di dati, i quali, per poter essere gestiti correttamente, devono essere memorizzati secondo una logica ben precisa, in modo tale che la ricerca e la consultazione possano essere gestite nel modo più efficiente possibile. 1.1 Introduzione: azienda, organizzazione, sistema informativo, sistema informatico Informazione, azienda e organizzazione. L'azienda, qualsiasi tipo di azienda, deve essere vista come un sistema le cui parti (ad esempio: reparto produttivo, vendite, amministrazione personale, marketing, ricerca e sviluppo eccetera) devono essere coordinate in modo da raggiungere gli obiettivi per cui l'azienda stessa esiste. Ad esempio: per una classica ditta privata massimizzare il profitto o, per una ONLUS, la quantità e la qualità dei servizi offerti. L'insieme delle persone che coordinano l'uso di tutte le risorse (capitali, persone, strutture, macchinari ecc.) a disposizione dell'azienda si chiama organizzazione. L'organizzazione può svolgere efficacemente ed efficientemente il suo compito solo se ha a disposizione le informazioni necessarie. E per questa necessità fa affidamento sul cosiddetto sistema informativo. Il Sistema Informativo di un'organizzazione è l'insieme di procedure manuali e automatiche e di risorse umane e materiali finalizzate alla gestione di informazioni (dati) rilevanti per la vita e la gestione dell'organizzazione. Ormai da decenni il sistema informativo è stato informatizzato. Il componente software più importante di ogni sistema informativo informatizzato è il DBMS (Data Base Management System) un software che gestisce gli archivi aziendali. Il sistema informativo non va confuso quindi con la sua informatizzazione che si identifica usando la parola Sistema Informatico; quest'ultimo è un sottoinsieme del primo. In effetti possiamo definire il sistema Informativo come un insieme di procedure manuali ed automatiche e di risorse materiali (PC, rete dati, stampanti, fax,...) finalizzate alla gestoine su supporto elettronico di informazioni (dati) rilevanti per l'organizzazione. Le procedure automatiche in questo caso saranno svolte dai calcolatori. 1.2 Definizione di archivio Un archivio è una raccolta organizzata di informazioni, che possono essere elaborate in modo manuale (archivi cartacei), oppure automatico (nel caso di elaborazione tramite calcolatori). L’archivio, pertanto, è una struttura di dati astratta, costituita da un insieme di record. Ogni record all’interno di un archivio è identificato per mezzo della sua posizione, che costituisce il suo indirizzo logico. 1.3 Differenza fra archivio e file Gli archivi memorizzati su memoria di massa prendono il nome di file. Un file è un insieme di registrazioni omogenee, memorizzate in modo permanente su memoria di massa. Il file è quindi una struttura fisica di memoria in cui è possibile memorizzare informazioni sotto forma di sequenza di byte (file di byte) o sequenza di record (file di record). Rappresenta, pertanto, la struttura concreta idonea a implementare la struttura astratta archivio. All’interno di un file ogni record è individuato tramite un indirizzo fisico. Un archivio è sempre implementato mediante uno o più file, ma un file non sempre è un archivio. Spesso nel gergo informatico, viene usato il termine “archivio” come sinonimo di “file”, ma tra i due termini c’è una differenza sostanziale. Ad esempio, i dati anagrafici degli studenti, una rubrica telefonica sono archivi di dati e possono essere memorizzati su un disco come file di dati. Un programma scritto in linguaggio di programmazione, un documento scritto con Word, un foglio di Excel, pur essendo memorizzati come file, non costituiscono un archivio, in quanto i dati contenuti non sono memorizzati secondo una precisa organizzazione, come avviene invece negli esempi precedenti. 1.4 Record logico, chiave primaria e secondaria Record: sono gli elementi che compongono il file. Più record si dicono omogenei se contengono tutti le stesse informazioni, nello stesso ordine. Un record è formato da un insieme di campi (o attributi), ciascuno dei quali contiene una informazione. Si può pensare ad un file come a una tabella, le cui righe rappresentano le registrazioni (cioè ciascun record) e le cui colonne rappresentano sequenze di uno stesso campo. Esempio: archivio anagrafico relativo a un insieme di persone: Figura: Anagrafico Codice fiscale 2 COGNOME NOME DATA DI NASCITA PROFESSIONE RSSCRL…. Rossi Carlo 01/11/1961 avvocato RSSCRL…. Rossi Carlo 18/02/1965 medico BNCLST…. Bianchi Elisabetta 19/03/1970 insegnante NRIGVN…. Neri Giovanni 31/07/1988 studente Parlando genericamente di campo o attributo occorre sempre distinguere fra il suo VALORE ed il suo NOME. Ad esempio, con riferimento alla Figura - Anagrafico, il nome di un attributo è PROFESSIONE, il suo valore può essere ad es. “avvocato”, “insegnante”, “medico”. Un campo può essere formato da sottocampi. Ad esempio DATA potrebbe essere formato dai sottocampi: giorno, mese, anno. DI NASCITA, La struttura di un record è l'insieme dei campi che formano il record stesso. Tutti i record di uno stesso file hanno la stessa struttura. L'articolazione dei campi all'interno del record si chiama tracciato record, il quale informa sui dati registrati nel file, sul loro tipo e quindi sulle operazioni possibili. Es. di tracciato record: Codice fiscale Cognome Nome Alfanumerico (20) Alfabetico (20) Alfabetico (15) Data di nascita Alfanumerico (10) Professione Alfabetico (20) Viene definita chiave primaria (Primary Key) un campo mediante il quale è possibile identificare in modo univoco i record all'interno di un file. Sono esempi di chiave primaria: • • • • il numero di matricola di uno studente universitario; il numero del conto corrente bancario di un cliente; il codice dell'articolo di magazzino; il codice fiscale di una persona. Viene definita Chiave Secondaria un campo mediante il quale è possibile identificare uno o più record all'interno dell'archivio; la chiave viene detta selettiva se il numero dei record selezionati è basso. Riepiloghiamo: 1. 2. 3. 4. 5. l'elenco anagrafico è un insieme di record contenente le informazioni sulle persone, quindi è un archivio; ogni elemento dell'elenco, cioè l'insieme di tutte le informazioni relative allo stesso soggetto, è un record; ogni informazione è un campo; la struttura fisica che contiene l'archivio elettronico dell'elenco anagrafico è il file; un programma memorizzato in un file di byte non costituisce un archivio. Un archivio viene sempre creato, usato, aggiornato e distrutto, quando diventa obsoleto. 1.5 Record logici e record fisici, il concetto di blocco I file sono memorizzati in memoria di massa, che è considerata una memoria aggiunta all’elaboratore. Per poter elaborare tramite un programma i dati presenti nel file è necessario prima trasferirli nella memoria centrale. È compito 3 del sistema operativo (file system) trasportare fisicamente i dati dalla memoria di massa alla memoria centrale e viceversa. Il trasferimento dei dati dalle unità di memoria periferica (memoria di massa) alla memoria centrale è detta operazione di INPUT (o LETTURA); l'operazione inversa (dalla memoria centrale alla periferica) è un'operazione di OUTPUT (SCRITTURA). In particolare, con l’operazione di lettura viene fatto un accesso alla memoria di massa e viene ricopiato una record dalla memoria di massa alla memoria centrale. Con l'operazione di scrittura avviene il contrario e cioè il record presente nella memoria centrale viene copiato nella memoria di massa. Questa operazione riguarda un insieme di caratteri (e non un solo carattere alla volta) e viene detta blocco. Per quest'operazione di trasferimento tra periferica e memoria centrale, il computer utilizza una particolare zona di lavoro della memoria centrale detta buffer di I/O. 1.5.1 Il record logico Finora abbiamo parlato di record come insieme di dati informativi relativi all'entità logica, definita a seconda delle esigenze dell'applicazione. Questa definizione coincide con quella di record logico, ossia la descrizione di come il programmatore vuole suddividere il gruppo di informazioni che caratterizzano l'oggetto osservato (tracciato record). Il record logico ha una lunghezza in byte pari alla somma della dimensione dei suoi campi. Sulla memoria di massa dove risiede il file, invece, i record logici sono raggruppati in blocchi o record fisici (di lunghezza prefissata) e costituiscono l'unità di trattamento fisico. 1.5.2 Il record fisico Il record fisico (o blocco) rappresenta l'insieme dei byte che possono essere letti o scritti in memoria di massa con una singola operazione di lettura o scrittura. Un blocco può contenere più record logici. Quindi le operazioni di lettura/scrittura su un file riguardano gruppi di record logici: in questo modo diminuisce il numero di accessi alla periferica (perché ogni volta che si accede alla periferica vengono letti o scritti più record alla volta), che sono operazioni più lente rispetto agli accessi ai dati contenuti nella memoria centrale. 1.5.3 Il fattore di blocco Si chiama fattore di blocco il numero di record logici contenuti in un blocco. Tale numero può essere maggiore, minore o uguale a uno. In particolare: 4 • • • se è maggiore di uno, ossia se ogni record fisico contiene più record logici, i record si dicono bloccati; se è = 1, i record si dicono sbloccati; se < 1, ossia se sono necessari più record fisici per memorizzare un record logico, si parla di multiblocco. Riportiamo un esempio di come un record logico costituito da due campi rispettivamente di 7 caratteri per il nome e da 2 caratteri per l'eta viene visto dal programmatore e dal file system: mentre il file system vedrà dei blocchi (record fisici) di 18 caratteri. Record logico 1 … R O S S I Record logico 2 Record logico 3 5 7 B I A N C H I 4 2 V E R D I 2 4 … Record fisico 1.6 Organizzazione degli archivi: organizzazione fisica e logica Gli archivi sono dati strutturati e formano un sistema organizzato per la conservazione e il trattamento dei dati. Tale struttura è quindi caratterizzata da una organizzazione. Per organizzazione o implementazione di un archivio si intende sia il modo in cui esso è rappresentato sul supporto fisici di memoria, sia il modo in cui è elaborato. L’organizzazione, pertanto, si distingue in: FISICA: riguarda il supporto fisico di memorizzazione → dalla parte della macchina. LOGICA: riguarda le modalità di gestione dei file → dalla parte del programmatore. Il supporto su cui sono memorizzati i dati individua l'organizzazione fisica e condiziona l'organizzazione logica. L'utente interagisce sempre con l'archivio logico, ma deve sapere che questo si basa su un archivio fisico. Organizzazione Fisica Logica Supporti ad accesso sequenziale (Nastri) sequenziale Supporti ad accesso diretto (Dischi) sequenziale ad indici ad accesso diretto 1.7 Fattori che influenzano la scelta dell'organizzazione La scelta del tipo di organizzazione dell'archivio dipende da: • il tipo di supporto fisico su cui sarà memorizzato il corrispondente file che rappresenta l'archivio astratto; 5 • il tipo di operazioni che devono che devono essere effettuate sui file che rappresentano l'archivio astratto; • tempi e metodi di elaborazione: se le operazioni compiute sui file avvengono in modalità interattiva (online), occorre garantire tempi di risposta immediati; se, invece, le operazioni non sono interattive (offline), è accettabile un tempo di risposta più lungo; • linguaggio di programmazione utilizzato: non tutti i linguaggi sopportano tutti tipi di organizzazione. 1.8 Tipologie di organizzazione dei file Saltiamo le considerazioni sugli aspetti hardware che concorrono alle prestazioni di un sistema di memorizzazione e consideriamo quelle non meno importanti relative al modo in cui si decide di organizzare lo spazio di memoria dei supporti: sarebbe come avere a disposizione in squadra i migliori giocatori di calcio al mondo ma costringerli a giocare con tattiche da cortile! Attenzione però non stupiamoci se alle volte può essere quella da cortile la tattica giusta: è un modo per affermare che non c'è in assoluto “il modo migliore” di organizzare un archivio ma che sono tutti sempre disponibili ai programmatori a seconda delle situazioni. Fondamentalmente si hanno quattro possibilità: organizzazione sequenziale, organizzazione ad accesso diretto (chiamata anche random o relative), organizzazione ad indici, hashing, BTree. Nel proseguo vedremo in dettaglio tutti questi argomenti. 6 2. Gli archivi sequenziali 2.1 Archivi ad organizzazione sequenziale 2.1.1 Operazioni: inserimento, aggiornamento, cancellazione, ordinamento, ricerca 3 . Archivi sequenziale con indice Vediamo le proprietà di questo tipo di organizzazione. Questo tipo di archivi possono essere registrati solo su supporti di memoria di massa, supporti fisici, ad accesso diretto. Quindi sebbene abbiano una struttura logica sequenziale l'accesso ai record ammesso è sia sequenziale che diretto. 3.1 La struttura dell'archivio indice e dell'archivio principale Si possono distinguere, principalmente, due elementi fondamentali detti in gergo rispettivamente archivio indice e archivio primario. L'archivio primario è caratterizzato da record consecutivi (sequenziali) che possono essere registrati in modo non ordinato, ordinato, non ordinato e raggruppato (per pagine), ordinato e raggruppato (per pagine). L'archivio indice o dizionario, necessariamente ordinato, ad un livello oppure anche costruito su più livelli (gerarchia di indici). Gli elementi dell'archivio indice/indici sono generalmente composti da due campi: un campo chiave kh contenente la chiave del record, e un campo puntatore P contenente la posizione del record all'interno dell'archivio. 3.2 Operazioni: inserimento, aggiornamento, cancellazione, ordinamento, ricerca Per questo tipo di archivi vedremo quindi tutte le operazioni come sono logicamente implementate. 3.2.1 Ricerca e scansione in struttura sequenziale con indice e archivio primario non ordinato Quindi con questo tipo di organizzazione, accanto alla zona dove sono registrati i record nell'ordine di immissione, viene gestita una tabella delle chiavi: la ricerca del record avviene leggendo la tabella delle chiavi e non i record come succede nei file sequenziali. In questo modo l'utente può accedere direttamente al record specificandone solo la chiave, senza dover scorrere tutti quelli che lo precedono. Si utilizza questa organizzazione quando si deve elaborare un solo record alla volta, cioè quando si devono effettuare operazioni locali (ad es. modificare l'indirizzo di un cliente). È possibile comunque accedere a tutti i record del file leggendoli in modo sequenziale a partire dal primo, sempre seguendo l'ordine delle chiavi. Possiamo notare come la dimensione dell'archivio indice è pari alla dimensione dell'archivio primario in quanto la funzione è biunivoca. Si tratta però di una soluzione dispendiosa sia in termini di occupazione di memoria che di tempo di accesso. 3.2.2 Ricerca e scansione in strutture sequenziali con indice e archivio primario ordinato Se però manteniamo ordinato anche l'archivio primario allora è possibile anche la seguente implementazione che prevede la suddivisione dell'archivio primario in pagine o sottoarchivi. L'archivio indice, ordinato nelle chiavi, non contiene più tutte le chiavi ma solo la chiave più alta per ciascuna pagina; il record ora è del tipo (Kh,P) dove Kh è la chiave più alta che troviamo nel sottoarchivio con numero P (ovvero nell'implementazione fisica si tratterà ancora di un puntatore che punta all'inizio della pagina di memoria). Testo 3.1: Disegnare un esempio di figura che mostri il mapping fra Archivio Indice e Sottoarchivi o Archivio primario paginato. Algoritmo di ricerca di una chiave K: Vediamo quindi in linguaggio naturale l'algoritmo di ricerca di un record nel file primario usando come entry la chiave K. 1. Ricerca nell'archivio indice la prima chiave Kh>=K 2. Accedi al sottoarchivio P associato alla chiave Kh 3. Ricerca (binaria, sequenziale, interpolata) della chiave K nel sottoarchivio P Algoritmo di scansione sequenziale di tutti i record Nel caso si voglia la semplice elencazione di tutti i record ordinati secondo la chiave Kh l'algoritmo di visita è rappresentato da: 1. Finchè ci sono chiavi Kh nell'archivio indice 2. accedi al sottoarchivio P associato alla chiave Kh 3. scandisci in sequenza tutti i record della pagina 10 3.2.3 Inserimento e cancellazione in strutture sequenziali con indice e archivio primario ordinato Ora le operazioni di inserimento e di cancellazione avvengono comunque in un archivio sequenziale ordinato e che come sappiamo sono fonte di problemi. Ma la soluzione che abbiamo visto in precedenza fa uso di apposite aree di overflow (distribuite o concentrate) e che periodicamente poi saranno soggette a “fusione” come si è detto. Una delle tecniche che viene usata più frequentemente prevede che l'archivio indice sia strutturato con record del tipo ( Kh,P, Khovf, Povf), dove Kovf e Povf indicano rispettivamente la chiave più alta e il puntatore di accesso a tale area (kovf chiave di overflow, Po\vf puntatore all'area di overflow). K SOTTOARCHIVIO 1 Kh P Kovf Povf 20 1 Nil Nil 85 2 Nil Nil 190 3 Nil Nil 214 4 Nil Nil 2 ... ... ... 5 ... ... ... 20 ... ... ... k SOTTOARCHIVIO 2 Altri dati info Altri dati info 48 ... ... ... 52 ... ... ... 85 ... ... ... Se ora proviamo ad inserire il record di chiave 58 possiamo vedere che l'inserimento andrebbe eseguito nel sottoarchivio P=2 ma questa pagina risulta essere piena ecco allora che dobbiamo procedere all'inserimento di un record nell'area di trabocco o overflow e questo lavoro può essere fatto seguendo questa soluzione di massima: Algoritmo di inserimento Sono verificate le seguenti due ipotesi: - Siano Kh' l'ultima chiave presente nella pagina P - Ktrab la chiave traboccata ovvero che non può essere inserita nella pagina P perché ormai piena. PROCEDURE Inserimento (record chiave K) BEGIN Ricerca nell'archivio indice la prima chiave Kh o Kovf > di K IF K < Kh Then Inserisci il record nella pagina P osservando l'ordinamento If c'è trabocco di pagina con Ktrab Then inserisci in testa all'area di overflow il record traboccato If è il primo trabocco Then sostituisci (Kh,P,Nil,Nil) con (Kh',P,Ktrab,Povf) 11 Else sostituisci Kh con Kh' EDNIF ENDIF Else Inserisci il record nell'area di overflow rispettando l'ordinamento ENDIF END; N.B. Questo algoritmo non prevede la gestione di quando l'area di trabocco viene completamente riempita! Inoltre l'area di trabocco può essere distribuita o concentrata come si è detto ma in genere nelle implementazioni si preferisce riservare un'area di overflow ogni N sottoarchivi dell'archivio primario. 3.3 Indici multipli o a più livelli (file ISAM) Nel caso in cui il numero di sottoarchivi diventi rilevante e, di conseguenza, il numero di record presenti nell'indice (al livello 1) cominci a diventare considerevole (appesantendo la ricerca), è possibile organizzare l'archivio indice come un archivio sequenziale con indice. Si dà così luogo a sottoindici di differente livello, che permettono una diminuzione del tempo di scansione dell'indice stesso. Un indice a due livelli è costituito per esempio dall'indice analitico di un libro: 1. si ricerca la lettera corrispondente alla parola che si vuole trovare 2. nel blocco che racchiude tutte le parole che iniziano per la lettera in questione si cerca la parola e si preleva il numero di pagina 3. si apre il libro alla pagina ottenuta e si leggono le informazioni Il primo livello è rappresentato dalle lettere dell'alfabeto Il secondo livello è costituito da tanti indici quanti sono le lettere e ciascun indice è composto da tutti i termini che iniziano con la lettera collegata. 12 4 . Gli archivi non sequenziali 4.1 Archivi ad organizzazione relative ... 4.2 Archivi ad organizzazione hash Bibliografia Pag. 263 “Niklausus Wirth Algoritmi+Strutture Dati = Programmi” - Ed. Tecniche Nuove (versione italiana) Pag. 307 Paolo Camagni “Algoritmi Strutture dati e programmazione ad oggetti” vol. 2 - Ed. Hoepli. 4.2.1 Introduzione L'idea di organizzare gli archivi sfruttando la tecnica hash nasce dalle tabelle di hash. Una tabella di hash è una struttura di dati costruita in RAM e la sua idea parte dalle seguenti considerazioni: • Una tabella hash è una struttura di dati dinamica efficace per realizzare i dizionari. Una tabella hash è la generalizzazione del più semplice concetto di array ordinario. È una tecnica di accesso diretto all'informazione memorizzata (su array e/o file). • La tecnica Hash si basa sull'utilizzo di funzioni che, partendo dalla chiave primaria Kh di ciascun record, trasforma quest'ultimo in un numero intero, che rappresenta l'indirizzo logico, detto indirizzo Hash, del record stesso. Più in generale, applicare la tecnica Hash significa definire una funzione di randomizzazione H (funzione Hash) che associ a ogni record l'indirizzo di un record logico in cui è possibile memorizzarlo, attraverso la trasformazione della chiave Kh in un numero intero x: H k =x 4.2.2 Tabelle hash Sono strutture di memorizzazione interna alle quali si accede alla posizione di memorizzazione del dato di interesse con tecnica ad indirizzamento immediato. Questa proprietà è garantita da un opportuno algoritmo di randomizzazione. 4.2.2.1 Algoritmi di randomizzazione Il problema è quello di memorizzare un certo oggetto (in genere un record) avente una chiave o numerica o alfanumerica all'interno di un array (o tabella 1 ) o più in generale un archivio non sequenziale (implementato poi come file ad accesso diretto). Essendo la chiave numerica o alfanumerica distribuita casualmente in un certo intervallo o insieme2 e dal momento che l'elaboratore non tratta direttamente tali chiavi3 è necessario inserire nel programma un algoritmo di randomizzazione per ottenere chiavi numeriche uniformemente distribuite. In questi casi l'algoritmo precede l'operazione di seek(record, n) in quanto n esprime il valore calcolato. Es. Gestione automatizzata di una biblioteca. Codice a 4 cifre: armadio, scaffale e due cifre per la posizione del libro nello scaffale. Il codice 2304 indica che il libro è il 4 del 3 scaffale nel 2 armadio. Provare a pensare ad un tracciato record per l'archivio dei libri. L'algoritmo deve produrre una corrispondenza biunivoca tra il codice “logico” del libro e l'indirizzo fisico del record. Se indichiamo con K la chiave numerica e con N la posizione nel file la formula che fornisce la corrispondenza biunivoca è: N=(C1-1)*S*L+(C2-1)*L+C dove C1, C2, C sono C1 = INT(K/1000) C2 = INT((K-C1*1000)/100) C = K – C1*1000-C2*100 Dove in questa formula si sarà fissato il numero massimo di libri per scaffale L, il numero massimo di scaffali per armadio S. Se poi fisicamente i record devono essere allocati a partire dall'indirizzo I, la determinazione di N si ottiene dall'assegnazione N = (I-1) + N. 4.2.2.2 Introduzione agli Algoritmi di hashing Sono algoritmi di randomizzazione che trattano chiavi alfanumerica e l'idea di fondo è quella di trattare i caratteri della chiave come se fossero numeri, e di associare ad ogni chiave il numero ottenuto mediante un qualche procedimento di calcolo. Il numero è l'hash-indirizzo del record ed indica la posizione del record all'interno dell'archivio-file (o del vettore o della tabella). L'esempio che propongo è tratto da un articolo di D. E. Knuth. L'idea è quella di trattare le parole come se fossero dei numeri a=1, b=2,... e quindi ottenere da ogni parola un singolo numero mediante un qualche procedimento di calcolo. Se consideriamo come chiavi di un record le 31 parole 1 2 3 14 Una tabella è semplicemente un array di record. L'insieme U di distribuzione delle chiavi ha un sua dimensione dim(U) che può essere minore maggiore o uguale alla dim(T) dell'array/tabella/archivio sul quale andiamo a depositare i record vedere successivamente per una spiegazione maggiormente dettagliata. Non è in grado di far corrispondere una posizione a partire da una chiave alfanumerica, è necessario operare una qualche trasformazione per ottenere così un numero n naturale. più comuni della lingua inglese si può convertire ogni chiave in un numero compreso fra 1 e 32 sommando il valore delle rispettive lettere e prendendo il resto della divisione per 32 (definizione della funzione hash!). “the” = (20+8+5) mod 32 =1 “of”=(15+6) mod32 = 21 e così via per tutte le 31 chiavi. La condizione di funzionamento dell'algoritmo richiede che il numero di record m della tabella sia maggiore almeno di una unità rispetto alle 31 chiavi; nell'esempio prendiamo m = 32. ALGORITMO DI INSERIMENTO N = H(z) fino a quando l'indirizzo N è pieno (collisione) se N = 1 poni N = 32 altrimenti N = N-1 scrivi il record di chiave z nella posizione N Suppongo di immetterle ordinate secondo la loro frequenza: the[1], of[21], and[19], to[3], a[1,32] (non si hanno collisioni fino alla 'a' ) . Dopo aver riempito il file avremo che la posizione 5 è libera e se cerco 'do' che vale 19 scorrendo tutto il file a ritroso mi fermo nella posizione 5 e dico che non c'è! ALGORITMO DI RICERCA Calcola N = H(z) esegui fintanto che non Trovato o Vuoto; se la chiave in N è x allora Trovato se la chiave in N non esiste è Vuoto altrimenti scorri a ritroso N. Svantaggi Questa tecnica che organizza i file in modo direct presenta l'indubbio vantaggio di consentire un accesso veloce ad un qualsiasi record ma ha come suo limite il vincolo che impone record di lunghezza prefissata e quindi in sede di creazione del file risulta fissata la sua lunghezza e la conseguente occupazione nella memoria di massa che lo ospita: N·L byte è la lunghezza del file di N record lunghi L byte. Tale spazio è sempre occupato anche se il numero dei record è molto inferiore a N. Per risolvere questo problema si passa ai file sequenziali con indice .... 4.2.2.3 La funzione H(.) e il suo fattore di carico Quindi la tecnica si basa su una funzione detta di hashing H(.) (tecnica di hash) che mappa un certo insieme di chiavi (Universo delle chiavi) U in un secondo insieme T (tabella[0, ... , m-1]). Gli insiemi o spazi U e T possono avere diversi rapporti fra le loro dimensioni; il parametro fondamentale α viene definito fattore di carico o di riempimento: 15 = n , numero di chiavi K al momento memorizzate m , dimensione tabella T al momento disponibile L'ordine di complessità di questi algoritmi è O(1). Classe di complessità4: O(1) o O(C), che indica la complessità degli algoritmi che eseguono lo stesso numero di operazioni indipendentemente dalla dimensione dei dati di input. Quindi questa tecnica garantisce un tempo di accesso costante all'informazione. La tecnica di hash è usata non soltanto della gestione degli archivi ma trova largo impiego in informatica come ad esempio negli algoritmi di cifratura delle firme digitali (MD5 Rivest 1992, un messaggio M in input di lunghezza arbitraria genera in output un message digest (128 bit); questo indirizzo x viene visto come una rappresentazione non ambigua e non falsificabile del messaggio M), nei compilatori di linguaggi (le parole chiave del linguaggio sono viste come chiavi stringa, vedi l'esempio introduttivo sopra esposto di D.E. Knuth!), in programmi di crittografia (il famoso PGP pretty good privacy ideato nel 1991 dallo statunitense Philip Zimmermann) Come funziona la tecnica hash? Consiste nell'individuare una funzione chiamata funzione di hash che faccia passare dalla chiave k alla posizione x di una tabella T[0..m-1] chiamata tabella di hash. Quindi l'elemento di chiave k viene memorizzato nella tabella proprio in posizione x. Facciamo ora un esempio. Supponiamo che h(k)=2·(k-1) e che le chiavi k siano numeri interi nel range 1 – 100 (universo di valori U). Possiamo osservare le posizioni x di memorizzazione dei dati associati alle chiavi 1,2,10,90,100. Calcoliamole: 0, 2, 18, 178, 198. In questo esempio possiamo osservare che abbiamo un raddoppio della dimensione della tabella dim(T) = m rispetto alla dimensione dim(U) = n dell'universo delle chiavi. Verificare che la metà delle posizioni della tabella non sono occupate mai da nessun dato (quelle con indirizzo dispari); Dare la definizione del fattore di carico α? Definizio fattore di carico o di riempimento di una tabella è il rapporto dim(U)/dim(T) = α Il suo significato è legato al valore medio del numero di chiavi che mediante la funzione h( ) vengono mappate nella medesima posizione o indirizzo della tabella T, si tratta delle collisioni di indirizzamento. α<1 Nell'esempio abbiamo che α=100/200 =0,5 e quindi nessuna chiave genera 4 16 pag. 15 Piero Gallo Fabio Salerno “Informatica Generale Teoria e tecnologie digitali dell'informazione e della comunicazione” - vol. 2 - Ed. Minerva mai il medesimo indirizzo. α=1 Se invece usiamo una funzione iniettiva del tipo h(k)=k-1 si ha che α=100/100 =1 e quindi nessuna chiave genera mai il medesimo indirizzo e nessuna posizione della tabella rimane vuota: si tratta di una tabella a indirizzamento diretto del tipo “tabelle con indice”. Quale scopo ha la funzione di hash? La funzione h() ha lo scopo di ridurre drasticamente la dimensione U ovvero di mappare le chiavi k in una tabella T molto più piccola della dimensione di U. Quindi indipendentemente dalla dimensione n=dim(U) del problema avremo una complessità O(1) costante, ovvero un tempo costante per l'accesso ad ogni elemento associato alla chiave k da cercare in T. Le collisioni che cosa sono? Le collisioni entrano in gioco proprio a causa di questa diversità nelle dimensioni fra U e T. La funzione di hash non è più iniettiva e quindi ora può accadere con sempre maggior probabilità che k1 e k2 abbiano lo stesso indirizzo nella tabella T. Le collisioni vanno gestite opportunamente e vedremo le diverse tecniche ma possiamo dire fin d'ora che le soluzioni dovranno essere tali da limitare la perdita delle prestazioni che la tecnica subisce anche con elevati coefficienti di collisione. Come viene scelta una buona funzione di hash? 1. Sia facilmente calcolabile; cioè composta da calcoli che siano i più semplici possibile, in modo da non appesantire il procedimento di conversione della chiave e diminuire, così, il tempo di accesso; 2. Deterministica cioè produca sempre lo stesso indirizzo o posizione a partire dalla stessa chiave K; 3. Criterio dell'uniformità semplice deve generare gli indirizzi x uniformemente distribuiti nell'ambito dell'archivio ovvero vengono generati indirizzi in modo equiprobabile in T; 4. Generare indirizzi casualmente distribuiti nell'ambito dell'archivio anche quando le chiavi sono simili (ad esempio X1,X2,... oppure chiavi anagramma o quasi anagramma DARE, MARE, CARE); 5. Copra per quanto possibile l'intero intervallo degli indirizzi nell'archivio evitando, se possibile, che ci siano indirizzi che non vengono mai generati; 6. Criterio di generazione completa: deve utilizzare tutte le cifre della chiave (che nel caso di chiavi alfanumeriche è un criterio più difficile da rispettare) 7. Generi indirizzi diversi se le chiavi sono diverse. 17 Trasformazione perfetta. Relativamente all'ultimo punto, quindi, la funzione ideale è quella che a ogni valore della chiave primaria fa corrispondere sempre un indirizzo diverso, di modo che ogni record possa essere raggiunto tramite un solo accesso. Quando ciò accade si parla di trasformazione perfetta. Se la chiave alfanumerica è solo chiave numeriche Per queste chiavi la funzione è semplice: x = K mod n; le collisioni prendono anche il nome di sinonimi. Chiavi alfanumeriche Un metodo molto usato è quello di stabilire la conversione tra sequenze di simboli interpretati come numeri in sistemi di numerazione in base diversa che permetta di rappresentare tutti i simboli alfanumerici utilizzati nella chiave. In genere si usa la codifica ASCII come sistema di numerazione in base a 128. ali (97,108,105) = 1.603.177 baba (98,97,98,97)=205.041.890 97⋅1282 108⋅1281105⋅1280 è cioè un polinomio di grado 2 nella base 128 L'algoritmo che trasforma una chiave alfanumerica in un numero intero compreso fra 0 e n dimensione della tabella T. - trasformare ogni carattere nel corrispondente codice ASCII decimale - sommare i singoli termini con il loro peso ( 128n ) - applicare la funzione di hash Per esempio chiave k alfanumerica= “ali” chiave numerica = 1.603.177 si corrisponde con l'indirizzo k mod 1024 = 617. NB. mod 1024 mi da i resti che vanno da 0 fino a 1023: quindi un vettore con 1024 posti da gestire. Perché è necessario spezzare le chiavi troppo lunghe? La funzione di hash in questo caso è detta funzione di hash modulare infatti questa trasforma un pezzo di chiave alla volta e somma i risultati parziali. Si basa sull'algoritmo di Horner che sfrutta le proprietà aritmetiche dell'operazione di modulo. s⋅12811 a⋅12810 Partendo dalla prima lettera della chiave alfanumerica: - numeric_key=(“s”*128+“a”) - ripetendo (numeric_key * 128 + prossimo_car_chiave_alfa) Ma a noi non interessa il risultato anche perché produrrei comunque un overflow, ma soltanto il resto della divisione per m (con m=1024 in questo esempio) - x=(“s”*128+“a”) MOD 1024 18 - ripetendo (x * 128 + prossimo_car_chiave_alfa) MOD 1024 4.2.3 Categorie di funzioni hash Quali tipi o categorie di funzione hash ci sono? 4.2.3.1 A divisione Sono quelli che abbiamo visto appena sopra; bisogna prestare attenzione ai valori di m che devono essere diversi dalle potenze del 2 o del 10, l'esperienza mostra che una buona scelta per m è un numero primo non troppo vicino alle potenze del 2. PRO e CONTRO: Molto veloce ma ci sono valori critici per m dimensione della tabella T. 4.2.3.2 A moltiplicazione La chiave k viene moltiplicata per una costante A compresa in [0,1] e si estrae la parte frazionaria del risultato (MOD 1), il risultato viene poi moltiplicato per m e se ne prende la sua parte intera. PRO e CONTRO: Il vantaggio del metodo della moltiplicazione rispetto a quello della divisione è di non avere valori critici per m. 4.2.3.3 Metodo della funzione universale Dato che al verificarsi di una collisione è necessario attuare una tecnica di gestione particolare per generare un nuovo indirizzo sulla medesima chiave in modo che sia diverso dal precedente e questo potrebbe richiedere numerose operazioni aggiuntive e, quindi, all'aumentare della frequenza delle collisioni viene rallentato l'accesso alla struttura dati, peggiorando le prestazioni allora l'idea è quella di usare una seconda funzione hash eventualmente una terza ... (ho una famiglia di h( ) ciascuna con una sua particolare proprietà e sono scelte in modo pseudocasuale). Con questo metodo si riduce a zero la probabilità che esista un insieme di chiavi che porti al caso peggiore: tutte le chiavi sono sinonimi. 4.2.4 Gestione delle collisioni Ricordiamo ancora la definizio di fattore di carico o di riempimento di una tabella è il rapporto dim(U)/dim(T) = α Come abbiamo detto questo valore esprime la probabilità media di collisione. La soluzione si basa principalmente sulle seguenti due strategie: 1. metodo di concatenazione (chaining) 2. metodo di indirizzamento aperto (open addressing) Ci sono diverse varianti nel metodo di concatenazione a seconda che i record che collidono vengano poi memorizzati in un'area di overflow oppure nella stessa area primaria. 19 4.2.5 Chaining o metodi di concatenazione 4.2.5.1 Tramite liste separate α qualunque La tabella si riduce cioè ad un vettore di puntatori a record; ogni elemento del vettore punta quindi alla testa di una lista di elementi. In ogni lista il numero medio di elementi memorizzati è pari al fattore di carico. Nel caso in cui tutti gli elementi collidono, caso peggiore, il tutto si riduce ad un'unica lista di lunghezza n e in questo caso la complessità di calcolo diventa O(n). Ma tale caso non viene contemplato se la funzione di hash verifica il criterio dell'uniformità semplice così ogni lista ha lunghezza α e il tempo medio di accesso risulta essere O(1+ α). 4.2.5.2 Chaining tramite liste confluenti α qualunque Genero una unica lista per ogni gruppo di p chiavi non sinonimi, in questo caso la lista ha una lunghezza media pari a pα. Introduco un ritardo rispetto al caso precedente nel recupero dell'informazione. Usato raramente. 4.2.5.3 Chaining in area separata (area di overflow o dei trabocchi) α<2 Si tratta del caso limite della precedente: si usa una unica lista e la si memorizza in un'apposita area di overflow diversa da quella primaria. Questa tecnica è usata in archivi in cui si ha un numero limitato di chiavi m e il valore di α è inferiore a 2. Il tempo medio di accesso per ritrovare un elemento è O(1+m α/2). 4.2.6 Open addressing o metodo di indirizzamento aperto 4.2.6.1 tecniche di scansione: lineare, quadratica, hashing doppio α <1 In questa seconda soluzione si sfrutta l'idea di memorizzare tutti gli elementi nella tabella stessa e quindi si evita la complicazione di dover gestire liste oppure aree aggiuntive: al verificarsi di una collisione si registra l'elemento nell'area “successiva”; sembrerebbe paradossale in quanto forse vado ad aumentare il numero di collisioni! È infatti necessario dire subito che questa considerazione è vera ma se si sceglie una condizione su α che deve essere sempre inferiore a 1 allora il metodo funziona bene. Questa condizione sul fattore di carico significa che il numero totale degli elementi n è sempre minore del numero totale delle posizioni m nella tabella e dunque anche se fossero presenti tutti i record nella tabella sarebbero lasciate libere molte posizioni. La funzione che genera la scansione è pseudocasuale Le funzioni che determinano la sequenza di scansione devono soddisfare allo stesso principio di uniformità che la funzione hash verifica: la sequenza di scansione deve essere in certa misura pseduocasuale in modo da limitare la sovrapposizione delle catene ovvero limitarne la loro intersezione. 20 Le soluzioni che si propongono possono essere classificate nelle seguenti tre famiglie: 1. scansione lineare (linear pobing) 2. scansione quadratica 3. hashing doppio • • • • • Nella scansione lineare come si inserisce un elemento che ha subito una collisione? Nella scansione lineare esiste il problema della agglomerazione (clustering) primaria di che cosa si tratta? ... si creano lunghi tratti in cui tutte le posizioni sono occupate Nella metodo a scansione lineare come si cerca un elemento? Nella scansione lineare che cosa si deve fare quando si cancella un elemento compreso in una sequenza dal vettore? Nella scansione lineare si ottiene un miglioramento se si cambia il passo di scansione da 1 a 2 o 3 perché non si generano più posizioni adiacenti ma rimane un problema quale? ... le catene si sovrappongono o si intersecano! Esempio: h(k)= K MOD 31 e s=2 le chiavi 1206 e 218 generano le sequenze: (28; 30; 1; 3; 5; 7; 9; ...) e (1; 3; 5; 7; 9; ...) dove è immediato comprendere che le due sequenze si intersecano nel punto “1” e poi da li in avanti coincidono! • Nella tecnica della scansione quadratica quale formula genera le sequenze dei 2 posti dopo una collisione? ... c 1⋅ic 2⋅i dove c 1 e c 2 sono costanti vincolate, da determinarsi volta per volta, per poter utilizzare tutte le posizioni della tabella! Esempio: c1=0 e c2=1 allora la formula che permette di determinare la sequenza è: h k =k MOD 311⋅i 2 MOD 31 e le due chiavi dell'esempio precedente ora generano le sequenze: (28; 29; 1; 6; 13; ...) e (1; 2; 5; 10; 17; ...) che si intersecano ma non si sovrappongono. Per risolvere il problema delle agglomerazioni e approssimare in modo migliore la proprietà di uniformità, si ricorre al metodo dell'hashing doppio il quale ha: 1. h 1 usata per la prima posizione 2. h 2 per generare i valori di salto ( s=h2 k ) Esempio: una tabella di 13 elementi con le seguenti funzioni di hash: h 1=k mod 13 e h 2=1k mod 8 . 21 5. Organizzazione ad indici B-Tree 5.1 Introduzione Innanzitutto precisiamo che si tratta di una variante dell'organizzazione ad indici multilivello. Sappiamo infatti che l’indice è mantenuto in una forma che rende assai veloce trovare i valori che interessano (un certo codice o un certo cognome ecc.): ad esempio potrebbe essere ordinato (per sfruttare una ricerca dicotomica) ed eventualmente strutturato come un albero binario di ricerca; è proprio in questo contesto che nasce la fortunatissima tecnica che sfrutta una forma modificata degli alberi binari, i “Btree” per l'appunto, che oltre a beneficiare in ricerca delle caratteristiche di un albero bilanciato ha una idea organizzativa fondamentale: quella di raggruppare più chiavi in uno stesso nodo dell'albero in modo che questo blocco di dati corrisponda per dimensione a quel del blocco fisico che il dispositivo di memorizzazione può leggere o scrivere con una sola operazione in modo da ottimizzare il numero di queste ultime. NB. I B+tree sono utilizzati da tutti i principali DBMS. 5.2 BTree I BTree sono alberi bilanciati di ricerca progettati per essere memorizzati su memorie esterne tipicamente più lente rispetto a quelle interne. Quindi la misura di efficienza (principale) non è il tempo necessario per accedere ad un determinato dato, ma la quantità di operazioni di I/O effettuate. In un BTree esistono strategie per mantenere bassa l'altezza dell'albero dato che, in tutte le operazioni effettuate, il numero di I/O aumenta con l'aumentare dell'altezza. Esistono poi delle varianti ottimizzate dei BTree come B*Tree, Btree+, Btree++. 5.3 Obiettivo limitare le operazioni di I/O In informatica quando si cerca di risolvere un problema l'analisi è una parte vitale dell'intero processo di progettazione. I due aspetti da considerare principalmente sono: • i tipi di dati sui quali lavorare • le operazioni da eseguire su quei dati per ottenere il risultato voluto. I due aspetti sono strettamente legati fra loro e la scelta della struttura dati influenza l'algoritmo in modo significativo. In molti casi sono possibili più strutture dati alternative tutte apparentemente idonee a risolvere ugualmente bene il problema. L'analisi preliminare dei costi in termini di operazioni necessarie, consente di preferire una struttura rispetto alle altre: infatti nel caso in cui viene scelta una struttura dati non ottimale potrebbe accadere di dover scrivere degli algoritmi estremamente complessi in casi in cui questo è evitabile. Tra le operazioni che i calcolatori sono in grado di svolgere, la memorizzazione delle informazioni comporta l'utilizzo di una unità esterna (oltre la RAM che viene intesa memoria interna). La memoria esterna più comune è l'Hard Disk la cui struttura è costituita da uno o più piatti ricoperti da materiale magnetico su entrambe le facce. Le unità possono avere dieci o più piatti disposti uno sull'altro su un asse passante per il centro del piatto. Ciascuna delle superfici è fornita di una propria testina di lettura e tutte le testine si muovono insieme verso l'interno o verso l'esterno, per leggere l'informazione che si trova a differenti distanze dal centro. Per leggere o scrivere un determinato byte su un disco, dobbiamo: 1. posizionare le testine in modo che una di esse stia sopra la traccia del byte prescelto; 2. aspettare che, per la rotazione del disco, il byte prescelto si posizioni sotto la testina. Ciascuna operazione può impiegare, in media, qualche centesimo di secondo; il tempo, in realtà, dipende non solo dalle caratteristiche dell'unità a disco, ma anche dal numero di tracce che le testine devono attraversare e dalla posizione del byte rispetto alle testine, nel momento in cui queste raggiungono la traccia. Spesso il tempo necessario per accedere e leggere una informazione memorizzata in un disco magnetico è superiore al tempo necessario all'elaboratore per esaminare tutta l'informazione letta! Proprio per questo motivo, quando occorre lavorare con grandi quantità di dati è spesso impossibile (o non opportuno) mantenere l'intera struttura dati all'interno della RAM. Invece è fortemente consigliato tenere in RAM una piccola porzione di struttura dati e quando necessario recuperarne altre porzioni dalla memoria di massa. I dati inseriti in RAM possono venire manipolati con una maggior velocità grazie ai tempi di accesso 105 volte superiori a quelli della memoria secondaria. In conclusione possiamo dedurre che la performance globale dipende non più dalla velocità di accesso di un “singolo dato (item)” ma dalla quantità di operazioni di Input/Ouput che vengono eseguite per risolvere l'intero problema. 24 5.4 Perché usiamo i BTree? I BTree sono alberi bilanciati di ricerca progettati per le operazioni sui dischi magnetici o altri dispositivi di memoria secondaria ad accesso diretto. Grazie all'uso della struttura dati ad albero e la caratteristica di poter memorizzare più chiavi in un nodo-pagina, il BTree (o Balanced Tree) è la struttura dati migliore per la gestione sia delle memorie interne che di quelle esterne. Infatti il BTree è ottimizzato per mantenere più elementi in un singolo nodopagina (infatti può corrispondere ad una pagina della memoria virtuale da qui il nome usato in questo contesto) cosicché vengono minimizzati i tempi di accesso al disco per trovare la chiave richiesta. Il numero di elementi in un nodo è legato al “grado t di ramificazione” dell'albero. Un grado di ramificazione elevato riduce sensibilmente sia l'altezza dell'albero, che il numero di accessi necessari a trovare una chiave qualsiasi. 5.5 Perché usare le varianti ottimizzate dei BTree Le varianti dei BTree hanno l'obiettivo di diminuire il tempo di accesso per letture sequenziali dei dati, al fine di incrementare le prestazioni in quelle applicazioni che richiedono sia un accesso random che sequenziale (per es. streaming Video/Audio o grandi database). 5.6 Caratteristiche I BTree sono alberi bilanciati di ricerca, i cui nodi possono avere anche parecchi figli, da poche decine a diverse centinaia, a seconda dal valore del grado minimo t, o fattore di ramificazione dell'albero che può variare da 3 a molte centinaia. Un nodo che contiene n chiavi avrà n+1 figli. Le chiavi sono sostanzialmente punti di divisione: dividono l'intervallo di chiavi gestite dal nodo in n+1 sotto-intervalli, dove ciascun sotto-intervallo è gestito da un figlio. Il grado minimo t, o fattore di ramificazione, definisce i limiti superiori e inferiori del numero di chiavi presenti in un nodo, con la seguente regola: Nel caso in cui il numero di elementi in un nodo è uguale al limite inferiore/superiore il nodo stesso si dice magro/pieno. 25 I nodi a profondità massima vengono chiamati foglie, tutte le foglie sono alla stessa profondità che coincide con altezza dell'albero. In un BTree con n chiavi il più lungo percorso dalla radice ad una foglia qualsiasi è al più: n1 log t 2 Il bilanciamento è garantito dal metodo di inserimento e cancellazione delle chiavi ed è molto importante. Infatti se consideriamo che un nodo può essere contenuto nella memoria primaria e che per leggere un altro nodo bisogna effettuare una lettura in memoria esterna, il bilanciamento aiuta ad ottimizzare lo spazio contenuto in un nodo risparmiando il costo di un'eventuale lettura su un albero sbilanciato. 5.7 Costo delle operazioni Poiché la visita di un nodo in un BTree richiede un accesso alla memoria secondaria, il numero di nodi visitati durante un'operazione forniscono una misura dei costi che è quasi sempre proporzionale all'altezza. 5.8 Altezza di un Btree Considerando un albero minimo, ad eccezione della radice, ogni nodo in un BTree ha al più t-1 discendenti diretti, mentre la radice ne ha almeno 2. Cosicché il numero di nodi è in relazione con l'altezza nel seguente modo: Quindi il numero totale di chiavi è: h n=1∑ 2 t i −1 t−1 i =1 n1 ≥t h 2 n1 h≤log t 2 Da ciò possiamo dedurre che in un BTree con grado minimo t=50 e circa 1.000.000 di record, una chiave può essere cercata con al più 4 accessi al di26 sco! 5.9 Operazioni Base Le operazioni effettuate in un BTree rispecchiano quelle di un qualunque albero binario con, in aggiunta, la gestione dei nodi magri e pieni. Si possono schematizzare nel seguente modo: 1. 2. 3. 4. 5. Creazione di un BTree Inserimento di una chiave • Gestione dei nodi pieni (operazione di SplitChild) Eliminazione di una chiave • Gestione dei nodi magri (operazione di Merge) Ricerca di una chiave Visita del BTree 5.9.1 Divisione di un nodo Procedura SplitChild La procedura divide un nodo y pieno, all'altezza della mediana, in due nodi magri. La chiave mediana viene spostata nel padre x del nodo y, che naturalmente non deve essere pieno, in modo tale si possa identificare il punto di divisione dei due nuovi sotto-alberi. Se il nodo y è la radice, allora l'albero aumenta di una unità in altezza. N.B. L'operazione di Split è un'operazione di crescita degli alberi. 5.9.2 Inserimento Quando si vuole inserire una chiave in un BTree il concetto è semplice. Se durante lo scorrimento dell'albero per trovare il nodo interessato, viene incontrato un nodo pieno avviene uno split (divisione). Algoritmo in linugaggio naturale: 1. Scorrere l'albero a partire dalla radice 2. se “la radice” è piena allora avviene uno split con un nuovo nodo (vuoto) e viene chiamata ricorsivamente la procedura di inserimento facendola partire dal sottoalbero dove dovrebbe essere inserita la chiave N.B. In questa fase viene incrementata l'altezza dell'albero. 3. Viene controllato se il nodo di partenza è foglia, se lo è viene inserita la chiave all'interno del nodo 4. Altrimenti, se il valore non è presente nel nodo e la radice del corrispondente sottoalbero è piena, viene eseguito lo Split della radice del sottoalbero 5. Viene chiamata ricorsivamente la funzione di inserimento facendola partire dal sottoalbero corrispondente Al più la funzione di inserimento viene richiamata h volte. 27 5.9.3 Fusione dei nodi Procedura Merge La fusione tra due nodi avviene quando i nodi interessati sono magri, così che il nodo ottenuto dalla fusione risulti pieno. 5.9.4 Cancellazione Analogamente all'operazione di inserimento, quando viene incontrato un nodo magro si cerca di effettuare un merge con il fratello oppure una donazione dal fratello. Naturalmente la donazione tra fratelli nodi interni avviene coinvolgendo anche i sottoalberi interessati. Proceura DeleteRecord 28 Casi: Nei casi 2a, 2b, 2c la chiave da cancellare è in un nodo interno magro. • case_2a: Il nodo ha un fratello dx non magro. Avviene quindi uno scambio di chiavi che porta l'incremento aggiornamento della chiave nel padre e l'incremento del numero di chiavi nel nodo interessato. • case_2b: Analogo al case_2a con la differenza che il nodo ha un fratello sx non magro. • case_2c: Avviene quando entrambi i nodi sono magri (viene richiamato un merge) Nei casi 3a, 3b la chiave non è nel nodo preso in considerazione durante la cancellazione e la radice del sottoalbero interessato è magra. • case_3a avviene una donazione da dx rendendo cosìla radice del sottoalbero non magra. • case_3b analogo al case_3a considerando la radice del sottoalbero sx. Fasi: 1. Si controlla se il valore da cancellare è presente nel nodo preso in considerazione (nel primo caso è la radice). 2. Se è presente avvengono ulteriori controlli per individuare la funzione da uti29 lizzare (case_2a, case_2b, case_2c) 1. Nel caso in cui è foglia, non magra e uguale alla radice si effettua una cancellazione del valore in quel nodo. 2. Altrimenti se la foglia è magra ed il sottoalbero fratello dx non è magro siamo nel case_2a. 3. Invece se il sottoalbero fratello sx non è magro siamo nel case_2b. 4. Altrimenti entriamo nel case_2c. 3. Se invece il valore non è presente nel nodo preso in considerazione: 1. Se non siamo in foglia viene controllato il sottoalbero relativo e se non è magro viene richiamata la funzione di cancellazione facendola partire dal quel sottoalbero. 2. Altrimenti se il sottoalbero corrispondente ha fratello dx o sx, viene chiamato rispettivamente il case_3a o case_3b e poi la funzione di cancellazione (ricorsivamente). N.B. Se il sottoalbero ha solo il fratello sx siamo in case_3b altrimenti se ha solamente il fratello dx siamo in case_3a. [Per approfondimenti si veda 4] 5.10 Codice B-Tree 5.10.1 B-Tree-Search(x, k) i <- 1 while i <= n[x] and k > keyi[x] do i <- i + 1 if i <= n[x] and k = keyi[x] then return (x, i) if leaf[x] then return NIL else Disk-Read(ci[x]) return B-Tree-Search(ci[x], k) 5.10.2 B-Tree-Create(T) x <- Allocate-Node() 30 leaf[x] <- TRUE n[x] <- 0 Disk-Write(x) root[T] <- x 5.10.3 B-Tree-Split-Child(x, i, y) z <- Allocate-Node() leaf[z] <- leaf[y] n[z] <- t - 1 for j <- 1 to t - 1 do keyj[z] <- keyj+t[y] if not leaf[y] then for j <- 1 to t do cj[z] <- cj+t[y] n[y] <- t - 1 for j <- n[x] + 1 downto i + 1 do cj+1[x] <- cj[x] ci+1 <- z for j <- n[x] downto i do keyj+1[x] <- keyj[x] keyi[x] <- keyt[y] n[x] <- n[x] + 1 Disk-Write(y) Disk-Write(z) Disk-Write(x) 5.10.4 B-Tree-Insert(T, k) r <- root[T] if n[r] = 2t - 1 then s <- Allocate-Node() root[T] <- s leaf[s] <- FALSE n[s] <- 0 c1 <- r B-Tree-Split-Child(s, 1, r) B-Tree-Insert-Nonfull(s, k) else B-Tree-Insert-Nonfull(r, k) 5.10.5 B-Tree-Insert-Nonfull(x, k) i <- n[x] if leaf[x] then while i >= 1 and k < keyi[x] do keyi+1[x] <- keyi[x] i <- i - 1 keyi+1[x] <- k n[x] <- n[x] + 1 Disk-Write(x) else while i >= and k < keyi[x] do i <- i - 1 i <- i + 1 Disk-Read(ci[x]) if n[ci[x]] = 2t - 1 then B-Tree-Split-Child(x, i, ci[x]) 31 if k > keyi[x] then i <- i + 1 B-Tree-Insert-Nonfull(ci[x], k) 32 Indice analitico A agglomerazione, problema nella tecnica hashing..................................21 Algoritmi di randomizzazione.............14 Algoritmo di Hash........................13 algoritmo di Horner, vedi algoritmo di hashing..................................18 archivio indice...........................9 archivio primari..........................9 aree di overflow, vedi inserimento archivi sequenziali ordinati.....................11 B blocco....................................4 blocco, lettura di un.....................4 buffer di I/O.............................4 C categorie di funzione hash ..............19 chiave primaria...........................3 Chiave Secondaria.........................3 chiave selettiva..........................3 collisioni di indirizzamento, vedi hashing .........................................16 Collisioni, chaining in area separata....20 Collisioni, chaining tramite liste confluenti...............................20 Collisioni, chaining tramite liste separate .........................................20 collisioni, gestione chaining, gestione open addressing..........................19 Collisoni, open addresing hashing doppio.20 Collisoni, open addresing scansione lineare .........................................20 Collisoni, open addresing scansione quadratica...............................20 concentrate, vedi aree di overflow.......11 D D. E. Knuth, esempio di hashing..........14 distribuite, vedi aree di overflow.......11 dizionario, vedi archivio indice..........9 F fattore di blocco.........................4 fattore di carico, vedi hashing..........16 funzione di hash modulare (per chiavi lunghe)..................................18 G gerarchia di indici, vedi livelli indice..9 H Hash, indirizzo di ......................13 hashing, categoria a divisione...........19 hashing, categoria a moltiplicazione.....19 hashing, funzione di scansione pseudocasuale............................20 hashing, metodo della funzione universale19 I ISAM, file con indici multipli...........12 L livelli di indice.........................9 M MD5, algoritmo di cifratura..............16 metodo polinomiale 128, vedi algoritmo di hashing..................................18 O organizzazione fisica.....................5 organizzazione logica.....................5 organizzazione, implementazione di un archivio..................................5 overflow, aree concentrate o aree distribuite..............................11 P pagine di un archivio primario...........10 PGP, algoritmo di crittografia...........16 Philip Zimmermann, vedi PGP..............16 proprietà aritmetiche delle operazioni modulari, vedi hashing modulare..........18 Q qualità, bonta di una funz di hash.......17 R record record record record record bloccato...........................5 fisico.............................4 logico.............................4 multibolocco.......................5 sbloccato..........................5 S selettiva, chiave selettiva...............3 sinonimi, chiavi sinomini, vedi collisionioni hash.......................18 Sistema Informatico.......................1 Sistema Informativo.......................1 sottoarchivi, vedi pagine di un archivio primario.................................10 sottoindici, vedi indici multipli o file ISAM.....................................12 T tabella hash.............................13 Tabelle hash.............................13 tempo di accesso costant.................16 tracciato record..........................3