并发编程原理与实战(三十八)高并发利器ConcurrentHashMap 数据结构与核心API深度剖析

深入解析 OpenClaw(小龙虾)源码:架构设计与核心实现 OpenClaw用清晰、工程化的代码结构,将复杂的Agent交互流程变成了可管理、可扩展的流水线。无论是想深入了解Agent系统设计,还是希望二次开发定制自己的AI助理,阅读OpenClaw源码都是一次绝佳的学习旅程。希望本文能帮助你快速掌握OpenClaw的核心脉络。如果你对某个模块(如技能开发、安全机制)感兴趣,欢迎继续探讨。本文基于OpenClaw社区版本源码分析,具体实现可能随版本迭代有所调整,请以最新源码为准。 阅读详情

上一篇介绍了ConcurrentHashMap的核心特性,本文来分析ConcurrentHashMap的核心数据结构和API方法。

核心数据结构

ConcurrentHashMap的核心数据结构是:数组(Node[] table)+链表+红黑树的形式,而这核心数据结构主要由一个基础父类节点和四个子类节点构成。

基础节点Node

public class ConcurrentHashMap<K, V> {
    // 哈希表数组,存储Node节点
    transient volatile Node<K, V>[] table;

    // 内部类:Node节点,存储键值对及下一个节点的引用
    static class Node<K, V> implements Map.Entry<K, V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
    	// Node的构造函数等...
	}
}

Node节点是哈希表结构的基础存储单元,作为其他四种节点类型的父类。Node是普通的Entry结点,以链表形式存储实际数据。其定义包含以下关键字段:

final int hash:键的哈希值
final K key:不可变的键
volatile V val:使用volatile修饰的值,保证可见性
volatile Node<K,V> next:链表指针,同样使用volatile修饰

当发生哈希冲突时,新节点会以链表形式连接在桶位的头节点之后。

红黑树容器TreeBin和节点TreeNode

// 内部类:TreeBin红黑树节点容器
static final class TreeBin<K,V> extends Node<K,V> {
    TreeNode<K,V> root;//红黑树根节点
    volatile TreeNode<K,V> first;//双向链表头节点(first)
    volatile Thread waiter;
    volatile int lockState;
    ...
}

// 内部类:TreeNode节点,继承自Node,用于红黑树结构
static final class TreeNode<K, V> extends Node<K, V> {
    TreeNode<K, V> parent;  // 父节点
    TreeNode<K, V> left;    // 左子节点
    TreeNode<K, V> right;   // 右子节点
    boolean red;            // 节点颜色(红或黑)

    // TreeNode的构造函数及红黑树相关方法等...
}

TreeBin和TreeNode是ConcurrentHashMap中处理红黑树结构的关键组件,它们之间是容器与节点的关系。

TreeBin作为红黑树的容器节点,内部持有指向TreeNode双向链表头节点(first)和红黑树根节点(root)的引用。

TreeNode则代表红黑树中的单个节点,同样继承自Node类,但额外包含红黑树所需的parent、left、right等指针以及颜色标记red。

这种设计实现了TreeBin对红黑树的结构管理和并发控制,而TreeNode专注于存储数据和维护树结构。

/**
 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2, and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 */
static final int TREEIFY_THRESHOLD = 8;

将链表结构转换为红黑树结构的桶节点数阈值。当向一个桶中添加元素时,如果该桶中的节点数达到或超过此值,就会将链表转换为红黑树。此值必须大于2,并且为了与树结构在收缩时转换回普通桶的假设相协调,该值至少应为8。此时桶位中存储的是TreeBin对象而非TreeNode对象。

扩容节点ForwardingNode

// 内部类:ForwardingNode节点,用于在扩容时转发请求到新的table
static final class ForwardingNode<K, V> extends Node<K, V> {
    final Node<K, V>[] nextTable; // 指向新的哈希表数组
    // ForwardingNode的构造函数等...
}

在ConcurrentHashMap的扩容过程中,ForwardingNode节点发挥着关键的协调和转发作用。当某个哈希桶内的所有节点都完成数据迁移到新数组(nextTable)后,就会在旧数组的对应桶位放入ForwardingNode节点作为标记。

这个节点本身不存储实际的数据键值对,而是作为一个转发器。当其他线程在查询时遇到ForwardingNode,查询操作会被引导到扩容后的新数组上进行。如果有线程执行put操作时发现当前桶位已是ForwardingNode,该线程会调用helpTransfer()方法协助进行扩容操作,待扩容完成后再将新节点放入新数组的对应位置。作为内部机制的一部分来协调并发操作。

