Java技术栈总结:容器集合篇

news2024/9/28 7:25:21

一、List

1、ArrayList

(1)底层数据结构

底层数据结构为数组。数组是一种用连续的内存空间存储相同数据类型数据的线性数据结构。

Q:为什么数组索引下标从0开始?

A:从0开始,对应寻址公式:a[i] = baseAddress + i * dataTypeSize;

如果从1开始,则变为:a[i] = baseAddress + (i-1)* dataTypeSize;需要增加一次减法操作,对于CPU来说就多了一次指令,性能不高。

其中,baseAddress: 数组的首地址,dataTypeSize:代表数组中元素类型的大小,int型的数据,dataTypeSize=4个字节

(2)实现原理

ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10;

ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数。

【添加逻辑】

  • 添加过程确保数组已使用长度(size)加1之后足够存下下一个数据​ ;
  • 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍)
  • 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。​

(3)数组和List转换

  • 数组转List ,使用JDKjava.util.Arrays工具类的 asList 方法;
  • lList转数组,使用 List 的 toArray 方法。无参 toArray 方法返回 Objec t数组,传入初始化长度的数组对象,返回该对象数组。

Q:① 用Arrays.asListList后,如果修改了数组内容,list受影响吗;② List用toArray转数组后,如果修改了List内容,数组受影响吗

A: 

  • Arrays.asList转换list之后,如果修改了数组的内容,list会受影响,因为它的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,把我们传入的这个集合进行了包装而已,最终指向的都是同一个内存地址;
  • list用了toArray转数组后,如果修改了list内容,数组不会影响,当调用了toArray以后,在底层是它是进行了数组的拷贝,跟原来的元素就没啥关系了,所以即使list修改了以后,数组也不受影响。

2、LinkedList

LinkedList 是双向链表的数据结构实现。

3、线程安全的List

<Vector、synchronizedList、CopyOnWriteArrayList>

(1)Vector

  • 使用synchronized修饰主要的方法;
  • 对整个list对象加锁。

(2)synchronizedList

通过Collections的静态方法创建。Collections.synchronizedList(List<T> list)

  • 使用synchronized修饰代码块;
  • 读写操作都会加锁。

(3)CopyOnWriteArrayList

读读操作及读写操作均不互斥,读操作不加锁,写操作(set、add、remove等)使用ReentrantLock加锁。

  • 列表内的数组“array”使用volatile修饰,保证不同线程间的可见性;
  • 写操作创建新的“数组”复制旧“数组”的数据,在新数组上进行修改,修改后调用“setArray”方法,将列表引用的数组指向到新数组;
  • 写操作加了“独占锁”,写操作本身不会出现线程安全问题。

【总结】:

  • 线程安全的List可以通过Vector、Collections.synchronizedList()方法、CopyOnWriteArrayList三种方式实现;
  • 读多写少的情况下,推荐使用CopyOnWriteArrayList;
  • 读少写多的情况下,推荐使用Collections.synchronizedList()。

二、Map

1、HashMap

散列表(Hash Table)又名哈希表/Hash表,是根据键(Key)直接访问在内存存储位置的值(Value)的数据结构,由数组演化而来的,利用了数组支持按照下标进行随机访问数据的特性。

将键(key)映射为数组下标的函数叫做散列函数。可以表示为:hashValue = hash(key)

散列函数的基本要求:

  • 散列函数计算得到的散列值必须是大于等于0的正整数,因为hashValue需要作为数组的下标;
  • 如果key1==key2,那么经过hash后得到的哈希值也必相同即:hash(key1) == hash(key2);
  • 如果key1 != key2,那么经过hash后得到的哈希值也必不相同即:hash(key1) != hash(key2);

散列冲突,不同的key经过hash运算得到相同的值。

散列冲突的解决,拉链法

