mysql索引之B+树

news2024/11/19 14:44:40

1.概述

提到B+树就不得不提及二叉树,平衡二叉树和B树这三种数据结构了。B+树就是从他们三个演化来的。众所周知B+树是一种常见的数据结构,被广泛应用于数据库和文件系统等领域,B+树的设计目标是保持树的平衡性,以提供稳定的性能,并且适用于大规模数据存储。B+树由一个根节点、内部节点和叶子节点组成,其中内部节点用于索引和导航,而叶子节点存储实际的数据。

2.B+树基本结构

1.keyAndValue:键值对--key是标识;value是存储的具体数据

2.node:节点的子节点,存储的是具体的子节点

3.next:节点的后节点,标记后一个节点

4.previous:节点的前节点,标记前一个节点

5.head:节点的父节点,标记本机的父节点

3.探索三层结构

此部分依据以下图片展开描述

                                                                        B+树的结构图             

   上图的结果是按照B+树的原理添加13个数据以java代码实现的结果,通过过图片的形式可视化了

此次实现我们将阶数设定为了4阶

4.深入了解节点的生成

 以上图片的结果是怎么实现的呢,下面我们将展开描述

4.1 存储第一条数据

存储第一条数据时此时没有节点,首先判断有没有头节点,没有头节点就将第一条数据添加到一个存放键值对的集合keyAndValue中,并依据此集合初始化一个节点,并将这个新初始化节点分别标记为根节点和头节点。此结果如下图

4.2 单个节点

没有超过设定的阶数

当存储第二和三个数据时,首先判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。如果是则再判断有没有超过设定的阶数,没有则将将数据直接插入到当前结点。当前的结点是最后一个结点并且没有超过设定的阶数,因此直接将二三个数据直接插入到当前的结点当中 。 效果入下图所示

 超过设定的阶数

在存储第四个数时,首先判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。如果是再判断是否超过设定的阶数了,超过了则取出原来key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点);此时将头节点重置为前节点leftNode;新建一个子节点childNode并将前节点leftNode和后节点rightNode添加进去,然后构造一个父节点parentNode结构为:子节点(childNode),键值对(midKeyAndValue),前节点(null),后节点(null),父节点(null)。并将子节点与父节点进行关联。将当前父节点设置为根节点。此时就转变成“一父二子”了;结果如下图

4.3三个节点

在上图节点的基础上我们再添加第六个数据时(如下图)节点超过阶数就会分裂,以下是分裂时的情况

 

超过阶数

在添加第六条数据的时候首先以L1节点为head判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。

1.如果是再判断是否超过设定的阶数了,此时设定的场景是要超过设定的阶数,先取出L1节点的key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点),新建一个子节点集合childNodes并分别将前节点leftNode,后节点rightNode添加进去;如果头结点是当前要分隔的节点则将头节点重置为前节点leftNode,获取到当前L1节点的父节点parentNode,并获取到父节点的所有子节点,将这些子节点全部添加到子节点集合childNodes中,然后删除当前的L1节点,然后将子节点重置成新的子节点集合childNodes;继续以父节点为依据判断是否超过设定的阶数了,此时没有超过阶数,将键值对midKeyAndValue直接保存到当前的父节点中,并进行排序操作,此时就变成“一夫三子”了

2.如果不是最后一个结点或者要插入的键值对的键的值大于下一个结点的的键的最小值,此时移动指针,将R1作为依据再进行判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值,此时肯定符合条件的,再判断是否超过设定的阶数了,此时设定的场景是要超过设定的阶数,先取出R1节点的key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点),新建一个子节点集合childNodes并分别将前节点leftNode,后节点rightNode添加进去;如果头结点是当前要分隔的节点则将头节点重置为前节点leftNode,获取到当前L1节点的父节点parentNode,并获取到父节点的所有子节点,将这些子节点全部添加到子节点集合childNodes中,然后删除当前的R1节点,然后将子节点重置成新的子节点集合childNodes;继续以父节点为依据判断是否超过设定的阶数了,此时没有超过阶数,将键值对midKeyAndValue直接保存到当前的父节点中,并进行排序操作,生成一个一父三子的结构如下图()

4.4八个节点

一父四子后再添加数据,部分满跟都满都会使得节点再次分裂,分裂成一父二子的情况

在添加数据的时候首先以左边第一个节点为head判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。

