請問二叉樹等數據結構的物理存儲結構是怎樣的?


  請問二叉樹等數據結構的物理存儲結構是怎樣的?

  好吧,咱們書上說了,一般兩種存儲方式: 1. 以完全二叉樹的形式用連續空間的數組存儲; 2. 以鏈表形式存儲,即各個數據之間保存了相關的數據的指針地址!
  如果回答就是這樣,那么我想大家也不費那神了,直接洗洗睡吧?咱們能不能深入點? 

  數組是好理解的,在內存在磁盤都是一樣的,連續相鄰的空間,挨着存放到磁盤就可以了;好吧,就算你正確?那么鏈表呢?拿簡單的單鏈表來說,上一個節點保存下一個節點的指針?是如何保存的?我們能想到的,就是一個上一節點存儲了下一節點的絕對地址或者偏移地址,好像是這樣的!

  那么問題來了,這個下一節點地址到底是什么樣的呢?是相對地址還是絕對地址?這個地址是怎么算出來的?存儲在內存上是肯定沒有問題的!但是如果存儲在磁盤上呢?如果這個地址是固定的,那么,如果換了硬盤(換了存儲介質),是否就找不到該地址(因為每個設備的地址自然是不一樣的)?

  針對這個問題,很是困擾了我很久!也詢問過幾個身邊的小伙伴,也都說不知道。后來在一次面試中,一面試官剛好問我這問題,我把自己的見解說完后,說我確實不知道是怎么存儲的。最后我要求他給予答案,然后,他說,就是存儲的下一節點的地址(內存地址),壓根不存在什么數據結構存儲於磁盤這種說法,內存中,是動態計算的值。如果存在內存拷貝,那么,也會重新計算這些地址的,所以看起來相同的結構,在不同存儲工具上,會會表現出不同的地址空間。

  好吧,我將信將疑!被丟了n個鄙視的表情,然后被pass掉了。

  那么,到底內存中的二叉樹怎么存儲在硬盤上的呢?

  其實硬盤上並沒有什么二叉樹的,硬盤只是充當了一個存儲介質,只是提供你要讀的時候可以取而已,而真正的數據結構,則需要在用的時候再還原出原來的樹形結構!

下面以一個簡單的示例來展示磁盤上的數據結構的存儲方式:

 

public class BinTreeDiskSample {
    private static int h = -1;
    private static Node root;
    static class Node implements Serializable {
        private static final long serialVersionUID = -4780741633734920991L;
        int data;
        transient Node left;
        transient Node right;
        int lHeight = -1, rHeight = -1;
        public Node(int data) {
            this.data = data;
        }
        public Node setLeft(Node left) {
            this.left = left;
            return this;
        }
        public Node setRight(Node right) {
            this.right = right;
            return this;
        }
        public Node getLeft() {
            return left;
        }
        public Node getRight() {
            return right;
        }
        // 后續遍歷寫入,先序遍歷讀出
        public int write(ObjectOutputStream out) throws IOException {
            if (left != null) {
                lHeight = left.write(out);
            }
            if (right != null) {
                rHeight = right.write(out);
            }
            h++;
            out.writeObject(this);
            return h;
        }
        private void init(List<Node> list) {
            if (lHeight != -1) {
                left = list.get(lHeight);
                left.init(list);
            }
            if (rHeight != -1) {
                right = list.get(rHeight);
                right.init(list);
            }
        }

    }
    public static void binTreePreOrderPrint(Node root) {
        System.out.print(root.data + " ");      // visit root
        if(root.left != null) {
            binTreePreOrderPrint(root.left);
        }
        if(root.right != null) {
            binTreePreOrderPrint(root.right);
        }
    }
    // 先序遍歷讀出
    public static void read(ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        List<Node> list = new ArrayList<Node>();
        Node n;
        Object obj;
        try {
            while ((obj = in.readObject()) != null) {
                n = (Node) obj;
                list.add(n);
            }
        }
        catch (Exception e) {
            // EOFException ...
//            e.printStackTrace();
        }
        root = list.get(list.size() - 1);
        root.init(list);
    }
    public static void main(String args[]) throws FileNotFoundException,
            IOException, ClassNotFoundException {
        // 構造一棵二叉樹 11 21 41 61 51 31
        Node n6 = new Node(61);
        Node n4 = new Node(41).setLeft(n6);
        Node n5 = new Node(51);
        Node n2 = new Node(21).setLeft(n4).setRight(n5);
        Node n3 = new Node(31);
        Node n1 = new Node(11).setLeft(n2).setRight(n3);
        root = n1;
        System.out.println("output node: ");
        binTreePreOrderPrint(root);
        // 將數據寫稿磁盤
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("btree.bin"));
        root.write(out);
        out.close();