在散列表中,数组的每个下标位置我们可以称之为 桶(bucket或者 槽(slot,每个桶()会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。

(1)实现原理

数据结构:数组+链表+红黑树

位置:根据key的hash值确定在数组中的位置,方法“hash(key) & (n-1)”,其中 n 为数组长度。

链表与红黑树转换:链表长度到 8,且数组长度达到64,转为红黑树;减少到 6,转为链表。

1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标

2. 存储时,如果出现hash值相同的key,此时有两种情况。

 a. 如果key相同,则覆盖原始值;

 b. 如果key不同(出现冲突),则将当前的key-value放入链表或红黑树中

3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。


(2)put方法执行流程

  • 判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化);
  • 根据键值key计算hash值得到数组索引((n-1) & hash);
  • 如果 table[i]==null,直接新建节点添加;
  • 如果 table[i]!=null,
    • 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value;
    • 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对;
    • 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,遍历过程中若发现key已经存在直接覆盖value;
  • 插入成功后,判断实际存在的键值对数量size是否超过了最大容量 threshold(数组长度*0.75),如果超过,进行扩容。

注:扩容因子为0.75,即数组中存储数据量达到总容量的0.75出发扩容。


(3)扩容机制

  • 在添加元素或初始化的时候会调用resize方法进行扩容,首次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容阈值(数组长度 * 0.75);
  • 每次扩容的时候,都是扩容之前容量的2
  • 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中;
    • 没有hash冲突的节点,则直接使用 e.hash & (newCap - 1) 计算新数组的索引位置;
    • 如果是红黑树,走红黑树的添加;
    • 如果是链表,则需要遍历链表,可能需要拆分链表。判断(e.hash & oldCap)是否为0,为0则停留在原始下标位置,不为0则移动到 原始位置+旧数组大小 这个位置上。

Q:为何HashMap的数组长度一定是2的次幂(2倍扩容)?

A:

(1)计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模;

(2)扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap

注:oldCap表示旧数组长度。

Q:hashMap的寻址算法?

A:

(1)计算对象的 hashCode();

(2)再进行调用 hash() 方法进行二次哈希,hashcode值右移16位再异或运算,让哈希分布更为均匀;

(3)最后 (capacity – 1) & hash 得到索引。


(3)jdk1.7多线程情况下死循环问题

jdk1.7hashmap中在数组进行扩容的时候,因为链表是头插法,在进行数据迁移的过程中,有可能导致死循环

比如说,现在有两个线程

线程一:读取到当前的hashmap数据,数据中一个链表,在准备扩容时,线程二介入

线程二:也读取hashmap,直接进行扩容。因为是头插法,链表的顺序会进行颠倒过来。比如原来的顺序是AB,扩容后的顺序是BA,线程二执行结束。

线程一:继续执行的时候就会出现死循环的问题。

线程一先将A移入新的链表,再将B插入到链头,由于另外一个线程的原因,Bnext指向了A,所以B->A->B,形成循环。

当然,JDK 8 将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),尾插法,就避免了jdk7中死循环的问题。


2、hashTable 与 hashMap 区别

1)继承的父类

HashMap继承自AbstractMap;HashTable继承自Dictionary类,该类已经被标记为废弃。

2)线程安全

hashTable线程安全,hashMap线程不安全;

3)解决hash冲突的方式

  • hashMap在1.7及之前,使用链表;1.8开始,在链表长度到 8 且数组长度到 64,转为红黑树;节点数减少到 6,转回链表;
  • hashTable使用链表。

4)扩容方式

hashMap 初始大小为16,2倍扩容;

hashTable 初始大小为11,2n+1;

5)是否允许null值;

HashMap键和值都运行为null;HashTable都不允许为null,否则会抛出空指针异常。


参考:

https://www.bilibili.com/video/BV1yT411H7YK

https://zhuanlan.zhihu.com/p/646536067 ;

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

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

相关文章

FLStudio21.3.12中文破解版本安装包win+mac电脑安装包下载

&#x1f3a4; FL Studio 21中文版&#xff1a;音乐制作新宠&#xff0c;让你的创作起飞&#xff01; 嗨&#xff0c;亲爱的音乐创作者们&#xff01;&#x1f44b;今天要和大家分享一个让我超级兴奋的宝藏软件——FL Studio 21中文版&#xff01;这不仅仅是一款音乐制作软件&…

科研绘图系列:R语言金字塔图(pyramid plot)

介绍 金字塔图(Pyramid chart)是一种用于展示人口统计数据的图表,特别是用于展示不同年龄段的人口数量。这种图表通常用于展示人口结构,比如性别和年龄的分布。 特点: 年龄分层:金字塔图按年龄分层,每一层代表一个年龄组。性别区分:通常,男性和女性的数据会被分别展…

Linux命令-grep/wc/管道符

1、Linux命令-grep/wc/管道符 2、echo/tail/重定向符 3、vi/vim 编辑器

有哪些好用的考勤管理系统?

&#x1f308; 对于企业而言&#xff0c;考勤管理不仅仅是支持员工工资计算&#xff0c;还会对实际的运营产生很大影响。一个好用的考勤管理系统能够实现考勤数据的实时采集和管理&#xff0c;保证考勤数据的稳定运行&#xff0c;从而实现复杂的工作安排&#xff0c;有效降低人…

uniapp上架到appstore遇到的问题

1、appstore在美国审核&#xff0c;需要把服务器接口的国外访问权限放开 2、登陆部分 a、审核时只能有密码登陆&#xff0c;可以通过接口响应参数将其他登陆方式暂时隐藏&#xff0c;审核成功后放开即可 b、需要有账号注销功能 3、使用照相机和相册功能时需要写清楚描述文案

具有 0.5V 超低输入电压的 3A 升压转换器TPS61021

1 特性 输入电压范围&#xff1a;0.5V 至 4.4V 启动时的最小输入电压为 0.9V 可设置的输出电压范围&#xff1a;1.8V 到 4.0V 效率高达 91%&#xff08;VIN 2.4V、VOUT 3.3V 且 IOUT 1.5A 时&#xff09; 2.0MHz 开关频率 IOUT > 1.5A&#xff0c;VOUT 3.3V&#xff08;V…

OSINT 项目:以太坊可视化工具

