主頁 > 後端開發 > B+ tree implemented in Java

B+ tree implemented in Java

2023-06-26 07:40:44 後端開發

B+樹相關介紹

B+樹是一棵多叉排序樹,即每個非葉子節點可以包含多個子節點,其整體結構呈扁平化,所以其非常適配于資料庫和作業系統的檔案系統中,且B+樹能夠保持資料的穩定有序,插入和洗掉都擁有較穩定的對數時間復雜度

B+樹的特性:以 m 階為例,m 表示內部節點即非葉子節點可以包含的最大子節點個數 maximumNum

  • 若一個內部節點有 \(n(n <= m)\) 個子節點,則該內部節點應包含 \(n - 1\) 個關鍵字,也就是索引值
  • 除根節點和葉子節點外,其他節點至少包含 Math.ceil(m / 2) 個子節點,這是因為B+樹的生成順序導致的
    • 最開始,B+樹只有一個根節點和若干不超過m個的葉子節點;
    • 逐漸添加,導致葉子節點超過m時,此時根節點的子節點個數大于m,不符合要求,需要分裂
    • 分裂則導致增加2個內部節點,其中一個內部節點個數為(m+1)/2,另一個為(m+2)/2
    • 其他內部節點也是如此規律形成,所以所有內部節點的子節點個數均大于Math.ceil(m / 2)
  • 內部節點即對應索引部分,節點中僅包含子樹中最大/最小的索引值
  • 葉子節點即對應資料部分,節點中不僅包含索引值,也包含其他的值資訊
  • 最底層所有葉子節點通過雙向鏈表串聯,優化范圍查詢

B+樹實作

目前實作的B+樹的簡易版,葉子節點是存盤的Entry<Integer, String>鍵值對,內部節點存盤的是Integer索引,后續有時間再進行泛型的通用擴展,

節點定義

抽象公共父類 Node

package bplustree;

public abstract class Node {
    InternalNode parent;    // 父節點
	
    public Node() {}
    
    public abstract boolean isValid();          // 判斷洗掉節點后各B+樹節點是否滿足要求
    public abstract boolean isAvailable();      // 判斷B+樹節點是否可以分裂節點給其他節點
    public abstract boolean isMergeable();      // 判斷B+樹節點是否可以和其他節點合并
}

內部節點定義

public class InternalNode extends Node{
    int maxChildNodes;                   // 子節點個數最大值 m,m為階數
    int minChildNodes;                   // 除根節點及葉子節點外,子節點個數最小值 ceil(m / 2)
    
    int curNodesNum;                     // 內部節點當前的子節點個數
    InternalNode leftSibling;            // 左兄弟節點
    InternalNode rightSibling;           // 右兄弟節點
    Integer[] keys;                      // 內部節點當前的索引值,最多有 m - 1 個
    Node[] childPointers;                // 內部節點當前的子節點,最多有 m 個
}

葉子節點定義

public class LeafNode extends Node {
    int maximumNum;                      // 葉子節點最多元素個數 m - 1
    int minimumNum;                      
    int curNum;                          // 葉子節點當前的元素個數
    LeafNode leftSibling;                // 左兄弟和右兄弟形成雙向鏈表         
    LeafNode rightSibling;
    Entry[] entries;                     // 葉子節點鍵值對,不僅存盤索引值,也存盤其他值資訊
}


class Entry implements Comparable<Entry> {
    Integer key;
    String value;

    public Entry(Integer key, String value) {
        this.key = key;
        this.value = https://www.cnblogs.com/ylyzty/p/value;
    }

    @Override
    public int compareTo(Entry o) {
        return key.compareTo(o.key);
    }
}

B+樹定義

public class BPlusTree {
    int m;
    InternalNode root;         // B+樹根節點
    LeafNode head;             // 葉子節點的首元素
}

查詢操作

單值查詢

B+樹的查找程序:根據查找的索引值k,從根節點向葉子節點搜索,對數時間復雜度

public String search(int key) {
    if (isEmpty()) {
        return null;
    }

    // 樹查找
    LeafNode leafNode = (this.root == null) ? this.head : findLeafNode(key);
    Entry[] entries = leafNode.entries;
    
    // 葉子節點內進行二分查找
    int index = binarySearch(entries, leafNode.curNum, key, null);

    if (index == -1) {
        return null;
    }
    else {
        return entries[index]
    }
}