临时节点ReservationNode

// 内部类:computeIfAbsent and compute时使用
static final class ReservationNode<K,V> extends Node<K,V> {
    ReservationNode() {
        super(RESERVED, null, null);
    }

    Node<K,V> find(int h, Object k) {
        return null;
    }
}

ReservationNode是ConcurrentHashMap中的一种特殊占位符节点,主要在compute()和computeIfAbsent()方法中使用。该节点在正式赋值之前起到暂时占位的作用,和TreeBin、ForwardingNode节点一样,不存储实际的数据键值对。

当执行compute或computeIfAbsent操作时,如果目标桶位为空,ConcurrentHashMap会先创建一个ReservationNode作为临时占位符。这个机制确保了在复杂的函数式计算过程中,其他线程不会同时修改同一个桶位,从而保证了操作的原子性和线程安全性。在计算完成后,这个ReservationNode占位符会被替换为包含实际数据的普通Node节点。作为内部机制的一部分来协调并发操作。

核心方法‌

ConcurrentHashMap的节点类型构成了其并发架构的基础,核心API方法通过这些精心设计的节点体系实现高效的并发控制,主要包括基础数据操作、状态查询和特殊功能三大类控制。

核心操作方法

put() / putVal()方法‌

这是最核心的插入方法,采用CAS+synchronized实现线程安全。当目标桶位为空时,使用CAS操作直接插入新节点;当桶位不为空时,对桶位的头节点加synchronized锁,然后在链表或红黑树中执行插入操作。如果插入后链表长度达到TREEIFY_THRESHOLD(默认8)且数组容量≥64,链表会转换为红黑树结构。

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 参数校验:ConcurrentHashMap不允许key或value为null
    if (key == null || value == null) throw new NullPointerException();
    
    // 计算key的哈希值,通过spread方法分散哈希碰撞
    int hash = spread(key.hashCode());
    int binCount = 0;  // 记录链表长度或树节点数
    
    // 无限循环,确保在各种情况下都能完成插入操作
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        
        // 情况1:哈希表未初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();  // 初始化哈希表
        
        // 情况2:目标桶位为空
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 使用CAS无锁操作插入新节点
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;  // 插入成功则退出循环
        
        // 情况3:当前桶位正在扩容迁移
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);  // 协助扩容
        
        // 情况4:检查首节点是否匹配且onlyIfAbsent为true
        else if (onlyIfAbsent // 不加锁检查首节点
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                 && (fv = f.val) != null)
            return fv;  // 直接返回现有值,不进行插入
        
        // 情况5:需要处理哈希冲突
        else {
            V oldVal = null;
            // 对桶位头节点加锁,保证线程安全
            synchronized (f) {
                // 双重检查:确认加锁后头节点未被修改
                if (tabAt(tab, i) == f) {
                    
                    // 子情况5.1:处理链表结构
                    if (fh >= 0) {
                        binCount = 1;  // 链表节点计数从1开始
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 查找是否已存在相同key的节点
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;  // 保存旧值
                                if (!onlyIfAbsent)
                                    e.val = value;  // 覆盖现有值
                                break;
                            }
                            Node<K,V> pred = e;
                            // 到达链表尾部,插入新节点
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value);
                                break;
                            }
                        }
                    }
                    
                    // 子情况5.2:处理红黑树结构
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;  // 树节点计数设为2
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;  // 更新树节点值
                        }
                    }
                    
                    // 子情况5.3:处理ReservationNode(递归更新检测)
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            
            // 后处理:检查是否需要树化或返回旧值
            if (binCount != 0) {
                // 链表长度达到阈值时进行树化
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);  // 链表转红黑树
                // 如果覆盖了现有值,返回旧值
                if (oldVal != null)
                    return oldVal;
                break;  // 插入成功,退出循环
            }
        }
    }
    
    // 更新元素计数,采用分片计数减少竞争
    addCount(1L, binCount);
    return null;  // 新插入节点返回null
}

CAS插入节点方法

