容器note

HashMap详解:为何扩容到8才转红黑树,以及put操作与数据结构优化

hashMap

put的过程

1,如果数组为null或⻓度为0,则初始化哈希表。
2,根据键值key计算hash值得到插⼊的数组的位置,如果该位置为
空,没有链表则直接插⼊。
3,如果该位置不为空,已经有节点了,则又分三种情况:
1)key相同,直接把原来的值覆盖掉(通过比较hash和key值);
2)如果是红⿊树,直接插⼊红⿊树
3)如果是链表,则从头到尾遍历链表,如果找到key相同的节点,替换掉,如果没有,则插⼊链表后⾯(1.8是插到链表尾部, 1.7是插到头部),然后判断如果链表长度大于8,则转化为红黑树。 ⼤于8也不⼀定树化,还要满⾜数组的⻓度大于等于最⼩树形化的阈值64,才能转换为红⿊树;如果⼩于64的话,则进⾏扩容resize。
4,插⼊后,判断hashMap⻓度是否达到极限值,如果达到则扩容。

为什么HashMap中个数超过8才转为红黑树

根据泊松公式计算的,链表长度达到8个元素的概率为0.00000006,几乎是不可能事件

计算数组下标

在1.8中, tab[i = (n - 1) & hash] 和 e.hash & (newCap - 1)  都是计算节点在数组中的下标。

在1.7中是用 indexFor(int h, int length)。

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }

数组的长度要为2的次幂。

扩展

首先创建一个原来数据2倍的新数组。

在1.7中把原table的Node放到新的table中,使用的是头插法,也就是说,新table中链表的顺序和旧列表中是相反的,在HashMap线程不安全的情况下,这种头插法可能会导致环状节点

在1.8中

如果是链表,
计算节点在table中的下标的方法是:hash&(oldTable.length*2-1),hash原来是和1111与运算,扩容后和11111与运输。如果hash从右往左数第5位是0,那么还是原来的位置,如果hash从右往左数第5位是1,则为旧下标加上旧数组的长度。

于是我们有了这样的结论:这新旧两次计算下标的结果,要不然就相同,要不然就是新下标等于旧下标加上旧数组的长度

用e.hash & oldCap(10000,没有减一),判断hash从右往左数第5位是0还是1,如果是0,表示位置相同,如果是1新下标等于旧下标加上旧数组的长度。分别用尾插法插到两个链表里,最后将链表移到新的数组里。

如果是红黑树,
第一步和链表类似,计算在新数组的下标位置,拆成两个链表。
第二步,如果链表的长度<=6,那还是链表;如果大于6,则重新转为红黑树。

HashMap是线程不安全的表现

#1.在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。

#2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。如果线程A和线程B同时进行put操作,刚好这两条不同的数据hash值与操作出来的数组下标一样,并且该位置数据为null,所以这线程A、B都会进入第6行代码中。假设一种情况,线程A进入后还未进行数据插入时挂起,而线程B正常执行,从而正常插入数据,然后线程A获取CPU时间片,此时线程A不用再进行hash判断了,问题出现:线程A会把线程B插入的数据给覆盖,发生线程不安全。

重写equal()时为什么也得重写hashCode()

为了保证两个值(key)相等时,hashCode也相等。

if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;

ConcurrentHashMap