1.如果是再判断是否超过设定的阶数了,此时设定的场景是要超过设定的阶数,先取出当前节点的key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点),新建一个子节点集合childNodes并分别将前节点leftNode,后节点rightNode添加进去;如果头结点是当前要分隔的节点则将头节点重置为前节点leftNode,获取到当前节点的父节点parentNode,并获取到父节点的所有子节点,将这些子节点全部添加到子节点集合childNodes中,然后删除当前节点,然后将子节点重置成新的子节点集合childNodes;

2.继续以父节点为依据判断是否超过设定的阶数了,此时超过阶数,超过了则取出原来key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点);判断当前结点是否有孩子节点,此场景是有孩子节点的,获取到所有孩子节点存储在nodes集合中,并新建两个集合leftNodes与rightNodes分别存储左节点的子节点与右节点的子节点,通过遍历取得当前孩子节点的最大键值,小于mid的键的数是左节点的子节点;大于mid的键的数是右节点的子节点,将leftNodes添加为leftNode的子节点;将rightNodes添加为rightNode的子节点。此时将头节点重置为前节点leftNode;新建一个子节点childNode并将前节点leftNode和后节点rightNode添加进去;然后判断当前结点是否有父节点,此时有父节点,获取到当前节点的父节点parentNode,并获取到父节点的所有子节点,将这些子节点全部添加到子节点集合childNodes中,然后删除当前的节点,然后将子节点重置成新的子节点集合childNodes;继续以父节点为依据判断是否超过设定的阶数了,此时没有超过阶数,将键值对midKeyAndValue直接保存到当前的父节点中,并进行排序操作。最终效果如下图所示()

4.5 java代码实现

1.创建一个B+树的容器对象Node.java-描述节点的基本结构

package com.sbxBase.testBTree;


import java.util.List;

/*节点类*/
public class Node {

    //children
    //节点的子节点
    private List<Node> nodes;
    //节点的键值对
    private List<KeyAndValue> keyAndValue;
    //节点的后节点
    private Node nextNode;
    //节点的前节点
    private Node previousNode;
    //节点的父节点
    private Node parantNode;


    public Node( List<Node> nodes, List<KeyAndValue> keyAndValue, Node nextNode,Node previousNode, Node parantNode) {
        this.nodes = nodes;
        this.keyAndValue = keyAndValue;
        this.nextNode = nextNode;
        this.parantNode = parantNode;
        this.previousNode = previousNode;
    }

    boolean isLeaf() {
        return nodes==null;
    }

    boolean isHead() {
        return previousNode == null;
    }

    boolean isTail() {
        return nextNode == null;
    }

    boolean isRoot() {
        return parantNode == null;
    }


    List<Node> getNodes() {
        return nodes;
    }

    void setNodes(List<Node> nodes) {
        this.nodes = nodes;
    }


    List<KeyAndValue> getKeyAndValue() {
        return keyAndValue;
    }
    
    Node getNextNode() {
        return nextNode;
    }

    void setNextNode(Node nextNode) {
        this.nextNode = nextNode;
    }

    Node getParantNode() {
        return parantNode;
    }

    void setParantNode(Node parantNode) {
        this.parantNode = parantNode;
    }

    Node getPreviousNode() {
        return previousNode;
    }

    void setPreviousNode(Node previousNode) {
        this.previousNode = previousNode;
    }
}

2.创建一个数据存储对象KeyAndValue.java存储要保存到节点中的数据

package com.sbxBase.testBTree;

import java.util.ArrayList;
import java.util.Collections;

public class KeyAndValue implements Comparable<KeyAndValue>{
     /*存储索引关键字*/
     private int key;
     /*存储数据*/
     private Object value;


     @Override
     public int compareTo(KeyAndValue o) {
         //根据key的值升序排列 从小到大
         //  1  3 4 6
         //  0  1 2 3
         return this.key - o.key;
     }

     public int getKey() {
         return key;
     }

     public void setKey(int key) {
         this.key = key;
     }

     public Object getValue() {
         return value;
     }

     public void setValue(Object value) {
         this.value = value;
     }

      KeyAndValue(int key, Object value) {
         this.key = key;
         this.value = value;
     }
 }

3.B+树逻辑实现代码--处理

package com.sbxBase.testBTree;


