HashMap数据结构为数组加链表加红黑树、只有当链表数量大于8时、才将链表转换为红黑树、时间复杂度由链表的O(N)转换为红黑树的O(logN)
// 主要看getNode下的方法、传入key的hash值和key
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
// 返回一个Node对象、包含了key和value、在get方法中在返回value值
final Node<K,V> getNode(int hash, Object key) {
// tab:Node<K, V>对象数组
Node<K,V>[] tab;
// first: 指向key hash值对应的数组值 e: first对应Node对象的下一个节点
Node<K,V> first, e;
// n: 指向当前HashMpa的数组长度
int n;
// k: 临时变量、指向 key
K k;
// 这里检测HashMap对象的数组是否存在、长度是否大于0、((n - 1) & hash)根据此表达式算出key对应的数组位置、在检查是否存在对象。
if (
(tab = table) != null &&
(n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null
) {
// first当前值为一个Node节点
// 这个if是检测当前的first指向的Node是否是要获取的对象
// 直接判断first的hash值和要获取的hash值是否一直、并且key的值是否一直、通过 ==判断地址!=null和equals判断、key赋值为first的key
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
// 判断一致后直接返回要获取的Node节点给get、get在返回Node的value值
return first;
// 由于上面查找都查找不到、所以要查找Node的下一个节点、即查询链表或者红黑树
if ((e = first.next) != null) {
// 检查first对象是否是TreeNode(红黑树)
if (first instanceof TreeNode)
// 当前first为红黑树对象、直接根据key调用内部的检索方法获取对应的value
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
// 链表查询、由于first上面判断过不是要查找的对象、e在上面语句已经指向first下一个节点、所以直接开始判断
// 和上面的判断一样、检查hash值和key、qeuals判断、如果有则返回对应的Node对象、没有则最终执行下面的return null;语句。
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
// 表示当前对象并没有存储相关的key值、返回null
return null;
}
public V put(K key, V value) {
// 内部调用putVal设置值、参数如下int hash, K key, V value, boolean onlyIfAbsent(如果为 true,则不更改现有值),boolean evict(如果为 false,则表处于创建模式)
return putVal(hash(key), key, value, false, true);
}
// 参数如下int hash, K key, V value, boolean onlyIfAbsent(如果为 true,则不更改现有值),boolean evict(如果为 false,则表处于创建模式)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// tab指向当前HashMap对象数组
Node<K,V>[] tab;
// p指向key的hash值所在的数组Node对象
Node<K,V> p;
// n:HashMap数组的长度、i:key的hash值对应的索引(index)
int n, i;
// 判断当前HashMap的数组对象是否为空、并且长度是否为0
if ((tab = table) == null || (n = tab.length) == 0)
// 分配数组空间并把长度返回给n
n = (tab = resize()).length;
// 计算hash对应的索引是否有对象存在、没有的话则创建Node对象、并将要put的值写入Node对象、在返回给数组
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 要将Node对象写入到链表或者红黑树中
else {
// e: 代表最终你要写入的Node对象
Node<K,V> e;
// k: 指向hash值对象的Node节点的key
K k;
// 检查是否是同一个hash值、key是否相对或者进行equals判断
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 代表同一个对象、赋值给e、最后在写入值
e = p;
// 检测是否是红黑树节点
else if (p instanceof TreeNode)
// 检测是红黑树对象、直接调用内部的写入方法、在返回一个Node<K, V>节点对象、最后在写入值、putTreeVal里面其实已经写入value了、后面在写入一次。
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 链表操作、binCount检测有多少个链表节点、根据TREEIFY_THRESHOLD常量设定的值8、超过8个链表节点、则将该链表转换为红黑树
for (int binCount = 0; ; ++binCount) {
//
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 将数组传入
treeifyBin(tab, hash);
break;
}
// 检测当前插入的对象是否一致、一致的话直接返回e、下面在将要写入的值赋值给变量e对象
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 检测e对象是否不为空、不为空则下面写入对应的value值
if (e != null) { // existing mapping for key
V oldValue = e.value;
// onlyIfAbsent为true表示不更新对象
if (!onlyIfAbsent || oldValue == null)
// 将值写入
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 当前数组长度自增1大于上次扩容长度后、重新扩容并且把重新扩容的大小赋值给threshold
if (++size > threshold)
// 重新调整数组长度
resize();
// LinkedHashMap中重写了、HashMap中没有具体实现
afterNodeInsertion(evict);
// 对应的key无法写入、返回null
return null;
}
final Node<K,V>[] resize() {
// 获取当前数组
Node<K,V>[] oldTab = table;
// 获取数组长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// size > threshold (capacity * loadFactor) 执行扩容
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// MAXIMUM_CAPACITY数组最大容量、如果大于等于则不扩容、直接返回数组
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// oldCap << 1: 数组长度 * 2
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// newThr = oldThr * 2
newThr = oldThr << 1;
}
// 数组长度为0时的情况、并且调用构造函数初始话过 loadFactor和threshold
else if (oldThr > 0)
// 初始化新创建的数组长度
newCap = oldThr;
// 默认情况、即无参构造函数、第一次数组扩容会走这里
else {
// 默认数组长度16
newCap = DEFAULT_INITIAL_CAPACITY;
// 默认 threshold = 默认装载因子0.75f * 默认数组长度16 = 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 计算newThr、防止过大
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
// 抑制警告
@SuppressWarnings({"rawtypes","unchecked"})
// 创建新的数组、并返回给HashMap对象
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 对整个数据结构的重新操作、即将旧的数据结构(数组+链表+红黑树)中的数据一个个转移至新创建的数组中
if (oldTab != null) {
// 先对之前的长度遍历
for (int j = 0; j < oldCap; ++j) {
// 临时Node节点
Node<K,V> e;
// 遍历之前的数组、从中取出数据转移至新数组中、并将之前的数组引用着的对象置为null、方便垃圾回收
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 数组中获得的节点没有下一个节点的情况
if (e.next == null)
// 直接给新数组传递Node节点
newTab[e.hash & (newCap - 1)] = e;
// 当数组中的节点存在下一个节点并且是红黑树时
else if (e instanceof TreeNode)
// 红黑树数据转移
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 当数组中的节点存在下一个节点并且是链表
else {
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
// 循环遍历当前链表中的Node节点
do {
// 先获取下一个Node节点
next = e.next;
// 判断当前节点是否需要移位、即在新数组中的索引位置和旧数组的索引位置是否相同
// 等价于 (e.hash & (newCap - 1)) == j
// 不移位的Node节点
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
// 移位的Node节点
else {
if (hiTail == null)
hiHead = e;
else
hiTail.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) {
// 获取要转移的红黑树Node节点
TreeNode<K,V> b = this;
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
// 遍历红黑树的Node节点、因为该Node继承了LinkedHashMap.Entry、所有可以根据next遍历整个树的节点
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
// 判断当前节点是否需要移位、即在新数组中的索引位置和旧数组的索引位置是否相同
// 等价于 (e.hash & (newCap - 1)) == j
// 不移位的Node节点
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
// 移位的Node节点
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
// 检查红黑树节点数量、小于6则转换为链表
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null)
// 将新的节点转换为红黑树
loHead.treeify(tab);
}
}
if (hiHead != null) {
// 检查红黑树节点数量、小于6则转换为链表
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
// 将新的节点转换为红黑树
hiHead.treeify(tab);
}
}
}
size表示HashMap中存放KV的数量(为链表和树中的KV的总和)。
capacity译为容量。capacity就是指HashMap中数组的数量。默认值为16。一般第一次扩容时会扩容到64,之后好像是2倍。总之,容量都是2的幂。
loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。计算HashMap的实时装载因子的方法为:size/ capacity
默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4
对于一个默认的HashMap来说,默认情况下,当其size大于12(16*0.75)时就会触发扩容。
threshold表示当HashMap的size大于threshold时会执行resize(数组扩容)操作。
threshold = capacity * loadFactor
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标
网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.
1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>
参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍 介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。 内容有: ①:Hub模型的方法介绍 ②:服务器端代码介绍 ③:前端vue3安装并调用后端方法 ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke() 去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on
一、机器人介绍 此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接
目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'
我想使用ruby-prof和JMeter分析Rails应用程序。我对分析特定Controller/操作/或模型方法的建议方法不感兴趣,我想分析完整堆栈,从上到下。所以我运行这样的东西:RAILS_ENV=productionruby-prof-fprof.outscript/server>/dev/null然后我在上面运行我的JMeter测试计划。然而,问题是使用CTRL+C或SIGKILL中断它也会在ruby-prof可以写入任何输出之前杀死它。如何在不中断ruby-prof的情况下停止mongrel服务器? 最佳答案
文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cppdumper例子2-森林whoishe后记认识unity打包目录结构dll一般很大,因为里面是所有的游戏功能编译成的二进制码游戏逆向流程开发人员代码被编译打包到GameAssembly.dll中使用il2ppDumper工具,并借助游戏名_Data\il2cpp_data\Metadata\global-metadata.dat