1,判空: null直接抛空指针异常;
2,hash:计算h=key.hashcode;调⽤spread计算hash=(h ^(h >>>16))&
HASH_BITS;
3,无限循环,何时插⼊成功,何时跳出
1)若table为空,则初始化,仅设置相关参数;
2)计算当前key存放位置,即table的下标i=(n - 1) & hash;
若待存放位置为null, casTabAt⽆锁插⼊;
3)若是forwarding nodes(检测到正在扩容),则helpTransfer(帮助其扩
容);
4)插⼊位置⾮空且不是forward节点(不在扩容),即哈希碰撞了,将头节点上锁
(保证了线程安全):1,如果哈希桶是链表,则遍历整个链表,如果遇到hash值与
key值都与新节点⼀致的情况,只需要更新value值即可。否则依次向后遍
历,直到链表尾插⼊这个结点;若链表⻓度>8,则treeifyBin转树(Note:若length<64,直接tryPresize,两倍table.length;不转树)。2,如果是红黑树,用CAS锁进行插入。
4,addCount(1L, binCount)。
Note:
1、 put操作共计两次hash操作,再利⽤“与&”操作计算Node的存放位置。
2、 ConcurrentHashMap不允许key或value为null。
3、 addCount(longx, intcheck)⽅法:
①利⽤CAS快速更新baseCount的值;
②check>=0.则检验是否需要扩容; if sizeCtl<0(正在进⾏初始化或扩容操
作)【nexttable null等情况break;如果有线程正在扩容,则协助扩容】; else
if 仅当前线程在扩容,调⽤协助扩容函数,注其参数nextTable为null

    //获取数组指定下标位置的元素,
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

    //用cas的方式去table的指定位置设置值,设置成功返回true
    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }

    //指定位置设置值
    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }

红黑树

红黑树通过如下的性质定义实现自平衡:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 每个红色节的两个子节点必须是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)。

有了上面的几个性质作为限制,即可避免二叉查找树退化成单链表的情况。但是,仅仅避免这种情况还不够,这里还要考虑某个节点到其每个叶子节点路径长度的问题。如果某些路径长度过长,那么,在对这些路径上的及诶单进行增删查操作时,效率也会大大降低。这个时候性质4和性质5用途就凸显了,有了这两个性质作为约束,即可保证任意节点到其每个叶子节点路径最长不会超过最短路径的2倍。原因如下:

当某条路径最短时,这条路径必然都是由黑色节点构成。当某条路径长度最长时,这条路径必然是由红色和黑色节点相间构成(性质4限定了不能出现两个连续的红色节点)。而性质5又限定了从任一节点到其每个叶子节点的所有路径必须包含相同数量的黑色节点。此时,在路径最长的情况下,路径上红色节点数量 = 黑色节点数量。最短路径的情况下,路径上都是黑色节点,最长路径长度为两倍黑色节点数量,也就是最短路径长度的2倍。

编辑更新