import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class MyBtree {

    //B+树的阶数
    private int rank;

    //根节点
    private Node root;
    //头结点
    private Node head;

    MyBtree(int rank) {
        this.rank = rank;
    }

    public Node getRoot() {
        return root;
    }

    public void insert(KeyAndValue entry) {
        //新建一个容器存储键值对
        List<KeyAndValue> keyAndValueArrayList = new ArrayList<KeyAndValue>();

        //插入第一个数据的时候
        if (head == null) {
            keyAndValueArrayList.add(entry);
            root = new Node(null, keyAndValueArrayList, null, null, null);
            head = new Node(null, keyAndValueArrayList, null, null, null);
        } else {
            Node node = head;

            while (node != null) {
                List<KeyAndValue> keyAndValue = node.getKeyAndValue();

                for (KeyAndValue KV : keyAndValue) {
                    if (KV.getKey() == entry.getKey()) {
                        KV.setValue(entry.getValue());
                        return;
                    }
                }
                //如果当前结点为最后结点或者当前数据的键大于当前节点的下一个节点的键值对中最小的键值
                if (node.getNextNode() == null || entry.getKey() <= node.getNextNode().getKeyAndValue().get(0).getKey()) {
                    splidNode(node, entry);
                    break;
                }

                //移动指针
                node = node.getNextNode();
            }

        }

    }

    /**
     * [方法概述]:
     * 处理节点、节点的键值对。
     * [业务逻辑]:
     * 首先判断当前节点的键值对是否超过阶数。
     * 1.如果没超过则将新的键值对插入到当前节点的键值对中。
     * 2.如果超过:
     * 2.1 则将节点,和节点的键值对进行拆分。
     * 首先先将当前节点的键值对÷2获得中间下标,根据中间下标拆分为左右两部分。 (左键值对是当前数字较小的,右键值对是数字较大的)
     * 再将节点进行拆分。生成左右两个节点。左节点的nextNode属性指向右节点,左节点的previousNode属性指向当前节点前节点的nextNode。右节点的previousNode属性指向左节点,右节点的nextNode属性指向当前节点后节点的previousNode。形成双向链表结构。并将上一步的左右键值对分别保存到左右节点中。
     * 2.2 判断是否有子节点
     * 如果有子节点,就将当前节点的所有子节点的最大键值与中间下标的键进行比较,小于中间下标键的子节点保存到左节点的子节点中,大于中间下标键的子节点保存到右节点的子节点中
     * 2.3 判断是否有前后节点
     * 如果当前节点有前后节点,将2.1中的左节点保存到前节点的nextNode属性中,将右节点保存到后节点的previousNode属性中
     * 2.4 判断当前节点是否有父节点:
     * 如果有父节点则将父节点保存到左右节点中。在将左右节点存放到父节点的nodes属性中。也形成了类似链表的结构。使得父子节点之间建立联系。并移除当前节点,在对当前节点的父节点进行递归,检查父节点是否超出阶数
     * 如果没有父节点。则生成一个父节点,将中间下标的值存放到父节点的键值对中()。在将左右节点存放到父节点的nodes属性中。然后将父节点保存在左右节点的parantNode属性。
     * <p>
     * ? 将中间下标的值存放到父节点的键值对中:
     * 因为将当前节点拆分成两部分后需要指向一个父节点,根据父节点去区分左右两部分,当前节点的中间下标值可以起到这个作用所以才将中间下标的值存放到父节点的键值对中
     * ?为什么移除当前节点:
     * 因为已经将当前节点拆分成两个新节点了,因此不需要当前节点了
     *
     * @param node  当前节点
     * @param entry 键值对
     */
    private void splidNode(Node node, KeyAndValue entry) {
        List<KeyAndValue> keyAndValues = node.getKeyAndValue();

        //将数据添加到节点的键值对中,并排序
        keyAndValues.add(entry);
        Collections.sort(keyAndValues);
        //判断当前节点的键值是否超过规定阶数
        if (keyAndValues.size() == rank) {
            //取出当前节点键值对的中间位置及数据数据
            int mid = keyAndValues.size() / 2;
            int key = keyAndValues.get(mid).getKey();
            //存储作为某个节点的根节点的键值对
            KeyAndValue midKeyAndValue = new KeyAndValue(key, "");
            //存储左节点的键值对
            List<KeyAndValue> leftKeyAndValue = new ArrayList<>();
            for (int i = 0; i < mid; i++) {
                leftKeyAndValue.add(keyAndValues.get(i));
            }

            //存储右节点的键值对
            List<KeyAndValue> rightKeyAndValue = new ArrayList<>();
            int k = node.isLeaf() ? mid : mid + 1;
            for (int i = k; i < rank; i++) {
                rightKeyAndValue.add(keyAndValues.get(i));
            }
            //排序
            Collections.sort(leftKeyAndValue);
            Collections.sort(rightKeyAndValue);
            //创建左右节点
            Node rightNode;
            Node leftNode;
            leftNode = new Node(null, leftKeyAndValue, null, node.getPreviousNode(), node.getParantNode());
            rightNode = new Node(null, rightKeyAndValue, node.getNextNode(), leftNode, node.getParantNode());
            leftNode.setNextNode(rightNode);


            //判断当前节点是否有子节点
            if (node.getNodes() != null) {
                List<Node> nodes = node.getNodes();
                List<Node> leftnodes = new ArrayList<>();
                List<Node> rightnodes = new ArrayList<>();


                for (Node node1 : nodes) {
                    int max = node1.getKeyAndValue().get(node1.getKeyAndValue().size() - 1).getKey();
                    if (midKeyAndValue.getKey() > max) {
                        leftnodes.add(node1);
                        node1.setParantNode(leftNode);
                    } else {
                        rightnodes.add(node1);
                        node1.setParantNode(rightNode);
                    }


                }
                leftNode.setNodes(leftnodes);
                rightNode.setNodes(rightnodes);
            }

            //当前节点如果有前节点,将左节点添加为前节点的后节点
            if (node.getPreviousNode() != null) {
                node.getPreviousNode().setNextNode(leftNode);
            }
            //当前节点有后节点,将右节点添加为后节点的前节点
            if (node.getNextNode() != null) {
                node.getNextNode().setPreviousNode(rightNode);
            }
            //如果当前拆分的节点是头节点,则将左节点作为头节点
            if (node == head) {
                head = leftNode;
            }


            List<Node> childNodes = new ArrayList<>();
            childNodes.add(leftNode);
            childNodes.add(rightNode);
            if (node.getParantNode() == null) {
                List<KeyAndValue> partentKeyAndValues = new ArrayList<>();
                partentKeyAndValues.add(midKeyAndValue);
                //创建父节点
                Node partentNode = new Node(childNodes, partentKeyAndValues, null, null, null);
                leftNode.setParantNode(partentNode);
                rightNode.setParantNode(partentNode);
                root = partentNode;

            } else {

                Node parantNode = node.getParantNode();

                //将所有的子节点整合在一起
                childNodes.addAll(parantNode.getNodes());
                //移除当前节点
                childNodes.remove(node);
                //将新的子节点放入父节点的子节点中
                parantNode.setNodes(childNodes);

                //左右节点都添加父节点
                leftNode.setParantNode(parantNode);
                rightNode.setParantNode(parantNode);

                //父节点的父节点为空,此父节点为根节点
                if (parantNode.getParantNode() == null) {
                    root = parantNode;
                }

                //当前节点有父节点,继续调用此方法,对父节点进行判断拆分
                splidNode(parantNode, midKeyAndValue);


            }

        }
    }

    /**
     * [方法概述]:
     * 打印B+树 传来的节点和这个节点的子子孙孙
     * [业务逻辑]:
     * 1.判断次节点是否是根节点:
     * 如果是就将打印根节点内的元素
     * 2.判断此节点是否为空:
     * 如果为空就返回
     * 3.判断此节点的子节点是否为空:
     * 如果不为空,拿到当前节点的子节点并循环遍历,
     * 3.1判断子节点是否有前节点:
     * 如果没有,将此节点约定为最左节点,并保存当前节点
     * 3.2while循环读打印左子节点内的元素,打印完成之后将左子节点的右节点重置为左节点,再进行遍历
     * 3.3然后再对此节点的最左节点进行递归查询
     *
     * @param root 根节点
     */
    void printBtree(Node root) {
        if (root == this.root) {
            //打印根节点内的元素
            printNode(root);
            System.out.println();
        }
        if (root == null) {
            return;
        }

        //打印子节点的元素
        if (root.getNodes() != null) {
            //找到最左边的节点
            Node leftNode = null;
            Node tmpNode = null;
            List<Node> childNodes = root.getNodes();
            for (Node node : childNodes) {
                if (node.getPreviousNode() == null) {
                    leftNode = node;
                    tmpNode = node;
                }
            }

            while (leftNode != null) {
                //从最左边的节点向右打印
                printNode(leftNode);
                System.out.print("|");
                leftNode = leftNode.getNextNode();
            }
            System.out.println();
            printBtree(tmpNode);
        }
    }


    /**
     * 打印节点内的元素
     * 1.获取到当前节点的键值对集合,循环遍历的打印
     *
     * @param node
     */
  

    private void printNode(Node node) {
        List<KeyAndValue> keyAndValues = node.getKeyAndValue();
        String key = "";
        for (KeyAndValue keyAndValue : keyAndValues) {
           key =  StringUtils.isEmpty(key)? ""+keyAndValue.getKey():","+keyAndValue.getKey();
        }
        System.out.print(key);
    }

}

