链表相关知识总结

1、数据结构

基本概念:

  • 数据项:一个数据元素可以由若干个数据项组成
  • 数据对象:有相同性质的数据元素的集合,是数据的子集
  • 数据结构:是相互之间存在一种或多种特定关系的数据元素的集合

逻辑结构和物理结构:

  • 逻辑结构:是指数据对象中数据元素之间的相互关系。比如集合结构、线性结构、树形结构、图形结构
  • 物理结构:是指数据的逻辑结构在计算机中的存储形式。比如顺序存储结构、链式存储结构

数据结构研究的内容:

  • 线性表:零个或多个数据元素的有序序列
  • 队列:只允许在一端插入,而在另一端进行删除操作的线性表
  • 堆栈:栈是限定仅在表尾进行插入和删除操作的线性表
  • 树:树是 n 个节点的有序集。节点可以像树一样越向叶子节点就没有交集
  • 图:由顶点的又穷空集合和顶点之间边的集合组成
  • 排序和查找算法:排序是对数据进行顺序排列,查找是在大量数据中寻找我们需要的数据的过程

本系列的源码如无特殊说明均来自 JDK 1.8

2、线性表

2.1 基本概念

先来看数组,数组的特点:

  • 简单:数组是一种最简单的数据结构
  • 占据连续内存:数组空间连续,按照申请的顺序存储,但是必须制定数组大小
  • 数组空间效率低:数组中经常有空闲的区域没有得到充分的应用
  • 操作麻烦:数组的增加和删除操作很麻烦(需要移动被操作位置后续的元素)

线性表是零个或多个数据元素的有序序列,它有两种存储结构:

  1. 顺序存储结构(顺序表),内部实际上还是数组
  2. 链式存储结构(链表),物理地址不是连续的,但是通过指针保存了下一个内存单元的首地址,形成了逻辑上的连续

Java 中对顺序表的实现主要是 ArrayList,对链表的实现主要是 LinkedList。

2.2 顺序表的增删改查

