1. 程式人生 > >請問二叉樹等資料結構的物理儲存結構是怎樣的?

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

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

  好吧,咱們書上說了,一般兩種儲存方式: 1. 以完全二叉樹的形式用連續空間的陣列儲存; 2. 以連結串列形式儲存,即各個資料之間儲存了相關的資料的指標地址!
  如果回答就是這樣,那麼我想大家也不費那神了,直接洗洗睡吧?

咱們能不能深入點: 

  陣列是好理解的,在記憶體在磁碟都是一樣的,連續相鄰的空間;好吧,就算你正確?那麼連結串列呢?拿簡單的單鏈表來說,上一個節點儲存下一個節點的指標?是如何儲存的?我們能想到的,就是一個下一節點的儲存地址,好像是這樣的!

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

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

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

  那麼,到底記憶體中的二叉樹怎麼儲存在硬碟上的呢?

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

下面以一個簡單的示例來展示磁碟上的資料結構的儲存方式:

 

public class BinTreeDiskSample {
    private static int Sn = -1;
    private static Node root;
    static private 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); } Sn++; out.writeObject(this); return Sn; } 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 { // 構造一棵二叉樹 /* * 1 2 3 4 5 6 */ 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在其所在檔案中的偏移量就可以表示了。