static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    return U.compareAndSetReference(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

第一个参数表示需要进行原子更新的哈希表对象。

第二个参数表示目标字段在对象中的内存偏移量。

第三个参数表示期望的旧值,传入的是null。

第四个参数表示要设置的新值,也就是新插入的节点。

方法执行流程总结
1.参数校验‌:确保key和value都不为null
2.哈希计算‌:通过spread方法优化哈希分布
3.表初始化‌:延迟初始化哈希表数组
4.空桶处理‌:使用CAS无锁插入
5.扩容协助‌:发现正在扩容时协助迁移
6.快速检查‌:在onlyIfAbsent模式下不加锁检查首节点
7.哈希冲突处理‌:对头节点加锁后处理链表或树
8.数据结构转换‌:必要时进行链表到红黑树的转换
9.计数更新‌:采用分片计数机制

get()方法‌

get方法通过volatile读实现无锁查询。首先根据key的hash值定位到数组索引,然后遍历该桶位的链表或红黑树查找匹配的节点。由于Node的val和next字段都使用volatile修饰,保证了多线程环境下的内存可见性。

public V get(Object key) {
    // 定义局部变量:tab-哈希表数组,e-当前节点,p-临时节点,n-数组长度,eh-节点哈希值,ek-节点键值
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    
    // 计算key的哈希值,通过spread方法分散哈希冲突
    int h = spread(key.hashCode());
    
    // 步骤1:基础条件检查
    // 检查哈希表已初始化、数组长度有效,并且目标桶位不为空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        
        // 步骤2:检查首节点是否匹配
        // 比较哈希值,如果哈希值匹配再比较key
        if ((eh = e.hash) == h) {
            // 通过引用相等或equals方法比较key
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;  // 匹配成功,返回节点值
        }
        
        // 步骤3:处理特殊节点(哈希值为负)
        // 当eh < 0时,表示当前节点是特殊节点:TreeBin(哈希值-2)、ForwardingNode(哈希值-1)或ReservationNode(哈希值-3)
        else if (eh < 0)
            // 调用对应节点的find方法进行查找
            // - TreeBin:在红黑树中查找
            // - ForwardingNode:转发到新表中查找
            // - ReservationNode:返回null
            return (p = e.find(h, key)) != null ? p.val : null;
        
        // 步骤4:遍历链表查找
        // 如果首节点不匹配且不是特殊节点,则遍历链表
        while ((e = e.next) != null) {
            // 对每个节点进行哈希值和key的匹配检查
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;  // 找到匹配节点,返回值
        }
    }
    
    // 步骤5:未找到匹配的key,返回null
    return null;
}

方法执行流程总结

1.哈希计算阶段

2.首节点快速检查(这种设计优化了最常见的命中情况)

3.特殊节点处理

4.链表遍历阶段

remove()方法‌

删除操作同样采用synchronized锁住桶位头节点的方式保证线程安全。在删除节点后,如果树结构的节点数减少到UNTREEIFY_THRESHOLD(默认6)以下,红黑树会退化为链表。

状态查询方法

size() / mappingCount()方法‌

size()方法返回int类型的元素个数,而mappingCount()返回long类型,更推荐使用后者以避免整数溢出。

isEmpty()方法‌

通过遍历数组检查是否存在非空桶位来实现,由于不需要精确计数,性能比size()方法更高。

原子更新方法

compute() / computeIfAbsent() / computeIfPresent()方法‌

这些方法支持原子性的函数式更新。在执行过程中,如果目标桶位为空,会先创建ReservationNode作为占位符,待计算完成后替换为实际节点。这种机制确保了在复杂的函数式计算过程中,其他线程不会同时修改同一个桶位。

merge()方法‌

合并操作也采用类似的并发控制机制,当原值不存在时直接插入新值,存在时通过BiFunction合并原值和新值。

扩容相关方法

transfer()方法‌

负责数据迁移的核心扩容方法,采用多线程并发迁移策略。每个线程负责迁移数组的一个分片,迁移完成后在旧桶位中放入ForwardingNode节点标记该桶位已迁移完成。

helpTransfer()方法‌

当其他线程在操作过程中发现当前桶位是ForwardingNode时,会调用此方法协助进行数据迁移。

三种Map的并发性能对比

下面通过一个例子来对比ConcurrentHashMap、HashMap、HashTable的并发性能,这个例子采用10个线程并发往map中添加数据,每个线程添加10万次。观察三种Map的并发添加耗时。


public class MapConcurrentTest {
    private static final int THREAD_COUNT = 10;
    private static final int OPERATION_COUNT = 100000;

    public static void main(String[] args) throws InterruptedException {
        // 测试三种Map的并发性能
        testHashMap();
        testHashTable();
        testConcurrentHashMap();
    }

