LinkedHashMap详解

news2025/1/18 20:11:06

目录

  • 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 的 LRUCache
        LRUCache<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 方法
    // 当添加一个新的键值对时,此方法会被调用,以判断是否需要删除最老的条目
    @Override
    protected 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 和 next
        super(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 last
    LinkedHashMap.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 没有前一个节点,则它是头节点,更新头节点为 a
        else
            b.after = a; // 否则,将前一个节点的 after 指针指向 a
        if (a != null)
            a.before = b; // 如果 p 有后一个节点,将后一个节点的 before 指针指向 b
        else
            last = b; // 如果 p 是当前的尾节点,更新 last 为 b
        // 如果 last 为空,表示链表为空,将 p 设置为头节点
        if (last == null)
            head = p;
        else {
            // 否则,将 p 插入到链表尾部
            p.before = last;
            last.after = p;
        }
        // 更新尾节点为 p
        tail = p;
        // 增加修改计数,以反映结构性修改
        ++modCount;
    }
}

afterNodeInsertion方法

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

@Override
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);
    }
}

看了上面的代码注释就会明白,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)
        // 如果节点不存在,返回 null
        return 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 指针置为 null
    p.before = p.after = null;
    // 更新前一个节点和后一个节点的指针
    if (b == null)
        head = a; // 如果 p 没有前一个节点,则它是头节点,更新头节点为 a
    else
        b.after = a; // 否则,将前一个节点的 after 指针指向 a
    if (a == null)
        tail = b; // 如果 p 没有后一个节点,则它是尾节点,更新尾节点为 b
    else
        a.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;
            // 初始化当前节点为null
            current = 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.coloradmin.cn/o/1853164.html

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

相关文章

JAVA复习3

目录 19. 下列关于 do…while 语句和 while 语句的叙述中错误的是&#xff08; C &#xff09; 20. 若有定义 int a9, b6; System.out.println(a > b) 的结果是&#xff08; D &#xff09; 21. 关于接口和抽象类&#xff0c;下列说法正确的是&#xff08;A&#xff09; …

C++并发之协程实例(三)(co_await)

目录 1 协程2 实例3 运行 1 协程 协程(Coroutines)是一个可以挂起执行以便稍后恢复的函数。协程是无堆栈的&#xff1a;它们通过返回到调用方来暂停执行&#xff0c;并且恢复执行所需的数据与堆栈分开存储。这允许异步执行的顺序代码&#xff08;例如&#xff0c;在没有显式回调…

Interleaving Retrieval with Chain-of-Thought Reasoning for ... 论文阅读

Interleaving Retrieval with Chain-of-Thought Reasoning for Knowledge-Intensive Multi-Step Questions 论文阅读 文章目录 Interleaving Retrieval with Chain-of-Thought Reasoning for Knowledge-Intensive Multi-Step Questions 论文阅读 Abstract介绍相关工作开放域QA提…

Python+Pytest+Yaml+Request+Allure接口自动化测试框架详解

PythonPytestYamlAllure整体框架目录&#xff08;源代码请等下篇&#xff09; 框架详解 common:公共方法包 –get_path.py:获取文件路径方法 –logger_util.py:输出日志方法 –parameters_until.py&#xff1a;传参方式方法封装 –requests_util.py&#xff1a;请求方式方法封…

Cadence 16.6与17.4个人学习版推荐

一. 简介与下载 Cadence个人学习版是基于Cadence官方发行的安装包做了适当精简和优化的二次打包版本&#xff0c;包括了Cpature原理图设计、PSpice 电路仿真以及Allegro PCB设计等以电子产品设计为主的主要功能&#xff0c;能满足绝大部分硬件工程师的使用需求。 学习版预先已…

我国人工智能核心产业规模近6000亿元

以下文章来源&#xff1a;中国证券报 2024世界智能产业博览会6月20日至6月23日在天津举行。会上发布的《中国新一代人工智能科技产业发展报告2024》显示&#xff0c;我国人工智能企业数量已经超过4000家&#xff0c;人工智能已成为新一轮科技革命和产业变革的重要驱动力量和战略…

stm32学习笔记---OLED调试工具(理论部分和代码部分)

目录 理论部分 三种常用的程序调试方法 第一种是串口调试 第二种是显示屏调试 第三种是Keil调试模式 其他调试方式 OLED显示屏的介绍 OLED的硬件电路 OLED驱动程序中所包含的驱动函数 OLED_Init(); OLED_Clear(); OLED的显示函数 OLED_ShowChar(1, 1, A); OLED_S…

【SSM】

Spring常见面试题总结 Spring 基础 什么是 Spring 框架? Spring 是一款开源的轻量级 Java 开发框架&#xff0c;旨在提高开发人员的开发效率以及系统的可维护性。 我们一般说 Spring 框架指的都是 Spring Framework&#xff0c;它是很多模块的集合&#xff0c;使用这些模块…