// 從根節點開始查找
private LeafNode findLeafNode(Integer key) {
    return findLeafNode(root, key);
}

// 找到索引值所在的葉子節點
private LeafNode findLeafNode(InternalNode internalNode, Integer key) {
    Integer[] keys = internalNode.keys;

    int i;
    for (i = 0; i < internalNode.curNodesNum - 1; i++) {
        if (key.compareTo(keys[i]) < 0) {
            break;
        }
    }

    Object child = internalNode.childPointers[i];
    if (child instanceof LeafNode) {
        return (LeafNode) child;
    }
    else {
        return findLeafNode((InternalNode) child, key);
    }
}
區間查詢

B+樹區間查詢左值可能在的葉子節點位置,然后通過雙向鏈表向后遍歷,

// 閉區間 [left, right]
public ArrayList<String> searchRange(int left, int right) {
    List<String> values = new ArrayList<>();
    LeafNode leafNode = findLeafNode(left);    // 查找左值可能存在的位置,并從該位置向后遍歷

    boolean flag = true;
    while (leafNode != null && flag) {
        Entry[] entries = leafNode.entries;
        for (Entry entry : entries) {
            if (entry == null) {
                break;
            }
            
            if ( entry.key > right) {
                flag = false;
                break;
            }

            if (left <= entry.key && right >= entry.key) {
                values.add(entry.value);
            }
        }

        leafNode = leafNode.rightSibling;
    }

    return values;
}

插入操作

B+樹的插入操作僅在葉子節點進行:

  1. 若為空樹,則創建一個葉子節點,該葉子節點同時也是根節點,插入操作結束;
  2. 根據插入的 key 值,找到應該在的葉子節點插入;
    1. 若插入后葉子節點個數符合要求即小于m,則插入結束
    2. 若插入后葉子節點個數不符合要求即大于等于m,將該節點分裂成兩半,則判斷當前葉子節點是否為根節點
      1. 若當前葉子節點為根節點,則構建一個新的root節點,指向分裂后的兩個子節點
      2. 若當前葉子節點不為根節點,則在父節點處添加一個新的子節點,新子節點則存盤原節點一半的值,并回圈向上判斷中間節點是否滿足要求
public void insert(int key, String value) {
    if (isEmpty()) {
        this.head = new LeafNode(this.m, new Entry(key, value));
    }
    else {
        LeafNode leafNode = (this.root == null) ? this.head : findLeafNode(key);

        // 插入葉子節點失敗,即葉子節點中存盤已到達上限
        if (!leafNode.insert(new Entry(key, value))) {
            leafNode.entries[leafNode.curNum++] = new Entry(key, value);
            sortEntries(leafNode.entries);

            // 葉子節點分裂的位置
            int mid = getIndexOfMidPointer();
            Entry[] halfEntry = splitEntries(leafNode, mid);

            // 若葉子節點為根節點,即parent為null
            if (leafNode.parent == null) {
                Integer[] parent_keys = new Integer[m];    
                parent_keys[0] = halfEntry[0].key;

                // 創建新的 root
                InternalNode parent = new InternalNode(m, parent_keys);
                leafNode.parent = parent;
                parent.appendChildPointer(leafNode);
            }

            // 若葉子節點不為根節點
            else {
                int newParentKey = halfEntry[0].key;
                leafNode.parent.keys[leafNode.parent.curNodesNum - 1] = newParentKey;
                Arrays.sort(leafNode.parent.keys, 0, leafNode.parent.curNodesNum);
            }

            // 分裂后的另一半葉子節點,添加到父節點
            LeafNode newLeafNode = new LeafNode(this.m, halfEntry, leafNode.parent);

            // 分裂后的另一半葉子節點對應的下標
            int index = leafNode.parent.getIndexOfPointer(leafNode) + 1;
            for (int i = index; i < leafNode.parent.childPointers.length - 1; i++) {
                leafNode.parent.childPointers[i + 1] = leafNode.parent.childPointers[i];
            }
            leafNode.parent.childPointers[index] = newLeafNode;


            // 關聯兄弟節點
            newLeafNode.rightSibling = leafNode.rightSibling;
            if (newLeafNode.rightSibling != null) {
                newLeafNode.rightSibling.leftSibling = newLeafNode;
            }
            leafNode.rightSibling = newLeafNode;
            newLeafNode.leftSibling = leafNode;

            if (this.root == null) {
                this.root = leafNode.parent;
            }
            else {
                // 逐漸上浮,判斷插入是否會導致B+樹內部節點不符合要求
                InternalNode internalNode = leafNode.parent;
                while (internalNode != null) {
                    if (internalNode.isOverfull()) {
                        splitInternalNode(internalNode);
                    }
                    else {
                        break;
                    }

                    internalNode = internalNode.parent;
                }
            }
        }
    }
}