Java 中对顺序表的典型实现就是 ArrayList,我们先看如何向 ArrayList 添加数据。

	public boolean add(E e) {// 至少要保证容量为 size + 1 才能添加一个新元素ensureCapacityInternal(size + 1);  // Increments modCount!!// 在尾部添加新元素elementData[size++] = e;return true;}private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}

add(E) 会在顺序表尾部添加元素,添加前需要通过 ensureCapacityInternal() 保证顺序表的容量充足,具体做法是计算出添加元素后所需要的容量,如果容量不足就计算后进行扩容:

	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};private static final int DEFAULT_CAPACITY = 10;private static int calculateCapacity(Object[] elementData, int minCapacity) {// 如果是初始状态,顺序表是空的,那么就在 10 和 minCapacity 选大的作为初始容量if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;}private void ensureExplicitCapacity(int minCapacity) {// 更新对当前 ArrayList 对象的修改次数modCount++;// 计算出需要增加的容量并扩容if (minCapacity - elementData.length > 0)grow(minCapacity);}private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;// 扩容是增加原来容量的一半int newCapacity = oldCapacity + (oldCapacity >> 1);// 如果扩容后还是比需要的最小容量 minCapacity 要小,就直接扩容到 minCapacityif (newCapacity - minCapacity < 0)newCapacity = minCapacity;// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);}private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}

计算好了扩容后的容量 newCapacity 之后,通过 Arrays.copyOf() 将原来的 ArrayList 的所有元素拷贝到扩容后的新的 ArrayList 中。

此外还可以进行指定位置的添加:

	public void add(int index, E element) {// 检查 index 是否越界rangeCheckForAdd(index);ensureCapacityInternal(size + 1);  // Increments modCount!!// 把 index 位置空出来,需要将原本 [index,size-1] 位置上的元素向后挪一位// 在 JDK 1.8 中是一个 Native 方法,参数含义是(源表,起始位置,目标表,目标位置,拷贝个数)// 即从哪个表的哪个起始位置开始拷贝,拷贝到哪个表的哪个位置,拷贝多少个元素System.arraycopy(elementData, index, elementData, index + 1,size - index);// 空出的 index 位置保存新添加的元素elementData[index] = element;size++;}

在 ArrayList 的中间位置插入元素,需要通过 System.arraycopy() 将 index 这个位置开始到后面的元素都向后移动一位,空出的 elementData[index] 才能保存新插入的元素。这就是 ArrayList 在中间进行添加/删除元素效率低的主要原因。

其余的添加方法,如 addAll 之类的也是类似的,就不多赘述。

remove() 可以传索引,也可以传对象。先看传 index 的:

	public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);// 如果需要移动的元素个数大于 0 就需要通过 arraycopy 将元素前移一位// 只有当 index = size - 1,即 index 在表尾部删除元素时才不用移位int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);// 将原本最后一个位置的对象置为 null,因为它不再是 ArrayList 使用的一员,// 需要触发 GC 尽快回收它elementData[--size] = null; // clear to let GC do its workreturn oldValue;}

与 add(index) 类似,也是在尾部删除元素时才不用移动后续数据,否则都要用 System.arraycopy() 将数据前移,影响执行效率。

如果是直接移除对象的话,需要从前至后遍历 ArrayList,找到第一个与传入的参数 o 相同的对象并移除掉它:

	public boolean remove(Object o) {// 如果 o 是 null 则移除掉第一个 null 元素if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {// 如果 o 不是 null 则移除掉第一个与 o 值相同的元素for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;}private void fastRemove(int index) {modCount++;int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its work}

由于是顺序表可以很容易的根据 index 获取到对应的元素,直接去改:

	public E set(int index, E element) {rangeCheck(index);E oldValue = elementData(index);elementData[index] = element;return oldValue;}

2.3 ArrayList 继承关系

2024-07-25.ArrayList继承关系

ArrayList 是一个容器,它通过继承抽象类 AbstractCollection 实现了容器接口 Collection,还直接实现了 List 接口。

既然 ArrayList 实现了那么多接口,肯定会具有相应接口的特性,我们主要看对 Iterator 的实现。

Iterator

Iterator 接口内容如下:

public interface Iterator<E> {// 是否还有下一个元素boolean hasNext();// 获取下一个元素E next();// 默认方法,移除(下一个)元素default void remove() {throw new UnsupportedOperationException("remove");}// 默认方法,对元素内剩余的每一个元素执行 action 操作default void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);while (hasNext())action.accept(next());}
}

迭代器接口主要是用于向后遍历元素,即快速轮询容器,方法功能已经在注释上标出。

ArrayList 通过内部类 Itr 实现 Iterator 接口:

	private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;Itr() {}public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}@Override@SuppressWarnings("unchecked")public void forEachRemaining(Consumer<? super E> consumer) {Objects.requireNonNull(consumer);final int size = ArrayList.this.size;int i = cursor;if (i >= size) {return;}final Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) {throw new ConcurrentModificationException();}while (i != size && modCount == expectedModCount) {consumer.accept((E) elementData[i++]);}// update once at end of iteration to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}

next() 和 remove() 操作的都是 elementData[lastRet],在 next() 内将 lastRet 指向下一个元素,而 remove() 删除的也正是 next() 返回的下一个元素。

面试常问问题

都是些简单问题:

  1. ArrayList 的大小是如何自动增加的?
    • 在 add() 时检查容量是否足以再添加一个元素,如果不够就进行扩容,增加元容量的一半(oldCapacity + oldCapacity >> 1)
  2. 什么情况下你会使用 ArrayList?
    • 考察应用场景:在尾部插入或删除元素时,需要随机访问或修改容器内元素时(在 ArrayList 中间插入或删除节点需要通过 System.arrayCopy() 移动元素,会影响效率,此时应该使用链表而不是顺序表)
  3. 在索引中 ArrayList 的增加或者删除某个对象的运行过程的效率很低吗?解释一下为什么?
    • 在最后一个位置增删效率还是高的,但是在中间增删效率就低了,因为需要通过 System.arrayCopy() 移动因为被增加/删除元素所影响到的元素,该方法是一个很耗时的操作
  4. ArrayList 如何顺序删除节点?
    • 从尾部向头部删除效率高,因为删除 ArrayList 的最后一个元素时不用进行 System.arrayCopy() 操作;如果从头删除,删除掉第一个元素之后,需要把第二个元素到最后一个元素通过 System.arrayCopy() 向前移一位
  5. ArrayList 的遍历方法?
    • 推荐用迭代器提供的 hasNext() 和 next() 方法进行遍历
    • 使用 for 循环(或 forEach)遍历也是可行的,但是尽量就只使用 get(i) 去获取元素,不要进行增删改操作,容易出问题

2.4 链表

定义:线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的(扯犊子定义,这是特点):

// 单向链表
class Node {// 保存数据Object data;// 保存下一个元素的引用(地址)Node next;
}// 双向链表(使用了泛型的形式)
class Node<E> {// 保存数据E data;// 下一个元素的引用Node<E> next;// 前一个元素的引用Node<E> prev;
}

链表又分为单向链表和双向链表,我们着重看看两种链表的增删改查。

单向链表的增删改查

2024-07-25.单链表的增删改查

简单描述一下上述过程:

  1. 增:一定是先执行 s.next = p.next,如果先执行了 p.next = s,那么原本的 p.next 就和整个链表断开了,无法指定 s.next,因为此时 p.next 已经是 s 了
  2. 删:比增简单一些,直接让待删的前驱结点的 next 指向待删的后继节点即可
  3. 改:直接修改链表节点的数据即可
  4. 查:要从链表头部开始遍历去找目标节点,因此在查找上的效率要低于支持随机访问的顺序表(但是增删效率高)

单链表的主要应用在源码中就是 JDK 1.7 之前的 HashMap,用于解决哈希冲突(当然从 JDK 1.8 开始就不用单链表改用红黑树进一步提升性能)。

双向链表的增删改查

双向链表的增删改查会结合 LinkedList 的源码来看,因为 JDK 的 LinkedList 就是通过双向链表实现的。

首先是增:

2024-07-15.双向链表增加元素

在双向链表中增加一个节点需要四步:

  1. s.next = p.next
  2. p.next.prev = s
  3. s.prev = p
  4. p.next = s

注意第 2 步与第 4 步的顺序不能颠倒,否则会出现 a1.next 指向 a2,而 a2.prev 指向 a1 的循环关系。

在具体的代码实现上,看 LinkedList 的 add 方法:

	public void add(int index, E element) {// 检查 index 的合法性 —— 是否超出边界checkPositionIndex(index);// 如果 index 在链表尾部,则在尾部插入if (index == size)linkLast(element);elselinkBefore(element, node(index));}

如果在尾部插入,通过 linkLast() 执行尾插:

	/*** Links e as last element.*/void linkLast(E e) {// last 是当前的尾节点final Node<E> l = last;// 为 e 创建 Node 对象,prev 指向当前链表的尾节点final Node<E> newNode = new Node<>(l, e, null);// 更新尾节点引用last = newNode;// 如果原来的尾节点为空,说明队列为空,需要把头节点引用也指向 newNodeif (l == null)first = newNode;else// 将原本尾节点的 next 指向 newNode 完成双向互指l.next = newNode;size++;modCount++;}

如果不是在链表尾部插入,通过 linkBefore(element, node(index)) 来完成插入。首先要看 node(index) 如何生成 index 指定的 Node 节点:

	/*** Returns the (non-null) Node at the specified element index.*/Node<E> node(int index) {// assert isElementIndex(index);// 如果 index 在队列的前一半(离队头近),就从队头向后遍历找 index 指定的 Nodeif (index < (size >> 1)) {Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {// 否则(离队尾近)从队尾开始向前遍历找 index 指定的 NodeNode<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}}

找到 index 指定的 Node 后才由 linkBefore() 将 Node(e) 插入到 Node(index) 的前面:

	/*** Inserts element e before non-null Node succ.*/void linkBefore(E e, Node<E> succ) {// assert succ != null;final Node<E> pred = succ.prev;final Node<E> newNode = new Node<>(pred, e, succ);succ.prev = newNode;if (pred == null)first = newNode;elsepred.next = newNode;size++;modCount++;}

以上是双向链表增加元素的分析,删除元素相对要简单一点,只需要被删除元素的一个引用即可:

2024-07-15.双向链表删除元素

源码实现看 LinkedList 的 remove(),该方法有多个重载方法,看核心的 remove(index):

	public E remove(int index) {checkElementIndex(index);return unlink(node(index));}E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;final Node<E> prev = x.prev;// x 前驱节点处理// 如果 prev 为空说明 x 是队头,删除队头直接将 first 指向 next 即可if (prev == null) {first = next;} else {// prev 存在,将 prev 的 next 指向 next,并断开 x 的 prevprev.next = next;x.prev = null;}// x 后继节点处理// next 为空说明 x 是队尾,删除 x 需要让 last 指向 x 的前驱节点if (next == null) {last = prev;} else {// next 存在,让 next 的 prev 指向前驱节点,并断开 x 的 nextnext.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element;}

2.5 线性表总结

首先是三种表的知识总结与应用场景:

2024-07-16.双向链表知识总结

访问和尾插可以用顺序表(ArrayList),涉及到数据的增删改则使用链表,双向链表(LinkedList)的效率比单向链表更高。

List 总结图:

2024-07-16.List总结

大致总结:

  1. Iterator 迭代器接口定义了向后迭代的接口方法 hasNext() 和 next(),一个容器想要拥有迭代功能都应该实现该接口
  2. ListIterator 继承了 Iterator 接口,又额外定义了 hasPrevious()、previous() 这两个向前迭代的方法,此外还定义了 nextIndex() 与 previousIndex() 这两个用于获取前后索引值的方法,再就是对集合的增删改操作 add()、remove() 和 set(),其中 remove() 是将父接口的默认方法覆盖为接口方法,另外两个是新增的接口方法。可以看到 ListIterator 就是为了针对 List 这种结构而设计的接口方法
  3. Collection 作为所有容器的接口,通过返回 Iterator 实例的接口方法 iterator() 依赖于 Iterator 接口,同时还提供了容器通用操作的 size()、isEmpty()、contains()、toArray()、add()、remove()、containsAll()、addAll() 等接口方法
  4. List 接口继承自 Collection 接口,同时通过 ListIterator<E> listIterator() 接口方法依赖 ListIterator
  5. AbstractCollection 为 Collection 接口的部分方法提供了实现,如 contains()、isEmpty() 等,但是像 iterator()、size() 这种需要子类实现的方法就覆盖为抽象方法了
  6. AbstractList 继承 AbstractCollection 且实现 List 接口,它还是在父类(接口)的前提下,实现了部分方法,比如 add(E)、indexOf()、lastIndexOf() 等,但是仍有一个抽象方法 get() 等待子类去实现,另外像 set()、add(int, E)、remove() 等方法提供的默认实现都是抛出 UnsupportedOperationException,还是需要子类重写的
  7. ArrayList 继承 AbstractList 同时也实现了 List 和 Serializable 接口(为什么父类 AbstractList 已经实现了 List,ArrayList 还要再实现一次呢?是不是因为父类的实现不满足使用需求呢?)
  8. LinkedList 继承自 AbstractSequentialList,还实现了 List、Deque、Serializable 接口
  9. Vector 是 JDK 1.0 版本就有的容器,但是后来继承了 JDK 1.2 版本才诞生的 AbstractList,此外还实现了 List、RandomAccess、Serializable 接口,这一支的容器用的就很少了

3、链表与 LRU 算法

3.1 缓存与 LRU 算法

缓存分为硬件缓存和软件缓存,最早诞生的是硬件缓存:

  • 硬件缓存:位于 CPU 与内存之间的临时存储器,解决 CPU 和内存之间的速度差异问题
  • 软件缓存:一般用三级缓存,速度依次递减:内存缓存、数据库缓存、网络缓存

内存缓存是指预先将数据写到了容器(List、Map、Set)等数据存储单元中,就是软件内存缓存。

内存空间有限,因此内存缓存也有限,这就涉及到内存缓存的淘汰策略算法:

  • FIFO(First In First Out):先进先出
  • LFU(Least Frequently Used):最低使用频率
  • LRU(Least Recently Used):最近最少使用

LRU 算法步骤:

  1. 新数据插入到链表头部
  2. 当缓存命中(即缓存数据被访问),数据要移到表头
  3. 当链表满的时候,将链表尾部的数据丢弃

2024-07-26.LRU算法示意图

3.2 算法实现

单链表实现:

public class LinkedList<T> {private Node head;private int size;public LinkedList() {head = null;size = 0;}/*** 在链表头部添加元素*/public void add(T data) {head = new Node(data, head);size++;}/*** 在指定位置添加元素*/public void add(T data, int index) {checkPositionIndex(index);if (index == 0) {add(data);} else {Node node = head;// 从链表头部开始遍历,找到指定位置的前一个节点for (int i = 0; i < index - 1; i++) {node = node.next;}// 添加节点node.next = new Node(data, node.next);size++;}}private void checkPositionIndex(int index) {// 边界判断if (index < 0 || index > size) {throw new IndexOutOfBoundsException();}}/*** 删除头部节点*/public T remove() {Node deletedNode = head;if (deletedNode != null) {head = deletedNode.next;size--;T data = deletedNode.data;// 促进 GC 回收该对象,不置为 null 的话可能会因为指向其他节点而无法回收造成内存泄漏deletedNode = null;return data;}return null;}/*** 删除指定位置的节点*/public T removeAt(int index) {checkPositionIndex(index);Node curNode = head;// 找到指定位置的前一个节点for (int i = 0; i < index - 1; i++) {curNode = curNode.next;}Node deletedNode = curNode.next;curNode.next = curNode.next.next;T data = deletedNode.data;// GCdeletedNode = null;size--;return data;}/*** 删除尾部节点*/public T removeLast() {return removeAt(size - 1);}/*** 修改指定位置的元素*/public void set(T data, int index) {checkPositionIndex(index);Node curNode = head;for (int i = 0; i < index; i++) {curNode = curNode.next;}curNode.data = data;}/*** 获取头部节点数据*/public T get() {return head != null ? head.data : null;}/*** 获取指定位置的元素*/public T get(int index) {checkPositionIndex(index);Node curNode = head;for (int i = 0; i < index; i++) {curNode = curNode.next;}return curNode.data;}@Overridepublic String toString() {Node node = head;StringBuilder sb = new StringBuilder();while (node != null) {sb.append(node.data).append(" ");node = node.next;}sb.append("\n");return sb.toString();}class Node {T data;Node next;public Node(T data, Node next) {this.data = data;this.next = next;}}public static void main(String[] args) {LinkedList<Integer> linkedList = new LinkedList<>();for (int i = 0; i < 10; i++) {linkedList.add(i);}System.out.println(linkedList);}
}

LRU 算法实现,继承 LinkedList 在其基础上扩展:

public class LruLinkedList<T> extends LinkedList<T> {private static final int DEFAULT_CAPACITY = 10;private int capacity;public LruLinkedList() {this(DEFAULT_CAPACITY);}public LruLinkedList(int capacity) {this.capacity = capacity;}public void put(T data) {if (size >= capacity) {removeLast();}// 无论 size >= capacity 是否成立,都要将 data 加到链表头add(data);}public T delete() {return removeLast();}/*** 获取指定索引的元素,并将该元素移到链表的头部* 访问元素也要将该元素移到链表的头部*/public T get(int index) {checkPositionIndex(index);// 目标节点Node curNode = head;// 目标节点的前驱结点Node preNode = head;for (int i = 0; i < index; i++) {preNode = curNode;curNode = curNode.next;}// 目标节点的前驱节点的 next 指向目标节点的下一个节点preNode.next = curNode.next;// 将目标节点插入到链表的头部curNode.next = head;// 更新头部head = curNode;// 返回目标节点的数据return curNode.data;}public static void main(String[] args) {LruLinkedList<Integer> lruLinkedList = new LruLinkedList<>(5);for (int i = 0; i < 5; i++) {lruLinkedList.put(i);}System.out.println(lruLinkedList); // 4 3 2 1 0lruLinkedList.get(3);System.out.println(lruLinkedList); // 1 4 3 2 0lruLinkedList.put(10);lruLinkedList.put(20);System.out.println(lruLinkedList); // 20 10 1 4 3lruLinkedList.delete();System.out.println(lruLinkedList); // 20 10 1 4}
}

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

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

相关文章

蓝桥杯备考-》单词接龙

很明显&#xff0c;这道题是可以用DFS来做的&#xff0c;我们直接暴力搜索&#xff0c;但是这里有很多点是我们需要注意的。 1.我们如何确定两个单词能接上&#xff1f; 比如touch和choose 应该合成为touchoose 就是这样两个单词&#xff0c;我们让一个指针指着第一个字符串…

C语言-访问者模式详解与实践

C语言访问者模式详解与实践 - 传感器数据处理系统 1. 什么是访问者模式&#xff1f; 在嵌入式系统中&#xff0c;我们经常需要对不同传感器的数据进行多种处理&#xff0c;如数据校准、过滤、存储等。访问者模式允许我们在不修改传感器代码的情况下&#xff0c;添加新的数据处…

(UI自动化测试web端)第二篇:元素定位的方法_xpath路径定位

1、第一种xpath路径定位&#xff1a; 绝对路径&#xff1a;表达式是以 /html开头&#xff0c;元素的层级之间是以 / 分隔相同层级的元素可以使用下标&#xff0c;下标是从1开始的需要列出元素所经过的所有层级元素&#xff0c;工作当中一般不使用绝对路径 例&#xff1a;/html/…

设置GeoJSONVectorTileLayer中的line填充图片

设置GeoJSONVectorTileLayer中的line填充图片 关键&#xff1a;linePatternFile const style [{filter: true,renderPlugin: {dataConfig: {type: "line",},type: "line",},symbol: {linePatternFile: "http://examples.maptalks.com/resources/pat…

electron框架(4.0)electron-builde和electron Forge的打包方式

----使用electron-builder打包&#xff08;需要魔法&#xff09; --安装electron-builder: npm install electron-builder -D--package.json中进行相关配置&#xff1a; {"name": "video-tools","version": "1.0.0","main&quo…

让 MGR 不从 Primary 的节点克隆数据?

问题 MGR 中&#xff0c;新节点在加入时&#xff0c;为了与组内其它节点的数据保持一致&#xff0c;它会首先经历一个分布式恢复阶段。在这个阶段&#xff0c;新节点会随机选择组内一个节点&#xff08;Donor&#xff09;来同步差异数据。 在 MySQL 8.0.17 之前&#xff0c;同…

第三十二篇 深入解析Kimball维度建模:构建企业级数据仓库的完整框架

目录 一、维度建模设计原则深度剖析1.1 业务过程驱动设计1.2 星型模式VS雪花模式 二、维度建模五步法实战&#xff08;附完整案例&#xff09;2.1 业务需求映射2.2 模型详细设计2.3 缓慢变化维处理 三、高级建模技术解析3.1 渐变维度桥接表3.2 快照事实表设计 四、性能优化体系…

IntelliJ IDEA 中 Maven 的 `pom.xml` 变灰带横线?一文详解解决方法

前言 在使用 IntelliJ IDEA 进行 Java 开发时&#xff0c;如果你发现项目的 pom.xml 文件突然变成灰色并带有删除线&#xff0c;这可能是 Maven 的配置或项目结构出现了问题。 一、问题现象与原因分析 现象描述 文件变灰&#xff1a;pom.xml 在项目资源管理器中显示为灰色。…

缓存过期时间之逻辑过期

1. 物理不过期&#xff08;Physical Non-Expiration&#xff09; 定义&#xff1a;在Redis中不设置EXPIRE时间&#xff0c;缓存键永久存在&#xff08;除非主动删除或内存淘汰&#xff09;。目的&#xff1a;彻底规避因缓存自动过期导致的击穿&#xff08;单热点失效&#xff…

基于WebAssembly的浏览器密码套件

目录 一、前言二、WebAssembly与浏览器密码套件2.1 WebAssembly技术概述2.2 浏览器密码套件的需求三、系统设计思路与架构3.1 核心模块3.2 系统整体架构图四、核心数学公式与算法证明4.1 AES-GCM加解密公式4.2 SHA-256哈希函数五、异步任务调度与GPU加速设计5.1 异步任务调度5.…

Qt的内存管理机制

在Qt中&#xff0c;显式使用new创建的对象通常不需要显式调用delete来释放内存&#xff0c;这是因为Qt提供了一种基于对象树(Object Tree)和父子关系(Parent-Child Relationship)的内存管理机制。这种机制可以自动管理对象的生命周期&#xff0c;确保在适当的时候释放内存&…

数据结构之双向链表-初始化链表-头插法-遍历链表-获取尾部结点-尾插法-指定位置插入-删除节点-释放链表——完整代码

数据结构之双向链表-初始化链表-头插法-遍历链表-获取尾部结点-尾插法-指定位置插入-删除节点-释放链表——完整代码 #include <stdio.h> #include <stdlib.h>typedef int ElemType;typedef struct node{ElemType data;struct node *next, *prev; }Node;//初化链表…

【Linux网络-五种IO模型与阻塞IO】

一、引入 网络通信的本质就是进程间的通信&#xff0c;进程间通信的本质就是IO&#xff08;Input&#xff0c;Output&#xff09; I/O&#xff08;input/output&#xff09;也就是输入和输出&#xff0c;在冯诺依曼体系结构当中&#xff0c;将数据从输入设备拷贝到内存就叫作…

算法-最大公约数

1、约数&#xff1a; 1.1 试除法求约数 原理&#xff1a;只需要遍历最小的约数即可&#xff0c;较大的那个可以直接算出来。 import java.util.*; public class Main {static Scanner sc new Scanner(System.in);public static void main(String[] args) {int t sc.nextIn…

湖北楚大夫

品牌出海已成为众多企业拓展业务、提升竞争力的关键战略。楚大夫(chudafu.com)作为一家专注于品牌出海、海外网络营销推广以及外贸独立站搭建的公司&#xff0c;凭借其专业、高效、创新的服务模式&#xff0c;致力于成为中国企业走向国际市场的坚实后盾与得力伙伴。楚大夫通过综…

Flutter 学习之旅 之 flutter 使用 connectivity_plus 进行网路状态监听(断网/网络恢复事件监听)

Flutter 学习之旅 之 flutter 使用 connectivity_plus 进行网路状态监听&#xff08;断网/网络恢复事件监听&#xff09; 目录 Flutter 学习之旅 之 flutter 使用 connectivity_plus 进行网路状态监听&#xff08;断网/网络恢复事件监听&#xff09; 一、简单介绍 二、conne…

从零开始实现 C++ TinyWebServer 处理请求 HttpRequest类详解

文章目录 HTTP 请求报文HttpRequest 类实现 Init() 函数实现 ParseRequestLine() 函数实现 ParseHeader() 函数实现 ParsePath() 函数实现 ParseBody() 函数实现 ParsePost() 函数实现 ParseFromUrlEncoded() 函数实现 UserVerify() 函数实现 Parse() 函数HttpRequest 代码Http…

systemd-networkd 的 *.network 配置文件详解 笔记250323

systemd-networkd 的 *.network 配置文件详解 笔记250323 查看官方文档可以用 man systemd.network命令, 或访问: https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html 名称 systemd.network — 网络配置 概要 network.network 描述 一个纯…

自定义mavlink 生成wireshark wlua插件错误(已解决)

进入正题 python3 -m pymavlink.tools.mavgen --langWLua --wire-protocol2.0 --outputoutput/develop message_definitions/v1.0/development.xml 编译WLUA的时候遇到一些问题 1.ERROR:SCHEMASV:SCHEMAV_CVC_ENUMERATION_VALID 3765:0:ERROR:SCHEMASV:SCHEMAV_CVC_ENUMERAT…

计算机操作系统(四) 操作系统的结构与系统调用

计算机操作系统&#xff08;四&#xff09; 操作系统的结构与系统调用 前言一、操作系统的结构1.1 简单结构1.2 模块化结构1.3 分层化结构1.4 微内核结构1.5 外核结构 二、系统调用1.1 系统调用的基本概念1.2 系统调用的类型 总结&#xff08;核心概念速记&#xff09;&#xf…