【详谈】HashMAP深度剖析,全面消化吸收

news2024/12/24 21:53:15

【万字长文】
还没写完!!还没写完!!!还在码字中,只是先放上,防止又写着没了,自己文件没了…

最近,可以说的上自己博客停更大约有一两个月了,一直在忙于公司中的项目和业务,典型的牛马看了都流泪

今天,自己刚刚优化了一个查询耗时的BUG,在这里我就引入的hashmap作为我存储队列的优选

我啪啪码完,突然有人问我,HASHMAP有啥好处啊!我本想装逼的,但是好像话到嘴边,又没了

说白了,还是停留在只会用的阶段,现在我们来一起精通它!!!

你说为啥啊!不为别的,就为自己心里那点优越感!哈哈哈!!!

(这里我结合问题的方式来探讨,目的理解更加深刻!!!)

本文总纲:

1.HashMAP的底层实现原理

重点是1.8,先别纠结1.7的,它懂了,就拿下半壁江山了~嘿嘿


2.Hashmap的数据结构(拿来的图哈)

图:

img

JDK1.8中,Hashmap的数据结构是数据+链表+红黑树

在链表的长度超过阈值8时,数据结构由链表转换为红黑树,这样的作用就是大大减少查询的时间

可能有的小伙伴会问,为啥要采用这种组合的数据结构啊?我还是不怎么云里雾里!哈哈哈,别急,我来解释噻

说之前,我们先看下数组和链表

众所周知:

  • 数组的特点是查询效率高,但是插入和删除的效率低
  • 链表就刚好反过来,删插6的不行,但是读查的效率低

那么我们有没有一种鱼和熊掌可兼得的数据结构来将数组和链表的优点结合呢?

不卖关子,有地! 哈希表,(就是大学期末考试最喜欢考的,喵的)

可能有的小伙伴忘记了,我在这里解释下哈希表:(懂的,可以不看)

哈希表介绍:

哈希表(Hash Table),也称为散列表,是一种数据结构,它实现了关联数组的概念,即通过键值对(key-value pairs)存储和检索数据。哈希表使用哈希函数将键映射到数组的一个位置上,从而能够快速地访问所存储的值。

哈希表的主要优点是其查找、插入和删除操作可以在常数时间内完成,即O(1)的时间复杂度,但这在理想情况下才能实现,即没有或很少发生哈希冲突的情况下。哈希冲突是指不同的键通过哈希函数映射到了同一个数组索引位置上。

为了处理哈希冲突,通常有以下几种方法:

  1. 链地址法(Separate Chaining):在每个数组位置存储一个链表,当多个键映射到同一位置时,这些键值对被链接在一起。
  2. 开放定址法(Open Addressing):当发生冲突时,寻找下一个可用的数组位置来存储元素,如线性探测(Linear Probing)、二次探测(Quadratic Probing)或双散列(Double Hashing)。

哈希表的性能取决于以下几个关键因素:

  • 哈希函数的质量:应该均匀分布键值,减少冲突。
  • 负载因子(Load Factor):是表中元素数量与表大小的比值。高负载因子会增加冲突的概率。
  • 解决冲突的方法:不同的策略影响查找效率和存储空间。

在实际应用中,哈希表广泛用于数据库索引、缓存机制、编译器符号表、字符串查找算法等场景。

接下来分析下JDK1.8源码中涉及到的数据结构,也解释下为什么链表长度为8时要转换为红黑树的

我们看代码里面的

transient Node<K,V>[] table;   //数组


链表

我们看到数组元素Node<K,V>实现了Entry接口,是单项链表

/**
 * Basic hash bin node, used for most entries.  (See below for
 * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
 */
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

红黑树

/**
 * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
 * extends Node) so can be used as extension of either regular or
 * linked node.
 */
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }

    /**
     * Returns root of tree containing this node.
     */
    final TreeNode<K,V> root() {
        for (TreeNode<K,V> r = this, p;;) {
            if ((p = r.parent) == null)
                return r;
            r = p;
        }
    }

jdk1.8使用红黑树改进

我们进行源码的阅读时,在JDK1.8中,HashMAP处理碰撞增加了红黑树这种数据结构,当碰撞节点较少时,采用链表存储,当较大时(也就是大于8个),我们采用红黑树

