java中的ArrayList和LinkedList的底层剖析

news2024/11/19 1:36:40

引入:

数据结构的分类,数据结构可以分成:线性表,树形结构,图形结构。

  • 线性结构(线性表)包括:数组、链表、栈队列

  • 树形结构:二叉树、AVL树、红黑树、B树、堆、Trie、哈夫曼树、并查集

  • 图形结构:邻接矩阵、邻接表

在这里插入图片描述

线性表是具有存储n个元素的线性序列,常见的线性表有顺序表(数组)和链表两种实现方式。

为什么会出现ArrayList动态数组?

  • 在很多开发语言中数组有一个致命的缺点,就是数组的长度一旦确定,就不能再更改。
  • 我们在实际的开发中,更希望数组是可变的。

这个时候我们就可以使用数组去封装一个动态数组,java中的ArrayList的出现就是为了弥补数组长度不可变的缺点。

ArrayList的实现:

我们要对数组进行封装,也就是让这个数组如果元素装满了之后,重新创建一个更大的数组,将原来的元素复制过去。

分析:

实现动态数组,我们要创建一个类叫做ArrayList,因为他能存储所有引用类型的元素,我们要给它加一个泛型/,它要能动态获取它存储的元素的数量,所以它可以得到它应该有一个size成员,既然是一个动态数组,它的类成员一定有一个容器数组elements。这样我们就可以确定两个成员size和elements;

这个动态数组需要创建,所以我们得提供构造方法,既然它底层还是数组,那么创建的时候还是需要指定大小,可以自定义也可以创建一个默认大小的数组。java底层默认创建的是一个大小为10的数组。

package com.lut.dynamicArray;

public class ArrayList<E> {
	//所装元素的数量
    public int size;
    //数组
    private E[] elements;
	//创建ArrayList底层数组默认的大小
    private static final int DEFAULT_CAPACITY = 10;
    
    //构造方法,自定义初始的ArrayList的大小,和java.util.ArrayList实现有所不同,可以自己进源码查看
    public ArrayList(int capacity){
        capacity = (capacity < DEFAULT_CAPACITY)?DEFAULT_CAPACITY:capacity;
        elements = (E[]) new Object[capacity];
    }
    public ArrayList(){
        //elements=new int[DEFAULT_CAPACITY];
        this(DEFAULT_CAPACITY);
    }
}

接口(方法)分析:

​ 这个类已经创建出来了,我们就需要向外界提供操作这个动态数组的方法。比如必须能够向这个ArrayList的对象里面添加元素、删除元素、按索引查找元素、按元素查索引、获取这个对象里面存储的元素个数等可以操作的方法。

​ 它至少包括以下方法:

int size();// 元素的数量
boolean isEmpty(); // 是否为空
boolean contains(E element);// 是否包含某个元素
void add(E element);// 添加元素到最后面
E get(int index);/返回index位置对应的元素
E set(int index, E element);//设置index位置的元素
void add(int index, E element);//往index位置添加元素
E remove(int index);// 删除index位置对应的元素
int index0f(E element);// 查看元素的位置
void clear();// 清除所有元素

需要注意的是,我们需要通过add方法不断地向ArrayList的对象里面添加元素,如果元素操作了我们创建ArrayList指定的大小,这个时候就需要扩容,所以我们在添加元素之前,需要调用ensureCapacity方法(这个方法只是为了方便理解,官方方法写的很复杂,名字也不叫ensureCapacity,不够调用的时机和位置一样),确保下一个元素不会索引越界,如果越界就需要进行扩容处理,扩容是直接在原数组的大小基础上扩大1.5倍。

我们在查找元素的时候或者是按索引添加元素的时候可能存在索引大于了ArrayList的默认的size或者为负数等不合理的输入,这个时候我们需要在这些方法前面调用一个rangeCheck(index)函数,确保传入的索引合适,而添加的时候索引可以比size大1,所以单独定义了一个rangeCheckForAdd(index),如果rangeCheck或者rangeCheckForAdd方法在检查索引发现不合适的时候就会调用outOfBounds方法。