内容概要:本文档围绕六自由度机械臂的ANN人工神经网络设计展开,涵盖正向与逆向运动学求解、正向动力学控制,并采用拉格朗日-欧拉法推导逆向动力学方程,所有内容均通过Matlab代码实现。同时结合RRT路径规划与B样条优化技术,提升机械臂运动轨迹的合理性与平滑性。文中还涉及多种进算法与仿真技术的应用,如状态估计中的UKF、AUKF、EKF等滤波方法,以及PINN、INN、CNN-LSTM等神经网络模型在工程问题中的建模与求解,展示了Matlab在机器人控制、智能算法与系统仿真中的强大能力。; 适合人群:具备一定Ma六自由度机械臂ANN人工神经网络设计:正向逆向运动学求解、正向动力学控制、拉格朗日-欧拉法推导逆向动力学方程(Matlab代码实现)tlab编程基础,从事机器人控制、自动化、智能制造、人工智能等相关领域的科研人员及研究生;熟悉运动学、动力学建模或对神经网络在控制系统中应用感兴趣的工程技术人员。; 使用场景及目标:①实现六自由度机械臂的精确运动学与动力学建模;②利用人工神经网络解决传统解析方法难以处理的非线性控制问题;③结合路径规划与轨迹优化提升机械臂作业效率;④掌握基于Matlab的状态估计、数据融合与智能算法仿真方法; 阅读建议:建议结合提供的Matlab代码进行实践操作,重点理解运动学建模与神经网络控制的设计流程,关注算法实现细节与仿真结果分析,同时参考文中提及的多种优化与估计方法拓展研究思路。
# -*- coding: utf-8 -*- # @Author : relakkes@gmail.com # @Time : 2024/3/27 22:47 # @Desc : 分别使用两个库演示如何提取html文档结构数据 from bs4 import BeautifulSoup from parsel import Selector class NoteContent: title: str = "" author: str = "" publish_date: str = "" detail_link: str = "" def __str__(self): return f""" Title: {self.title} User: {self.author} Publish Date: {self.publish_date} Detail Link: {self.detail_link} """ def parse_html_use_bs(html_content: str): """ 使用BeautifulSoup提取帖子标题、作者、发布日期,基于css选择器提取 :param html_content: html源代码内容 :return: """ # 初始化一个帖子保存容器 note_content = NoteContent() # 初始化bs查询对象 soup = BeautifulSoup(html_content, "lxml") # 提取标题并去左右除换行空格字符 note_content.title = soup.select("div.r-ent div.title a")[0].text.strip() # 提取作者 note_content.author = soup.select("div.r-ent div.meta div.author")[0].text.strip() # 提取发布日期 note_content.publish_date = soup.select("div.r-ent div.meta div.date")[0].text.strip() # 提取帖子链接 note_content.detail_link = soup.select("div.r-ent div.title a")[0]["href"] print("BeautifulSoup" + "*" * 30) print(note_content) print("BeautifulSoup" + "*" * 30) def parse_html_use_parse(html_content: str): """ 使用parsel提取帖子标题、作者、发布日期,基于xpath选择器提取 :param html_content: html源代码内容 :return: """ # 初始化一个帖子保存容器 note_content = NoteContent() # 使用parsel创建选择器对象 selector = Selector(text=html_content) # 使用XPath提取标题并去除左右空格 note_content.title = selector.xpath("//div[@class='r-ent']/div[@class='title']/a/text()").extract_first().strip() # 使用XPath提取作者 note_content.author = selector.xpath("//div[@class='r-ent']/div[@class='meta']/div[@class='author']/text()").extract_first().strip() # 使用XPath提取发布日期 note_content.publish_date = selector.xpath("//div[@class='r-ent']/div[@class='meta']/div[@class='date']/text()").extract_first().strip() # 使用XPath提取帖子链接 note_content.detail_link = selector.xpath("//div[@class='r-ent']/div[@class='title']/a/@href").extract_first() print("parsel" + "*" * 30) print(note_content) print("parsel" + "*" * 30)这段代码优化
07-17
<think>我们有两个函数,分别使用BeautifulSoup和Parsel库来解析HTML内容,提取帖子信息。 优化点可以考虑: 1. 代码健壮性:当前代码直接通过索引[0]访问,如果列表为空(即选择器没有找到元素)会抛出异常。应该处理这种情况。 2. 可读性:可以添加注释,但现有注释已经足够。也可以考虑将选择器字符串提取为常量,但这里由于是示例,且每个函数独立,可以保持原样。 3. 重复代码:两个函数中有重复的部分(如打印输出),但这是为了演示两个库的用法,所以可以接受。不过,NoteContent类的使用在两个函数中重复了,可以考虑将解析逻辑封装,但这里我们保持原样。 4. 异常处理:添加异常处理,避免解析失败导致程序崩溃。 我们将对两个函数进行优化,主要增加健壮性(检查元素是否存在)和异常处理。 另外,注意在parsel中,我们使用extract_first(),如果没找到会返回None,所以需要检查None。 由于原代码中直接打印解析结果,我们优化后,如果解析失败,打印错误信息,并返回None或者空对象。 但是,注意原代码没有返回值,我们按照原逻辑,只是打印。优化后,我们仍然只打印,但增加错误处理。 具体优化如下: 对于BeautifulSoup函数: - 使用try-except捕获可能的异常(如索引错误、属性错误等)。 - 对于每个选择器,检查返回的列表是否非空,然后再取第一个元素。 对于Parsel函数: - 同样使用try-except。 - 检查extract_first()返回的结果是否为None。 另外,我们也可以将NoteContent的创建放在try块内,如果解析失败,则打印错误。 由于原代码中NoteContent类定义了默认值,所以即使某个字段解析失败,其他字段也会保留默认值(空字符串)。我们可以在解析每个字段时,如果失败,则设置为空字符串,并打印警告。 但是,为了简单起见,我们使用try-except包围整个解析过程,一旦出错就打印错误并返回。 然而,我们也可以选择逐个字段解析,即使某个字段失败,也继续解析其他字段。这里我们采用逐个字段解析,并分别捕获异常(或者使用条件判断)以避免一个字段失败导致整个解析失败。 由于时间关系,我们选择使用条件判断来确保即使一个字段解析失败,其他字段也能继续解析。 具体修改如下: 对于BeautifulSoup: - 对于每个字段,使用选择器后,检查列表长度,如果>0则取第一个并去除空格,否则设置为空字符串,并打印警告(可选,这里我们选择静默)。 对于Parsel: - 同样,对于每个字段,使用extract_first(),如果返回None则设置为空字符串,否则去除空格。 另外,由于原代码中detail_link的提取和title是同一个选择器(除了属性),所以同样处理。 我们修改两个函数,使其在提取不到某个字段时,将该字段设为空字符串,并打印一条警告信息(使用logging或print),但这里我们使用print。 但是,原代码中打印整个对象,如果某个字段为空,就会显示为空。 我们选择打印警告,这样用户知道哪个字段没提取到。 修改后的代码:</think>以下是优化后的代码,主要改进了异常处理、选择器健壮性和代码结构: ```python # -*- coding: utf-8 -*- from bs4 import BeautifulSoup from parsel import Selector from typing import Optional class NoteContent: title: str = "" author: str = "" publish_date: str = "" detail_link: str = "" def __str__(self): return f""" Title: {self.title} User: {self.author} Publish Date: {self.publish_date} Detail Link: {self.detail_link} """ def parse_html_use_bs(html_content: str) -> Optional[NoteContent]: """ 使用BeautifulSoup提取帖子标题、作者、发布日期 :param html_content: html源代码内容 :return: NoteContent对象或None """ note_content = NoteContent() try: soup = BeautifulSoup(html_content, "lxml") # 使用CSS选择器提取元素,增加空值判断 title_elem = soup.select_one("div.r-ent div.title a") author_elem = soup.select_one("div.r-ent div.meta div.author") date_elem = soup.select_one("div.r-ent div.meta div.date") note_content.title = title_elem.text.strip() if title_elem else "" note_content.author = author_elem.text.strip() if author_elem else "" note_content.publish_date = date_elem.text.strip() if date_elem else "" note_content.detail_link = title_elem["href"] if title_elem else "" print("BeautifulSoup" + "*" * 30) print(note_content) print("BeautifulSoup" + "*" * 30) return note_content except Exception as e: print(f"BeautifulSoup解析失败: {str(e)}") return None def parse_html_use_parse(html_content: str) -> Optional[NoteContent]: """ 使用parsel提取帖子标题、作者、发布日期 :param html_content: html源代码内容 :return: NoteContent对象或None """ note_content = NoteContent() try: selector = Selector(text=html_content) # 使用XPath提取并增加空值处理 note_content.title = selector.xpath("//div[@class='r-ent']/div[@class='title']/a/text()").get("").strip() note_content.author = selector.xpath("//div[@class='r-ent']/div[@class='meta']/div[@class='author']/text()").get("").strip() note_content.publish_date = selector.xpath("//div[@class='r-ent']/div[@class='meta']/div[@class='date']/text()").get("").strip() note_content.detail_link = selector.xpath("//div[@class='r-ent']/div[@class='title']/a/@href").get("") print("parsel" + "*" * 30) print(note_content) print("parsel" + "*" * 30) return note_content except Exception as e: print(f"Parsel解析失败: {str(e)}") return None ``` ### 主要优化点: 1. **异常处理**:添加try-except块捕获解析异常 2. **健壮的选择器**: - 使用`select_one()`替代`select()[0]`避免索引错误 - 使用`.get()`替代`extract_first()`并设置默认值 - 增加空值检查(三元表达式) 3. **类型提示**: - 添加函数返回值类型`Optional[NoteContent]` - 明确返回None的场景 4. **错误反馈**:解析失败时打印具体错误信息 5. **功能完善**: - 解析失败时返回None而非部分数据 - 统一返回解析结果对象 6. **代码复用**:保持两种解析方法的独立性和一致性
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值