/**
 * 葉子節點插入,導致的上層內部節點分裂
 */
private void splitInternalNode(InternalNode internalNode) {
    InternalNode parent = internalNode.parent;

    int mid = getIndexOfMidPointer();
    Integer newParentKey = internalNode.keys[mid];

    // 內部節點的 node 分裂
    Node[] halfPointers = splitChildPointers(internalNode, mid);
    // 內部節點的 key 分裂
    Integer[] halfKeys = splitKeys(internalNode.keys, mid);

    // 分裂后內部節點的子節點個數
    internalNode.curNodesNum = linearNullSearch(internalNode.childPointers);
    InternalNode sibling = new InternalNode(this.m, halfKeys, halfPointers);
    for (Node pointer : halfPointers) {
        if (pointer != null) {
            pointer.parent = sibling;
        }
    }

    sibling.rightSibling = internalNode.rightSibling;
    internalNode.rightSibling = sibling;
    sibling.leftSibling = internalNode;
    if (sibling.rightSibling != null) {
        sibling.rightSibling.leftSibling = sibling;
    }

    // root node
    if (parent == null) {
        Integer[] keys = new Integer[this.m];
        keys[0] = newParentKey;

        InternalNode newRoot = new InternalNode(this.m, keys);
        newRoot.appendChildPointer(internalNode);
        newRoot.appendChildPointer(sibling);
        this.root = newRoot;

        internalNode.parent = newRoot;
        sibling.parent = newRoot;
    }
    else {
        parent.keys[parent.curNodesNum - 1] = newParentKey;
        Arrays.sort(parent.keys, 0, parent.curNodesNum);

        int index = parent.getIndexOfPointer(internalNode) + 1;
        parent.insertChildPointer(sibling, index);
        sibling.parent = parent;
    }
}


private Node[] splitChildPointers(InternalNode node, int split) {
    Node[] pointers = node.childPointers;
    Node[] newPointers = new Node[this.m + 1];

    for (int i = split + 1; i < pointers.length; i++) {
        newPointers[i - split - 1] = pointers[i];
        node.removePointer(i);
    }

    return newPointers;
}


private Integer[] splitKeys(Integer[] keys, int split) {
    Integer[] newKeys = new Integer[m];
    keys[split] = null;

    for (int i = split + 1; i < keys.length; i++) {
        newKeys[i - split] = keys[i];
        keys[i] = null;
    }

    return newKeys;
}

洗掉操作

B+樹的洗掉操作僅在葉子節點進行:

  1. 若洗掉后,葉子節點中的索引個數仍然滿足要求即大于等于Math.ceil(m / 2)時,將該葉子節點的其他索引左移一位,洗掉結束;
  2. 若洗掉后,葉子節點中的索引個數不滿足最低要求,則查詢左右兄弟節點:
    1. 若左/右兄弟節點中索引個數大于Math.ceil(m / 2),則從左/右兄弟節點中移動一個索引項到當前葉子節點中,并修改父節點的索引值,洗掉結束
    2. 若左/右兄弟節點中索引個數等于Math.ceil(m / 2),則將左/右節點與當前節點合并,修改父節點的索引記錄,并向上逐級判斷內部節點是否因為頁合并導致索引項不滿足最低要求,洗掉結束