海洋生物识别系统+图像识别+Python+人工智能课设+深度学习+卷积神经网络算法+TensorFlow

一、介绍 海洋生物识别系统。以Python作为主要编程语言&#xff0c;通过TensorFlow搭建ResNet50卷积神经网络算法&#xff0c;通过对22种常见的海洋生物&#xff08;‘蛤蜊’, ‘珊瑚’, ‘螃蟹’, ‘海豚’, ‘鳗鱼’, ‘水母’, ‘龙虾’, ‘海蛞蝓’, ‘章鱼’, ‘水獭’, …

计算机系统基础实训五—CacheLab实验

实验目的与要求 1、让学生更好地应用程序性能的优化方法&#xff1b; 2、让学生更好地理解存储器层次结构在程序运行过程中所起的重要作用&#xff1b; 3、让学生更好地理解高速缓存对程序性能的影响&#xff1b; 实验原理与内容 本实验将帮助您了解缓存对C程序性能的影响…

地推利器Xinstall:全方位二维码统计,打造高效地推策略,轻松掌握市场脉搏!

在移动互联网时代&#xff0c;地推作为一种传统的推广方式&#xff0c;依然占据着重要的地位。然而&#xff0c;随着市场竞争的加剧&#xff0c;地推也面临着诸多挑战&#xff0c;如如何有效监测下载来源、解决填码和人工登记的繁琐、避免重复打包和iOS限制、以及如何准确考核推…

安装vue时候发现npm淘宝镜像不能使用,报出:npm.taobao.org和registry.npm.taobao.or

2024.3.12 安装vue时候发现npm淘宝镜像不能使用&#xff0c;需要重新更换源&#xff0c;简单来说就是更换镜像 使用 npm config get registry 查看当前的镜像&#xff1b; npm config get registry 使用npm config set registry http://mirrors.cloud.tencent.com/npm/ &…

嵌入式实验---实验五 串口数据接收实验

一、实验目的 1、掌握STM32F103串口数据接收程序设计流程&#xff1b; 2、熟悉STM32固件库的基本使用。 二、实验原理 1、STM32F103R6能通过查询中断方式接收数据&#xff0c;每接收到一个字节&#xff0c;立即向对方发送一个相同内容的字节&#xff0c;并把该字节的十六进…

见证数据的视觉奇迹——DataV Atlas

引言 前段时间一直沉迷于AI方向&#xff0c;几乎很久没碰大数据开发的相关内容了&#xff0c;今天突然看到阿里活动又推出DataV的体验了&#xff0c;我直接“啪”的一下就点进来了&#xff0c;很快啊&#xff01;本来之前开发数字孪生的时候就接触过基础的DataV操作了&#x…

Monica

在 《long long ago》中&#xff0c;我论述了on是一个刚出生的孩子的脐带连接在其肚子g上的形象&#xff0c;脐带就是long的字母l和字母n&#xff0c;l表脐带很长&#xff0c;n表脐带曲转冗余和连接之性&#xff0c;on表一&#xff0c;是孩子刚诞生的意思&#xff0c;o是身体&a…

Redis 的安装与部署

本文为Redis的Linux版单机部署。 上传 redis-3.2.8 源码到 /opt/software/ 解压到 /opt/module/ [huweihadoop101 software]$ tar -zxvf redis-3.2.8.tar.gz -C /opt/module/安装依赖 [huweihadoop101 software]$ sudo yum -y install gcc-c tclRedis是C语言编写的 编译安装…

文件顺序读取--函数解析

fgetc和fputc 函数 fgetc和fputc是C语言中用于文件操作的函数&#xff0c;分别用于从文件中读取字符和向文件中写入字符。以下是这两个函数的详细原型和说明&#xff1a; fgetc函数原型 int fgetc(FILE *stream);参数说明&#xff1a; FILE *stream&#xff1a;一个指向FIL…

【Linux系列】find命令使用与用法详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

淘酒屋荣获2024中法贸易杰出服务商称号暨夏季窖主大会圆满召开

淘酒屋荣获中法贸易杰出服务商称号&#xff0c;暨闪光的创始人2024夏季窖主大会圆满召开 2024年&#xff0c;作为中法建交60周年的重要节点&#xff0c;同时迎来了中法文化旅游年&#xff0c;这为两国文化交流与合作开启了新的篇章。在庆祝中法贸易交流的重要时刻&#xff0c;…

Java——集合(一)

前言: Collection集合&#xff0c;List集合 文章目录 一、Collection 集合1.1 集合和数组的区别1.2 集合框架1.3 Collection 集合常用方法1.4 Collction 集合的遍历 二、List 集合2.1 List 概述2.2 List集合的五种遍历方式2.3 List集合的实现类 一、Collection 集合 1.1 集合和…