LinkedHashMap详解

目录

  • LinkedHashMap详解
    • 1、LinkedHashMap的继承体系
    • 2、LinkedHashMap的特性介绍和代码示例
      • ①、特性
      • ②、适用场景
      • 使用LinkedHashMap 实现最简单的 LRU缓存
    • 3、LinkedHashMap的构造函数
    • 4、LinkedHashMap是如何存储元素的,底层数据结构是什么?
      • LinkedHashMap的属性注释
      • LinkedHashMap的静态内部类`Entry<K,V>`
      • 从TreeNode的继承结构引发一个关于设计类继承关系的思考?
    • 5、LinkedHashMap的put方法
      • `newNode`方法
      • `linkNodeLast`方法
      • `afterNodeAccess`方法
      • `afterNodeInsertion`方法
    • 6、LinkedHashMap的get方法
    • 7、LinkedHashMap的remove方法
    • 8、LinkedHashMap的迭代器
    • 9、LinkedHashMap的一些常见问题
      • LinkedHashMap 和 HashMap 的区别及适用场景
      • 数据结构对比
      • 插入和迭代性能
      • 适用场景

LinkedHashMap详解

基于JDK8
LinkedHashMap相比于HashMap最显著的一个特性就是维持了插入的顺序,也可以设置其按照访问顺序排序。

1、LinkedHashMap的继承体系

public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>

在这里插入图片描述
可以看到LinkedHashMap继承了HashMap,所以HashMap能做的事情LinkedHashMap都能做。

还记得HashMap详解这篇文章里面提到的两个方法吗?
afterNodeAccessafterNodeInsertion 在 LinkedHashMap中就有对应的实现。下面会详细说到。

2、LinkedHashMap的特性介绍和代码示例

LinkedHashMap 是 HashMap 的子类,与 HashMap 类似,它也基于哈希表来存储键值对。但是,LinkedHashMap 维护了一个双向链表来记录插入顺序或者访问顺序,因此具备一些特有的特性和功能。

双向链表在 LinkedList详解这篇文章中有介绍。

LinkedHashMap 中使用双向链表维护顺序的图示:
绿色连线表示 双向链表的 pre和next指针。
在这里插入图片描述

①、特性

插入顺序:默认情况下,LinkedHashMap 按照键值对的插入顺序来维护顺序。
插入顺序代码示例