下面是自定义低配版ArrayList的简单实现

private void ensureCapacity(int capacity) {
    int oldCapacity = elements.length;
    
    if(oldCapacity>=capacity) return;

    //新容量为旧容量的1.5倍
    int newCapacity = oldCapacity+(oldCapacity >> 1);
    //位运算右移1除以2,相当于旧容量1.5倍,相反左移1相当于乘以2
    E[] newElements = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newElements[i]=elements[i];
    }
    elements=newElements;
    System.out.println(oldCapacity+"扩容为:"+newCapacity);
}

下面对ArrayList进行了简单的实现

package com.lut.dynamicArray;

@SuppressWarnings("unchecked")
public class ArrayList<E> {
    //元素的数量
    public int size;
    //所有的元素
    private E[] elements;
    //static能保证内存中只有一份此数据
    private static final int DEFAULT_CAPACITY = 10;
    //元素是否找到
    private static final int ELEMENT_NOT_FOUND = -1;
    public ArrayList(int capacity){
        capacity = (capacity < DEFAULT_CAPACITY)?DEFAULT_CAPACITY:capacity;
        elements = (E[]) new Object[capacity];//释放时机和外面的ArrayList同时释放
    }

    public ArrayList(){
        this(DEFAULT_CAPACITY);
    }

    /**
     * 清楚所有元素
     */
    public void clear(){
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }

    /**
     * 元素的数量
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 是否为空
     * @return
     */
    public boolean isEmpty(){
        return size==0;
    }

    /**
     * 是否包含某个元素
     * @param element
     * @return
     */
    public  boolean contains(E element){
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }

    /**
     * 添加到元素尾部
     */
    public void add(E element){
        add(size,element);
    }

    /**
     * 获取index位置的元素
     */
    public E get(int index){
        rangeCheck(index);
        return elements[index];
    }

    /**
     * 设置index位置的元素
     * @return 原来的元素
     */
    public E set(int index,E element){
        rangeCheck(index);
        E old = elements[index];
        elements[index] = element;
        return old;
    }

    /**
     * 在index位置插入一个元素
     */
    public void add(int index,E element){
        rangeCheckForAdd(index);

        ensureCapacity(size+1);

        for (int i = size; i >index; i--) {
            elements[i]=elements[i-1];
        }
        elements[index]=element;
        size++;
    }

    /**
     * 删除index位置的元素
     * @return 返回删除的元素
     */
    public E remove(int index){
        rangeCheck(index);

        E temp = elements[index];
        for (int i = index+1; i < size; i++) {
            elements[i-1]=elements[i];
        }
        //size--;
        elements[--size] = null;
        return temp;
    }

    /**
     * 查看元素的索引,没找到返回-1
     */
    public int indexOf(E element){
        if(element == null){
            for (int i = 0; i < size; i++) {
                if(elements[i] == null) return i;
            }
        }else {
            for (int i = 0; i < size; i++) {
                //未重写的equals方法,就是默认比较的两个对象的地址
                if(element.equals(elements[i])) return i;
            }
        }

        return ELEMENT_NOT_FOUND;
    }

    /**
     * 保证要有capacity的容量
     * @param capacity
     */
    private void ensureCapacity(int capacity) {
        int oldCapacity = elements.length;
        
        if(oldCapacity>=capacity) return;

        //新容量为旧容量的1.5倍
        int newCapacity = oldCapacity+(oldCapacity >> 1);
        //位运算右移1除以2,相当于旧容量1.5倍,相反左移1相当于乘以2
        E[] newElements = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newElements[i]=elements[i];
        }
        elements=newElements;
        System.out.println(oldCapacity+"扩容为:"+newCapacity);
    }

    private void outOfBounds(int index){
        throw new IndexOutOfBoundsException();
    }

    private void rangeCheck(int index){
        if(index<0||index>=size){
           outOfBounds(index);
        }
    }

    private void rangeCheckForAdd(int index){
        if(index<0||index>size){
            outOfBounds(index);
        }
    }
    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(",[");
        for (int i = 0; i < size;java i++) {
            if(i!=0){
                string.append(",");
            }
            string.append(elements[i]);
        }
        string.append("]");
        return string.toString();
    }
}