    // 测试HashMap(非线程安全)
    public static void testHashMap() throws InterruptedException {
        Map<String, Integer> map = new HashMap<>();
        System.out.println("=== HashMap 测试 ===");
        long startTime = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    String key = "key-" + threadId + "-" + j;
                    map.put(key, j);
                }
            });
            threads[i].start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("HashMap 最终大小: " + map.size() +
                " (期望: " + (THREAD_COUNT * OPERATION_COUNT) + ")");
        System.out.println("HashMap 耗时: " + (endTime - startTime) + "ms");
        System.out.println();
    }

    // 测试HashTable(线程安全但性能较低)
    public static void testHashTable() throws InterruptedException {
        Map<String, Integer> map = new Hashtable<>();
        System.out.println("=== HashTable 测试 ===");
        long startTime = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    String key = "key-" + threadId + "-" + j;
                    map.put(key, j);
                }
            });
            threads[i].start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("HashTable 最终大小: " + map.size() +
                " (期望: " + (THREAD_COUNT * OPERATION_COUNT) + ")");
        System.out.println("HashTable 耗时: " + (endTime - startTime) + "ms");
        System.out.println();
    }

    // 测试ConcurrentHashMap(高性能线程安全)
    public static void testConcurrentHashMap() throws InterruptedException {
        Map<String, Integer> map = new ConcurrentHashMap<>();
        System.out.println("=== ConcurrentHashMap 测试 ===");
        long startTime = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            final int threadId = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    String key = "key-" + threadId + "-" + j;
                    map.put(key, j);
                }
            });
            threads[i].start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("ConcurrentHashMap 最终大小: " + map.size() +
                " (期望: " + (THREAD_COUNT * OPERATION_COUNT) + ")");
        System.out.println("ConcurrentHashMap 耗时: " + (endTime - startTime) + "ms");
        System.out.println();
    }
}

运行结果:

=== HashMap 测试 ===
HashMap 最终大小: 768182 (期望: 1000000)
HashMap 耗时: 277ms

=== HashTable 测试 ===
HashTable 最终大小: 1000000 (期望: 1000000)
HashTable 耗时: 385ms

=== ConcurrentHashMap 测试 ===
ConcurrentHashMap 最终大小: 1000000 (期望: 1000000)
ConcurrentHashMap 耗时: 323ms

从运行结果可以看出,HashMap的最终大小小于期望值,可能出现数据丢失,这证实了其非线程安全的特性。HashTable的运行结果数据一致性得到保证,但执行时间明显长于其他两种实现,这反映了其全局锁机制带来的性能开销。ConcurrentHashMap的运行结果数据一致性得到保证,性能优于HashTable。

华为OD机考双机位C卷 - 手机App防沉迷系统(Java & Python& JS & C/C++ & GO ) 华为OD机考双机位C卷 - 手机App防沉迷系统(OD上机考试双机位C卷)。提供在线OJ刷题。Java、C++、Python、GO语言、C语言、JavaScript题解,并提供完整思路讲解。 阅读详情

相关推荐

iPhone用户必看:不装App也能搞定GitHub 2FA验证的3种方法(附恢复码管理技巧)

本文为iPhone用户提供了无需安装第三方App即可完成GitHub双重身份验证(2FA)的三种原生方案:利用相机扫码与iCloud钥匙串自动填充、使用iCloud钥匙串内置的TOTP验证码生成器,以及借助Safari浏览器扩展。文章详细解析了每种方法的操作步骤、适用场景,并重点强调了恢复码的安全管理与多层备份策略,帮助开发者在提升账户安全的同时,享受苹果生态的无缝便捷体验。

weixin_29324401的博客 653

【数据结构】基于比较的排序算法 - 交换排序

本文介绍交换排序中的冒泡排序与快速排序。冒泡排序通过相邻元素比较交换,实现升序,时间复杂度最坏为O(N²),空间复杂度O(1),稳定且适合小规模有序数据。快速排序采用分治思想,以基准值划分区间,平均时间复杂度O(N log N),空间复杂度O(log N),但不稳定,实际应用中性能优异,可通过随机基准、小区间优化等提升效率。

careathers的博客 327

避坑指南:华为2288H-V5服务器安装系统时常见的RAID与驱动问题解决方案