public static void main(String[] args) {LinkedHashMap<String, String> map = new LinkedHashMap<>();// ========== 演示插入顺序 ===============map.put("a","1");map.put("b","2");map.put("c","3");map.put("d","4");System.out.println("插入顺序遍历:");for (Map.Entry<String, String> entry : map.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}// 插入顺序遍历://a: 1//b: 2//c: 3//d: 4}

访问顺序:可以通过构造函数设置 accessOrder 参数为 true,使其按照访问顺序来维护顺序。

访问顺序代码示例:

public static void main(String[] args) {Map<String, String> map = new LinkedHashMap<>(16, 0.75f, true);// ========== 插入元素 ===============map.put("a","1");map.put("b","2");map.put("c","3");map.put("d","4");// ========== 访问其中一些元素 ===============map.get("c");map.get("a");System.out.println("访问顺序遍历:");for (Map.Entry<String, String> entry : map.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}// ========= 最新一次访问的排在最后// 访问顺序遍历://b: 2//d: 4//c: 3//a: 1}

②、适用场景

**需要有序遍历的场景:**当需要按插入顺序或访问顺序遍历键值对时,LinkedHashMap 是一个很好的选择。

**缓存:**由于 LinkedHashMap 可以按照访问顺序来维护键值对的顺序,因此非常适合用来实现 LRU(Least Recently Used,最近最少使用)缓存。

使用LinkedHashMap 实现最简单的 LRU缓存

import java.util.LinkedHashMap;
import java.util.Map;public class TestA {public static void main(String[] args) {// 创建一个容量为 3 的 LRUCacheLRUCache<Integer, String> cache = new LRUCache<>(3);// 添加一些键值对到缓存中cache.put(1, "one");cache.put(2, "two");cache.put(3, "three");// 打印当前缓存内容System.out.println("Cache: " + cache); // Cache: {1=one, 2=two, 3=three}// 访问键 1 的值,使其成为最近访问的条目cache.get(1);System.out.println(cache); // {2=two, 3=three, 1=one}// 添加新的键值对 4 -> "four"  由于超过缓存容量 所以会删除 最近最久没使用的数据cache.put(4, "four");// 打印当前缓存内容,观察最老的条目是否被移除   (2被删除)System.out.println("访问1 添加 4 后的缓存: " + cache); // 访问1 添加 4 后的缓存: {3=three, 1=one, 4=four}// 访问键 3 的值,使其成为最近访问的条目cache.get(3);System.out.println(cache); // {1=one, 4=four, 3=three}// 添加新的键值对 5 -> "five"   由于超过缓存容量 所以会删除 最近最久没使用的数据cache.put(5, "five");// 打印当前缓存内容,观察最老的条目是否被移除    (1被删除)System.out.println("访问3 添加5 后的缓存: " + cache);  // 访问3 添加5 后的缓存: {4=four, 3=three, 5=five}}
}// LRUCache 类,继承 LinkedHashMap 实现最近最少使用 (LRU) 缓存
class LRUCache<K, V> extends LinkedHashMap<K, V> {// 缓存的最大容量private final int capacity;// 构造函数,接受缓存的最大容量作为参数public LRUCache(int capacity) {// 调用父类的构造函数// initialCapacity: 初始容量,设置为传入的 capacity// loadFactor: 负载因子,设置为默认值 0.75F // accessOrder: 设置为 true,表示按照访问顺序排序super(capacity, 0.75F, true);this.capacity = capacity; // 初始化缓存容量}// 重写 LinkedHashMap 的 removeEldestEntry 方法// 当添加一个新的键值对时,此方法会被调用,以判断是否需要删除最老的条目@Overrideprotected boolean removeEldestEntry(Map.Entry<K, V> eldest) {// 当缓存元素个数大于我们设置的容量时  删除 最近最少使用的缓存元素return size() > capacity;}
}

3、LinkedHashMap的构造函数

  • ①、空参构造
public LinkedHashMap() {super(); // 调用父类(HashMap)的空参构造accessOrder = false; // 不按照访问顺序排序}
  • ②、有参构造1
    接收 initialCapacity 初始容量 loadFactor 负载因子
public LinkedHashMap(int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor); // 设置自定义的哈希表容量 和负载因子accessOrder = false; // 不按照访问顺序排序}
  • ③、有参构造2
    接收 initialCapacity: 初始容量 loadFactor: 负载因子 accessOrder: 是否按照访问顺序排序
    上面实现的LRUCache就是用的这个构造方法。
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}
  • ④、有参构造3
    接收一个Map的实现。
public LinkedHashMap(Map<? extends K, ? extends V> m) {super(); // 调用父类(HashMap)的空参构造accessOrder = false; // 不按照访问顺序排序putMapEntries(m, false); // 调用HashMap的 putMapEntries方法}

上面只列举了4个构造函数,还有一个直接收initialCapacity参数的就不列举了。

4、LinkedHashMap是如何存储元素的,底层数据结构是什么?

在HashMap详解的文章中 我们知道 HashMap的数组存储的是 Node<K,V>

而我们看LinkedHashMap的put方法是直接调用的父类HashMap的put方法。
上面已经介绍过了,LinkedHashMap使用双向链表维护每一个元素的插入顺序。
那么LinkedHashMap是如何实现双向链表的,LinkedHashMap的底层数据结构是什么?
下面就来探讨这两个问题。
在看LinkedHashMap源码之前,我们可以把HashMap和LinkedHashMap 类比ArrayList和LinkedList。
先猜测下LinkedHashMap里面一定也有类似LinkedList的Node节点,并且有pre和next指针实现双向链接。

LinkedHashMap的属性注释

public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>
{// 指向双向链表的头节点,即最早插入的键值对transient LinkedHashMap.Entry<K,V> head;// 指向双向链表的尾节点,即最后插入或访问的键值对transient LinkedHashMap.Entry<K,V> tail;// 标识链表是否按访问顺序维护// 如果为 true,则每次访问(get 或 put)某个键值对时,该键值对将被移到链表尾部final boolean accessOrder;
}

可以看到LinkedHashMap是通过 LinkedHashMap.Entry<K,V>这个内部类 来保存链表节点的。

LinkedHashMap的静态内部类Entry<K,V>

// LinkedHashMap 的静态内部类 Entry,继承自 HashMap.Node
// 此类在 LinkedHashMap 中用于维护双向链表,以记录键值对的插入顺序或访问顺序
static class Entry<K,V> extends HashMap.Node<K,V> {// 指向链表中前一个节点的引用Entry<K, V> before;// 指向链表中后一个节点的引用Entry<K, V> after;// 构造函数,用于创建一个新的 Entry 节点// hash: 键的哈希值// key: 键// value: 值// next: 指向哈希表中下一个节点的引用(用于处理哈希冲突)Entry(int hash, K key, V value, Node<K, V> next) {// 调用父类 HashMap.Node 的构造函数,初始化 hash, key, value 和 nextsuper(hash, key, value, next);}
}

可以看到不仅LinkedHashMap继承了HashMap,就连LinkedHashMap保存的元素 Entry都是继承HashMap的Node Entry<K,V> extends HashMap.Node<K,V> ,LinkedHashMap.Entry在HashMap.Node的基础上 添加了两个属性before和after用来保存链表的前一个和后一个引用。

可以看下内部类的继承图:
在这里插入图片描述
图中可以看到,还有个TreeNode(红黑树的节点)是继承Entry的。

我在HashMap详解的文章中并没有去看 TreeNode的源码,因为实现红黑树数据库结构的源码比较多,也比较难理解。要想写好注释 比较费力。

从TreeNode的继承结构引发一个关于设计类继承关系的思考?

     为什么在TreeNode和Node之间 还要搞个Entry来实现链表的功能,不如直接在Node节点加上before和after属性实现双向链表功能得了,这样还省事就不用再写一个Entry夹在中间了是不是。

     这样做当然是可以的,只不过这样设计会使得HashMap本身不需要链表结构的每个元素都有before和after属性,当元素存储很多的是时候对于空间来说是浪费的。 如果再设计个Entry夹在中间,LinkedHashMap需要双向链表结构就用Entry,但是TreeNode有时候需要双向链表(比如LinkedHashMap需要转红黑树的时候),有时候不需要双向链表(比如HashMap需要转红黑树的时候)。这个时候的TreeNode不管需不需要双向链表结构,都是已经继承Entry的了,所以会多出before和after属性。
     在HashMap需要转红黑树的情况下继承Entry实际上是一种空间浪费,但是别忘了概率, hash 值如果足够随机,则在 hash 表内按泊松分布,在负载因子 0.75 的情况下,长度超过8的链表出现概率是0.00000006。这么低的概率正常情况下注定红黑树节点在哈希表中不会有很多。

     所以这么分析下来 搞个Entry夹在中间是个非常不错的设计。 既节省了HashMap的Node节点的空间占用,Entry又复用了Node的代码,TreeNode又复用了Entry的代码,实在是妙呀!

5、LinkedHashMap的put方法

是的你没有看错,LinkedHashMap并没有重写HashMap的put方法,直接调用的就是HashMap的put方法。

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

那LinkedHashMap在put元素的时候又是如何把每个元素都链在一块形成双向链表的呢?

LinkedHashMap实际上并不需要整体重写put方法,只需要重写newNode方法即可。
HashMap的newNode方法

Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {return new Node<>(hash, key, value, next);}

LinkedHashMap在重写的newNode方法中新建LinkedHashMap.Entry<K,V>元素。然后把元素链接到链表的尾部。

newNode方法

@Override
Node<K, V> newNode(int hash, K key, V value, Node<K, V> e) {// 创建一个新的 LinkedHashMap.Entry 节点,并初始化其哈希值、键、值和下一个节点指针LinkedHashMap.Entry<K, V> p = new LinkedHashMap.Entry<>(hash, key, value, e);// 将新节点插入到双向链表的尾部linkNodeLast(p);// 返回新创建的节点return p;
}

linkNodeLast方法

private void linkNodeLast(LinkedHashMap.Entry<K, V> p) {// 当前的尾节点LinkedHashMap.Entry<K, V> last = tail;// 将新节点设置为尾节点tail = p;if (last == null) {// 如果链表为空,新节点即为头节点head = p;} else {// 否则,将当前尾节点的 after 指针指向新节点p.before = last;last.after = p;}
}

afterNodeAccess方法

还记得 HashMap详解这篇文章中提过这个方法吗
LinkedHashMap就重写了 HashMap给的扩展方法。

afterNodeAccess 方法在访问节点后调用,用于将被访问的节点移动到双向链表的尾部。
这对于按访问顺序维护的 LinkedHashMap 特别重要。

@Override
void afterNodeAccess(Node<K, V> e) { // move node to lastLinkedHashMap.Entry<K, V> last;// 检查是否按访问顺序维护链表,并且被访问的节点不是当前的尾节点if (accessOrder && (last = tail) != e) {// 将节点 e 转换为 LinkedHashMap.Entry 类型LinkedHashMap.Entry<K, V> p = (LinkedHashMap.Entry<K, V>) e;// 获取节点 p 的前一个和后一个节点LinkedHashMap.Entry<K, V> b = p.before, a = p.after;// 将 p 的 after 指针置为 null,表示它将成为新的尾节点p.after = null;// 更新前一个节点和后一个节点的指针if (b == null)head = a; // 如果 p 没有前一个节点,则它是头节点,更新头节点为 aelseb.after = a; // 否则,将前一个节点的 after 指针指向 aif (a != null)a.before = b; // 如果 p 有后一个节点,将后一个节点的 before 指针指向 belselast = b; // 如果 p 是当前的尾节点,更新 last 为 b// 如果 last 为空,表示链表为空,将 p 设置为头节点if (last == null)head = p;else {// 否则,将 p 插入到链表尾部p.before = last;last.after = p;}// 更新尾节点为 ptail = p;// 增加修改计数,以反映结构性修改++modCount;}
}

afterNodeInsertion方法

afterNodeInsertion 方法在插入节点后调用,用于检查是否需要移除最老的节点(头节点)。
这对于按插入顺序维护的 LinkedHashMap 特别重要。

@Override
void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K, V> first;// 如果需要移除元素,并且头节点不为空,并且需要移除最老的节点if (evict && (first = head) != null && removeEldestEntry(first)) {// 获取头节点的键K key = first.key;// 根据键移除节点removeNode(hash(key), key, null, false, true);}
}

看了上面的代码注释就会明白,afterNodeAccess和afterNodeInsertion方法的主要目的都是为了实现按照访问顺序处理元素的位置。

6、LinkedHashMap的get方法

LinkedHashMap重写了 HashMap的get方法,主要新增了按访问顺序维护链表的功能。

@Override
public V get(Object key) {Node<K, V> e;// 调用 HashMap 的 getNode 方法,使用键的哈希值查找节点if ((e = getNode(hash(key), key)) == null)// 如果节点不存在,返回 nullreturn null;// 如果 accessOrder 为 true,表示按访问顺序维护链表if (accessOrder)// 调用 afterNodeAccess 方法,将访问的节点移动到链表尾部afterNodeAccess(e);// 返回节点的值return e.value;
}

afterNodeAccess在上面已经详细注释了。

7、LinkedHashMap的remove方法

LinkedHashMap的remove方法在设计上和put方法有相似之处,LinkedHashMap并没有重写HashMap的remove方法,而是重写了afterNodeRemoval方法,在删除节点时维护双向链表。

void afterNodeRemoval(Node<K, V> e) { // unlink// 将节点 e 转换为 LinkedHashMap.Entry 类型LinkedHashMap.Entry<K, V> p = (LinkedHashMap.Entry<K, V>) e;// 获取节点 p 的前一个和后一个节点LinkedHashMap.Entry<K, V> b = p.before, a = p.after;// 将节点 p 的 before 和 after 指针置为 nullp.before = p.after = null;// 更新前一个节点和后一个节点的指针if (b == null)head = a; // 如果 p 没有前一个节点,则它是头节点,更新头节点为 aelseb.after = a; // 否则,将前一个节点的 after 指针指向 aif (a == null)tail = b; // 如果 p 没有后一个节点,则它是尾节点,更新尾节点为 belsea.before = b; // 否则,将后一个节点的 before 指针指向 b
}

最后再整体看下HashMap中留给LinkedHashMap扩展的几个方法:

// Callbacks to allow LinkedHashMap post-actions// 在访问节点(即调用 get 方法)后调用。// 主要用途:LinkedHashMap 重写此方法,用于在访问一个节点后将其移动到双向链表的尾部,以维护访问顺序void afterNodeAccess(Node<K,V> p) { }// 在插入新节点后调用  // 主要用途:LinkedHashMap 重写此方法,用于在插入新节点后检查并移除最老的节点,以维护固定大小的缓存或按照插入顺序迭代。void afterNodeInsertion(boolean evict) { }// 在删除节点后调用// 主要用途:LinkedHashMap 重写此方法,用于在删除节点后调整双向链表的指针,确保链表的完整性。void afterNodeRemoval(Node<K,V> p) { }

其中afterNodeInsertion方法的参数 boolean evict,该参数指示当前的操作是否在创建模式下。如果为 false,表示哈希表处于创建模式;如果为 true,表示哈希表处于正常操作模式。此参数通常在初始化哈希表时使用,以避免在创建模式中触发删除操作。

evict 参数为 false 的情况:
在哈希表初始化时,通过 putMapEntries 方法调用 putVal,设置 evict 为 false。 HashMap的readObject反序列化方法也会调用 putVal,设置 evict 为 false。

evict 参数为 true 的情况:
正常操作(非创建模式)下,插入或更新元素时,evict 为 true,允许执行淘汰策略。

8、LinkedHashMap的迭代器

LinkedHashMap的迭代器可以对比 HashMap详解 这篇文章的 HashMap的迭代器来学习。

    二者本质的区别是由 LinkedHashMap 维护了双向链表而决定的。 LinkedHashMap的迭代器不会像HashMap的迭代器那样遍历全部的数组节点,而是通过双向链表的头结点,以及after指针一个一个往下遍历,这种方式就少了HashMap那种遍历全部数组节点的过程,直接通过after指针就能遍历全部的元素,这种方式比 HashMap 的迭代更为直接高效。

abstract class LinkedHashIterator {// 下一个要返回的节点LinkedHashMap.Entry<K,V> next;        // 当前的节点LinkedHashMap.Entry<K,V> current;     // 用于快速失败的期望修改计数int expectedModCount;  // 构造方法LinkedHashIterator() {// 初始化下一个节点为链表的头节点next = head;// 初始化期望的修改计数,等于当前的修改计数expectedModCount = modCount;// 初始化当前节点为nullcurrent = null;}// 判断是否有下一个元素public final boolean hasNext() {return next != null;}// 获取下一个节点final LinkedHashMap.Entry<K,V> nextNode() {// e用于存储当前的下一个节点LinkedHashMap.Entry<K,V> e = next;// 如果哈希表的修改计数与期望的修改计数不同,抛出并发修改异常if (modCount != expectedModCount)throw new ConcurrentModificationException();// 如果下一个节点为空,抛出没有元素异常if (e == null)throw new NoSuchElementException();// 将当前节点设为下一个节点current = e;// 更新下一个节点为当前节点的下一个节点(after)next = e.after;// 返回当前的节点return e;}}

9、LinkedHashMap的一些常见问题

LinkedHashMap 和 HashMap 的区别及适用场景

数据结构对比

特性HashMapLinkedHashMap
数据结构数组 + 链表(红黑树)数组 + 链表(红黑树)+ 双向链表
顺序保证按插入顺序或访问顺序
空间开销较低较高(需要额外维护链表指针)

插入和迭代性能

操作HashMapLinkedHashMap
插入O(1) 平均,O(n) 最坏(哈希冲突)O(1) 平均,O(n) 最坏(哈希冲突)
删除O(1) 平均,O(n) 最坏(未转红黑树的情况)O(1) 平均,O(n) 最坏(未转红黑树的情况)
查找O(1) 平均,O(n) 最坏(未转红黑树的情况)O(1) 平均,O(n) 最坏(未转红黑树的情况)
迭代与数据插入顺序无关,但是要循环数组按插入顺序或访问顺序,直接遍历链表,性能稍好

适用场景

场景HashMapLinkedHashMap
快速查找和插入
需要保证元素顺序
LRU 缓存实现
内存使用较少的场景
  • HashMap:

    • 适用于对元素顺序没有要求的场景。
    • 高效的查找、插入和删除操作。
    • 内存占用较少。
  • LinkedHashMap:

    • 需要维护元素顺序的场景。
    • 实现 LRU(最近最少使用)缓存。
    • 适合需要顺序遍历的场景,且可以按插入顺序或访问顺序遍历。
    • 较高的内存开销。

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

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

相关文章

学习前台开发主要掌握的技能

学习前端开发需要掌握一系列的技能&#xff0c;这些技能大致可以分为以下几个类别&#xff1a;基础技术、前端框架和库、开发工具和环境、版本控制、性能优化和最佳实践。以下是详细的技能清单&#xff1a; 基础技术 1.HTML&#xff08;超文本标记语言&#xff09; HTML5的新…

C++: version `GLIBCXX_3.4.29‘ not found

最近遇到一个错误,在一个机器上编译sockperf,但是运行的时候出现错误: # ./sockperf ./sockperf: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.29 not found (required by ./sockperf)[root@RH8

用LoRA微调 Llama 2:定制大型语言模型进行问答

Fine-tune Llama 2 with LoRA: Customizing a large language model for question-answering — ROCm Blogs (amd.com) 在这篇博客中&#xff0c;我们将展示如何在AMD GPU上使用ROCm对Llama 2进行微调。我们采用了低秩适配大型语言模型(LoRA)来克服内存和计算限制&#xff0c;…

SAP赋能食品行业,确保安全与品质的双重飞跃

品安全与品质是消费者最关心的问题&#xff0c;也是食品企业的生命线。随着科技的发展和消费者需求的日益多样化&#xff0c;食品行业正面临着前所未有的挑战和机遇。SAP作为全球领先的企业资源规划&#xff08;ERP&#xff09;系统&#xff0c;为食品行业提供了全面的解决方案…

RealityCheck™电机监测和预测性维护模型

RealityCheck™电机 一个附加的软件工具箱&#xff0c;可实现条件监测和预测性维护功能&#xff0c;而无需依赖额外的传感器。相反&#xff0c;它使用来自电机控制过程的电子信息作为振动和其他传感器的代理。凭借其先进的信号处理和机器学习(ML)模型&#xff0c;RealityCheck …

惠普8596E频谱分析仪

8590E系列频谱分析仪具有各种各样的性能、功能&#xff0c;其价格亦是为适应用户的承受能力而确定的。用户可以从价格低廉、具有基本性能的分析仪直至高性能分析仪中进行挑选&#xff0c;无论选择哪种分析仪&#xff0c;都会感受到8590系列频谱分析仪便于使用且高度可靠。这些仪…

js获取年月日时分秒及星期几

最近发现好像写这种基础博客的很少&#xff0c;文章大部分都是几年前的&#xff0c;之前对于时间这块都是直接使用day.js 来处理&#xff0c;废话不多说&#xff0c;直接进入正题 const now new Date();//初始值 now.getFullYear()//年 now.getMonth() 1 //月 now.getDate()…

Palo Alto GlobalProtect App 6.3 (macOS, Linux, Windows, Andriod) - 端点网络安全客户端

Palo Alto GlobalProtect App 6.3 (macOS, Linux, Windows, Andriod) - 端点网络安全客户端 Palo Alto Networks 远程访问 VPN 客户端软件 请访问原文链接&#xff1a;https://sysin.org/blog/globalprotect-6/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。…

呼叫中心许可证如何续证?

我们知道自从2019年下半年&#xff0c;工信部开始整治营销骚扰电话&#xff0c;呼叫中心许可证开始政策严控&#xff0c;虽未出台暂停审批的文件&#xff0c;但实际从2019年10月左右就暂停审批发新证包括续证。 2020年下半年逐步放开&#xff0c;但审核极其严格&#xff0c;20…

从0开始C++(三):构造函数与析构函数详解

目录 构造函数 构造函数的基本使用 构造函数也支持函数重载 构造函数也支持函数参数默认值 构造初始化列表 拷贝构造函数 浅拷贝和深拷贝 析构函数 总结 练习一下ヽ(&#xffe3;▽&#xffe3;)&#xff89; 构造函数 构造函数的基本使用 构造函数是一种特殊的成…

腾讯地图撒点并默认显示点位信息

实现步骤如下&#xff1a; 1、注册腾讯位置服务账号并获取 Key 2、需要创建一个地图容器&#xff0c;并使用腾讯地图的 API 初始化地图。通常涉及到设置地图的中心点、缩放级别和地图样式。 map new TMap.Map(document.getElementById(‘container’), { center: center, zo…

JavaScript 中 this 的使用方法详解

一、全局环境中的 this 在全局环境中&#xff0c;this 指向全局对象。在浏览器中&#xff0c;全局对象是 window&#xff1b;在 Node.js 中&#xff0c;全局对象是 global。 console.log(this); // 浏览器中输出&#xff1a;window在严格模式下&#xff0c;this 的值为 undef…

C++中的enum(枚举)是什么,以及与C中enum的不同之处

最近在看《A Tour of C 3rd》的时候发现 C 和 C 的 enum虽然使用起来比较相似&#xff0c;但是目的却略有不同。关于枚举的概念还请见之前写过一篇关于 C 的那篇博客《C语言中enum&#xff08;枚举&#xff09;详解》&#xff0c;这里不再赘述。本文侧重 C 与 C 不同的地方。 …

Vue56-组件的自定义事件

一、什么是自定义事件 二、子组件—【传值】—>父组件 2-1、prop属性 2-2、自定义事件 v-on在谁身上&#xff0c;就给谁绑定事件&#xff01; 给谁绑定的事件&#xff0c;想触发就找谁&#xff01; 2-3、prop属性VS自定义属性 2-4、简写形式 2-5、ref属性实现 加了ref属性…

软件监控发展简史

软件监控简史&#xff0c;从 00 年代开始。发生了什么变化&#xff1f;为什么事情变得如此神秘&#xff1f; 终端设备上日益重要的用户体验通过边缘计算和分布式计算不断得到改善。然而&#xff0c;服务质量的测量仍然使用基于服务器的原语进行。 我们的 2000 年软件监控是这样…

wvp-GB28181-pro 源码分析-服务启动流程及IPC注册(一)

文章目录 启动顺序1、VManageBootstrap文件中的main2、优先加载的bean3、gb28181/SipLayer.java4、media/MediaServerConfig.java5、conf/SipPlatformRunner.java6、gb28181/task/SipRunner.java2024年6月20日下载的wvp-GB28181-pro,版本号为2.7.2,使用ZLMediakit主干版本。 …

程序员兼职接单有哪些渠道?一篇文章带你了解!

2024年&#xff0c;程序员兼职接单别只盯着朋友圈啦&#xff01;这些兼职接单渠道你一个都不容错过&#xff01;想要通过兼职接单获取收入的程序员&#xff0c;一定不能错过这篇文章&#xff01; 程序员兼职接单的渠道可以简单的分类为兼职平台和程序员论坛和自身人脉拓展三个…

【SD3辅助工具推荐】InstantX发布了三种SD3专属的ControlNet模式——Pose、Canny和Tile

InstantX 是一家专注于人工智能内容生成的独立研究机构。此前&#xff0c;曾开源著名的InstantID和论文《InstantID : Zero-shot Identity-Preserving Generation in Seconds》。随着本月12号&#xff0c;Stability AI正式开源了其产品 Stable Diffusion 3&#xff0c;这家机构…

吃透Flink State面试题和参考答案

目录 什么是 Flink 中的状态(State)? Flink 支持哪两种状态类型? 解释一下什么是 Keyed State 和 Operator State。 Flink 中的状态是如何存储的? 什么是 Flink 的状态后端(State Backend)? 比较 MemoryStateBackend、FsStateBackend 和 RocksDBStateBackend 的区…

js笔试题目2024

字符串按字符出现频次排序 "Aacbbcc" 输出 "cccbbAa" const s "Aacbbcc"function setString(string) {const map new Map();let res for(let char of string){const val map.get(char)map.set(char, val?val 1:1)}const arr Array.from(m…