仔细阅读上面的代码我们可以发现ArrayList有以下缺点:

  1. 固定大小:ArrayList的大小是固定的,一旦初始化后,无法动态调整大小,如果需要动态增加或减少元素,就需要创建一个新的数组elements,并将元素复制过去,这样会增加额外的开销。
  2. 性能问题:在插入或删除元素时,ArrayList的elements数组需要移动其他元素来保持连续性,这会导致性能下降,尤其是在大型数据集上。
  3. 内存占用:ArrayList在内存使用上比较消耗,因为它需要额外的空间来存储元素的索引和容量信息。

那么有没有和ArrayList一样的数据结构能够方便元素的插入删除,同时能够灵活的利用内存空间呢?有,LinkedList

LinkedList实现

分析:

LinkedList也是一个动态的线性表,所以它就必须能够存储元素,能够获取大小,ArrayList底层是通过一个数组存储元素,LinkedList内部是通过一个结点类来存储元素,同时这个Node里面有两个指针,prev指向前一个结点、next指向下一个结点,同时提供一个构造方法,当我们需要添加一个元素的时候本质是添加一个结点,再把这个元素放入到这个结点内部,同时维护好这个结点的前后结点。

所以LinkedList需要两个成员至少size和一个内部类Node

Node:

	private static class Node<E> {
		E element;
		Node<E> prev;
		Node<E> next;
		public Node(Node<E> prev, E element, Node<E> next) {
			this.prev = prev;
			this.element = element;
			this.next = next;
		}
	}

为了方便获取头节点,我们还需要指定一个头结点,我们需要一个成员变量Node类型的first

package com.lut.list;

public class LinkedList<E>{
	private Node<E> first;
    private int size;
	
	private static class Node<E> {
		E element;
		Node<E> prev;
		Node<E> next;
		public Node(Node<E> prev, E element, Node<E> next) {
			this.prev = prev;
			this.element = element;
			this.next = next;
		}
	}
}

ArrayList和LinkedList我们经常使用,ArrayList又叫做动态数组底层是由数组实现的,LinkedList底层是基于链表实现的集合,它们都是线性的数据结构。

既然ArrayList和LinkedList都具有相同的方法,我们就可以把这些相同的方法给提取出来成为一个单独的接口,这个接口就是List接口。

public interface List<E> {
    //如果元素未找到返回-1
	static final int ELEMENT_NOT_FOUND = -1;
	//清除所有元素
	void clear();
	//元素的数量
	int size();
	// 是否为空
	boolean isEmpty();
	//是否包含某个元素
	boolean contains(E element);
	//添加元素到尾部
	void add(E element);
    //获取index位置的元素
	E get(int index);
    //获取index位置的元素
	E set(int index, E element);
    //在index位置插入一个元素
	void add(int index, E element);
    //删除index位置的元素
	E remove(int index);
    //查看元素的索引
	int indexOf(E element);
}

所以我们只需要去LinkedList去实现这些接口就可以了,然后对于LinkedList里面还有一些和ArrayList连实现都一样的方法,我们就可以单独写一个AbstractList/抽象类来复用相同的方法。

public abstract class AbstractList<E>  {
   /**
    * 元素的数量
    */
   protected int size;
   /**
    * 元素的数量
    * @return
    */
   public int size() {
      return size;
   }

   /**
    * 是否为空
    * @return
    */
   public boolean isEmpty() {
       return size == 0;
   }

   /**
    * 是否包含某个元素
    * @param element
    * @return
    */
   public boolean contains(E element) {
      return indexOf(element) != ELEMENT_NOT_FOUND;
   }

   /**
    * 添加元素到尾部
    * @param element
    */
   public void add(E element) {
      add(size, element);
   }
   
   protected void outOfBounds(int index) {
      throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
   }
   
   protected void rangeCheck(int index) {
      if (index < 0 || index >= size) {
         outOfBounds(index);
      }
   }
   java
   protected void rangeCheckForAdd(int index) {
      if (index < 0 || index > size) {
         outOfBounds(index);
      }
   }
}