4.定义一个方法入口

package com.sbxBase.testBTree;

public class TestMain {
    public static void main(String[] args) {
         MyBtree btree = new MyBtree(4);//4是生成B+树的阶数
        btree.insert( new KeyAndValue(1, "123"));
        btree.insert(new KeyAndValue(6, "123"));
        btree.insert(new KeyAndValue(10, "123"));
        btree.insert(new KeyAndValue(2, "123"));

        btree.insert(new KeyAndValue(8, "546"));
        btree.insert(new KeyAndValue(11, "123"));

        btree.insert(new KeyAndValue(18, "12345"));

        btree.insert(new KeyAndValue(3, "123"));

        btree.insert(new KeyAndValue(15, "12345"));

        btree.insert(new KeyAndValue(17, "12345"));

        btree.insert(new KeyAndValue(12, "123"));

        btree.insert(new KeyAndValue(13, "123"));


        btree.insert(new KeyAndValue(4, "123"));

        btree.insert(new KeyAndValue(9, "123"));

        btree.insert(new KeyAndValue(19, "12345"));

        btree.insert(new KeyAndValue(16, "12345"));

        btree.insert(new KeyAndValue(5, "123"));

        btree.insert(new KeyAndValue(20, "12345"));

        btree.insert(new KeyAndValue(7, "12300"));

        btree.insert(new KeyAndValue(21, "12345"));

        btree.printBtree(btree.getRoot());



    }

}

