JDK8 HashMap的实现原理
一 HashMap底层存储结构
HashMap底层结构采用(数组)+(链表 or 红黑树)的形式来存储节点。
- 首先HashMap是一个数组,而且数组里面每个位置可以放入多个元素,形象一点,咱们把数组的这些个位置称为桶。HashMap里面每个元素通过key值取hash在 & (数组长度容量-1)就可以唯一确定该元素属于哪个桶。
- HashMap为了最大限度的提高效率,在桶的设计上也是相当的精辟。桶可能是链表也可能是红黑树。开始桶里面元素不多的时候采用链表形式保存。后续随着HashMap里面的元素越来越多,桶里面里面的元素元素也越来越多,当元素个数>8(TREEIFY_THRESHOLD)并且数组的长度>64(MIN_TREEIFY_CAPACITY)的时候,会把链表变成红黑树(树化);如果桶当前采用的是红黑树保存节点元素信息随着节点个数的减少(HashMap remove操作),当节点元素个数<6(UNTREEIFY_THRESHOLD)的时候,又会把红黑树降级为链表。
看到这里,咱们可能会想了,HashMap的桶有了链表为啥还要转换为红黑树?HashMap源码大神考虑到链表太长的话。节点元素的查找效率不高。所以有链表转红黑树,红黑树转链表的操作。可能你又会想为啥不用平衡二叉树来替换红黑树。那是因为HashMap源码大神兼顾了节点的插入删除效率和节点的查询效率。红黑树不追求"完全平衡"。所以往红黑树里面插入或者删除节点的时候任何不平衡都会在三次旋转之内解决。而平衡二叉树插入或者删除节点的时候为了追求完全平衡,旋转的次数是不固定的,花费的时间跟多。(关于红黑树和平衡二叉树的更多知识,大家可以自行去百度下,我们这里就不具体展开了,里面还是挺有趣的)
二 HashMap源码分析
image-20200626155911680.png
了解了HashMap的存储结构之后,咱们着重对HashMap添加节点的过程做一个简单的分析。我相信只要咱们搞懂了HashMap里面添加元素的过程。HashMap里面大部分的实现逻辑咱们都能搞懂。因为HashMap中最关键的部分(扩容、树化)在HashMap添加元素过程中都很好的体现出来了。这里我们先给出HashMap添加元素的简单流程图(HashMap里面putVal()函数流程图)。
HashMap流程图.png
上面流程图里面有几个重要的地方是我们需要重点关注的:resize()扩容,treeifyBin()树化。下面我们对resize()扩容,treeifyBin()树化的具体逻辑做一个简单的分析。
2.1 准备工作
- HashMap里面一些关键属性介绍
/**
* 默认初始容量(默认数组桶的个数)16-必须为2的幂。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16/**
* 最大容量2的30次方。桶的最大个数
*/
static final int MAXIMUM_CAPACITY = 1 << 30;/**
* 构造函数中未指定时使用的负载系数,
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;/**
* 桶结构树化需要两个条件
* 1. 桶里面元素个数大于TREEIFY_THRESHOLD
* 2. 桶的个数(数组的长度)大于MIN_TREEIFY_CAPACITY
*/
static final int TREEIFY_THRESHOLD = 8;/**
* 如果当前桶采用的是红黑树保存节点,当桶里面的元素小于该值时,红黑树降级为链表。
*/
static final int UNTREEIFY_THRESHOLD = 6;/**
* 桶结构树化需要两个条件
* 1. 桶里面元素个数大于TREEIFY_THRESHOLD
* 2. 桶的个数(数组的长度)大于MIN_TREEIFY_CAPACITY
*/
static final int MIN_TREEIFY_CAPACITY = 64;/**
* HashMap数组。长度必须是2的幂次方
*/
transient Node<K,V>[] table;/**
* HashMap中所有元素节点的个数
*/
transient int size;/**
* 扩容(重hash)或者map结构修改的次数
*/
transient int modCount;/**
* 扩容阈值【当HashMap的所有元素个数大于threashold时会进行扩容操作】,threshold=容量*loadFactor(装载因子)
*/
int threshold;/**
* 装载因子,用来衡量HashMap满的程度。默认为0.75f
*/
final float loadFactor;
- HashMap容量(数组大小,桶的个数)永远是2的整数次幂
时时刻刻要记住HashMap的容量(桶的个数)永远是2的整数次幂。初始容量16,每次扩容之后的容量都是前一次容量的两倍。比如当前容量是16扩容一次编程32,再扩容一次变成64。
而且如果我们在new HashMap的时候,给了初始容量,但是给定的容量不是2的整数次幂,构造函数内部也会调用tableSizeFor()函数转换成2的整数次幂的。比如:传递3会转换成4,传递13会转换成16。
/**
* 返回大于输入参数且最近的2的整数次幂的数
* 比如 cap = 2 的时候返回 2
* cap = 3 的时候返回 4
* cap = 9 的时候返回 16
*/
static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
- HashMap里面的元素桶的位置是怎么确定的(HashMap元素在数组中的索引位置)
很简单通过对元素的key做hash处理在 &(容量-1),就可以精确找到找个这个元素应该放入哪个桶中。
2.2 resize()扩容
一定要时刻记住HashMap的容量(数组的大小,我们也说桶的个数)永远都是2的整数次幂,扩容就是把容量扩大一倍。扩容之后的容量=原来容量*2。(桶的个数翻倍)就算你new HashMap()的时候给定的容量不是2的整数次幂。HashMap内部也是会通过tableSizeFor()函数把容量转换成2的整数次幂。
HashMap确定元素属于哪个桶是通过对该元素的key取hash之后再 & (容量-1)。这样就找到了桶的位置(其实是数组的下标)。
扩容前后桶的关系也要特别注意,扩容前属于同一个桶(桶的索引位置相同)里面的元素,在扩容之后只会有两个桶来放他们:要不还保留扩容前的桶的索引位置,要不就是通过扩容前的索引位置+扩容前的容量得和值确定位置。我们举个例子,假设原来的容量是16,那么扩容之后的容量就是32。假设原来桶的位置为index。那么这个桶里面的元素只会去到扩容之后桶的index位置,或者桶的index+16位置。为什么会有这样的规律,关键在容量永远都是2的整数次幂。而且扩容是*2的扩容。为了加深大家的理解,我用一个图例来说明。
有了上面的理解,接下来,我们看HashMap的扩容逻辑就简单了,我们就直接贴代码,加注释了。
/**
* HashMap扩容函数
* 1. 容量,扩容阙值等都相应的扩大两倍
* 2. 扩容前每个桶里面的元素,重新放入扩容之后对应桶里面
*/
final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;// 扩容前容量(扩容前桶的个数)int oldThr = threshold; // 扩容前阈值int newCap, newThr = 0; // 扩容后容量,扩容后阈值if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {// 如果扩容前容量大于最大容量threshold = Integer.MAX_VALUE; // 阈值设置为最大值return oldTab; // 直接返回不需要扩容}// 扩容,容量,阈值都扩大一倍else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // initial capacity was placed in threshold// 初始容量设置为阈值newCap = oldThr;else { // 零初始阈值表示使用默认值newCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {// 如果扩容后的阈值等于0,重新计算扩容后的阈值 = 扩容后的容量*默认扩容因子float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr; // 扩容后的阈值赋值给阈值@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 创建扩容之后的桶数组table = newTab; // 赋值给tableif (oldTab != null) {// 遍历扩容之前的桶数组,遍历扩容前的每个桶for (int j = 0; j < oldCap; ++j) {Node<K,V> e;// 如果扩容之前的桶里面有元素if ((e = oldTab[j]) != null) {oldTab[j] = null;if (e.next == null)// 扩容前桶里面只有一个元素,直接放到新数组中(是可以直接放的,因为不会有别的桶的元素放到这个位置的)newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)// 扩容前桶是红黑树,做对应红黑树的拆分处理((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve order// 扩容前桶是链表,这里要再次强调下,扩容前同一个桶里面的元素,扩容之后只会往两个桶放这些元素,我们前面讲的很清楚。要不就是还是保留原来的索引位置,要不就是原来的索引位置在加上扩容前的容量Node<K,V> loHead = null, loTail = null; // 保留扩容前索引位置的,头和尾Node<K,V> hiHead = null, hiTail = null; // 扩容索引位置+扩容前容量的位置,头和尾Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) { // 扩容后桶的索引位置还是扩容前索引位置if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else { // 扩容后桶的索引位置 = 扩容前索引位置 + 扩容前容量if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;// 把链表的头告诉桶}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;// 把链表的头告诉桶}}}}}return newTab;
}
/**
* 红黑树的拆分处理
*/
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {TreeNode<K,V> b = this;// Relink into lo and hi lists, preserving order/*这里需要再次强调一点,在扩容的过程中,桶一个桶里面的元素,在扩容之后只会在两个位置,要么还是保留扩容前桶索引位置,要么去到扩容前桶索引位置+扩容前容量什么意思,我们举个例子。假设原来的HashMap的数组长度是16,那么扩容之后的数组的长度是32在比如这个时候,扩容前HashMap,数组下标3里面的所有的元素。在扩容之后。要么在扩容之后数组下标3里面要么在下标 3+16=19里面*/TreeNode<K,V> loHead = null, loTail = null; // 低位(还是放入扩容器前桶的索引位置),loHead首节点,loTail尾节点TreeNode<K,V> hiHead = null, hiTail = null; // 高位(放入扩容前桶索引位置+扩容前容量),hiHead首节点,hiTail尾节点int lc = 0, hc = 0;/*先把红黑树里面的每个元素,找到对应的数组的数组下标,并且组成一个双向链表*/for (TreeNode<K,V> e = b, next; e != null; e = next) {next = (TreeNode<K,V>)e.next;e.next = null;if ((e.hash & bit) == 0) { // 扩容之后还是保留原来的索引位置if ((e.prev = loTail) == null)loHead = e;elseloTail.next = e;loTail = e;++lc;}else { // 扩容之后去到 扩容前索引位置+扩容前容量if ((e.prev = hiTail) == null)hiHead = e;elsehiTail.next = e;hiTail = e;++hc;}}if (loHead != null) {if (lc <= UNTREEIFY_THRESHOLD)// 如果loHead上的树节点小于等于6个那就去树化变回链表tab[index] = loHead.untreeify(map);else {tab[index] = loHead;if (hiHead != null) // (else is already treeified)// 转换成红黑树loHead.treeify(tab);}}if (hiHead != null) {if (hc <= UNTREEIFY_THRESHOLD)// 如果loHead上的树节点小于等于6个那就去树化变回链表tab[index + bit] = hiHead.untreeify(map);else {tab[index + bit] = hiHead;if (loHead != null)// 转换成红黑树hiHead.treeify(tab);}}
}
2.3 treeifyBin()树化
HashMap的树化,就是把链表转换为红黑树。当往HashMap里面添加元素的时候,随着桶里面元素的增加,当桶里面元素的个数大于8(TREEIFY_THRESHOLD),并且HashMap的容量大于64(MIN_TREEIFY_CAPACITY)的时候才会把链表树化成红黑树。先转换成二叉树,在对二叉树做红黑树的平衡旋转处理。关于红黑树的原理,建议大家去网上找一些资料看看,还是挺有意思的。
红黑树特性
- 左子节点小于右子节点。(方便搜索)
- 节点是红色或者黑色。
- 根节点是黑色。
- 每个叶子的节点都是黑色的空节点(NULL)。
- 每个红色节点的两个子节点都是黑色。(从每个叶子节点到根节点的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。
/**
* treeifyBin方法用于把桶的两遍转换为红黑树
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)// 虽然桶里面的元素大于8了,但是容量还没到到64(MIN_TREEIFY_CAPACITY),还是进行扩容resize();else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K,V> hd = null, tl = null; // hd首节点,tl尾节点do {// Entry做结构转换每个节点都转换成TreeNode,先组成一个双向链表(每个节点都有prev,next)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);// 上面部分组成了一个双向链表(每个节点都有prev,next)// 把组成的双向链表,红黑树树化,转换成一个红黑树if ((tab[index] = hd) != null)hd.treeify(tab);}
}
/**
* 红黑树的树化过程
*/
final void treeify(Node<K,V>[] tab) {TreeNode<K,V> root = null; // 红黑树的根节点/*依次遍历,双向链表的每个节点。root是组成之后红黑树的根节点,x是当前想放入红黑树的节点,next是下一个想放入红黑树的节点*/for (TreeNode<K,V> x = this, next; x != null; x = next) {next = (TreeNode<K,V>)x.next;x.left = x.right = null; // 当前操作节点的左节点,右节点清空if (root == null) {// 处理第一个节点x.parent = null;x.red = false;root = x;}else {K k = x.key; // 准备放入红黑树节点对应的keyint h = x.hash; // 准备放入红黑树节点对应的hashClass<?> kc = null;for (TreeNode<K,V> p = root;;) { // 从红黑树的根节点开始遍历,p是红黑树中当前遍历到的节点int dir, ph; // dir:-1或0 左孩子,1 右孩子(红黑树也是二叉树,要保证左孩子小于右孩子)K pk = p.key;/*比较hash的大小,红黑树也是二叉树,要保证左孩子小于右孩子*/if ((ph = p.hash) > h) // 红黑树遍历到的节点的hash大于当前准备放入节点的hash。所以需要放入的节点肯定放在红黑树遍历到的当前节点的左孩子里面dir = -1;else if (ph < h) // 红黑树遍历到的节点的hash小于当前准备放入节点的hash。所以需要放入的节点肯定放在红黑树遍历到的当前节点的右孩子里面dir = 1;else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0) // 相等的情况下,左其他方式的比较dir = tieBreakOrder(k, pk);TreeNode<K,V> xp = p;/*找到需要放入红黑树节点的位置,*/if ((p = (dir <= 0) ? p.left : p.right) == null) {x.parent = xp;if (dir <= 0)xp.left = x; // 左孩子elsexp.right = x; // 右孩子root = balanceInsertion(root, x); // 红黑树平衡操作-其实上面一大段还只是形成了二叉树,只有对二叉树做了红黑树的平衡操作,才能成为红黑树break;}}}}moveRootToFront(tab, root);
}
/**
红黑树的平衡算法,当树结构中新插入了一个节点后,要对树进行重新的结构化,以保证树始终维持红黑树特性
*/
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {x.red = true; // 新插入的节点标记为红色节点/*这一步即定义了变量,又开启了循环,循环没有控制条件,只能从内部跳出xp:父节点、xpp:爷爷节点、xppl:左叔叔节点、xppr:右叔叔节点*/for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {// 如果父节点为空、说明当前节点就是根节点,那么把当前节点标为黑色,返回当前节点if ((xp = x.parent) == null) {x.red = false;return x;}else if (!xp.red || (xpp = xp.parent) == null)return root;if (xp == (xppl = xpp.left)) { // 父节点是爷爷节点的左孩子if ((xppr = xpp.right) != null && xppr.red) { // 如果右叔叔不为空 并且 为红色xppr.red = false; // 右叔叔置为黑色xp.red = false; // 父节点置为黑色xpp.red = true; // 爷爷节点置为红色x = xpp;}else { // 如果右叔叔为空 或者 为黑色if (x == xp.right) { // 如果当前节点是父节点的右孩子root = rotateLeft(root, x = xp); // 父节点左旋xpp = (xp = x.parent) == null ? null : xp.parent; // 获取爷爷节点}if (xp != null) { // 如果父节点不为空xp.red = false; // 父节点 置为黑色if (xpp != null) { // 爷爷节点不为空xpp.red = true; // 爷爷节点置为 红色root = rotateRight(root, xpp); //爷爷节点右旋}}}}else { // 父节点是爷爷节点的右孩子if (xppl != null && xppl.red) { // 如果左叔叔是红色xppl.red = false; // 左叔叔置为 黑色xp.red = false; // 父节点置为黑色xpp.red = true; // 爷爷置为红色x = xpp;}else { // 如果左叔叔为空或者是黑色if (x == xp.left) { // 如果当前节点是个左孩子root = rotateRight(root, x = xp); // 针对父节点做右旋xpp = (xp = x.parent) == null ? null : xp.parent; // 获取爷爷节点}if (xp != null) { // 如果父节点不为空xp.red = false; // 父节点置为黑色if (xpp != null) { //如果爷爷节点不为空xpp.red = true; // 爷爷节点置为红色root = rotateLeft(root, xpp); // 针对爷爷节点做左旋}}}}}
}
三 总结
通过对HashMap的实现做简单的分析,咱们可以总结出如下信息:
- HashMap的初始容量是16。
- HashMap的容量永远都是2的整数次幂,扩容之后的容量 = 扩容之前的容量*2。
- HashMap扩容时机,当HashMap里面的元素个数 > 容量 * loadFactor (默认0.75)。
- HashMap树化时机(链表转红黑树),当桶里面的元素个数 >= 8(TREEIFY_THRESHOLD),并且HashMap的容量 > 64(MIN_TREEIFY_CAPACITY)。
- HashMap去树化的时机(红黑树转链表),当红黑树里面的元素个数 <= 6(UNTREEIFY_THRESHOLD)。
- HashMap是线程不安全的,我们在分析过程中没有看到任何线程安全的保障。
相关文章:
JDK8 HashMap的实现原理
一 HashMap底层存储结构 HashMap底层结构采用(数组)(链表 or 红黑树)的形式来存储节点。 首先HashMap是一个数组,而且数组里面每个位置可以放入多个元素,形象一点,咱们把数组的这些个位置称为桶…...
YOLO拓展-锚框(anchor box)详解
一.锚框(anchor box)概述 1.1什么是锚框 锚框就是一种进行预测的像素框,通过遍历输入图像上所有可能的像素框,然后选出正确的目标框,并对位置和大小进行调整就可以完成目标检测任务。 对于yolo锚框的建设须基于实际…...
U-Boot(Universal Bootloader)简介
U-Boot 是一种开源的、高度可定制的 引导加载程序(Bootloader),专为嵌入式系统和特定硬件平台设计。它负责在设备上电后初始化硬件、加载操作系统内核,并将控制权移交给操作系统,是嵌入式设备启动过程中不可或缺的核心…...
turtle库绘制进阶图形
要求: 1.绘制嵌套彩色五角星(大小逐层递减) 2. 设计函数绘制自定义正多边形(边数与颜色参数化) 3. 扩展:实现动态旋转花瓣图案 代码: import turtledef draw_nested_star():colors ["…...
[matlab]南海地形眩晕图代码
[matlab]南海地形眩晕图代码 请ChatGPT帮写个南海地形眩晕图代码 图片 图片 代码 .rtcContent { padding: 30px; } .lineNode {font-size: 12pt; font-family: "Times New Roman", Menlo, Monaco, Consolas, "Courier New", monospace; font-style: n…...
3. 进程概念
目录 1. 冯诺依曼体系结构 2. 操作系统 3. 理解进程的一般思路 4. 查看进程 5. fork初识 6. 进程状态 6.1 一般操作系统 6.2 Linux系统是怎么维护进程状态的 7. 进程优先级 先谈硬件-再谈软件-最后谈进程。 1. 冯诺依曼体系结构 我们常见的计算机(笔记本电…...
yolov8的数据处理lableimg的安装以及使用
视频数据集准备 video cv2.VideoCapture("./BVN.mp4") num 0 # 计数器 save_step 30 # 间隔帧 while True:rel, frame video.read()if not ret:breaknum 1if num % save_step 0:cv2.imwrite("./demo images/" str(num) ".jpg", frame)l…...
小刚说C语言刷题——1035 判断成绩等级
1.题目描述 输入某学生成绩,如果 86分以上(包括 86分)则输出 VERY GOOD ,如果在 60到 85之间的则输出 GOOD (包括 60和 85),小于 60 的则输出 BAD。 输入 输入只有一行,包括 1个整数。 输出 输出只有一行…...
Spring 依赖冲突解决方案详解
引言 在Spring框架中,依赖管理是一个核心功能,它使得开发者能够轻松地管理应用程序中的各种组件和服务。然而,随着项目的增长和复杂度的增加,依赖冲突问题也变得日益常见。本文将详细介绍Spring中不同类型的依赖冲突及其解决方法…...
P11299 [NOISG 2021 Finals] Fraud 题解
题目背景 你被任命为第 24 届全国信息学奥林匹克竞赛的负责人! 题目描述 本次竞赛共有 N 名参赛者和 2 轮比赛。第 i 名参赛者在第一轮获得了分,在第二轮获得了 分。 每轮比赛分别有一个正整数权重 X 和 Y。第 i 名参赛者的最终得分 计算公式为&a…...
AI时代下 你需要和想要了解的英文缩写含义
在AI智能时代下,越来愈多的企业都开始重视并应用以及开发AI相关产品,这个时候都会或多或少的涉及到英文,英文还好,但是如果是缩写,如果我们没有提前了解过,我们往往很难以快速Get到对方的意思。在这里&…...
大数据平台简介
一、分布式系统基础架构 (一)定义与核心特征 分布式系统是由多台计算机(节点)通过网络协作组成的系统,对外表现为一个统一整体。其核心特征包括: 去中心化:节点平等或分角色协作(如…...
电脑端移植至手机平板:攻克难题,仙盟架构显神通——仙盟创梦IDE
在将电脑端应用移植到手机和平板的过程中,常面临诸多棘手问题。像 1.x 号关闭按钮因位置设计欠佳,难以被用户精准点击,字体过小导致阅读与操作不便等。未来之窗仙盟创梦凭借创新的仙盟架构,巧妙且高效地化解了这些困扰开发者与用户…...
基于Python的中国象棋小游戏的设计与实现
基于Python的中国象棋小游戏的设计与实现 第一章 绪论1.1 研究背景1.2 研究意义 第二章 需求分析2.1 需求分析2.1.1核心功能需求2.1.2 用户体验需求2.1.3 衍生功能需求 2.2 可行性分析2.2.1 技术可行性2.2.2 经济可行性2.2.3 市场可行性2.2.4 法律与合规性 第三章 概要设计3.1 …...
HCIP --- OSPF综合实验
一、拓扑图 二、实验要求 1,R5为ISP,其上只能配置IP地址;R4作为企业边界路由器,出口公网地址需要通过PPP协议获取,并进行chap认证。 2,整个0SPF环境IP基于172.16.0.8/16划分。 3,所有设备均可访问R5的环…...
【OpenGL】OpenGL学习笔记-1:VS2019配置OpenGL开发环境
在Visual Studio 2019中可以通过手动配置库文件或NuGet包管理器快速安装的方法配置OpenGL环境,详细步骤如下: 一、打开VS2019,创建新的控制台项目 二、方法一:手动配置GLEW/GLFW/GLAD库 GLFW是窗口管理和输入事件的基础设施&…...
GWAS_LD
局部LDblock 绘图 1. 查看显著位点附近基因情况 链接pvalue显著位点文件 ln -s ~/yiyaoran/GWAS/my_GWAS_J/P3.GWAS/01.tassel/mlm_output.manht_figure.sigSite.out . #也可以自己筛选awk $2 9 && $4 < 0.000028481 mlm_output.manht_input>368_GWAS.snpsnp两…...
WinForms开发基础:实现带X按钮的ClearableTextBox控件
前言 我们经常看到这样的带X按钮的输入框 如果使用WinForms开发中,该如何进行设计,普通的TextBox控件如何进行改造?为了提升用户体验,在TextBox文本框内添加一个“x”按钮,方便用户一键清除内容。本文将介绍如何通过继…...
直线轴承常规分类知多少?
直线轴承的分类方式多样,以下是从材质、结构形状和常规系列三个维度进行的具体分类: 按主要材质分类 外壳材质:常见的有不锈钢,具有良好的耐腐蚀性,适用于一些对环境要求较高、易受腐蚀的工作场景;轴承…...
算法期末复习
算法期末复习 1.单选题 \1. 二分搜索算法是利用( A)实现的算法。 A. 分治策略 B. 动态规划法 C. 贪心法 D. 回溯法 \2. 回溯法解旅行售货员问题时的解空间树时( C ) 。 A. 子集树 B. 深度优先生成树 C. 排序树 D. 广度优先生成树 \3. 下列算法中通常以自底向上的方式求解最…...
LeetCode 5:最长回文子串
1、题目描述 给你一个字符串 s,找到 s 中最长的 回文 子串。 示例 1: 输入:s "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。 示例 2: 输入:s "cbbd" 输出&#…...
2025年4月19日 记录大模型出现的计算问题
2025年4月19日 记录大模型出现的计算问题,用了四个大模型计算json的数值,3个错误,1个正确 问题 Class Train Val answer 2574 853 screen 5025 1959 blackBoard 7847 3445 teacher 8490 3228 stand…...
Python语法系列博客 · 第3期 数据结构入门(列表、元组、字典、集合)
上一期小练习解答(第2期回顾) ✅ 练习1:判断一个数是正数、负数还是零 num float(input("请输入一个数:")) if num > 0:print("正数") elif num < 0:print("负数") else:print("零&q…...
【对Linux文件权限的深入理解】
Linux文件权限 Linux下权限概念概念相关命令 Linux的文件权限管理1.文件访问者的分类(⼈)文件类型和访问权限(事物属性)文件权限值的表示方法⽂件访问权限的相关设置方法目录的权限(比较重要)粘滞位 Linux下…...
2025.04.19【Spider】| 蜘蛛图绘制技巧精解
Basic multi-group radar chart Start with a basic version, learn how to format your input dataset Radar chart with ggradar A Spider chart made using the ggradar package and a lot of customization.A work by Tuo Wang 文章目录 Basic multi-group radar chartRa…...
AtCoder ABC402 A~D 题解
A - CBC 题目大意 给点字符串 S S S,输出其中所有大写字母。 思路 根据题意模拟即可。 代码 #include <cstdio> #include <iostream> #include <algorithm> using namespace std;int main() {string s;cin >> s;for (int i 0; i &l…...
双指针算法(部分例题解析)
快慢指针左右指针 前言 双指针,它通过设置两个指针来遍历数据,从而实现高效的查找、排序、去重等操作。双指针算法的核心在于通过合理地移动这两个指针,减少不必要的遍历,提高算法的效率。 283. 移动零 - 力扣(LeetCo…...
PHP怎样判断浏览器类型和浏览器语言?
获取浏览器类型 $_SERVER[HTTP_USER_AGENT]包含了用户代理字符串,该字符串包含了浏览器、操作系统等信息。通过分析这个字符串,可以大致判断用户使用的浏览器类型。 <?phpfunction getBrowserType() {$userAgent $_SERVER[HTTP_USER_AGENT];$brow…...
利用 i2c 快速从 Interface 生成 Class
利用 i2c 快速从 Interface 生成 Class(支持 TS & ArkTS) 在日常 TypeScript 或 ArkTS 开发中,需要根据 interface 定义手动实现对应的 class,这既重复又容易出错。分享一个命令行工具 —— interface2class,简称…...
Vue+Notification 自定义消息通知组件 支持数据分页 实时更新
效果图: message.vue 消息组件 子组件 <template><div class"custom-notification"><div class"content"><span click"gotoMessageList(currentMessage.split()[1])">{{ currentMessage.split()[0] }}</…...
机械设计【】技术要求(实际使用)
目录 台板技术要求加工件技术要求钣金件技术要求工装型腔技术要求铝型材框架技术要求装配体技术要求焊接件技术要求1(外形尺寸≥1500mm)焊接件技术要求2(外形尺寸<1500mm)焊接件技术要求3(不锈钢)其他要求台板技术要求 1.台板下表面周边不倒角,其余未注倒角C0.5; 2.去…...
遨游科普:防爆平板是指什么?有哪些应用场景?
在石油开采平台的炽热甲板、化工园区的反应釜旁、矿井巷道的瓦斯弥漫区,总能看到一群手持特殊设备的作业人员。他们手中的平板并非寻常消费电子产品,而是专门应对极端环境的防爆平板。防爆平板承载着工业安全的核心诉求,其技术演进与应用拓展…...
【GCC】gcc编译学习
文章目录 1. 过程2. 常用命令选项3. 多个源文件编译参考内容 1. 过程 step1 : 预处理,生成.i文件(预处理器cpp) gcc -E [源文件] -o [生成的.i文件] gcc -E test.c -o test.istep2 : 汇编,将预处理后的文件转换为汇编语言生成.s…...
不确定与非单调推理的可信度方法
可信度方法是肖特里菲(E.H.Shortliffe)等人在确定性理论(Theoryof Comfirmation)的基础上,结合概率论等提出的一种不确定性推理方法,首先在专家系统MYCIN中得到了成功的应用。由于该方法比较直观、简单,而且效果也比较好,因而受到人们的重视。目前,许多专家系统都是基于…...
个人自用-导入安装Hexo
因为本人原来就有备份好的资料,所以重新安装起来会很方便,这个教程也只适合我自己用 但是所有的命令行都要在Git的命令行里面使用(因为我就是这样操作的) 1 安装Git Git的官网 Git git --version 这个是查看Git的版本 git --…...
2025年最新版 Git和Github的绑定方法,以及通过Git提交文件至Github的具体流程(详细版)
文章目录 Git和Github的绑定方法与如何上传至代码仓库一. 注册 GitHub 账号二.如何创建自己的代码仓库:1.登入Github账号,完成登入后会进入如下界面:2.点击下图中红色框选的按钮中的下拉列表3.选择New repostitory4.进入创建界面后࿰…...
Java 动态代理实现
Java 动态代理实现 一、JDK动态代理二、CGLIB动态代理三、动态代理的应用场景四、JDK代理与CGLIB代理比较 动态代理是Java中一种强大的技术,它允许在运行时创建代理对象,用于拦截对目标对象的方法调用。 一、JDK动态代理 JDK动态代理是Java标准库提供的代…...
从零开始搭建CLIP模型实现基于文本的图像检索
目录 CLIP原理简介代码实现参考链接 CLIP原理简介 论文链接,源码链接 CLIP模型由OpenAI在2021年提出,利用双Decoder(Dual Encoder)的架构来学习图像和文本之间的对应关系,是多模态大模型的开创之作,为后续许…...
健康养生之道
在快节奏的现代生活中,健康养生不再是中老年人的专属话题,越来越多的人开始意识到,合理的养生方式是保持良好身体状态和生活质量的关键。 饮食养生是健康的基石。遵循 “食物多样、谷类为主” 的原则,保证每天摄入足够的蔬菜、…...
基于autoware.1.14与gazebo联合仿真进行Hybrid A* 算法规划控制代价地图版
1.首先安装autoware ,大家可以以下一下博客进行安装,如果缺少库什么的直接问ai安装对应的库就行。ubuntu18.04安装Autoware1.14---GPU版 最全环境配置说明_autoware1.14安装教程-CSDN博客 安装成功后运行: source install/setup.bash roslau…...
5G基站设计难题:尺寸、重量、功耗和散热
设计5G基站的工程师们必须应对能源消耗、重量、尺寸和散热等问题,这些因素会影响到设计决策。 5G新空口(NR)采用了多用户大规模多输入多输出(MU-MIMO)技术、集成接入与回传(IAB)技术࿰…...
【leetcode100】分割等和子集
1、题目描述 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 示例 1: 输入:nums [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11…...
sed命令笔记250419
sed命令笔记250419 sed(Stream Editor)是 Linux/Unix 系统中强大的流编辑器,主要用于对文本进行过滤和转换(按行处理)。它支持正则表达式,适合处理文本替换、删除、插入等操作。以下是 sed 的详细解析&…...
LinearLayout 线性布局
目录 Android LinearLayout(线性布局)简单介绍与使用示例 一、效果介绍 二、布局文件(XML) 三、Java 代码 四、程序运行效果 五、总结 在 Android 移动应用开发中,LinearLayout(线性布局)…...
System.in 详解
System.in 详解 System.in 是 Java 提供的标准输入流(InputStream 类型),默认关联键盘输入,通常用于从控制台读取用户输入。由于它是字节流(InputStream),直接使用较麻烦,一般会配合…...
JAVA IO、BIO、NIO、AIO及零拷贝
概述 IO,常写作 I/O,是 Input/Output 的简称,是 Input/Output 的简称,即输入/输出。通常指数据在内部存储器(内存)和外部存储器(硬盘、优盘等)或其他周边设备之间的输入和输出。 目前有三种 IO 共存。分别是 BIO、NIO 和 AIO。 BIO 全称 Block-IO 是一种同步且阻塞的…...
AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年4月19日第57弹
从今天开始,咱们还是暂时基于旧的模型进行预测,好了,废话不多说,按照老办法,重点8-9码定位,配合三胆下1或下2,杀1-2个和尾,再杀6-8个和值,可以做到100-300注左右。 (1)定…...
REST 架构详解:从概念到应用的全面剖析
REST(Representational State Transfer)即表述性状态转移,是一种用于构建网络应用程序的架构风格和设计理念,由计算机科学家罗伊・菲尔丁(Roy Fielding)在 2000 年提出。以下是关于它的详细介绍:…...
SICAR程序标准功能块 FB1512 “Robot_kuka_FB“
1、FB1512功能块截图 2、FB1512 功能块引脚功能定义 一、输入引脚 EN:使能输入,决定功能块是否执行。IDENTIFIER(WSTRING#"FW010_R01"):设备标识,指定关联的机器人设备。OPMODE_USER_INTERFACE_OUT:操作模式输入,定义机器人工作模式(如手动、自动),数据源…...
win安装软件
win安装软件 jdk安装 jdk安装 首先去官网下载适合系统版本的JDK,下载地址: http://www.oracle.com/technetwork/java/javase/downloads/index.html进入下载页面,如下图: 首先选择:Accept License Agreement单选按钮&…...