`
zhangwei_david
  • 浏览: 480445 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

ConcurrentHashMap 源码分析

    博客分类:
  • Java
阅读更多

ConcurrentHashMap在Java8中的实现改动较大,网上关于ConcurrentHashMap的文章也很少有基于java8的,将个人的一些理解记录下来以供分享。

Node

ConcurrentHashMap底层是通过数组+链表(树)来实现的,数组中存储的就是Node。它与HashMap中的定义很相似,但是有一些差别它对value和next属性设置了volatile同步锁,它不允许调用setValue方法直接改变Node的value域,它增加了find方法辅助map.get()方法。

transient volatile Node<K,V>[] table;

  static class Node<K,V> implements Map.Entry<K,V> {
       //当前节点的hash值
        final int hash;
       //当前节点的Key
        final K key;
       //当前节点的值,保证了可见性
        volatile V val;
       //下一个节点
        volatile Node<K,V> next;
...

TreeNode

树节点类,另外一个核心的数据结构。 当链表长度过长的时候,会转换为TreeNode。 但是与HashMap不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。 而且TreeNode在ConcurrentHashMap继承自Node类,而并非HashMap中的集成自LinkedHashMap.Entry

TreeBin

这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。它代替了TreeNode的根节点,也就是说在实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象,这是与HashMap的区别。另外这个类还带有了读写锁。 这里仅贴出它的构造方法。可以看到在构造TreeBin节点时,仅仅指定了它的hash值为TREEBIN常量,这也就是个标识位

ForwardingNode

一个用于连接两个table的节点类。它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1. 这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。通过名称也很容易理解这个节点的含义这个节点主要作用就是重定向,在resize过程中尽可能不影响对数据对读取。

关键方法

put方法

ConcurrentHashMap中key可为null吗?value可以为null吗?为什么呢?HashMap又是什么情况呢? 通过下面的代码可以发现,ConcurrentHashMap中的key和value都不能为null,这是因为在Node中计算hash的时候使用的是key.hashCode() ^ val.hashCode();而在HashMap中是Objects.hashCode(key) ^ Objects.hashCode(value);

主要的处理步骤有:

  • 计算key的hash值
  • 如果没有初始化需要进行初始化
  • 通过按位与进行快速取模计算出桶位置,如果该位置没有元素则通过CAS插入,如果该节点是FWD节点,则帮助完成扩容
  • 如果有元素,也不是FWD节点则对该节点进行加锁后插入元素
  • 插入元素后,进行元素计数加1,在该步操作中可能会出发扩容操作
final V putVal(K key, V value, boolean onlyIfAbsent) {
      //concurrentHashMap要求key,value都不能为null.为什么呢?这是因为 Node中计算hash的方法是key.hashCode() ^ val.hashCode();
     //如果key或者value为null在此处计算hash值的时候会出现NPE
        if (key == null || value == null) throw new NullPointerException();
        //计算key的hash值(两次hash计算,通过掩码运算得到内部hash值)
        int hash = spread(key.hashCode());
        int binCount = 0;
        //死循环直到操作成功
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //此处如果tab=null,或者tab.length-0表示表没有初始化,此处需要完成初始化!
            //这个就是ConcurrentHashMap将初始化延迟到第一次put操作时候完成
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
           //i=(n-1)&hash这个计算和HashMap相同,这个是对hash值对快速取模操作,i的值即表示该数据存放的桶号
            //如果在i位置的桶中没有元素则通过CAS操作试图将元素插入到该位置,如果插入成功则退出循环!在向空桶中添加元素时是无锁操作
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            //如果这个桶的节点的hash值是MOVED则表示该节点在进行扩容操作
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
               //对正常的桶通过synchronized进行加锁,synchronized锁已经进行了很大性能优化
                V oldVal = null;
                synchronized (f) {
                    //重复检查当前桶是否有发生改变,如果没有发生改变才做后续处理
                    if (tabAt(tab, i) == f) {
                         //fn是这个桶的hash值,大于等于0表示链表节点
                        if (fh >= 0) {
                            binCount = 1;
                            //向后遍历,如果存在重复的key则更新value否则插入到链表尾部跳出循环
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                   //put操作时候onlyIfAbsent为false,所以此处会进行值更新
                                    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, null);
                                    break;
                                }
                            }
                        }
                        //如果是树
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            //插入到红黑树中,并更新节点的值
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    //如果链表的长度大于等于8则需要将链表转换为树,可以发现首先是将数据插入到链表中再判断是否需要进行转换为树
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //计数增加1,该操作中可能会出发扩容操作
        addCount(1L, binCount);
        return null;
    }
private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        //BASECOUNT 为baseCount属性的偏移量,如果原值是baseCount则更新为b+x;
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            CounterCell a; long v; int m;
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            s = sumCount();
        }
        //counterCells为null,check>0 ,s=b+x, 在put方法中x=1,b=baseCount,在resize之前
        //sizeCtl=16*0.75=12
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                int rs = resizeStamp(n);
               //sc小于0表示 sc绝对值-1个线程在进行扩容,sc=0表示还没有初始化,此处不可能为0,如果大于0表示需要下次
               //扩容的阈值
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                //对sizeCtl更新如果成功就扩容,否则计算大小继续循环
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        //n为数组大小
        int n = tab.length, stride;
        //NCPU表示机器的CPU核心数,如NCPU=4,第一次n=16,stride=16;否则stride=n>>>3/ncpu
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
       //如果nextTab为空,表示尚未进行扩容处理
        if (nextTab == null) {            // initiating
            try {
                //创建长度为原来两倍的数组,并赋值给nextTab
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            //给nextTable赋值
            nextTable = nextTab;
            //transferIndex时扩容前数组的长度
            transferIndex = n;
        }
        //获取扩容后的数组长度
        int nextn = nextTab.length;
        //创建forwarding节点
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
         //死循环
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
             //只要advance=true就一直循环下去
            while (advance) {
                int nextIndex, nextBound;

                if (--i >= bound || finishing)
                    advance = false;
               //如果transferIndex<=0则退出while循环
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            //第一次 i=nextIndex-1,nextIndex=transferIndex=n=tab.length,即i是原有数组长度减去1
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            //如果数组i位置为空则直接fwd节点插入
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
           //如果节点的哈希值为MOVED表示已经处理过了,则将advance设置为true,处理其他的桶,这样有效避免了多线程重复处理过程
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
               //否则对该节点进行加锁
                synchronized (f) {
                    //加锁后再次判断i位置是否是加锁的对象
                    if (tabAt(tab, i) == f) {
                         //ln表示低位节点,hn表示高位节点,在经过一次扩容后,根据桶号计算 h&(n-1)可以知道
                         //该节点要不在原位置,要不就向后移动扩容大小位,ln就是位置不变的元素,hn就是移动扩容
                         //大小的元素
                        Node<K,V> ln, hn;
                        //fh节点的hash值,大于0标志正常链表节点
                        if (fh >= 0) {
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            //从f节点的下一个节点开始遍历
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            //从f节点开始遍历非最后一个节点,计算出高位节点和低位节点
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            //i是tab中元素的索引,n是tab的长度
                            //在nextTab中i节点中设置ln
                            setTabAt(nextTab, i, ln);
                            //在i+n位置上插入hn
                            setTabAt(nextTab, i + n, hn);
                            //将原有i位置设置为forwarding节点
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

 

0
0
分享到:
评论

相关推荐

    MATLAB实现能源集线器参与的电热综合能源市场双层出清模型

    内容概要:本文详细介绍了利用MATLAB和CPLEX求解器实现的电热综合能源市场双层出清模型。该模型分为上下两层,上层旨在最大化能源集线器的收益,下层则致力于最小化电力和热力市场的生产和出清成本。文中不仅提供了详细的代码示例,还解释了各个模块的功能及其背后的理论依据,如MPEC(数学规划与均衡约束)、KKT条件的应用等。此外,文章强调了代码的模块化设计和良好的注释,使得新手能够轻松理解和修改代码。最终,通过与参考文献的对比,证明了模型的有效性和准确性。 适用人群:适用于对综合能源系统优化感兴趣的初学者和有一定经验的研究人员。 使用场景及目标:①帮助研究人员理解电热综合能源市场的运作机制;②为开发更复杂的能源市场模型提供基础;③通过实际案例和数据验证模型的正确性和实用性。 其他说明:文章还提到了一些高级应用和技术细节,如储能参数调整对市场策略的影响、极端天气条件下不同能源设备的表现差异等。

    基于MATLAB的电动汽车多目标优化调度策略实现削峰填谷

    内容概要:本文详细介绍了利用MATLAB实现电动汽车多目标优化调度策略,旨在通过智能充放电管理实现电网的削峰填谷。具体来说,该策略将经济成本、峰谷差和负荷波动三个目标融合为一个综合优化目标,并通过严格的约束条件确保电池安全运行。文中展示了如何使用YALMIP和CPLEX求解器对大规模电动汽车群进行快速有效的调度,最终显著改善了电网负荷曲线并降低了电池损耗成本。 适合人群:从事电力系统优化、电动汽车调度研究的专业人士,以及对智能电网感兴趣的科研人员和技术开发者。 使用场景及目标:适用于需要优化电网负荷曲线、减少峰谷差、稳定负荷波动的实际应用场景。主要目标是在不影响用户体验的前提下,最大化电网效率和经济效益。 其他说明:文中提供了详细的代码片段和参数选择依据,帮助读者理解和复现实验结果。此外,还讨论了一些调参技巧和潜在改进方向,如引入光伏预测等。

    三菱R系列PLC多工位转盘机的面向对象编程与模块化架构设计

    内容概要:本文详细介绍了基于三菱R系列PLC的多工位转盘机项目的编程实践。作者摒弃传统的梯形图编程方式,采用ST语言进行面向对象编程,构建了一个类似工业级框架的模板程序。主要内容包括:使用结构体进行参数传递,确保参数管理和调试的便捷性;通过功能块(FB)封装工位控制逻辑,实现模块化和复用;采用层次化的程序架构,使主程序简洁高效;设计完善的异常处理机制,提高系统的稳定性和维护性。此外,文章还展示了如何快速扩展新工位以及热替换功能的优势。 适合人群:具备PLC编程基础,尤其是熟悉三菱PLC的工程师和技术人员。 使用场景及目标:适用于需要优化PLC编程流程、提升代码可读性和维护性的工程项目。目标是通过面向对象编程思想,实现PLC程序的模块化、标准化和高效化。 其他说明:文中提供了多个具体的代码示例,帮助读者更好地理解和应用所介绍的技术和方法。同时,强调了良好的架构设计对于提高开发效率和应对需求变更的重要性。

    这篇文章详细介绍了基于自适应可重构架构神经网络(RAWA-NN)的电磁场(EMF)暴露预测框架,旨在解决6G通信中20-100GHz频段的电磁场暴露评估问题(含详细代码及解释)

    内容概要:该论文聚焦于6G通信中20-100GHz频段的电磁场(EMF)暴露评估问题,提出了一种基于自适应可重构架构神经网络(RAWA-NN)的预测框架。该框架通过集成权重分析模块和优化模块,能够自动优化网络超参数,显著减少训练时间。模型使用70%的前臂数据进行训练,其余数据用于测试,并用腹部和股四头肌数据验证模型泛化能力。结果显示,该模型在不同参数下的相对差异(RD)在前臂低于2.6%,其他身体部位低于9.5%,可有效预测皮肤表面的温升和吸收功率密度(APD)。此外,论文还提供了详细的代码实现,涵盖数据预处理、权重分析模块、自适应优化模块、RAWA-NN模型构建及训练评估等内容。 适合人群:从事电磁兼容性研究、6G通信技术研发以及对神经网络优化感兴趣的科研人员和工程师。 使用场景及目标:①研究6G通信中高频段电磁暴露对人体的影响;②开发更高效的电磁暴露评估工具;③优化神经网络架构以提高模型训练效率和预测精度。 其他说明:论文不仅提出了理论框架,还提供了完整的代码实现,方便读者复现实验结果。此外,论文还讨论了未来的研究方向,包括扩展到更高频段(如300GHz)的数据处理、引入强化学习优化超参数、以及实现多物理场耦合的智能电磁暴露评估系统。建议读者在实际应用中根据具体需求调整模型架构和参数,并结合真实数据进行验证。

    工业自动化中S7-1200/1500 PLC与V90 PN伺服基于FB284功能块的控制实现

    内容概要:本文详细介绍了如何使用FB284功能块在西门子S7-1200/1500 PLC中实现对V90 PN伺服系统的控制。主要内容涵盖基础配置、主动回零、定位控制以及速度点动等功能的具体实现方法和技术细节。文中不仅提供了具体的代码示例,还分享了许多实际调试过程中积累的经验教训,帮助读者更好地理解和应用这些技术。 适合人群:从事工业自动化领域的工程师和技术人员,特别是那些需要进行伺服控制系统开发和维护的专业人士。 使用场景及目标:适用于需要精确位置控制的应用场合,如生产线上的物料搬运、加工中心的位置控制等。通过学习本文,读者能够掌握使用FB284功能块实现伺服控制的基本技能,提高工作效率并减少调试时间。 其他说明:文章强调了正确的参数配置对于伺服系统正常运行的重要性,并提供了一些常见问题及其解决方案。此外,还提到了一些优化建议,如合理的速度设置和平滑启动等措施,有助于提升系统的稳定性和可靠性。

    ### 基于数据空间的金融数据可信流通研究报告总结

    内容概要:本文是北京金融科技产业联盟发布的《基于数据空间的金融数据可信流通研究报告》,探讨了金融数据可信流通的现状、挑战和发展前景。文章首先介绍了金融数据在数字化转型中的重要性及其面临的隐私保护和安全挑战。接着,文章详细阐述了数据空间的概念及其发展历程,尤其是可信数据空间(TDM)在我国的发展情况。文中还深入分析了金融数据可信流通的典型应用场景、关键技术和方案架构,如数据访问控制、数据使用控制、智能合约、数据脱敏等。最后,文章展示了多个典型场景应用案例,如中信银行总分行数据流通管控、工银金租数据流通、银联安全生物特征支付等,并总结了当前可信数据空间建设中存在的法规、技术、标准和商业模式挑战,提出了相应的政策建议。 适用人群:金融行业从业者、数据安全管理人员、政策制定者、科技研发人员等。 使用场景及目标:①理解金融数据可信流通的重要性和挑战;②学习可信数据空间的关键技术和应用场景;③探索金融数据可信流通的具体实践案例;④了解当前可信数据空间建设的瓶颈和未来发展方向。 其他说明:本文不仅提供了详尽的技术和应用分析,还提出了具体的政策建议,有助于推动金融数据可信流通的健康发展。阅读本文可以帮助读者深入了解金融数据安全保护和高效利用的最佳实践,为相关政策和技术的发展提供参考。

    智能车环境障碍物静态与动态风险场模型的MATLAB实现及应用

    内容概要:本文详细介绍了智能车环境中静态和动态风险场模型的构建及其在MATLAB中的实现。首先,通过椭圆模型建立静态风险场,模拟车辆周围的碰撞风险分布,重点在于椭圆参数的选择和坐标变换的应用。其次,动态风险场考虑了车辆之间的相对运动,利用相对速度和方向因子来评估风险值的变化。两者结合形成综合风险场,用于优化车辆的轨迹规划。文中还讨论了模型的实际应用案例,如在弯道和十字路口场景中的表现,并提供了可视化技巧和调试经验。 适合人群:从事智能车研究、自动驾驶算法开发的研究人员和技术人员,以及对MATLAB编程有一定基础的学习者。 使用场景及目标:①帮助研究人员理解和实现智能车环境中的风险场模型;②提供具体的MATLAB代码示例,便于实际应用和调试;③通过实例展示风险场模型在复杂交通场景中的应用效果。 其他说明:文章不仅涵盖了理论推导和代码实现,还包括了实际测试中的经验和教训,有助于读者更好地掌握和应用这一技术。

    少儿编程scratch项目源代码文件案例素材-scratch格斗游戏引擎.zip

    少儿编程scratch项目源代码文件案例素材-scratch格斗游戏引擎.zip

    基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)

    基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业),个人经导师指导并认可通过的高分设计项目,评审分99分,代码完整确保可以运行,小白也可以亲自搞定,主要针对计算机相关专业的正在做大作业的学生和需要项目实战练习的学习者,可作为毕业设计、课程设计、期末大作业,代码资料完整,下载可用。 基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业

    少儿编程scratch项目源代码文件案例素材-3D环境.zip

    少儿编程scratch项目源代码文件案例素材-3D环境.zip

    基于Simplorer与Maxwell的电机控制系统联合仿真:电机场路耦合与SVPWM算法实现

    内容概要:本文详细介绍了如何利用Simplorer和Maxwell进行电机控制系统的联合仿真。主要内容分为四个部分:一是搭建电机场路耦合主电路,包括选择合适的电机模型、功率器件及其他必要元件,并进行正确的连接和参数设置;二是实现矢量控制SVPWM算法,涵盖SVPWM模块的搭建、参数设置以及信号连接;三是讨论仿真文件的可复制性和电机模型替换的具体步骤;四是总结联合仿真的优势及其应用价值。通过这种方式,不仅可以深入理解电机的工作原理,还可以优化控制算法,提升电机性能。 适合人群:从事电机控制研究和技术开发的工程师、研究人员,特别是对电机场路耦合仿真感兴趣的从业者。 使用场景及目标:适用于需要进行电机控制系统设计和优化的场合,如工业自动化、电动汽车等领域。主要目标是掌握Simplorer和Maxwell联合仿真的方法,提高电机控制系统的效率和可靠性。 其他说明:文中提供了大量实例代码和操作提示,有助于读者更好地理解和实践相关技术。同时强调了一些常见错误和解决办法,帮助初学者避开陷阱。

    西门子200smart与昆仑通态锅炉换热站自动化系统的实现及优化

    内容概要:本文详细介绍了基于西门子200smart PLC和昆仑通态触摸屏构建的锅炉换热站自动化系统的设计与实现。主要内容涵盖模拟量采集与处理、水泵切换逻辑、时间段控温和Modbus通讯控制等方面的技术细节及其优化措施。文中不仅展示了具体的编程技巧,如SCALE指令用于工程量转换、状态矩阵实现水泵故障记忆、时钟指令配合SFC20块搬移指令进行时间段控温等,还分享了许多实际调试过程中遇到的问题及解决方案,如通过硬件和软件滤波减少信号跳变、调整Modbus通讯参数提高稳定性等。 适合人群:从事工业自动化领域的工程师和技术人员,特别是对PLC编程和系统集成有一定经验的人士。 使用场景及目标:适用于小型工业自动化项目的开发与维护,旨在帮助技术人员掌握如何高效地搭建稳定可靠的自动化控制系统,提升系统的可靠性和易用性。 其他说明:作者通过亲身经历分享了许多宝贵的实战经验和教训,强调了理论与实践相结合的重要性。对于希望深入了解PLC编程和工业自动化应用的人来说,本文提供了丰富的参考资料和技术指导。

    T型三电平逆变器SVPWM开环控制的实现与优化

    内容概要:本文详细介绍了T型三电平逆变器的空间矢量脉宽调制(SVPWM)开环控制实现过程。首先阐述了T型三电平逆变器的基本拓扑结构和特点,接着通过MATLAB、Python和C语言代码展示了SVPWM的具体实现步骤,包括矢量分区、作用时间计算、PWM波生成以及中性点平衡处理。文中还讨论了一些常见的工程实践技巧,如零矢量分配策略、死区时间设置等,并提供了仿真测试结果和波形分析。 适合人群:从事电力电子、新能源项目开发的技术人员,尤其是对SVPWM算法感兴趣的工程师。 使用场景及目标:适用于需要理解和实现T型三电平逆变器SVPWM开环控制的工程项目。目标是帮助读者掌握SVPWM算法的核心原理和技术细节,能够在实际项目中进行有效的算法实现和优化。 其他说明:文中引用了几篇经典的学术文献作为理论支持,同时提供了一些实用的调试建议和注意事项,有助于提高读者的实际操作能力。

    麒麟arm64系统需要的qt 的离线环境,参考:https://blog.csdn.net/m0-53485135/article/details/135544602

    麒麟arm64系统需要的qt 的离线环境,参考:https://blog.csdn.net/m0-53485135/article/details/135544602

    三菱Q系列PLC与QD77MS16模块用于物流中心托盘堆垛项目的详细解析

    内容概要:本文详细介绍了在一个物流中心托盘堆垛项目中使用的三菱Q系列PLC和QD77MS16模块的配置和应用。主要内容涵盖硬件架构、伺服参数配置、定位数据表、HMI设计、安全回路设计以及调试经验和技巧。文中提供了具体的代码示例,如梯形图、结构化文本和VBS脚本,展示了如何实现精确的位置控制和平滑的运动轨迹。此外,还讨论了常见的调试问题及其解决方案,如轴号选择、信号抖动、同步移动时的轴间碰撞等。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是熟悉三菱PLC系统的从业者。 使用场景及目标:适用于需要深入了解三菱Q系列PLC和QD77MS16模块的应用场景,帮助工程师优化堆垛机控制系统的设计和调试,提高系统的可靠性和稳定性。 其他说明:本文不仅提供理论知识,还包括大量实战经验和具体代码示例,有助于读者更好地理解和应用相关技术。

    基于STM32F4的BMS电池管理系统:SOC均衡与电池管理控制器详解

    内容概要:本文详细介绍了基于STM32F407的电池管理系统(BMS)设计方案,重点探讨了SOC均衡的实现方法和技术细节。硬件方面,使用LTC6804进行12节锂电池的电压采集,LTC3300实现高效的双向主动均衡。软件部分涵盖了SOC算法的实现,包括安时积分法和开路电压校正,并讨论了LTC6804和LTC3300的具体控制代码。此外,文章分享了许多实际开发中的经验和教训,如PCB布局注意事项、通信时序优化等。 适合人群:从事电池管理系统开发的技术人员,尤其是有一定嵌入式开发经验的研发人员。 使用场景及目标:适用于电动车、储能系统等领域,旨在提高电池管理系统的可靠性和效率,确保电池组的安全运行和延长使用寿命。 其他说明:文章不仅提供了详细的硬件和软件设计指导,还包括了大量实用的经验总结和调试技巧,帮助开发者避免常见错误。

    西门子S7-1500与库卡机器人协同的汽车焊接自动化系统详解

    内容概要:本文详细介绍了基于西门子S7-1500 PLC和库卡机器人的汽车焊接自动化系统。系统涵盖PLC控制、机器人通信、HMI配置、多工位转台控制、SEW电机变频控制及多种运行模式。文中通过具体代码实例解释了各组件的工作原理及其相互协作的方式,强调了通讯协议、PID控制、触摸屏交互设计、异常处理机制等方面的技术细节。此外,还分享了许多来自实际项目的经验和技巧,如参数优化、安全防护措施等。 适合人群:从事工业自动化控制、PLC编程、机器人集成等相关工作的工程师和技术人员。 使用场景及目标:适用于学习和理解大型工业自动化系统的构建与实现,尤其是汽车制造行业的焊接生产线。目标是帮助读者掌握PLC与机器人通信、HMI配置、设备控制等核心技术,提升实际项目开发能力。 其他说明:文章不仅提供了详细的理论讲解,还包括大量实战经验和代码片段,有助于读者深入理解并应用于实际工作中。同时,文中提到的一些最佳实践和注意事项也能为后续维护和优化提供指导。

    少儿编程scratch项目源代码文件案例素材-Scratch 卡牌游戏.zip

    少儿编程scratch项目源代码文件案例素材-Scratch 卡牌游戏.zip

    少儿编程scratch项目源代码文件案例素材-RPG游戏引擎5.5c.zip

    少儿编程scratch项目源代码文件案例素材-RPG游戏引擎5.5c.zip

    工业自动化中欧姆龙NJ PLC与多品牌总线节点控制程序库的应用与调试技巧

    内容概要:本文详细介绍了欧姆龙NJ系列PLC与多个品牌总线节点(如汇川IS620N伺服、雷赛DMC-4080步进控制器、SMC电动缸等)的控制程序库应用及其调试技巧。主要内容涵盖PDO映射配置、扩展轴使能、电流参数读写、绝对定位控制、急停处理等方面的具体实现方法和常见问题解决方案。文中通过具体的代码示例展示了各品牌设备之间的通信方式和参数设置要点,并分享了作者在现场调试过程中积累的经验教训。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是那些需要进行多品牌总线设备集成和调试的专业人士。 使用场景及目标:适用于需要将欧姆龙NJ PLC与其他品牌总线设备集成并进行精确控制的工程项目。主要目标是帮助工程师快速掌握各种总线设备的配置方法,避免常见的调试陷阱,提高系统的可靠性和稳定性。 其他说明:文章强调了不同品牌设备间单位转换的重要性,提醒读者务必仔细核对参数设置,确保系统安全运行。此外,还提到了一些实用的调试工具和方法,如Wireshark抓包、SMC自带调试软件等,有助于提升工作效率。

Global site tag (gtag.js) - Google Analytics