public void delete(int key) {
    if (isEmpty()) {
        System.out.println("Invalid: The B+ tree is empty!");
    }
    else {
        LeafNode leafNode = this.root == null ? this.head : findLeafNode(key);
        int index = binarySearch(leafNode.entries, leafNode.curNum, key, null);

        if (index < 0) {
            System.out.println("Invalid: The key does not exist in the B+ tree!");
        }
        else {
            leafNode.deleteAtIndex(index);

            // 洗掉后,葉子節點仍然滿足要求,洗掉結束
            if (leafNode.isValid()) {
                LeafNode sibling;
                InternalNode parent = leafNode.parent;

                // 洗掉后,葉子節點不滿足要求,左兄弟節點可以移動一個索引項到當前葉子節點
                if (leafNode.leftSibling != null && leafNode.leftSibling.parent == parent && leafNode.leftSibling.isAvailable()) {
                    sibling = leafNode.leftSibling;
                    Entry entry = sibling.entries[sibling.curNum - 1];
                    leafNode.insert(entry);
                    sortEntries(leafNode.entries);

                    sibling.deleteAtIndex(sibling.curNum - 1);

                    // 更新 parent 的 key
                    int pointIndex = getIndexOfLeafNode(parent.childPointers, leafNode);
                    if (entry.key < parent.keys[pointIndex - 1]) {
                        parent.keys[pointIndex - 1] = entry.key;
                    }
                }
                // 洗掉后,葉子節點不滿足要求,右兄弟節點可以移動一個索引項到當前葉子節點
                else if (leafNode.rightSibling != null && leafNode.rightSibling.parent == parent && leafNode.rightSibling.isAvailable()) {
                    sibling = leafNode.rightSibling;
                    Entry entry = sibling.entries[0];
                    leafNode.insert(entry);
                    sortEntries(leafNode.entries);

                    sibling.deleteAtIndex(0);

                    // 更新 parent 的 key
                    int pointIndex = getIndexOfLeafNode(parent.childPointers, leafNode);
                    if (entry.key > parent.keys[pointIndex]) {
                        parent.keys[pointIndex] = entry.key;
                    }
                }
                // 洗掉后,葉子節點不滿足要求,左兄弟節點可以與當前葉子節點合并
                else if (leafNode.leftSibling != null && leafNode.leftSibling.parent == parent && leafNode.leftSibling.isMergeable()) {
                    sibling = leafNode.leftSibling;
                    int pointIndex = getIndexOfLeafNode(parent.childPointers, leafNode);

                    parent.removeKey(pointIndex - 1);
                    parent.removePointer(leafNode);

                    sibling.rightSibling = leafNode.rightSibling;
                    if (parent.isValid()) {
                        handleDeficiency(parent);
                    }
                }
                // 洗掉后,葉子節點不滿足要求,右兄弟節點可以與當前葉子節點合并
                else if (leafNode.rightSibling != null && leafNode.rightSibling.parent == parent && leafNode.rightSibling.isMergeable()) {
                    sibling = leafNode.rightSibling;
                    int pointIndex = getIndexOfLeafNode(parent.childPointers, leafNode);

                    parent.removeKey(pointIndex);
                    parent.removePointer(leafNode);

                    sibling.leftSibling = leafNode.leftSibling;
                    if (sibling.leftSibling == null) {
                        this.head = sibling;
                    }

                    // 逐級向上層判斷是否滿足要求
                    if (parent.isValid()) {
                        handleDeficiency(parent);
                    }
                }
                // 洗掉后,B+樹為空
                else if (this.root == null && this.head.curNum == 0) {
                    this.head = null;
                }
            }
        }
    }
}



/**
 * 處理不滿足要求的內部節點
 * @param internalNode
 */
