HashMap LinkedHashMap

news2024/10/6 16:16:06

1. HashMap概述

  • 定义:HashMap根据键的hash值来存储数据

  • 底层

    • JDK1.7底层:数组+链表
    • JDK1.8底层:数组+链表+红黑树
    1. 当链表长度达到8且数组长度长于64时,就会转换成红黑树(红黑树阈值)
    2. 如果数组长度小于64,就扩容数组
  • 其他相关:

    1. HashMap中最多允许一个key为null,但可以多个value为null
    2. HashMap是线程不安全的
    3. 解决hash冲突的方式是链址法
    4. 数组:称为桶
    5. 链表:哈希值相同的数据组成一个单链表,单链表的头指针存放在哈希表中的第i个单元中
    6. 扩容数组:初始capacity数组容量为16,阈值threshold是12(阈值的计算方式为 capacity * loadFactor,在HashMap中loadFactory是0.75)

2. HashMap的发展历程

  1. 最开始是数组存储,但这样效率低
  2. 引入了hash值后又导致桶数组范围过大
  3. 因此又引入了定位取模操作,这样又导致hash碰撞
  4. 所以又引入了 二次哈希(扰动处理)以及链表红黑树来解决这个问题。
  • 结论:数组 → hash值 → 定位取模 → 二次哈希、链表、红黑树

3. Hash值的运算和定位取模

  1. Hash值运算:先计算出Hash值

3.1 Hash值运算(HashMap.hash())

  • 为了避免哈希碰撞,HashMap.hash()中有两次hash处理。
  1. 第一次hash就是直接key.hashcode方法
  2. 第二次hash不同版本JDK有区别:
  3. 在JDK1.7时,二次哈希是通过5次异或和4次位运算来实现的。
  4. 在JDK1.8中,是通过高16位与低16位相异或实现的。(^)
JDK1.8:该方法主要是将Object转换成一个整型。
static final int hash(Object key) {
    int h;
    //先将key转换成哈希值,然后进行1次位运算+1次异或运算进行了2次扰动处理尽量避免hash冲突。
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

在这里插入图片描述

index =(table.length - 1) & key.hash();
//回忆前文中的内容,table.length是一个2的正整数次幂,类似于000100000,
//这样的值减一就成了000011111,通过这样的位运算来快速寻址定址。
//计算出索引值index,如果index位置没有元素(没有哈希碰撞),直接在该索引插入数据

在这里插入图片描述

3.2 定位取模(按位与运算)

  • 定位取模是通过hash值和数组长度-1 按位&运算得出的。得到该对象在数组中的下标(保存位)
  • 目的:将哈希值映射到数组的一个下标,从而实现快速的查找和索引。

在HashMap中,默认创建的数组长度是16,存储的数据哈希值都会大于16。因此,通过取模或位运算可以将哈希值映射到0到15的这个范围内,这样就可以直接在数组中进行查找和索引,大大提高了查找速度。

  • put和get的时候都会用到
key.hash() & (table.length-1)
  1. table.length是桶数组的长度,长度总是2的n次方
  2. 所以上面的方法就等价于对hash值对数组长度取模

hash值可能是负数

在这里插入图片描述

4. HashMap常用的方法

4.1 put()

//下标是hash()计算后的
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    
    //3.resize懒加载创建table数组
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null) //3.通过扰动后的哈希值计算出对应在table中的索引
        //4.如果索引处为空,则直接newNode()新建节点并覆盖原值。
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            //5.索引不为空
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            //5.a.红黑树的节点
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //5.b.链表的话就for遍历
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
  • 流程:
  1. (Hash值运算)先高16位和低16位异或扰动出一个新的hash值:(key == null) ? 0 : (h = key.hashcode()) ^ (h >>> 16)
  2. 判断是否存在table数组,没有的话通过resize懒加载创建扩容出table数组。
  3. (定位取模)通过扰动后的哈希值计算出对应在table中的索引:tab[i = (n - 1) & hash])
  4. 如果索引处为空,则直接newNode()新建节点并插入数据
  5. 如果索引不为空
    1.如果tab[i]是红黑树的节点,那么就执行红黑树中添加节点的操作
    2.如果是链表,则遍历。
    1. 如果遍历到尾,则newNode创建节点放到链末尾。
    2. 如果链表长度大于等于8,就调用treeifyBin(tab, hash);:判断数组长度是否大于64( MIN_TREEIFY_CAPACITY),大于就会转换为树,小于64就扩容数组。
    3. 如果找到了hash和key都相同的节点,break。
  6. 如果key值相同,就覆盖原值(在一个链表中如果key值相同,首先它们Hash值相同,同时原数的key相同,那么基于HashMap的规则,会用新的Value覆盖原数)
  7. 最后比较size(当前数组已经有的链表条数)是否大于threshold阈值,大于则resize方法扩容。
  • 细节:HashMap允许put key为null的键值对,这种键值对都放到了桶数组的第0个桶中。
  • 源码中的Node<K,V>,K是个泛型。

4.2 get()

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(key)) == null ? null : e.value;
}

final Node<K,V> getNode(Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & (hash = hash(key))]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
  • 流程
  1. 还是通过(n - 1) & hash 算出在数组中的位置
  2. 看数组的索引处的链表或者树的头的key是不是我们要找到,是则直接返回
  3. 不是则遍历红黑树/遍历链表

4.3 resize()

  • 使用场景
    1. 初始化哈希表
    2. 当前数组容量过小,需要扩容
  • 大致流程
    1. 计算出扩容后数组的容量(capacity)和阈值(threshold)是否超过最大值,不超则变为原来的2倍,并创建新数组。
    2. 如果旧数组没有初始化,直接返回创建的新数组,其长度为16(DEFAULT_INITIAL_CAPACITY)、负载因子为0.75。
  • 扩容流程
    1. JDK1.7:没有红黑树,所以只存在链表扩容。(先遍历原HashMap,对每个元素重新计算索引,然后再放到对应链表的头部(等价于一次链表反转,先插入的会被放在链表的尾部))
    2. JDK1.8:首先遍历旧table数组
    3. 如果只是一个节点,不是链表不是树,则直接取模运算后放入到新数组中。(位运算 e.hash & (newCap-1))
    4. 如果是链表,由于扩容是直接乘以2。元素的位置要么低位在之前的位置,要么高位在之前的位置加上扩容大小的位置。(通过分出一个高低位来实现)
    • 分位:通过hash值与原数组容量oldCap进行&运算,判断是否为0。将原链表分为高低两个链表,通过尾插法放入新数组对应的位置,链表整体顺序不变。
    1. 如果是红黑树。将其分为两棵树放在新数组上。

每次扩容的下标 e.hash & (newCap - 1),保证了下标范围不超过newCap。
在这里插入图片描述

5. HashMap的线程安全

  • HashMap如何实现线程安全
  1. 用HashTable
  2. 用同步包装器Collections的synchronizedMap方法使HashMap具有线程安全的能力
static Map<String, String> results = Collections.synchronizedMap(new HashMap<String, String>());
  1. 使用ConcurentHashMap

6. 相关问题

6.1 为什么哈希桶的长度总是2的n次方?

在这里插入图片描述

6.2 扩容的具体条件是什么?

在这里插入图片描述

6.3 树化的具体条件是什么?

在这里插入图片描述

6.4 HashMap在JDK1.7和JDK1.8的区别?

在这里插入图片描述

6.5 HashMap在1.7版本由于头插法而产生死循环的原因是什么?

//e代表当前遍历到的节点(也就是要插入的节点)
//1.保存原链表的下一个节点
//2.将要插入的节点指向新的链表头(头插法)
//3.更改新的链表的头部(新头部为我们插入的新结点)
//4.将之前保存的下一个节点给e
Entry<K,V> next = e.next;
e.next = newTable[i];
newTable[i] = e;
e = next;

在这里插入图片描述

6.6 HashMap的初始容量为什么是16?

在这里插入图片描述

6.7 HashMap的负载因子loadFactory为什么是0.75?(有关桶的阈值)

在这里插入图片描述

6.8 如何解决初始化时输入的值不是2的n次幂

在这里插入图片描述

6.9 桶中的元素链表何时转换为红黑树?什么时候转回?为什么要这么设计?

在这里插入图片描述

6.10 Java 8中为什么要引入红黑树,是为了解决什么场景的问题?

在这里插入图片描述

6.11 HashMap如何处理Key为null的键值对?

在这里插入图片描述

6.12 为什么HashMap中的String、Integer这样的包装类适合作为Key键?

在这里插入图片描述

6.13 为什么初始容量是16?

在这里插入图片描述

6.14 JDK1.8死循环

在这里插入图片描述

6.15 Hash冲突是什么?

在这里插入图片描述

6.16 为什么用红黑树而不是二叉搜索树,平衡二叉树等?

在这里插入图片描述

6.17 为什么1.8HashMap是先插入后扩容?

在这里插入图片描述

6.18 既然红黑树的效率高,为什么一开始不使用红黑树?

在这里插入图片描述

7. LinkedHashMap概述

  • LinkedHashMap继承了HashMap,是基于数组双向链表实现的。
  1. HashMap中的数据是无序的,也就是说访问的顺序不是存储的顺序。
  2. LinkedHashMap中的数据是有序的,并且我们可以自己决定使用哪种顺序
    在这里插入图片描述

8. LinkedHashMap的原理

  • 有序:LinkedHashMap可以实现有序,就是因为它的Node类中,比HashMap多了一个before和after引用,并且创建一个为null的Header(Node)作为遍历双向循环链表的入口。
  • LinkedHashMap的头部:头部存的是最久前访问的节点或最先插入的节点。尾部存的是最近访问的和最近插入的节点。
  • header的before和after指向双向链表的头部和尾部。
/**
 * LinkedHashMap中的node直接继承自HashMap中的Node。并且增加了双向的指针
 */  
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

9. put

  • LinkedHashMap没有重写HashMap的put()方法,而是重写了put()方法需要调用的内部方法newNode()。
  • newNode:在定位取模后对应的索引处为空时,会创建结点,并覆盖原值
  • 插入顺序和访问顺序都是 简单的插入到集合队尾中
HashMap.Node<K,V> newNode(int hash, K key, V value, HashMap.Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;      //只有一个节点?
    else {
        p.before = last;     //last是尾前一个节点
        last.after = p;
    }
}

10. get

  1. 插入顺序:直接取出
  2. 访问顺序:先取出,之后还要调用aferNodeAccess()方法把元素从原链表移除并放到队尾。
// LinkedHashMap 中覆写
public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 如果 accessOrder 为 true,则调用 afterNodeAccess 将被访问节点移动到链表最后
    if (accessOrder)
        //把元素从原链表移除并放到队尾。
        afterNodeAccess(e);
    return e.value;
}

// LinkedHashMap 中覆写
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {//accessOrder为true代表访问顺序
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        // 如果 b 为 null,表明 p 为头节点
        if (b == null)
            head = a;
        else
            b.after = a;
            
        if (a != null)
            a.before = b;
        /*
         * 这里存疑,父条件分支已经确保节点 e 不会是尾节点,
         * 那么 e.after 必然不会为 null,不知道 else 分支有什么作用 
         */
        else
            last = b;
    
        if (last == null)
            head = p;
        else {
            // 将 p 接在链表的最后
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

11. 相关问题

11.1 LinkedHashMap为什么使用双向链表,而不是单链表

在这里插入图片描述

11.2 LinkedHashMap如何保证访问顺序?

插入顺序都是直接put和get,访问顺序的话put也是直接,但是在get方法后,因为变成最近刚访问,所以还要移动到双向链表的尾部。

void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
  1. afterNodeAccess:在putVal和get中调用,根据accessOrder判断是否需要调整链表顺序,将被操作的元素维护到LinkedHashMap的尾部。
  2. afterNodeInsertion:移出最老插入的节点,在putVal中使用,可以用来写出一个LRU。(重写removeEldestEntry())
    • LRU:它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。
    • removeEldestEntry()默认返回false,所以正常情况下不会溢出最老插入的节点(LRU下重写该方法)
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    //removeEldestEntry(first)默认返回false,所以afterNodeInsertion这个方法其实并不会执行
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}
  1. afterNodeRemoval:在remove()的removeNode()中调用,作用是删除一个节点,也在双向链表中删掉这个节点的after和before指针,并把这个节点的前驱结点和后继节点连接起来。

11.3 put时调用的afterNodeAccess方法,调用的会是子类的afterNodeAccess方法吗?

  • map.put(key,value) -> HashMap#putVal(key,value) -> afterNodeAccess
    在这里插入图片描述

11.4 为什么LinkedHashMap能实现LRU缓存,把最不经常访问的那个元素淘汰。

  • put()方法中有一个afterNodeInsertion方法,会判断第一个元素是否超出了可容纳的最大范围,如果超出了,接会调用removeNode方法删除最不经常访问的那个元素。
  • 前提要重写removeEldestEntry()
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

12. LinkedHashMap实现LRU

  1. 重写LinkedHashMap,传入true表示访问顺序,重写方法
public class LRUCache extends LinkedHashMap<Integer , Integer> {
    private int capacity;
    
    public LRUCache(int capacity){
        //当accessOrder的参数为true时,就会按照访问顺序排序
        super(capacity , 0.75F , true);
        this.capacity = capacity;
    }

    public int get(int key){
        return super.getOrDefault(key , -1);
    }
    public void put(int key , int value){
        super.put(key , value);
    }

    //重写当前方法,当内存超出的时候返回true
    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity;
    }
}
  1. 内部封装LinkedHashMap
//2,使用LinkedHashMap的方式
//添加顺序
public class LRUTest{
    int capacity;
    LinkedHashMap<Integer, Integer> cache;
 
    LRUTest(int capacity){
        cache = new LinkedHashMap<>();
        this.capacity = capacity;
    }
 
    //访问元素
    public int get(int key){
        if(!cache.containsKey(key)){
            return -1;
        }
        //存在,先从链表头部删除删除,在插入到链表尾部
        int val = cache.get(key);
        cache.remove(key);
        cache.put(key, val);
        return val;
    }
 
    //添加元素
    public void put(int key, int val){
        if(cache.containsKey(key)){
            cache.remove(key);
        }
        //如果链表已经满了,则删除头部节点
        if(cache.size() == capacity){
            Set<Integer> keySet = cache.keySet();
            Iterator<Integer> iterator = keySet.iterator();
            cache.remove(iterator.next());
        }
        cache.put(key, val);
    }
}

public LinkedHashMap() {
    super();
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}

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

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

相关文章

如何成为开源组件库NutUI的contributor:解决issue并提交PR

前言 NutUI 是一套京东风格的轻量级移动端组件库。自诞生以来&#xff0c;一直在紧跟技术的发展不断推陈出新&#xff0c;始终保持稳健的发展态势&#xff0c;从一个单一的基础组件库发展到如今服务于数千开发者的多端 UI 组件库。NutUI 的成长离不开团队成员的努力&#xff0…

基于WebRTC的程序因虚拟内存不足导致闪退问题的排查以及解决办法的探究

目录 1、WebRTC简介 2、问题现象描述 3、将Windbg附加到目标进程上分析 3.1、Windbg没有附加到主程序进程上&#xff0c;没有感知到异常或中断 3.2、Windbg感知到了中断&#xff0c;中断在DebugBreak函数调用上 3.3、32位进程用户态虚拟地址和内核态虚拟地址的划分 …

JVM的几个面试重点

JVM的内存区域划分 JVM类加载机制 前言 Java程序最开始是一个 .java 的文件&#xff0c;JVM把它编译成 .closs 文件&#xff08;字节码文件&#xff09;&#xff0c;运行 Java 程序&#xff0c; JVM 就会读取 .class 文件&#xff0c;把文件内容读取到内存中&#xff0c;构造出…

C++:为什么析构函数一般写为虚函数

如果没有继承关系&#xff0c;析构函数写不写为虚函数都可以。 如果有继承关系、有多态性的使用需求时&#xff0c;就需要把析构函数写为虚函数&#xff0c;这样可以避免潜在的内存泄漏问题。 比如&#xff1a;当一个类被设计为作为基类&#xff0c;并且通过基类指针或引用dele…

【网络爬虫 | Python】数字货币ok链上bitcoin大额交易实时爬取,存入 mysql 数据库

文章目录 一、网站分析二、js 逆向获取 X-Apikey三、python 调用 js 获取 X-Apikey四、python 爬虫部分五、mysql 数据库、日志、配置文件、目录结构六、结尾 一、网站分析 oklink&#xff1a;https://www.oklink.com/ btc 大额交易&#xff1a;https://www.oklink.com/btc/tx-…

Fiddler抓包VSCode和探索

前言&#xff1a; 最近在使用 VSCode 调试 web 程序时&#xff0c;遇到一些问题&#xff0c;当时不知道如何是好。所以决定抓看来看一看&#xff0c;然后一顿操作猛如虎&#xff0c;成功安装了抓包软件 – Fiddler Classic。我并没有使用 Postman 这种重量级的 HTTP 测试软件&a…

windows系统kettle9.3一键安装启动

程序下载、解压 通过百度网盘下载&#xff0c;直接解压即可 解压之后 双击运行 程序路径 pdi-ce-9.3.0.0-428一键安装启动\pdi-ce-9.3.0.0-428\data-integration

【FA-BP预测】基于萤火虫算法优化BP神经网络回归预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux中常见的权限问题

目录 前言1. 目录权限2. umask3. 粘滞位结语 前言 在了解完上一篇文章 Linux权限的理解与操作 之后&#xff0c;还有一些比较常见的权限问题需要我们去了解。其中包括目录的权限&#xff0c;umask 以及 粘滞位的使用。 1. 目录权限 问题一&#xff1a;进入一个目录&#xff0…

STM32F4_FATFS

目录 前言 1. 文件系统简介 2. FATFS文件系统 2.1 实际演练 2.2 FATFS读书笔记整理 2.3 FAT文件系统的神秘面纱 2.3.1 引导扇区 2.3.2 引导代码 2.3.3 FSINF0信息扇区 2.3.4 FAT表 2.3.5 FAT32数据区 2.3.6 子目录 2.3.7 目录项 3. 实验程序 3.1 main.c 3.2 di…

美团面试:Oracle JDK那么好,为何要用Open JDK?

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 既然 Oracle JDK 这么好&#xff0c;那为什么还要有 OpenJDK&…

YZ系列工具之YZ12:VBA_4种方法设计下拉列表

我给VBA下的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套一部VBA手册&#xff0c;教程分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的…

OJ第五篇

文章目录 用队列实现栈用栈实现队列设计循环队列 用队列实现栈 链接&#xff1a;用队列实现栈 这道题是让我们用两个队列实现一个栈&#xff0c;简单来说&#xff0c;就是利用队列来实现一个先入后出的功能&#xff0c;我们知道队列是先入先出&#xff0c;如何用两个队列来实…

Vue2基础知识(四) 自定义指令

目录 一 自定义指令1.1 定义1.2 自定义局部指令1.3 全局注册指令1.4 钩子函数1.5 动态传参1.6 使用场景 &#x1f48c; 所属专栏&#xff1a;【Vue2】&#x1f600; 作 者&#xff1a;长安不及十里&#x1f4bb;工作&#xff1a;目前从事电力行业开发&#x1f308;目标&#xf…

全国342个城市往返最短通勤时间(铁路)数据

全国342个城市往返最短通勤时间&#xff08;铁路&#xff09;数据 1、时间&#xff1a;采集时间是2022年 2、来源&#xff1a;12306 3、数据说明&#xff1a;数据采集12306数据&#xff0c;整理全国342个城市往返最短通勤时间&#xff0c;本数据是铁路包含动车、高铁所有路线…

【GESP】2023年06月图形化三级 -- 计算最终值

文章目录 计算最终值【题目描述】【输入描述】【输出描述】【参考答案】其他测试用例 计算最终值 【题目描述】 默认小猫角色&#xff0c;白色背景。存在一种仅支持2种操作和1个变量的编程语言&#xff1a; X 使变量 “X” 的值增加1X-- 使变量 “X” 的值减少 1 最初&#…

基于SAE堆叠自编码器的单维时间序列预测研究(matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

在没有康托尔对角化方法的情况下证明实数的不可数性

乔治康托尔 |图片来源&#xff1a; 维基百科 一、说明 对于那些对数学感兴趣的人来说&#xff0c;无穷大实际上可以有不同的大小&#xff0c;这可能是一个众所周知的事实。事实上&#xff0c;最著名的例子是所有实数的集合比所有自然数的集合“大”。你可能知道&#xff0c;这实…

Docker部署SpringBoot +Vue项目流程详解(含域名 + HTTPS)

文章目录 前言1、选购服务器2、部署 Docker3、前端、后端项目打包4、配置 Dockerfile5、配置 Nginx6、配置 DockerCompose6、启动 DockerCompose 完成部署7、【可选】域名 SSL证书 前言 本次整体部署操作使用阿里云服务器&#xff0c;这里我选择的是香港地区的2核2G ECS&…

打击勒索病毒:防御.kat6.l6st6r勒索病毒的最佳策略

导言&#xff1a; 我们日常生活和工作的方方面面都离不开数字化&#xff0c;但这也意味着面临日益复杂的网络威胁。.kat6.l6st6r勒索病毒就是其中之一&#xff0c;如果你的计算机感染了这种恶意软件&#xff0c;你的数据可能会遭到加密并要求支付赎金才能解锁。在这篇终极指南…