        root = null;
        // 將數據從磁盤讀入,並進行數據結構的重新構建
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("btree.bin"));
        read(in);
        in.close();
        System.out.println("\nread node: ");
        binTreePreOrderPrint(root);
    }

}

 

輸出:

 

如上二叉樹的磁盤存儲,使用了java自帶的序列化工具,將節點寫入磁盤(注:這並不是一種好的實踐),然后在讀出的時候,按照寫稿時候的規則,進行重新構建二叉樹即可。

 所以:

  存儲磁盤的數據結構,只是一種約定的方式,只是為了方便在重新恢復鏈表,二叉樹等等內存結構的算法而已。

  如:數據庫索引是存儲在磁盤上,當表中的數據量比較大時,索引的大小也跟着增長,達到幾個G甚至更多。當我們利用索引進行查詢的時候,不可能把索引全部加載到內存中,只能逐一加載每個磁盤頁,這里的磁盤頁就對應索引樹的節點。

B+/-樹索引用使用很多的數據結構,下面做一點簡單介紹:

一、B-Tree
  m階B-Tree滿足以下條件:

  1、每個節點最多擁有m個子樹

  2、根節點至少有2個子樹

  3、分支節點至少擁有m/2顆子樹(除根節點和葉子節點外都是分支節點)

  4、所有葉子節點都在同一層、每個節點最多可以有m-1個key,並且以升序排列

二、B+Tree的定義
  B+Tree是B樹的變種,有着比B樹更高的查詢性能,來看下m階B+Tree特征:

  1、有m個子樹的節點包含有m個元素(B-Tree中是m-1)

  2、根節點和分支節點中不保存數據,只用於索引,所有數據都保存在葉子節點中。

  3、所有分支節點和根節點都同時存在於子節點中,在子節點元素中是最大或者最小的元素。

  4、葉子節點會包含所有的關鍵字,以及指向數據記錄的指針,並且葉子節點本身是根據關鍵字的大小從小到大順序鏈接。

 

下面讓我們來看看現代數據庫的磁盤存儲結構吧:

以下部分內容摘自: https://blog.csdn.net/qq910894904/article/details/39312901 

  我們都知道,數據庫通常使用B+樹作為索引,但是國內很少有人提到數據庫使用的是HeapFile來管理記錄的存儲。國外的一些大學在“數據庫系統實現”這門課上通常會讓學生實現一個簡單的數據庫,因此有不少HeapFile的資料。

基於Page的HeapFile
  采用鏈表形式的是HeapFile如下:

  Heap file和鏈表結構類似的地方:

    支持增加(append)功能
    支持大規模順序掃描
    不支持隨機訪問

  這種方式的HeapFile在尋找具有合適空間的半空Page時需要遍歷多個頁,I/O開銷大。因此一般常用的是采用基於索引的HeaFile.在HeapFile中使用一部分空間來存儲Page作為索引,並記錄對應Page的剩余量。如下:

像上圖那樣,索引單獨存在一個page上。數據記錄存在其他page上,如果有多個索引的page,則可以表示為:


下面是Heap file自有的一些特性:

數據保存在二級存儲體(disk)中:Heapfile主要被設計用來高效存儲大數據量,數據量的大小只受存儲體容量限制;

Heapfile可以跨越多個磁盤空間或機器:heapfile可以用大地址結構去標識多個磁盤,甚至於多個網絡;

數據被組織成頁;

頁可以部分為空(並不要求每個page必須裝滿);

頁面可以被分割在某個存儲體的不同的物理區域,也可以分布在不同的存儲體上,甚至是不同的網絡節點中。我們可以簡單假設每一個page都有一個唯一的地址標識符PageAddress,並且操作系統可以根據PageAddress為我們定位該Page。

一般情況下,使用page在其所在文件中的偏移量就可以表示了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM