当前位置: 首页 > news >正文

java中的容器(集合),HashMap底层原理,ArrayList、LinkedList、Vector区别,hashMap加载因子0.75原因

一、java中的容器

          集合主要分为Collection和Map两大接口;Collection集合的子接口有List、Set;List集合的实现类有ArrayList底层是数组、LinkedList底层是双向非循环列表、Vector;Set集合的实现类有HashSet、TreeSet;Map集合的实现类有HashMap、TreeMap、HashTable;

(补充:HashTable与HashMap类似,线程安全,子接口有Properties接口,线程安全)

1.HashMap底层原理?

        HashMap是以键值对形式存储数据的,底层由散列表组成,jdk1.8之前是数组+链表,jdk1.8之后数组+链表+红黑树组成。( 默认数组长度:16)

        当添加元素时,链表的长度大于等于8,数组的长度小于64,将数组长度扩容原数组长度的2倍;当链表的长度大于等于8,并且数组的长度大于等于64时将链表转为红黑树。红黑树是平衡二叉搜索树,效率高。

        当删除元素时,链表长度小于7,将红黑树转为链表。

        (补充:jdk1.8之前头插法,jdk1.8及之后尾插法;1.7创建map时默认容量16,1.8创建map时默认无容量,添加后为初始化长度为16

        Hash冲突:链地址法、开放地址法,再次hash法,建立公共溢出区)

2.ArrayList、LinkedList、Vector集合的区别?

ArrayList集合的底层是数组,适用于集合的遍历和随机访问某个元素的场景;添加元素时,每次扩容为原数组长度的1.5倍。(长度默认0,调用add方法后没有指定长度为10)

LinkedList集合的底层是双向非循环链表,中间插入和删除元素效率比较高,遍历效率比较低。

Vector集合与ArrayList类似,底层也是数组,线程是安全的,每个方法都由synchronized修饰,执行效率较低。(每次扩容为原数组长度2倍)

(补充:线程安全可以使用juc提供的集合CopyOnWriteArrayList写时复制)

二、为什么 HashMap 的加载因子是0.75?

为什么HashMap需要加载因子

  • 解决冲突有什么方法?

    • 1.开放定址法

    • 2.再哈希法

    • 3.建立一个公共溢出区

    • 4.链地址法(拉链法)

  • 为什么HashMap加载因子一定是0.75?而不是0.8,0.6?

  • 那么为什么不可以是0.8或者0.6呢?

        HashMap的底层是哈希表,是存储键值对的结构类型,它需要通过一定的计算才可以确定数据在哈希表中的存储位置:

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// AbstractMap
public int hashCode() {int h = 0;Iterator<Entry<K,V>> i = entrySet().iterator();while (i.hasNext())h += i.next().hashCode();return h;
}

        一般的数据结构,不是查询快就是插入快,HashMap就是一个插入慢、查询快的数据结构。

        但这种数据结构容易产生两种问题:

                ① 如果空间利用率高,那么经过的哈希算法计算存储位置的时候,会发现很多存储位置已经有数据了(哈希冲突);

                ② 如果为了避免发生哈希冲突,增大数组容量,就会导致空间利用率不高。

加载因子表示Hash表中元素的填满程度

1. 加载因子

加载因子 = 填入表中的元素个数 / 散列表的长度

加载因子越大,填满的元素越多,空间利用率越高,但发生冲突的机会变大了;

加载因子越小,填满的元素越少,冲突发生的机会减小,但空间浪费了更多了,而且还会提高扩容rehash操作的次数。

冲突的机会越大,说明需要查找的数据还需要通过另一个途径查找,这样查找的成本就越高。因此,必须在“冲突的机会”与“空间利用率”之间,寻找一种平衡与折衷。

所以我们也能知道,影响查找效率的因素主要有这几种:

  • 散列函数是否可以将哈希表中的数据均匀地散列?

  • 怎么处理冲突?

  • 哈希表的加载因子怎么选择?

2. 解决冲突有什么方法?

1. 开放定址法

Hi = (H(key) + di) MOD m,其中i=1,2,…,k(k<=m-1)

        H(key)为哈希函数,m为哈希表表长,di为增量序列,i为已发生冲突的次数。其中,开放定址法根据步长不同可以分为3种:

1.1 线性探查法(Linear Probing):di = 1,2,3,…,m-1

简单地说,就是以当前冲突位置为起点,步长为1循环查找,直到找到一个空的位置,如果循环完了都占不到位置,就说明容器已经满了。举个栗子,就像你在饭点去街上吃饭,挨家去看是否有位置一样。

1.2 平方探测法(Quadratic Probing):di = ±12, ±22,±32,…,±k2(k≤m/2)

相对于线性探查法,这就相当于的步长为di = i2来循环查找,直到找到空的位置。以上面那个例子来看,现在你不是挨家去看有没有位置了,而是拿手机算去第i2家店,然后去问这家店有没有位置。

1.3 伪随机探测法:di = 伪随机数序列

这个就是取随机数来作为步长。还是用上面的例子,这次就是完全按心情去选一家店问有没有位置了。

但开放定址法有这些缺点:

  • 这种方法建立起来的哈希表,当冲突多的时候数据容易堆集在一起,这时候对查找不友好;

  • 删除结点的时候不能简单将结点的空间置空,否则将截断在它填入散列表之后的同义词结点查找路径。因此如果要删除结点,只能在被删结点上添加删除标记,而不能真正删除结点;

  • 如果哈希表的空间已经满了,还需要建立一个溢出表,来存入多出来的元素。

2. 再哈希法

Hi = RHi(key), 其中i=1,2,…,k

        RHi()函数是不同于H()的哈希函数,用于同义词发生地址冲突时,计算出另一个哈希函数地址,直到不发生冲突位置。这种方法不容易产生堆集,但是会增加计算时间。

所以再哈希法的缺点是:增加了计算时间。

3. 建立一个公共溢出区

        假设哈希函数的值域为[0, m-1],设向量HashTable[0,…,m-1]为基本表,每个分量存放一个记录,另外还设置了向量OverTable[0,…,v]为溢出表。基本表中存储的是关键字的记录,一旦发生冲突,不管他们哈希函数得到的哈希地址是什么,都填入溢出表。

        但这个方法的缺点在于:查找冲突数据的时候,需要遍历溢出表才能得到数据。

4. 链地址法(拉链法)

将冲突位置的元素构造成链表。在添加数据的时候,如果哈希地址与哈希表上的元素冲突,就放在这个位置的链表上。

拉链法的优点:

  • 处理冲突的方式简单,且无堆集现象,非同义词绝不会发生冲突,因此平均查找长度较短;

  • 由于拉链法中各链表上的结点空间是动态申请的,所以它更适合造表前无法确定表长的情况;

  • 删除结点操作易于实现,只要简单地删除链表上的相应的结点即可。

拉链法的缺点:需要额外的存储空间。

从HashMap的底层结构中我们可以看到,HashMap采用是数组+链表/红黑树的组合来作为底层结构,也就是开放地址法+链地址法的方式来实现HashMap。

3. 为什么HashMap加载因子一定是0.75?而不是0.8,0.6?

        HashMap的底层其实也是哈希表(散列表),而解决冲突的方式是链地址法。HashMap的初始容量大小默认是16,为了减少冲突发生的概率,当HashMap的数组长度到达一个临界值的时候,就会触发扩容,把所有元素rehash之后再放在扩容后的容器中,这是一个相当耗时的操作。

而这个临界值就是由加载因子和当前容器的容量大小来确定的:

临界值 = DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR

即默认情况下是16x0.75=12时,就会触发扩容操作。

那么为什么选择了0.75作为HashMap的加载因子呢?这个跟一个统计学里很重要的原理——泊松分布有关。

        泊松分布是统计学和概率学常见的离散概率分布,适用于描述单位时间内随机事件发生的次数的概率分布。有兴趣推荐:维基百科或者阮一峰老师的这篇文章:泊松分布和指数分布

        等号的左边,P 表示概率,N表示某种函数关系,t 表示时间,n 表示数量。等号的右边,λ 表示事件的频率。

        在HashMap的源码中有这么一段注释:

* 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

        理想情况下,使用随机哈希码,在扩容阈值(加载因子)为0.75的情况下,节点出现在频率在Hash桶(表)中遵循参数平均为0.5的泊松分布。忽略方差,即X = λt,P(λt = k),其中λt = 0.5的情况,按公式:

        计算结果如上述的列表所示,当一个bin中的链表长度达到8个元素的时候,概率为0.00000006,几乎是一个不可能事件。

        所以其实常数0.5是作为参数代入泊松分布来计算的,而加载因子0.75是作为一个条件,当HashMap长度为length/size ≥ 0.75时就扩容,在这个条件下,冲突后的拉链长度和概率结果为:

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

4.为什么不可以是0.8或者0.6呢?

HashMap中除了哈希算法之外,有两个参数影响了性能:初始容量和加载因子。初始容量是哈希表在创建时的容量,加载因子是哈希表在其容量自动扩容之前可以达到多满的一种度量。

5. 在维基百科来描述加载因子:

        对于开放定址法,加载因子是特别重要因素,应严格限制在0.7-0.8以下。超过0.8,查表时的CPU缓存不命中(cache missing)按照指数曲线上升。因此,一些采用开放定址法的hash库,如Java的系统库限制了加载因子为0.75,超过此值将resize散列表。

        在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少扩容rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,以便减少扩容操作。

        选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择。

相关文章:

java中的容器(集合),HashMap底层原理,ArrayList、LinkedList、Vector区别,hashMap加载因子0.75原因

一、java中的容器 集合主要分为Collection和Map两大接口&#xff1b;Collection集合的子接口有List、Set&#xff1b;List集合的实现类有ArrayList底层是数组、LinkedList底层是双向非循环列表、Vector&#xff1b;Set集合的实现类有HashSet、TreeSet&#xff1b;Map集合的实现…...

Linux Server 终止后立即重启报错 bind error: Address already in use

先启动Server&#xff0c;再启动Client&#xff0c;然后使用CtrlC关闭Server&#xff0c;马上再运行Server&#xff0c;会得到以下结果&#xff1a; bind error: Address already in use这是因为&#xff0c;虽然Server的应用程序终止了&#xff0c;但TCP协议层的连接并没有完全…...

【Python 千题 —— 基础篇】分解数据

题目描述 题目描述 编写一个程序&#xff0c;输入一个类似 “233,234,235” 格式的字符串&#xff0c;然后提取字符串中的数字&#xff0c;将这些数字存储在列表中&#xff0c;并输出该列表。在这里&#xff0c;我们使用 eval 函数来解析字符串中的数字。 输入描述 输入一个…...

【C++】C++11新特性之右值引用与移动语义

文章目录 一、左值与左值引用二、右值与右值引用三、 左值引用与右值引用比较四、右值引用使用场景和意义1.左值引用的短板2.移动构造和移动赋值3.STL中右值引用的使用 五、万能引用与完美转发1.万能引用2.完美转发 一、左值与左值引用 在C11之前&#xff0c;我们把数据分为常…...

家庭燃气表微信抄表识别系统

1.背景需求 目前家里燃气度数的读数上报&#xff0c;每个月在社区微信群里面将手机拍摄的燃气表读数截图&#xff08;加住址信息水印&#xff09;&#xff0c;发到群里给抄表员。 2.总体设计 设计目标 功能一&#xff1a;手机上随时可以远程采集读数图片&#xff08;自动加住…...

EF执行迁移时提示provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的

ef在执行时提示provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的。 只需要在数据库链接字符串后增加EncryptTrue;TrustServerCertificateTrue;即可 再次执行...

视频标注的两个主要方法

视频标注技术 单一图像法 在自动化工具面世之前&#xff0c;视频标注效率不高。各公司使用单一图像法提取视频中的所有帧&#xff0c;然后使用标准图像标注技术将它们作为图像来标注。在30fps的视频中&#xff0c;每分钟有1800帧。这个过程没有利用视频标注的优势&#xff0c;…...

学成在线第一天-项目介绍、项目的搭建、开发流程以及相关面试题

目录 一、项目介绍 二、项目搭建 三、开发流程 四、相关面试题 五、总结 一、项目介绍 背景 业务 技术 背景&#xff1a;首先是整个这个行业的背景 然后基于这个行业的背景引出当前项目的背景 业务&#xff1a;功能模块 功能业务流程 技术&#xff1a;整体架构&am…...

《数据结构与算法之美》读书笔记1

Java的学习 方法参数多态&#xff08;向上和向下转型&#xff09; 向上转型&#xff1a; class Text{public static void main(String[] args) {Animals people1 new NiuMa();people1.eat1();//调用继承后公共部分的方法&#xff0c;没重写调用没重写的&#xff0c;重写了调…...