KennBro &#xff0c; iKy的开发者&#xff0c;正在构建一个令人兴奋的新工具。 他使用来自Etherscan区块浏览器的信息为以太坊创建了一个可视化浏览器。 使用免费的 API 密钥和此工具&#xff0c;您可以直观地了解交易和钱包。 我还没有时间自己安装它来测试它&#xff0c;…

Simscape物理建模步骤

为了介绍构建和仿真物理模型的步骤&#xff0c;这里以simulink自带示例模型Mass-Spring-Damper with Controller为例&#xff0c;下图为建立好的模型。 详细物理建模和仿真分析步骤如下&#xff1a; 步骤 1&#xff1a;使用 ssc_new 创建新模型 使用 ssc_new 是开始构建 Sims…

linux系统操作/基本命令/vim/权限修改/用户建立

Linux的目录结构&#xff1a; 一&#xff1a;在Linux系统中&#xff0c;路径之间的层级关系&#xff0c;使用:/来表示 注意:1、开头的/表示根目录 2、后面的/表示层级关系 二&#xff1a;在windows系统中&#xff0c;路径之间的层级关系&#xff0c;使用:\来表示 注意:1、D:表示…

职业本科计算机网络实训室

一、职业本科计算机网络实训室建设的背景 随着数字化时代的深入发展&#xff0c;计算机网络技术已经渗透到社会的每一个角落&#xff0c;成为推动社会进步的重要力量。在《中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要》中&#xff0c;建设数字中国…

2972.力扣每日一题7/11 Java(击败100%)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 解题思路 解题方法 时间复杂度 空间复杂度 Code 解题思路 该问…

vscode编译环境配置-c++

1. 支持跳转 安装c/c扩展 安装后即可支持跳转

Elasticsearch:介绍 retrievers - 搜索一切事物

作者&#xff1a;来自 Elastic Jeff Vestal, Jack Conradson 在 8.14 中&#xff0c;Elastic 在 Elasticsearch 中引入了一项名为 “retrievers - 检索器” 的新搜索功能。继续阅读以了解它们的简单性和效率&#xff0c;以及它们如何增强你的搜索操作。 检索器是 Elasticsearc…

还不懂 OOM ?详解内存溢出与内存泄漏区别!

内存溢出与内存泄漏 1. 内存溢出&#xff08;Out Of Memory&#xff0c;OOM&#xff09; 概念&#xff1a; 内存溢出是指程序在运行过程中&#xff0c;尝试申请的内存超过了系统所能提供的最大内存限制&#xff0c;并且垃圾收集器也无法提供更多的内存&#xff0c;导致程序无…

深入浅出Ansiable

目录 Ansible的起源 Ansible的发展史 Ansible的功能 Ansible的特性 Ansible的架构 Ansible的注意事项 Ansible入门 Ansible的安装 Ansible配置文件 配置文件解析 inventory主机配置清单 Ansible相关工具 Ansible的常用模块 Command模块 shell模块 Script模块 C…

Windows 电脑查看 WiFi 密码的方法都有哪些?

从设置面板中查看 当你使用的是笔记本电脑并且连接 WiFi 之后可以在设置面板中查看 WiFi 密码&#xff0c;首先打开设置界面&#xff0c;然后点击网络和 Internet&#xff0c;找到 WiFi 之后点击进入&#xff0c;然后点击管理已知网络。 然后点击已经连接好的无线网络。 进入之…

快速导入mysql百万用户数据

1. 前言 随着互联网的发展,大数据已经是很普遍的一个现象,已不再是零几年时的神话。数据资源意为着就是信息资源,很好的使用起来说是财富资源一点也不夸张.所以无论当下是小的还是大的互联网公司都会遇到大数的情况。只不过不同的业务逻辑需求与使用情况不一样罢了。 大数据我…

git为文件添加可执行权限

查看文件权限 git ls-files --stage .\SecretFinder.py100644 表示文件的所有者有读取和写入权限 添加可执行权限 git update-index --chmod x .\SecretFinder.py再次查看文件权限 git ls-files --stage .\SecretFinder.py100755 表示文件的所有者有读取、写入和执行权限

安卓逆向经典案例——XX优品(uniapp)

uni-app逆向 uniapp的目录结构 有一个io文件夹&#xff0c;下面有dcloud uniapp UniApp 可以用于开发 H5 应用&#xff0c;但它不仅仅局限于 H5 应用。UniApp 的特点包括&#xff1a; 1. 跨平台&#xff1a;可以一套代码同时生成适用于多个平台&#xff08;如 iOS、Android、…

STM32杂交版(HAL库、音乐盒、闹钟、点阵屏、温湿度)

一、设计描述 本设计精心构建了一个以STM32MP157A高性能单片机为核心控制单元的综合性嵌入式系统。该系统巧妙融合了蜂鸣器、数码管显示器、点阵屏、温湿度传感器、LED指示灯以及按键等多种外设模块&#xff0c;形成了一个功能丰富、操作便捷的杂交版智能设备。通过串口…