本文针对华为2288H-V5服务器在安装系统时常见的RAID与驱动问题,提供了详细的避坑指南。文章深度解析了从BIOS版本兼容性核查、RAID配置实战到驱动加载与集成的全流程,重点解决了安装过程中因RAID控制器驱动缺失或兼容性问题导致的硬盘无法识别等典型故障,帮助用户高效完成系统部署。

weixin_29240711的博客 976

pandas 深度入门:从数据结构到一份真实销售报表

本文系统讲解Pandas核心应用,从三分钟安装到完整月度报表生成,涵盖数据读取、清洗、筛选、分组聚合、表连接、透视表与时间序列等13个关键环节。强调“先看数据再动手”,掌握Series/DataFrame/Index结构本质,熟练运用loc/iloc、merge校验、agg/transform区别,通过真实案例实现高效报表自动化,告别手动Excel操作。最后提供性能优化与高频报错解决方案,助力读者快速上手,真正实现“写二十行代码,跑三秒出结果”。

信息安全领域优质创作者|操作系统领域专家 264

如何在离线的电脑上部署一个本地Deepseek(使用ollama)

例如,如果您的显卡显存为 16GB,官方推荐使用 32b 大小的 DeepSeek 模型。请根据您的硬件配置选择适合的模型版本,以获得最佳性能。首先我们需要将ollama和chatbox的安装包,Deepseek模型文件通过U盘或者光盘的形式导入到这台不能联网的电脑上。按照时间排序,找到最大的那个文件,就是gguf格式的模型,复制出来,改名为qwen2.5-coder0.5b.gguf。:基于开源模型(如 QWEN 和 LLAMA)微调,参数量从 1.5B 到 70B 不等,适合本地硬件部署。

qq_41854911的博客 1万+

【QMIX】一种基于Value-Based多智能体算法