这里我们提一下:链表的时间复杂度为o(n),但红黑树为o(logn),这样一看就知道为啥要优化了吧

为什么HASHMAP不直接使用红黑树呢?

正如上面我们看到的,从时间复杂度来看:

  • 红黑树的平均查找长度(ASL)是log(n),若查找长度是8,平均查找长度为log(8)=3

  • 链表的平均查找长度为n/2,当长度为8时,ASL=4时,这个时候转换为树就有必要了

    • 但是: 若ASL=6,对于链表的o(n)=3,红黑树的o(n)=log(6)=2.6
    • 这边区别是不大,但是考虑到转换为树结构和生成树的也是要时间的且不短

你可能说,我就是还是不怎么信服,总感觉还差点意思啊,我们直接看源码的解释

从这段源码的注释,我们看到:

  • 树节点所占的空间是普通节点的两倍,从而只有当节点足够多的情况下,才会使用树节点
  • 反之:节点少的时候,尽管红黑树的时间复杂度优于链表,但是红黑树的所占的空间比较大(这个缺点大于优点了),采用链表反而更好
  • 其实就是权衡利弊,为了在时间和空间上两者综合上达到最优
 /*
     *主要看这段=================================================================
     * Because TreeNodes are about twice the size of regular nodes, we
     * use them only when bins contain enough nodes to warrant use
     * (see TREEIFY_THRESHOLD). And when they become too small (due to
     * removal or resizing) they are converted back to plain bins.  In
     * usages with well-distributed user hashCodes, tree bins are
     * rarely used.  Ideally, under random hashCodes, the frequency of
     * nodes in bins follows a Poisson distribution
     * (http://en.wikipedia.org/wiki/Poisson_distribution) with a
     * parameter of about 0.5 on average for the default resizing
     * threshold of 0.75, although with a large variance because of
     * resizing granularity. Ignoring variance, the expected
     * occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
     * factorial(k)). The first values are:
     *
     * 0:    0.60653066
     * 1:    0.30326533
     * 2:    0.07581633
     * 3:    0.01263606
     * 4:    0.00157952
     * 5:    0.00015795
     * 6:    0.00001316
     * 7:    0.00000094
     * 8:    0.00000006
     * more: less than 1 in ten million
     *==================================================================
     */

为什么达到阈值8才会选择使用红黑树呢?

我们知道我们采用HASH表

  1. hashCode的离散型很好时,树型bin用到的概率很小(可以看上面的源码注释)
    1. 因为数据均匀的分布在每个bin中,几乎不会有bin中链表长度会达到阈值
    2. 又在随机的hashcode下,离散性可能会变差,然而JDK又不能阻止用户实现这种不好的HASH算法,就可能导致不均匀的数据分布
    3. 但是在理想情况下:随机的hashcode算法下所有的bin中节点分布频率会遵循泊松分布,且根据统计,一个bin的链表长度会达到8个元素的概率为 0.00000006(上面我们看到),几率相当于是不可能事件
    4. 而这时链表的性能已经很差了,在这种糟糕的环境下,链表才会转换为红黑树,来提高性能
  2. 而在大部分的情况下,我们使用的就是链表,如果理想的均匀分布的情况下,节点数不到8,Hashmap就会自动扩容,具体我们看下面的源码:
  3. 所以通常的情况下,我们用的都是链表,只有哈希表容量很大,链表长度=8,此时链表性能够很差了,我们要提高性能,就采用红黑树了
  4. 综上所述:就是链表长度为8转为红黑树的原因

翻译下: 除非hash表太小,我们调整大小,否则就替换给定的哈希值索引出bin中所有链接的节点

   //满足节点变成树的另一个条件,就是存放node的数组长度要达到64
	static final int MIN_TREEIFY_CAPACITY = 64; 

   /**
     * Replaces all linked nodes in bin at index for given hash unless
     * table is too small, in which case resizes instead.
     */
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //数组长度小于MIN_TREEIFY_CAPACITY,就会扩容,而不是直接转变为红黑树
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

hashmap的扩容问题呢?

Hashmap的构造函数

参数:

initialCapacity:初始容量

loadFactor:填充比

主要就四种:

//构造函数1(带有初始容量和加载因子的有参构造函数)
public HashMap(int initialCapacity, float loadFactor) {
    //指定的初始容量非负
    if (initialCapacity < 0)
        throw new IllegalArgumentException(Illegal initial capacity:  +
                                           initialCapacity);
    //如果指定的初始容量大于最大容量,置为最大容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //填充比为正
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException(Illegal load factor:  +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);//新的扩容临界值
}
 
//构造函数2(只带有初始容量的构造函数)
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
 
//构造函数3(无参构造函数)
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
 
//构造函数4(用m的元素初始化散列映射)
public HashMap(Map<!--? extends K, ? extends V--> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

Hashmap的存取原理

1.put的实现原理

1.判断key对数组table[]是否为空或null,否则默认大小resize()

2.根据key计算hash值插入到数组索引i,若table[i]==null,直接新建节点添加,否则下一步

3.判断当前数组中处理hash冲突的方式为链表还是红黑树(check第一个节点类型),分别处理

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
     /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; 
    Node<K,V> p; 
    int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
    /*如果table的在(n-1)&hash的值是空,就新建一个节点插入在该位置*/
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
    /*表示有冲突,开始处理冲突*/
        else {
            Node<K,V> e; 
        K k;
    /*检查第一个Node,p是不是要找的值*/
            if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
        /*指针为空就挂在后面*/
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
               //如果冲突的节点数已经达到8个,看是否需要改变冲突节点的存储结构,             
            //treeifyBin首先判断当前hashMap的长度,如果不足64,只进行
                        //resize,扩容table,如果达到64,那么将冲突的存储结构为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
        /*如果有相同的key值就结束遍历*/
                    if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
    /*就是链表上有相同的key值*/
            if (e != null) { // existing mapping for key,就是key的Value存在
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;//返回存在的Value值
            }
        }
        ++modCount;
     /*如果当前大小大于门限,门限原本是初始容量*0.75*/
        if (++size > threshold)
            resize();//扩容两倍
        afterNodeInsertion(evict);
        return null;
    }

Hashmap的get()方法

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
      /**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab;//Entry对象数组
    Node<K,V> first,e; //在tab数组中经过散列的第一个位置
    int n;
    K k;
    /*找到插入的第一个Node,方法是hash值和n-1相与,tab[(n - 1) & hash]*/
    //也就是说在一条链上的hash值相同的
        if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {
    /*检查第一个Node是不是要找的Node*/
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))//判断条件是hash值要相同,key值要相同
                return first;
      /*检查first后面的node*/
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                /*遍历后面的链表,找到key值和hash值都相同的Node*/
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
}

HashMAP的扩容机制:

HashMap 在 Java 中是一个常用的集合类,它基于哈希表实现,提供键值对的存储和检索。HashMap 的扩容机制是确保其性能的关键部分,因为随着元素的增加,哈希碰撞的可能性也会增加,这可能导致性能下降。因此,HashMap 设计了动态扩容机制以保持其效率。

在 Java 8 及以后的版本中,HashMap 的主要结构包括一个节点数组 Node<K,V>[] table 和一些内部节点类(如 NodeTreeNode)。HashMap 的初始容量通常是16,并且要求容量始终是2的幂。每次扩容都会使容量翻倍。

以下是 HashMap 扩容机制的主要步骤:

  1. 判断条件:当 HashMap 中的元素数量超过了当前容量乘以负载因子(默认是0.75)时,就会触发扩容操作。也就是说,当 size > capacity * loadFactor 时,HashMap 将进行扩容。

  2. 创建新数组:扩容时,HashMap 会创建一个新的节点数组,其长度是原数组长度的两倍。

  3. 重新哈希:所有旧数组中的元素必须重新计算它们在新数组中的位置。这是因为哈希值与数组大小相关,而数组大小已经改变。这个过程被称为“再哈希”(rehashing)。

  4. 迁移元素:每个元素从旧数组中移除并插入到新数组中适当的位置。这涉及到遍历旧数组中的每个桶,并根据新的哈希值和新数组的大小确定每个元素的新位置。

  5. 更新引用:一旦所有元素都已重新定位,HashMap 的内部引用将指向新数组,而旧数组会被垃圾回收。