5.总结

6.延伸

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/699606.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

磐石呼叫中心系统经常被UDP攻击的解方法

语音系统经常被UDP攻击&#xff0c;UDP攻击是一种常见的网络攻击形式&#xff0c;经常针对语音、视频和在线游戏等实时传输数据的应用程序。攻击者使用大量的UDP数据包向目标服务器发送请求&#xff0c;导致服务器过载&#xff0c;使其无法处理有效的请求&#xff0c;从而导致服…

01-基本数据类型和注释

基本数据类型 使用编程语言进行编程时&#xff0c;需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着&#xff0c;当您创建一个变量时&#xff0c;就会在内存中保留使用一些空间。 您可能需要存储各种数据类型&#xff08;比如字符型、整型、浮…

C++数据结构笔记(3)线性表的链式存储底层实现

本系列的帖子并不包含全部的基础知识&#xff0c;只挑一部分最核心的知识点总结&#xff0c;着重于具体的实现细节而并非理论的知识点总结&#xff0c;大家按需阅读学习。 链表的核心概念总结如下&#xff1a; 1.链式存储不需要连续的内存空间 2.链表由一系列的结点组成&…

阿里云ECS U实例评测

参与ECSU实例评测&#xff0c;申请免费体验机会&#xff1a;https://developer.aliyun.com/mission/review/ecsu What u1实例是什么&#xff1f; u1实例本质上还是ecs服务器&#xff0c;但是是阿里云推出的一种新型实例规格族 阿里云根据使用场景和业务场景将ecs划分为不同的…

如何使用低代码推动企业数字转型

企业创建其所需数字解决方案的方式和速度正在发生历史性变化。企业可以通过各种方式来实现这一转型&#xff0c;低代码作为其中一种转型方式&#xff0c;也越来越受到企业的喜欢&#xff0c;帮助企业适应不断变化的条件。下面我们用天翎低代码平台为例&#xff0c;浅谈一下&…

人脸考勤签到升级篇

目录 编写签到成功页面&#xff08;移动端&#xff09; 实现考勤成功页面&#xff08;持久层&#xff09; 实现考勤成功页面&#xff08;业务层&#xff09; 实现考勤成功页面&#xff08;Web层&#xff09; 一、查询用户的入职日期 二、实现查询考勤结果的Web方法 实现…

8年资深测试总结,性能测试-性能内存详解,揭秘你的疑惑...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 缓存 从 free 命令…

python实现基于SVD矩阵分解的电影推荐系统设计

大家好&#xff0c;我是带我去滑雪&#xff01; SVD 是一种矩阵分解技术&#xff0c;通过将空间维数从 N 维降到 K 维&#xff08;其中K<N&#xff09;来减少数据集的特征数。在推荐系统中&#xff0c;SVD 被用作一种协同过滤技术。使用矩阵结构&#xff0c;其中行表示用户&…