private void handleInvalidInternalNode(InternalNode internalNode) {
    InternalNode sibling;
    InternalNode parent = internalNode.parent;

    // 當前內部節點為根節點
    if (root == internalNode) {
        for (int i = 0; i < internalNode.childPointers.length; i++) {
            if (internalNode.childPointers[i] != null) {
                if (internalNode.childPointers[i] instanceof InternalNode) {
                    root = (InternalNode) internalNode.childPointers[i];
                    root.parent = null;
                }
                else if (internalNode.childPointers[i] instanceof LeafNode) {
                    root = null;
                }
            }
        }
    }

    // 左兄弟節點可以移動索引項
    else if (internalNode.leftSibling != null && internalNode.leftSibling.isAvailable()) {
        sibling = internalNode.leftSibling;
        Integer key = sibling.keys[internalNode.curNodesNum - 2];
        Node pointer = sibling.childPointers[internalNode.curNodesNum - 1];

        shiftKeys(internalNode.keys, 1);
        shiftPointers(internalNode.childPointers, 1);
        internalNode.keys[0] = key;
        internalNode.childPointers[0] = pointer;

        sibling.removePointer(pointer);
    }
    // 右兄弟節點可以移動索引項
    else if (internalNode.rightSibling != null && internalNode.rightSibling.isAvailable()) {
        sibling = internalNode.rightSibling;

        Integer key = sibling.keys[0];
        Node pointer = sibling.childPointers[0];

        internalNode.keys[internalNode.curNodesNum - 1] = parent.keys[0];
        internalNode.childPointers[internalNode.curNodesNum] = pointer;

        parent.keys[0] = key;

        sibling.removePointer(0);
        shiftPointers(sibling.childPointers, -1);
    }
    // 左兄弟節點可以合并
    else if (internalNode.leftSibling != null && internalNode.leftSibling.isMergeable()) {
        sibling = internalNode.leftSibling;
        int index = -1;
        for (int i = 0; i < parent.childPointers.length; i++) {
            if (parent.childPointers[i] == internalNode) {
                index = i;
                break;
            }
        }
        parent.keys[index - 1] = parent.keys[index];
        for (int i = index; i < parent.keys.length - 1; i++) {
            parent.keys[i] = parent.keys[i + 1];
        }

        shiftPointers(internalNode.childPointers, (int) Math.ceil(m / 2.0));
        for (int i = 0; i < (int) Math.ceil(m / 2.0); i++) {
            internalNode.childPointers[i] = sibling.childPointers[i];
        }

        internalNode.leftSibling = sibling.leftSibling;
        if (internalNode.leftSibling != null) {
            internalNode.leftSibling.rightSibling = internalNode;
        }

        if (parent != null && parent.isValid()) {
            handleInvalidInternalNode(parent);
        }
    }
    // 右兄弟節點可以合并
    else if (internalNode.rightSibling != null && internalNode.rightSibling.isMergeable()) {
        sibling = internalNode.rightSibling;
        int index = -1;
        for (int i = 0; i < parent.childPointers.length; i++) {
            if (internalNode == parent.childPointers[i]) {
                index = i;
                break;
            }
        }

        parent.keys[index] = parent.keys[index + 1];
        for (int i = index + 2; i < parent.keys.length; i++) {
            parent.keys[i - 1] = parent.keys[i];
        }
        for (int i = 0; i < (int) Math.ceil(m / 2.0); i++) {
            internalNode.childPointers[internalNode.curNodesNum++] = sibling.childPointers[i];
        }

        internalNode.rightSibling = sibling.rightSibling;
        if (internalNode.rightSibling != null) {
            internalNode.rightSibling.leftSibling = internalNode;
        }

        if (parent != null && parent.isValid()) {
            handleInvalidInternalNode(parent);
        }
    }
}

參考文章:

1. B+樹

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555894.html

標籤:Java

上一篇:面試官:MySQL 自增主鍵一定是連續的嗎?大部分人都會答錯!

下一篇:返回列表