需要注意的是,扩容操作是昂贵的,因为它涉及到遍历和再哈希整个哈希表。因此,选择合适的初始容量和负载因子可以减少扩容的频率,从而提高性能。此外,在高并发环境下,HashMap 的扩容操作可能会导致数据不一致,因此在多线程环境中使用时需要特别注意线程安全问题,或者考虑使用 ConcurrentHashMap 这样的线程安全替代品。

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

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

相关文章

Linux 某进程 CPU 高问题,用 Shell 脚本发现处理

发现高CPU使用率进程 首先&#xff0c;我们需要编写一个Shell脚本来发现系统中CPU使用率最高的进程。以下是一个简单的脚本示例&#xff1a; #!/bin/bash# 设置 CPU 使用率的阈值,一般设置90&#xff1b;这里是demo&#xff0c;所以用30 CPU_THRESHOLD30# 获取占用 CPU 最高的…

学习笔记 韩顺平 零基础30天学会Java(2024.7.24)

P416 匿名内部类本质 IA tiger new IA(){//IA是一个接口 //重写 System.out.println(“老虎叫唤。。。”); } P417 匿名内部类使用 基于类的匿名内部类 Father father new father(“jack”){};//不带大括号运行类型是Father&#xff0c;带大括号就是匿名内部类,相当于是匿名…

【深度学习入门】安装conda/miniconda、所需包类、CUDA与conda/Miniconda间的关系

深度学习入门 须知 本教程跟随李沐老师课程随笔&#xff0c;课程链接点击此处。 CUDA和Anaconda的关系 CUDA Toolkit是由Nvidia官方提供的完整工具包&#xff0c;其中提供了Nvidia驱动程序、开发CUDA程序相关的开发工具包等。 Anaconda在安装Pytorch等会用到的CUDA的框架时…

操作系统(三)中断----软中断

软中断与硬中断很像 软中断是纯软件实现的&#xff0c;宏观效果看上去和中断差不多的一种方式。 什么叫宏观效果呢&#xff1f;意思就是说&#xff0c;中断在宏观层面看来&#xff0c;就是打断当前正在运行的程序&#xff0c;转而去执行中断处理程序&#xff0c;执行完之后再返…

【Python机器学习】使用Matplotlib注解绘制树形图

通过数据集可以创建树&#xff0c;但是字典的表示形式非常不易于理解&#xff0c;而且直接绘制图形也比较困难。但是通过Matplotlib库可以绘制树形图。 决策树的主要优点就是直观、易于理解&#xff0c;如果不能将其直观的显示出来&#xff0c;就无法发挥其优势。 Matplotlib…

y=λsin(πx)分岔的研究

使用如下的迭代格式&#xff0c;λ为可变的参数 用如下代码对收敛的λ的值进行探究&#xff0c;这里的r代表λ %通过观察是否凝聚在同一个点来判断是否收敛 clear;clf; axis([0,4,0,4]); grid; hold on for r0:0.3:3.9x[0.1];for i2:150x(i)r*sin(pi*x(i-1));endpause(0.5);fo…

心动小站Ⅶ--人工智能的虚假承诺

前言 1770 年&#xff0c;匈牙利作家兼发明家 Wolfgang von Kempelen 推出了一款名为“土耳其机器人”的自动国际象棋机器。该机器在欧洲各地展示了其自动化国际象棋大师技能&#xff0c;在与人类对手的比赛中频频获胜。据说它甚至击败了拿破仑和本杰明富兰克林等著名人物。土…

Prometheus之数据类型和函数

前言&#xff1a; 在了解Prometheus数据类型前&#xff0c;我们先了解下面几个统计学名词概念&#xff1a; 平均数&#xff08;Mean&#xff09;&#xff1a; 平均数是所有数据加起来除以数据个数得到的结果。它表示数据的中心趋势。 最大值&#xff08;Maximum&#xff09…

Hadoop集群安装配置

文章目录 Hadoop部署配置集群配置历史服务器配置日志的聚集分发Hadoop群起集群Hadoop群起脚本 准备工作&#xff1a;需要3台虚拟机&#xff0c;每台虚拟机搭建好JDK并配置环境变量 Hadoop部署 1&#xff09;集群部署规划 注意&#xff1a;NameNode和SecondaryNameNode不要安…

Vue中el的两种写法

大家好我是前端寄术区博主PleaSure乐事。今天了解到了Vue当中有关el的两种写法&#xff0c;记录下来与大家分享&#xff0c;希望对大家有所帮助。 方法一 解释 第一种方法我们直接用new创建并初始化一个新的 Vue 实例&#xff0c;并定义了 Vue 实例的数据对象&#xff0c;在给…

数组算法--二分查找

目录 一.前言 二.算法的核心思路 三.算法的核心代码以注释详解 一.前言 二分查找也叫折中查找&#xff0c;为什么会这样叫呢&#xff1f;就是因为我们二分查找的核心逻辑就是每查找完一次&#xff0c;都能将查找的范围给缩小一半&#xff0c;也就是折中。但使用二分查找又有个…

宏VB的1004问题,方法作用于对象错误,如何解决?

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

RV1126 Linux 系统,接外设,时好时坏(二)排查问题的常用命令

在 RV1126 Linux 系统中,排查外设连接问题时,可以使用多种命令来诊断和调试。以下是一些常用的命令和工具: 1. 查看系统日志 dmesg: 显示内核环形缓冲区的消息,通常包含设备初始化、驱动加载和错误等信息。 dmesg | grep <设备名或相关关键字>journalctl: 查看系统…

windows 下删除一个文件夹及其子文件夹下相同后缀名的文件

问题 我有一个工作目录&#xff0c;沉积了四五年的工作 文件。其中有一个相同格式的中间过程文件暂用很大体积&#xff0c;也不需要保留&#xff0c;并且可以通过其他文件生成。因此想一次删除这个工作目录下的所有相同后缀的文件。 解决方法 在工作目录的地址栏输入“cmd”…

leaflet【九】使用天地图改变地图底色

本文将详细探讨如何在Leaflet地图框架中集成天地图&#xff0c;并介绍如何通过调整背景色和滤镜来改变地图的显示效果。首先&#xff0c;我们将解释如何在Leaflet中配置天地图作为底图&#xff0c;包括API密钥的获取与使用。接下来&#xff0c;文章将展示如何通过CSS和JavaScri…

高等数学 第六讲 一元微分学的应用(二)_中值定理,微分等式,微分不等式

高等数学 第6讲 中值定理 微分等式 微分不等式 文章目录 高等数学 第6讲 中值定理 微分等式 微分不等式1.涉及函数的中值定理1.1 有界与最值定理1.2 介值定理1.3 平均值定理1.4 零点定理 2.涉及导数(微分)的中值定理2.1 导数零点定理2.2 罗尔定理2.3 拉格朗日中值定理2.4 柯西中…

如何利用Jenkins自动化管理、部署数百个应用

目录 1. Jenkins 安装与部署步骤 1.1 系统要求 1.2 安装步骤 1.2.1 Windows 系统 1.2.2 CentOS 系统 1.3 初次配置 2. Gradle 详细配置方式 2.1 安装 Gradle 2.1.1 Windows 系统 2.1.2 CentOS 系统 2.2 配置 Jenkins 中的 Gradle 3. JDK 详细配置方式 3.1 安装 JD…

【MSP430】DriverLib库函数,UCS函数分析

MSP430F5xx_6xx_DriverLib_Users_Guide-2_91_13_01(函数库手册).pdf 在MSP430单片机中&#xff0c;UCS&#xff08;User Clock System&#xff09;模块提供了一组函数用于配置和管理时钟源&#xff0c;包括外部和内部振荡器以及时钟信号的路由和控制。这些函数对于确保系统在正…

layui改造优化ITtools技术笔记01—layui.js重要修正

问题现象&#xff1a; ittools教学平台自动生成的单选按钮渲染后无法切换选项。 故障排查&#xff1a; input[name xxx]&#xff0c;其中xxx含有特殊字符&#xff0c;如$等&#xff0c;导致layui渲染时&#xff0c;表达式出错&#xff0c;无法及时渲染。 解决方案&#xff1…

IO流综合练习

IO流综合练习 文章目录 IO流综合练习制造假数据需求利用糊涂包制造假数据&#xff0c;并写入文件中 随机点名器Student标准JavaBean类实现代码names.txt文件中的内容 登录 制造假数据 需求 制造假数据也是开发中的一个能力&#xff0c;在各个网上爬取数据&#xff0c;是其中一…