CC2530 外部中断配置步骤

第一章 硬件原理图分析 第二章 配置按键中断步骤

Python:通过飞书API接口发送通知消息

通过飞书发送应用消息&#xff0c;及时收到线上异常消息 总的来说&#xff0c;飞书消息发送的前戏&#xff0c;要比企业微信和钉钉稍复杂 相关连接 官网 https://www.feishu.cn/管理后台 https://www.feishu.cn/admin开放平台 https://open.feishu.cn/ 参数准备 首先&…

JS中的函数与数组

文章目录 函数的定义与调用①、函数定义②、函数调用 函数的参数与返回值let与var关键字区别创建数组并使用数组遍历 函数的定义与调用 ①、函数定义 函数定义有三种方式&#xff1a; 第一种&#xff1a;function 函数名(){ 函数体 } 第二种&#xff1a;定义变量的语法&…

LITE TRANSFORMER WITH LONG-SHORT RANGE ATTENTION

1.摘要 在这篇论文中&#xff0c;我们提出了一种高效的移动NLP架构——Lite Transformer&#xff0c;以在边缘设备上部署移动NLP应用。Transformer已经成为自然语言处理&#xff08;例如机器翻译、问答系统&#xff09;中无处不在的技术&#xff0c;但要实现高性能需要大量计算…

Elasticsearch 初步使用

本文是 Elasticsearch官方博客文档 阅读笔记记录&#xff0c;详细内容请访问官方链接&#xff0c;本文只做重点记录 index索引 对于经常看 Elastic 英文官方文档的开发者来说&#xff0c;我们经常会看到 index 这个词。在英文中&#xff0c;它即可以做动词&#xff0c;表示建…

ASP.Net Core Web API快速搭建后台服务搭载SQLServer+FreeSQL(一)

目录 一.建立WebAPI所需要的环境 1. IDE编辑器&#xff1a;VisualStudio2019 2.数据库安装&#xff1a;SqlServer 3.下载SQL Server Management Studio (SSMS) 二.创建ASP.Net Core Web API工程 1.创建模板工程 2. 试运行案例接口 3.安装FreeSQL工具包 三.设计数据库 启…

《养生大世界》简介及投稿要求

《养生大世界》简介及投稿要求 《养生大世界》是由国家新闻总署备案&#xff0c;中国老年保健协会主管的国家级学术期刊&#xff0c;全国公开发行正规刊物。 《养生大世界》传播健康中国文化理念&#xff0c;推动健康养生科技创新发展&#xff0c;助力健康产业惠及人民。 主…

蓝桥杯单片机竞赛主观题总结(全)(2.5W字)

前言 对于蓝桥杯的单片机竞赛&#xff0c;主观题很重要&#xff0c;占了百分之70-80的大头&#xff0c;因此主观题做得怎么样决定了比赛是否能拿奖&#xff0c;而客观题的正确率很大程度上决定了你能否获得省一&#xff0c;从而进入决赛。因此我在这里分享一期关于主观题中各个…

95道MongoDB面试题

1、mongodb是什么&#xff1f; MongoDB 是由 C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。 再高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。 MongoDB 旨在给 WEB 应用提供可扩展的高性能数据存储解决方案。 MongoDB 将数…

前端技术栈 - ES6 - Promise -模块化编程

文章目录 &#x1f55b;ES6⚡let变量⚡const常量⚡数组解构⚡对象解构⚡模板字符串⚡对象声明简写⚡对象方法简写⚡对象运算符扩展⚡箭头函数&#x1f98a;传统函数>箭头函数&#x1f98a;箭头函数对象解构 ⚡作业布置 &#x1f550;Promise⚡需求分析⚡传统ajax回调嵌套⚡p…

GO、KEGG(批量分组)分析及可视化

这篇帖子其实是更新、补充、解决一下问题的。我们号写过很多GO、KEGG富集分析的可视化&#xff0c;数不胜数&#xff0c;可以在公众号检索“富集”了解更多。我们演示的时候都是直接提供了富集的结果文件&#xff0c;一般演示为了图方便&#xff0c;也是利用在线工具cytoscape做…

网络工程师的副业,能有多野?

大家好&#xff0c;我是许公子。 在网工这行做了这么久&#xff0c;经常会有同事或者朋友来问我&#xff0c;有没有什么搞副业的路子。 别说&#xff0c;选择还真挺多。 前两天的文章里&#xff0c;这位朋友就秀了一波&#xff0c;这工作速度、便捷程度、收款金额&#xff0c…