接口测试经验合集

一 、接口测试常见问题 前景提要&#xff1a;由于本人测试小白&#xff0c;可能所遇问题都较为基础&#xff0c;测试小白可以参考 1.1 postman会报 connect ECONNREFUSED jemeter会报 org.apache.http.conn.HttpHostConnectException: Connect tofailed: Connection refus…...

Leetcode—2331.计算布尔二叉树的值【简单】

2023每日刷题&#xff08;六&#xff09; Leetcode—2331.计算布尔二叉树的值 递归实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool evaluateTree(struct TreeNod…...

Java面试(基础篇)——解构Java常见的基础面试题 结合Java源码分析

fail-safe 和fail-fast机制 Fail-fast&#xff1a;快速失败 Fail-fast &#xff1a; 表示快速失败&#xff0c;在集合遍历过程中&#xff0c;一旦发现容器中的数据被修改了&#xff0c;会立刻抛出ConcurrentModificationException 异常&#xff0c;从而导致遍历失败 package …...

Ubuntu 17.10的超震撼声音权限

从GNOME GUADEC 2017开发者大会归来之后&#xff0c;Canonical的Didier Roche就开始了一个日更博客系列&#xff0c;主要讲述即将带来的Ubuntu 17.10&#xff08;Artful Aardvark&#xff09;发行版将如何从Unity到GNOME Shell的转变。有趣的是&#xff0c;Ubuntu Unity桌面环境…...

图像信号处理板设计原理图:2-基于6U VPX的双TMS320C6678+Xilinx FPGA K7 XC7K420T的图像信号处理板

综合图像处理硬件平台包括图像信号处理板2块&#xff0c;视频处理板1块&#xff0c;主控板1块&#xff0c;电源板1块&#xff0c;VPX背板1块。 一、板卡概述 图像信号处理板包括2片TI 多核DSP处理器-TMS320C6678&#xff0c;1片Xilinx FPGA XC7K420T-1FFG1156&#xff0c;1片X…...

【数组】移除元素(暴力遍历×双指针√)

一、力扣题目链接 27.移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 你不需要考虑数组中超出新长度后面的元素。 二、思路 要知道数组的元素在内存地址中是连续的&#xff0c;不…...

【笔试题】华为研发工程师编程题

1.汽水瓶 某商店规定&#xff1a;三个空汽水瓶可以换一瓶汽水&#xff0c;允许向老板借空汽水瓶&#xff08;但是必须要归还&#xff09;。 小张手上有n个空汽水瓶&#xff0c;她想知道自己最多可以喝到多少瓶汽水。 数据范围&#xff1a;输入的正整数满足 1≤n≤100 1≤n≤…...

如何转换Corona和Vray材质?cr材质转vr材质的方法

cr材质转vr材质的方法一&#xff1a;使用CG Magic插件&#xff0c;一键转换 CG Magic是一款基于3ds Max深度开发的智能化辅助插件&#xff0c;上千项实用功能&#xff0c;降低渲染时长&#xff0c;节省时间和精力&#xff0c;大幅简化工作流程&#xff0c;助力高效完成创作。 …...

蓝桥每日一题(day 4: 蓝桥592.门牌制作)--模拟--easy

#include <iostream> using namespace std; int main() {int res 0;for(int i 1; i < 2021; i ){int b i;while(b){if (b % 10 2) res ;b / 10;}}cout << res; return 0; }...

leetcode(2)栈

leetcode 155 最小栈 stack相当于栈&#xff0c;先进后出 存储全部栈元素 [-3,2,-1] min_stack,存储栈当前位置最小的元素 [-3,-3,-3] class MinStack:def __init__(self):self.stack []self.min_stack [math.inf]def push(self, x: int) :self.stack.append(x)self.min_sta…...

有什么小程序可以下载视频号的视频?

​最近有一些朋友问我&#xff0c;【视频号下载助手】和【视频下载bot】小程序&#xff0c;有什么作用&#xff1f; 首先视频号下载助手是协助用户进行下载的&#xff0c;但由于下载要符合平台规定&#xff0c;我们就将视频下载助手与视频下载bot小程序想结合的模式&#xff0…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...