標籤雲
其他(161553) Python(38244) JavaScript(25513) Java(18253) C(15238) 區塊鏈(8272) C#(7972) AI(7469) 爪哇(7425) MySQL(7266) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4606) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2437) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) HtmlCss(1971) 功能(1967) Web開發(1951) C++(1942) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • B+ tree implemented in Java

    ## B+樹相關介紹 > B+樹是一棵**多叉排序樹**,即每個非葉子節點可以包含多個子節點,其整體結構呈扁平化,所以其非常適配于資料庫和作業系統的檔案系統中。且B+樹能夠保持資料的穩定有序,插入和洗掉都擁有較穩定的**對數時間復雜度**。 **B+樹的特性**:以 m 階為例,m 表示內部節點即非 ......

    uj5u.com 2023-06-26 07:40:44 more
  • 面試官:MySQL 自增主鍵一定是連續的嗎?大部分人都會答錯!

    ## 測驗環境: > MySQL版本:8.0 資料庫表:T (主鍵id,唯一索引c,普通欄位d) ![](https://img2023.cnblogs.com/other/1218593/202306/1218593-20230625093159551-1903519851.png) 如果你的業務 ......

    uj5u.com 2023-06-26 07:35:25 more
  • 【爬蟲案例】用Python爬大麥網任意城市的近期演出活動!

    [toc] # 一、爬取目標 大家好,我是[@馬哥python說](https://www.zhihu.com/people/13273183132) ,一枚10年程式猿。 今天分享一期python爬蟲案例,爬取目標是大麥網近期演出活動:[- 大麥搜索](https://search.damai.c ......

    uj5u.com 2023-06-25 07:39:19 more
  • [ARM 匯編]高級部分—性能優化與除錯—3.4.2 ARM匯編程式除錯技

    在ARM匯編程式開發程序中,除錯是一個關鍵環節。適當的除錯技巧可以幫助我們更快地定位問題、解決問題,從而提高開發效率。本節將講解一些ARM匯編程式的除錯技巧,并通過實體進行講解。 1. **使用GDB除錯** GDB(GNU除錯器)是一個功能強大的除錯工具,它支持ARM匯編程式的除錯。以下是使用GD ......

    uj5u.com 2023-06-25 07:39:09 more
  • 一文了解Go語言的匿名函式

    # 1. 引言 無論是在`Go`語言還是其他編程語言中,匿名函式都扮演著重要的角色。在本文中,我們將詳細介紹`Go`語言中匿名函式的概念和使用方法,同時也提供一些考慮因素,從而幫助在匿名函式和命名函式間做出選擇。 # 2. 基本定義 匿名函式是一種沒有函式名的函式。它是在代碼中直接定義的函式,沒有被 ......

    uj5u.com 2023-06-25 07:39:04 more
  • Scala練習題

    # SQL join語法案例 Data: ```Plain Text order.txt order011,u001,300 order012,u002,200 order023,u006,100 order056,u007,300 order066,u003,500 order055,u004,3 ......

    uj5u.com 2023-06-25 07:38:59 more
  • C++面試八股文:std::vector和std::list,如何選擇?

    某日二師兄參加XXX科技公司的C++工程師開發崗位第24面: > 面試官:`list`用過嗎? > > 二師兄:嗯,用過。 > > 面試官:請講一下`list`的實作原理。 > > 二師兄:`std::list`被稱為雙向鏈表,和C中手寫雙向鏈表本質上沒有大的區別。`list`物件中有兩個指標,一個 ......

    uj5u.com 2023-06-25 07:38:54 more
  • Python潮流周刊#8:Python 3.13 計劃將解釋器提速 50%!

    你好,我是貓哥。這里每周分享優質的 Python 及通用技術內容,部分為英文,已在小標題注明。(標題取自其中一則分享,不代表全部內容都是該主題,特此宣告。) 首發于我的博客:[https://pythoncat.top/posts/2023-06-24-weekly](https://pythonc ......

    uj5u.com 2023-06-25 07:38:32 more
  • 55基于java的在線零食超市系統設計與實作

    基于java在線零食超市系統設計與實作,可適用于零食小吃,在線零食小吃超市,線上超市,線上零食商城,美食商城,美食超市,校園超市,零食資訊等等。 ......

    uj5u.com 2023-06-25 07:37:44 more
  • Cookie和Session

    # Cookie和Session ## 會話 - 什么是會話? 會話是瀏覽器和服務器之間的多次請求和回應 也就是說,從瀏覽器訪問服務器開始,到訪問服務器結束,瀏覽器關閉為止的這段時間內容產生的多次請求和回應,合起來叫做瀏覽器和服務器之間的一次會話 - 有狀態會話:一個網站知曉你登陸過、存盤了一些基本 ......

    uj5u.com 2023-06-25 07:32:02 more