文章目录1. QMIX 解决了什么问题(Motivation)2. QMIX 怎样解决团队收益最大化问题(Method)2.1 算法大框架 —— 基于 AC 框架的 CTDE(Centralized Training Distributed Execution) 模式 QMIX 是一种基于 Value-Based 的多智能体强化学习算法(MARL),其基本思想来源于 Actor-Critic 与 DQN 的结合。使用中心式学习(Centralized Learning)分布式执行(Distributed

qq_38638132的博客 9859

工单系统的数据结构化:故障分类体系设计与报表可用性

很多团队上完工单系统半年,会发现一个尴尬的事实:单子照常在走,报表却始终出不来。导出的 Excel 几千行,看不出趋势、算不出口径、答不了"哪类故障占大头"。问题几乎都不在系统,而在数据模型的第一层——。这篇文章把这件事拆开讲:自由文本为什么会失效、分类体系怎么分层、表怎么建、老数据怎么迁、上线前怎么验收。

ITyunwei0987的博客 278

Image Sensor常见分辨率下HTS,VTS设置

image sensor,HTS,VTS

propor的专栏 4974

数据结构:9.Map和Set

有的讲解会说这里key是标识作用,这种说法其实没有太大意义,就算知道了他是标识value的作用又如何,在实际的使用过程中,我们只会根据具体的效果选择使用的方法等,不会执着于“标识”这种说法。2.由于哈希函数的设计原则太过理论,它仅仅是在认为:这些通过关键码算出来的index不同 的情况下设计的,然而这种理论化的设计原则在遇到冲突时,恰恰就出现了问题,而我们能做的,就是降低冲突率。虽然哈希表的使用过程中存在冲突,但是在实际的使用过程中,哈希表的冲突率是不高的,冲突的个数是可控的。

2602_95646088的博客 191

YOLOv11垃圾检测实战:从零搭建带GUI的智能分类系统(附完整数据集)

本文详细介绍了如何从零开始构建一个基于YOLOv11的智能垃圾检测系统,并集成图形化界面。内容涵盖数据集构建与增强、模型选型与轻量化改进(如引入注意力机制),以及使用PyQt5开发稳定GUI的完整流程,旨在解决实际部署中精度与速度的平衡问题,为环保科技等应用场景提供可落地的解决方案。

646

ANSYS_APDL实战入门——从零开始掌握模态分析

本文为ANSYS APDL模态分析实战入门指南,通过一个悬臂梁案例,手把手教你从零开始掌握APDL命令流。内容涵盖从模型创建、材料定义、网格划分到边界条件设置、求解及后处理结果查看的全流程,帮助工程师快速上手结构动力学分析,避免共振风险。

egg99的博客 1129

[C++高阶数据结构·图论下] 最短路径:为什么“挑最小的”一定正确?

本文系统对比最小生成树与最短路径的核心差异,聚焦三类最短路算法:Dijkstra(非负权、贪心确认)、Bellman-Ford(可负权、反复松弛)、Floyd(全源、分层中转)。核心区别在于:MST 关注“连通所有点”的最小代价(边数固定),而最短路关注“两点间路径最短”(边数可变)。关键反直觉点是:负权对 MST 无害,却使 Dijkstra 失效——因“绕远更近”破坏贪心前提。算法本质反映三种处理不确定性的策略:确认式(Dijkstra)、迭代修正(Bellman-Ford)、分层逼近(Floyd)。强

Tairitsu_青云 510

Python 爬虫实战:在房天下抓取房产信息,分析房价走势

通过本次 Python 爬虫实战,我们成功地从房天下爬取了房产信息,并进行了存储、处理、分析和可视化展示。在这个过程中,我们不仅掌握了爬虫技术的基本应用,还学会了如何对动态加载的数据进行处理和分析。然而,本次爬虫任务也存在一些局限性,例如仅爬取了部分区域的房产数据,且未对房产的图片和视频内容进行分析。在未来的工作中,我们可以进一步优化爬虫程序,爬取更多区域的房产数据,并深入分析房产的多媒体内容、用户行为模式等,为购房者和房地产从业者提供更有价值的数据支持。

u014481728的博客 1366

[C++高阶数据结构·图论上] 最小生成树:为什么“选边”和“选点”是两条路?

本文系统讲解最小生成树(MST)的核心思想与两大算法:Kruskal与Prim。强调MST目标是用最小权值总和连接所有顶点形成一棵树,而非单边最小。通过三条构造准则(用图中边、恰n-1条边、无环)统一框架。Kruskal按边贪心,依赖并查集判环,适合稀疏图;Prim按点扩张,利用割集特性天然避免成环,需起点,适合稠密图。两算法本质差异在于“判环”机制与数据结构选择,且与最短路径问题在连通性、负权处理上截然相反,需分篇厘清。

Tairitsu_青云 219

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Vue3 低代码移动表单设计器实战(二):缩放画布下的拖拽系统与树状级联数据结构

本文深入剖析Vue3低代码表单设计器在缩放画布下的拖拽失效问题,揭示SortableJS在CSS Transform下坐标崩溃的数学根源。通过自研独立视觉克隆壳层与逆向几何补偿算法,实现精准拖拽;采用扁平数据存储+运行时树状计算结构,解决级联依赖与深克隆难题,确保逻辑一致性与性能优化,为复杂低代码系统提供可复用的工程范式。

NanChen的博客 337

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

排序算法深度笔记(上):比较类排序的思想演进与代码实现

与。本文聚焦前者 —— 也就是通过元素间两两比较来确定相对顺序的排序算法,从最朴素的 O (n²) 基础排序,到工业级主流的 O (nlogn) 进阶排序,逐层拆解设计思想、带注释的代码实现与边界细节。当我们理解了比较排序的性能边界,也就自然引出了非比较排序的存在意义。所有代码均遵循统一的实现规范:入口函数前置空数组与单元素边界校验,基于数组原地修改,主函数附带可直接运行的测试用例,与练习代码风格完全对齐。

2401_87069538的博客 606

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

数据结构:双向链表实现,链表与顺序表的对比

本节讲解数据结构双向链表,链表和顺序表的特点对比,顺便解释了一下什么是CPU缓存和内存碎片

lei_jiale的博客 369

【数据结构】树和二叉树(下):讲透四种遍历、递归返回值与经典题

🔥 星光编译者📚 学习专栏:《C/C++ 成长笔记》《Linux 实践手册》《数据结构与算法》🌄 向云端飞扬,编译属于自己的代码星河。☕ 写在开篇你好,这里是星光编译者。这里记录我在C/C++、Linux、数据结构与算法学习中遇到的真实问题、亲手验证过的代码,以及那些容易被忽略的实现细节。比起简单罗列结论,我更愿意从问题出发,把一个知识点的来由讲清楚,把“为什么会这样”和“应该怎样解决”说明白,让每一次踩坑都沉淀成可以复用的经验。如果这篇记录能帮你少绕一点路。

星光编译者的技术博客,专注 C/C++、Linux、数据结构与算法。 391

【数据结构-3】算法美学之降复杂度

在while循环的循环体内,首先用临时变量保存最后一个位置的数据,再通过for循环把所有元素往后移动一位,最后把tmp中保存的值放到下标为0的位置上去。方法二的思路是:创建新数组,让原数组中后k个数据移动到新数组中前面的位置,让原数组中前numsSize-k个数据移动到新数组中后面的位置,这。这段代码的思路是:三次逆置,第一次逆置前numsSize-k个数据,第二次逆置后k个数据,最后整体逆置。这篇博文分享一道解法不一的算法题,求解过程的目的在于降低算法的时间复杂度,题源力扣。,属于空间换时间的操作。

weixin_44218929的博客 80

【数据结构-2】顺序表的实现

线性表是一种数据结构,它分为逻辑结构和物理结构,在逻辑结构上线性表是呈线性的,数据是是连续存储的;在物理结构(物理空间)上,线性表不一定是连续存储的。线性表的实现,主要分为数组和链式结构。顺序表是线性表的一种实现方式,顺序表在逻辑结构和物理结构上都是连续的。顺序表的底层实现是数组。

weixin_44218929的博客 207

数据结构(2)

不同点顺序表链表(单链表)存储空间上物理上一定连续逻辑上连续,但物理上不一定连续随机访问支持:O(1)不支持:O(N)任意位置插入或删除元素可能需要搬移元素,效率低 O(N)只需修改指针指向插入动态顺序表,空间不够时需要扩容和空间浪费没有容量的概念,按需申请释放,不存在空间浪费应用场景元素高效存储 + 频繁访问任意位置高效插入和删除。

likexuec的博客 147

数据结构—B树

一棵m(m>2)阶的B树,是一棵平衡的M路平衡树,可以说空树或满足以下6点。1.根节点至少有两个孩子2.每个分支节点都包含k-1个关键字和k个孩子,ceil(m/2)<=k<=m;孩子比关键字多一个ceil向上取整3.每个叶子节点都包含k-1个关键字,ceil(m/2)<=k<=m;4.所有的叶子节点在同一层。5.每个节点中的关键字从小到大排列,节点当中k-1个元素正好是k个孩子包含元素的值域划分。6.节点的结构是{n,A0,K1,A1,K2,........Kn,An};

fish_xk的博客 128

数据结构:顺序表

顺序表是用一段地址连续的存储单元依次存储线性表的数据元素的线性结构。它是线性表的顺序存储实现,在C语言中通常用数组来实现。

2502_92937908的博客 339

LangGraph 的 State 深入解析:从数据结构到工程实践

LangGraph 支持多种方式定义 State 的 Schema,各有适用场景。维度关键点数据结构TypedDict / Pydantic / dataclass 三种定义方式更新语义增量返回,无 Reducer 覆盖,有 Reducer 合并合并机制Reducer 决定并行与多次写入的结果边界设计输入/内部/输出 Schema 分离持久化Checkpointer 支持续跑、人工介入、时间旅行。

张晨光老师的播客 302

数据结构:单链表

单链表是一种链式存储的线性表。它由一系列节点数据域:存放数据元素指针域:存放指向下一个节点的指针内存不连续,节点分散存储插入删除效率高(O(1),前提是已定位到位置)查找效率低(O(n)),不支持随机访问相比数组,多消耗指针域空间// 定义节点结构体int data;// 数据域// 指针域,指向下一个节点} Node;// 或者使用 typedef 简化int data;// LinkList 是指向 Node 的指针类型中必须写,因为此时 typedef 尚未生效,不能写Node。

2502_92937908的博客 475

Python 开发的实践:从数据结构选型到框架决策

规模多大?决定用list还是set,Pandas 还是 Polars。瓶颈在哪?I/O 密集用异步,CPU 密集用进程/自由线程,内存受限用列式与惰性执行。代价是什么?消除手动维护窗口的出错面,TaskGroup消除失败任务不被取消的泄漏,消除 N+1,Protocol消除为类型约束而引入的继承层级——每个选择都在消除某类代价,同时也引入新的约束。能回答这三个问题,比记住更多 API 更有用。愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!

weixin_45809780的博客 367
上一篇: 并发编程原理与实战(三十七)高并发利器ConcurrentHashMap核心特性解析
下一篇: 开发避坑指南(70):Vue3 Http请求头携带token下载pdf文件解决方案
帧栈
博客等级 码龄2年 335粉丝 120原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帧栈

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值