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

一、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.mzph.cn/bicheng/45197.shtml

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

相关文章

Vuetify3 + Nuxt3:跳转详情

在Nuxt 3中&#xff0c;使用v-data-table组件时&#xff0c;我们想要在点击某个行或者某个单元格时进行页面跳转。可以通过监听组件的点击事件&#xff0c;并使用useRouter来实现页面跳转。 <template><v-data-table:headers"headers":items"items&qu…

dolphinScheduler + hive + datax报错记录

1、参数错误 报错信息 [INFO] 2024-04-11 06:43:18.386 - [taskAppIdTASK-29-3301-84461]:[498] - after replace sql , preparing : insertoverwrite table mis_month partition (dt) select nvl(sl.slid , ) as id,--水量 IDnvl(sl.hh …

MongoDB教程(二):mongoDB引用shell

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、MongoD…

了解AsyncRotationController

概述 基于android 15.0, 以从强制横屏App上滑退回桌面流程来分析 frameworks/base/services/core/java/com/android/server/wm/AsyncRotationController.javaAsyncRotationController 是一种控制器&#xff0c;用于处理设备显示屏旋转时非活动窗口的异步更新。这种控制器通过…

设计模式——适配器设计模式

设计模式——适配器设计模式 适配器设计模式1.1 基本介绍1.2 工作原理1.3 类适配器模式1.3.1 基本介绍1.3.2 示例1.3.3 代码实现1.3.4 注意事项 1.4 对象适配器模式1.4.1 基本介绍1.4.2 示例1.4.3 代码实现1.4.4 注意事项 1.5 接口适配器模式1.5.1 基本介绍1.5.2 示例1.5.3 代码…

如何处理Java中数据结构(如HashMap)导致的性能瓶颈

在Java开发过程中&#xff0c;HashMap 是一种常用的数据结构&#xff0c;它提供了高效的键值对存储和快速的查找、插入和删除操作。然而&#xff0c;在某些情况下&#xff0c;HashMap 可能会导致性能瓶颈。本文将探讨这些性能瓶颈的成因&#xff0c;并提供一些优化策略。 一、…

Webkit简介以及工作流程

Webkit简介 WebKit是一个开源的浏览器引擎&#xff0c;最初由苹果公司基于KHTML&#xff08;K Desktop Environment的HTML渲染引擎&#xff09;开发&#xff0c;并广泛应用于Safari浏览器。随着时间的推移&#xff0c;WebKit也被其他多款浏览器和应用所采用&#xff0c;成为We…

pudb: Python的图形化调试器

文章目录 pudb原理基础使用安装pudb启动pudb界面介绍常用操作 高级使用条件断点表达式求值自定义布局搜索和过滤插件和扩展 结论 pudb原理 pudb是一个基于文本的图形化Python调试器&#xff0c;它结合了pdb的强大调试功能与图形用户界面的易用性。pudb通过提供一个可视化的界面…

【操作系统】阻塞队列以及生产者消费者模型

目录 阻塞队列一. 概念二. 标准库中的阻塞队列三. 生产者消费者模型四. 阻塞队列实现 总结 阻塞队列 一. 概念 阻塞队列是⼀种特殊的队列.也遵守"先进先出"的原则. 阻塞队列能是⼀种线程安全的数据结构,并且具有以下特性: 当队列满的时候,继续⼊队列就会阻塞,直到…

Splashtop 在医疗与制药领域的业务增长近五倍

2024年7月10日 加利福尼亚州库比蒂诺 Splashtop 是安全远程访问和 IT 支持解决方案领域的领先企业&#xff0c;该公司今天宣布&#xff0c;在医疗与制药领域业务同比增长492%&#xff0c;取得了里程碑式的成就。快速发展的数字实验室环境和持续的网络安全威胁需要实施无缝、安…

Unity之VS脚本自动添加头部注释Package包开发

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之VS脚本自动添加头部注释Package包开发 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&…

模板语法指令语法——02

//指令语法&#xff1a; 1.什么是指定&#xff0c;有什么作用&#xff1f; 指令的职责是&#xff0c;当表达式的值改变时&#xff0c;将其产生的连带影响&#xff0c;响应式的作用语DOM 2.vue框架中的所有指令的名字都以v-开始的 3.插值是写在标签当中用的&#xff0c;指令…

WSGI 服务器教程:`execute` 方法解析

Python WSGI 服务器教程&#xff1a;execute 方法解析 在本文中&#xff0c;我们将详细解析一个用于 WSGI 服务器的 execute 方法。这个方法负责执行 WSGI 应用程序&#xff0c;处理其响应数据&#xff0c;并确保在应用程序执行过程中处理所有必要的清理工作。我们将逐行解释该…

uniapp启动图延时效果,启动图的配置

今天阐述uniapp开发中给启动图做延迟效果&#xff0c;不然启动图太快了&#xff0c;一闪就过去了&#xff1b; 一&#xff1a;修改配置文件&#xff1a;manifest.json "app-plus" : {"splashscreen" : {"alwaysShowBeforeRender" : false,"…

编程语言前途:探索未来的无限可能

编程语言前途&#xff1a;探索未来的无限可能 在科技日新月异的今天&#xff0c;编程语言作为连接人类与计算机世界的桥梁&#xff0c;其前途无疑是充满无限可能与挑战的。本文将从四个方面、五个方面、六个方面和七个方面&#xff0c;深入剖析编程语言的前途&#xff0c;带您…

vivado EDIF_EXTRA_SEARCH_PATHS、EQUALIZATION

EDIF_EXTRA_SEARCH_PATHS 此属性定义了Vivado Design Suite在当前文件集上的搜索路径&#xff0c;以 查找设计引用的EDIF文件。 提示&#xff1a;当Vivado设计套件无法执行以下操作时&#xff0c;在实现过程中会出现以下错误 定位与黑盒关联的EDIF网表。这可以通过定义 EDIF_EX…

法律咨询援助网站

1 项目介绍 1.1 摘要 随着互联网技术的飞速发展&#xff0c;公众对于便捷、高效的法律咨询服务需求日益增长。传统的法律咨询方式已难以满足人们即时性、多样化的咨询需求&#xff0c;促使法律咨询援助网站应运而生。这些平台旨在通过数字化手段&#xff0c;为用户提供法律知…

【TS】Typescript 的泛型

TypeScript 的泛型&#xff08;Generics&#xff09;是 TypeScript 的一个非常强大的特性&#xff0c;它允许你在编译时定义组件&#xff0c;这些组件可以工作于多种类型的数据上。泛型可以创建可重用的组件&#xff0c;这些组件是独立于任何特定类型的。这意味着你可以编写灵活…

apache:the requested operation has failed使用httpd -t

Apache24\bin cmd 回车 httpd -t 因为我重新压缩了&#xff0c;记住&#xff0c;重新压缩要使用原路径&#xff0c; 因为你安装的 时候使用的是原路径 还是不行就改个端口&#xff0c;切记修改配置文件httpd.conf先把Tomcat停了 Define SRVROOT "F:\Apache\Apache24&q…

C++类和对象学习笔记

1.类的定义 1.1类定义的格式 class是定义类的关键字&#xff0c;Date为类的名字&#xff0c;{ }中为类的主体&#xff0c;注意定义类结束时后面的分号不能省略。类中的内容称为类的成员&#xff1b;类中的变量称为类的属性或成员变量&#xff1b;类中的函数称为类的方法或者成…