现在我们就可以直接实现专注实现List里面的不同的方法了。

需要注意理解里面的add方法,添加的时候,无论是调用add(E element)还是add(int index,E element),我们调用的add(E element)方法均会成为调用add(int index,E element)方法,原因是 public void add(E element) {add(size, element);}。

还需要注意,如果我们第一次添加的时候,需要把first指向它

下面是LinkedList的简单实现:

package com.lut.list;

public class LinkedList<E> extends AbstractList<E> implements List<E>{
    private Node<E> first;

    private static class Node<E>{
        E element;
        Node<E> next;

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    @Override
    public void clear() {
        size = 0;
        first = null;
    }

    @Override
    public E get(int index) {
        return node(index).element;
    }
    //复杂度跟node方法有关,它的复杂度为O(n)

    @Override
    public E set(int index, E element) {
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }
    //O(n)

    @Override
    public void add(int index, E element) {
        if(index == 0){
            first = new Node<>(element,first);
        }else{
            Node<E> prev = node(index - 1);
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }
    //最好:O(1)
    //最坏:O(n)
    //平均:O(n)
    //可以换一个算法,传入一个节点直接可以删,这样才体现链表的节点,增删快查找慢
    @Override
    public E remove(int index) {
        E old = node(index).element;
        if(index == 0){
            first=first.next;
        }else{
            Node<E> prev = node(index - 1);
            prev.next = prev.next.next;
        }
        size--;
        return old;
    }
    //O(n)

    @Override
    public int indexOf(E element) {
        if (element == null) {
            for (int i = 0; i < size; i++) {
                if (element == node(i).element) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (element.equals(node(i).element)) {
                    return i;
                }
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    /**
     * 获取index位置对应的节点的对象
     */
    private Node<E> node(int index){
        rangeCheck(index);

        Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(",[");
        for (int i = 0; i < size; i++) {
            if(i!=0){
                string.append(",");
            }
            string.append(node(i).element);
        }
        string.append("]");
        return string.toString();
    }
}

ArrayList优化:上面我们的ArrayList只是在添加满的时候进行了扩容,但是对于删除的时候我们还需要进行缩容,不然会浪费存储空间。缩容的时机是当我们删除元素的时候,在remove方法之前进行判断是否可以进行缩容,如果可以就进行缩容操作。

public class ArrayList<E> extends AbstractList<E> implements List<E>{
   /**
    * 所有的元素
    */
   private E[] elements;
   private static final int DEFAULT_CAPACITY = 10;
   
   public ArrayList2(int capaticy) {
      capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
      elements = (E[]) new Object[capaticy];
   }
   
   public ArrayList2() {
      this(DEFAULT_CAPACITY);
   }
   
   /**
    * 清除所有元素
    */
   public void clear() {
      for (int i = 0; i < size; i++) {
         elements[i] = null;
      }
      size = 0;
      
      // 仅供参考
      if (elements != null && elements.length > DEFAULT_CAPACITY) {
         elements = (E[]) new Object[DEFAULT_CAPACITY];
      }
   }

   /**
    * 获取index位置的元素
    * @param index
    * @return
    */
   public E get(int index) { // O(1)
      rangeCheck(index);
      
      return elements[index]; 
   }

   /**
    * 设置index位置的元素
    * @param index
    * @param element
    * @return 原来的元素ֵ
    */
   public E set(int index, E element) { // O(1)
      rangeCheck(index);
      
      E old = elements[index];
      elements[index] = element;
      return old;
   }

   /**
    * 在index位置插入一个元素
    * @param index
    * @param element
    */
   public void add(int index, E element) { 
      /*
       * 最好:O(1)
       * 最坏:O(n)
       * 平均:O(n)
       */
      rangeCheckForAdd(index);
      
      ensureCapacity(size + 1);
      
      for (int i = size; i > index; i--) {
         elements[i] = elements[i - 1];
      }
      elements[index] = element;
      size++;
   } // size是数据规模

   /**
    * 删除index位置的元素
    */
   public E remove(int index) {
      /*
       * 最好:O(1)
       * 最坏:O(n)
       * 平均:O(n)
       */
      rangeCheck(index);
      
      E old = elements[index];
      for (int i = index + 1; i < size; i++) {
         elements[i - 1] = elements[i];
      }
      elements[--size] = null;
      
      trim();
      
      return old;
   }

   /**
    * 查看元素的索引
    */
   public int indexOf(E element) {
      if (element == null) {
         for (int i = 0; i < size; i++) {
            if (elements[i] == null) return i;
         }
      } else {
         for (int i = 0; i < size; i++) {
            if (element.equals(elements[i])) return i;
         }
      }
      return ELEMENT_NOT_FOUND;
   }
   
   /**
    * 保证要有capacity的容量
    * @param capacity
    */
   private void ensureCapacity(int capacity) {
      int oldCapacity = elements.length;
      if (oldCapacity >= capacity) return;
      
      // 新容量为旧容量的1.5倍
      int newCapacity = oldCapacity + (oldCapacity >> 1);
      
      // 新容量为旧容量的2倍
      // int newCapacity = oldCapacity << 1;
      E[] newElements = (E[]) new Object[newCapacity];
      for (int i = 0; i < size; i++) {
         newElements[i] = elements[i];
      }
      elements = newElements;
      
      System.out.println(oldCapacity + "扩容为" + newCapacity);
   }
   
   private void trim() {
      // 30
      int oldCapacity = elements.length;
      // 15
      int newCapacity = oldCapacity >> 1;
      if (size > (newCapacity) || oldCapacity <= DEFAULT_CAPACITY) return;
      
      // 剩余空间还很多
      E[] newElements = (E[]) new Object[newCapacity];
      for (int i = 0; i < size; i++) {
         newElements[i] = elements[i];
      }
      elements = newElements;
      
      System.out.println(oldCapacity + "缩容为" + newCapacity);
   }
   
   @Override
   public String toString() {
      // size=3, [99, 88, 77]
      StringBuilder string = new StringBuilder();
      string.append("size=").append(size).append(", [");
      for (int i = 0; i < size; i++) {
         if (i != 0) {
            string.append(", ");
         }
         
         string.append(elements[i]);
         
      }
      string.append("]");
      return string.toString();
   }
}

总结:

ArrayList 和 LinkedList 的区别是什么?

ArrayList 和 LinkedList 是 Java 中常用的两种集合类,它们在实现上有一些区别:

  1. 内部数据结构:

    • ArrayList 使用数组来存储元素,可以通过索引直接访问元素,因此查找速度较快,但插入和删除操作需要移动元素,效率较低。
    • LinkedList 使用双向链表来存储元素,每个元素都包含对前一个和后一个元素的引用,插入和删除操作只需改变相邻元素的引用,效率较高,但查找元素需要遍历链表,效率较低。
  2. 内存占用:

    • ArrayList 在添加或删除元素时可能需要重新分配内存空间,因为数组的大小是固定的,可能会导致内存碎片。
    • LinkedList 每个元素都需要额外的空间来存储前后元素的引用,可能会占用更多的内存空间。
  3. 随机访问和遍历:

    • ArrayList 支持随机访问,可以通过索引直接访问元素,适合需要频繁访问元素的场景。
    • LinkedList 需要从头或尾开始遍历链表才能访问元素,不支持随机访问,适合需要频繁插入和删除元素的场景。

综上所述,如果需要频繁进行插入和删除操作,可以选择使用 LinkedList;如果需要频繁进行随机访问操作,可以选择使用 ArrayList。

ArrayList的扩容和缩容是咋做的(基于官方的ArrayList)?

​ ArrayList在创建的时候可以指定一个数组的大小,或者默认给出一个长度为10的数组,当我们每次向这个集合添加元素的时候,先会去判断一下数组的容量还够不够,如果不够就需要去扩容,每次扩容会扩容原来容量的1.5倍。

​ 而ArrayList 的缩容是自动进行的,无需手动操作。当 ArrayList 中的元素数量减少到当前容量的一半以下时,ArrayList 会自动进行缩容操作,将容量减半。这样可以节省内存空间,并提高性能。因此,开发者无需手动调用任何方法来进行 ArrayList 的缩容操作。

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

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

相关文章

通信工程学习:什么是TDD时分双工

TDD:时分双工 TDD(时分双工,Time Division Duplexing)是一种在移动通信系统中广泛使用的全双工通信技术。以下是TDD的详细解释: 一、定义与原理 TDD是一种通过时间划分来实现双向通信的技术。在TDD模式中,接收和传送在同一频率信道(即载波)的不同时隙…

新品上市!智能无线接入型路由器ZX7981EP,WIFI6技术双频频段

在这个快节奏的时代 每一次点击都渴望即刻响应&#xff0c;每一份数据都期待安全传输 我们希望大家都能享有顶尖的网络体验&#xff0c;由此 启明智显ZX7891EP智能无线接入型路由器新品上市&#xff01; 2.4G/5G双频段&#xff0c;WAN口/LAN口皆齐全 最新802.1ax WiFi6技术…

【Linux】Linux工具——CMake入门

目录 1.什么是CMake 2.CMakeflie的安装和版本的查看 3.几个简单示例 3.1.编译一个.cc文件 3.2.编译一个.hpp文件和一个.cc文件 3.3.编译一个.hpp文件和两个.cc文件 3.4.编译两个.hpp文件和一个.cc文件 4.CMakeLists.txt 4.1.CMakeLists.txt常用的几条指令 4.2.变量和…

软件测试之单元测试/系统测试/集成测试详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、单元测试的概念 单元测试是对软件基本组成单元进行的测试&#xff0c;如函数或一个类的方法。当然这里的基本单元不仅仅指的是一个函数或者方法&#xff0…

基于densenet模型在RML201610a数据集上的调制识别【代码+数据集+python环境+GUI系统】

基于densenet模型在RML201610a数据集上的调制识别【代码数据集python环境GUI系统】 Loss曲线 背景意义 随着社会的快速发展&#xff0c;人们在通信方面的需求逐渐增加&#xff0c;特别是在无线通信领域。通信环境的复杂化催生了多种通信形式和相关应用&#xff0c;这使得调制…

最新版无忧二级域名分发源码,支持包月续费

目前版本支持&#xff0c;开通会员&#xff0c;会员组可以解析哪些域名 比如 用普通域名引流&#xff0c;免费使用&#xff0c;就可以注册就是普通用户组 会员组可以设置价格比如10块钱买永久会员&#xff0c;没有别的特权&#xff0c;只是会员才可以租备案域名&#xff0c; 设…

有源蜂鸣器(5V STM32)

目录 一、介绍 二、模块原理 1.有/无源蜂鸣器介绍 2.原理图 3.引脚描述 三、程序设计 main.c文件 beep.h文件 beep.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 蜂鸣器是一种能将音频信号转化声音信号的发音器件&#xff0c;在家电器上&#xff0c;在银行…

直播 SDK

直播 SDK 是音视频终端 SDK&#xff08;腾讯云视立方&#xff09;针对移动直播场景专属打造的一体化产品&#xff0c;支持直播推拉流、主播观众互动连麦、主播跨房 PK 等能力&#xff0c;为用户提供高质量直播服务&#xff0c;快速满足手机直播的需求。更多关于直播 SDK 的文档…

Ubuntu 22.04无法连接网络(网络图标丢失)解决方案

对于Ubuntu 22.04而言&#xff1a; sudo service NetworkManager stop sudo rm /var/lib/NetworkManager/NetworkManager.state sudo service NetworkManager start

嵌入式项目:STM32平衡车详解 (基础知识篇) (基于STM32F103C8T6)

前言&#xff1a; 本文是基于B站草履虫编写的平衡车相关内容&#xff0c;包括模块和基础知识&#xff0c;结合代码进行讲解&#xff0c;将知识进行汇总 &#xff08;由于本篇内容较长&#xff0c;请结合目录使用) 注&#xff1a;基于开源精神&#xff0c;本文仅供学习参考 目…

基于Node.js+Express+MySQL+VUE实现的计算机毕业设计旅游推荐网站

猜你喜欢评论 登录注册搜索 推荐定制景点/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序 功能图如下所示&#xff1a; 一、设计目标 本次计算机毕业设计项目的主要目标是设计和开发一款功能完善、用户友好的旅游推荐网站。该网站旨在为广大旅游爱好者提供一个便捷、…

蓝桥杯--STM32G431RBT6(TIM定时器的输出频率和占空比,含详细原理介绍和使用方法)

目录 一、前言 二、代码 实现功能&#xff1a;​编辑 按如图配置 定义变量 编写执行代码 显示在LCD上 加入按键效果 三、效果展示 四、代码开源 一、前言 ARR 即自动重装载值&#xff08;Auto Reload Register&#xff09;。相当于一个水杯&#xff0c;水杯容量&am…

sqlserver迁移数据库文件存储位置

业务背景&#xff1a;由于C盘爆满&#xff0c;需要将数据库文件迁移到别处比如D盘 下面以某一个数据库转移为示例&#xff1a;&#xff08;可以用SSMS工具&#xff0c;新建查询配合使用&#xff09; 1.查询数据库文件存储路径 sql语句&#xff1a; -- 查询路径 USE QiangTes…

[Redis][哨兵][上]详细讲解

目录 0.前言1.基本概念1.相关名词解释2.主从复制的问题3.人工恢复主节点故障4.哨兵自动恢复主节点故障 0.前言 说明&#xff1a;该章节相关操作不需要记忆&#xff0c;理解流程和原理即可&#xff0c;用的时候能自主查到即可Redis的主从复制模式下&#xff0c;⼀旦主节点由于故…

使用豆包MarsCode 实现高可用扫描工具

以下是「 豆包MarsCode 体验官」优秀文章&#xff0c;作者郝同学测开笔记。 前言&#xfeff; 最近接触K8s&#xff0c;了解到K8s提供了非常方便的实现高可用的能力&#xff0c;再加上掘金推出「豆包MarsCode初体验」征文活动&#xff0c;所以打算使用豆包 MarsCode IDE来实现…

UniApp基于xe-upload实现文件上传组件

xe-upload地址&#xff1a;文件选择、文件上传组件&#xff08;图片&#xff0c;视频&#xff0c;文件等&#xff09; - DCloud 插件市场 致敬开发者&#xff01;&#xff01;&#xff01; 感觉好用的话&#xff0c;给xe-upload的作者一个好评 背景&#xff1a;开发中经常会有…

Dubbo快速入门(一):分布式与微服务、Dubbo基本概念

文章目录 一、分布式与微服务概念1.大型互联网架构目标2.集群和分布式&#xff08;1&#xff09;集群 (Cluster)&#xff08;2&#xff09;分布式计算 (Distributed Computing)&#xff08;3&#xff09;集群与分布式的关系&#xff08;4&#xff09;实践中的应用案例 3.架构演…

【AI大模型】向量及向量知识库

一、词向量与向量 什么是词向量 在机器学习和自然语言处理&#xff08;NLP&#xff09;中&#xff0c;词向量&#xff08;word embedding&#xff09;是一种以单词为单位将每个单词转化为实数向量的技术。这些实数向量可以被计算机更好地理解和处理。 词向量背后的主要想法是…

.NET 6 中,使用 ActionFilterAttribute 实现 AOP(面向切面编程)

AOP概述&#xff1a;AOP&#xff08;面向切面编程&#xff09;是一种编程规范的风格&#xff0c;通过横切的思想&#xff0c;将系统功能和业务功能分离开&#xff0c;以提高代码的可维护性和清晰度。 系统功能模块&#xff1a; 1、缓存模块&#xff1a; 作用&#xff1a;提高…

OpenHarmony(鸿蒙南向)——平台驱动指南【MIPI CSI】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 CSI&#xff08;Camera Serial Interface&#xf…