【Java八股面试系列】Arraylist和HashMap的底层原理

文章目录

    • ArrayList源码
      • 总:
      • 构造方法
      • 扩容机制
      • remove
    • HashMap
      • 总:
        • 构造方法
        • 细节问题
        • putVal()方法
        • resize()方法
        • Hash值
      • HashMap常见问题
    • ConcurrentHashMap
      • 总:
        • putVal()方法
        • 自己的测试
    • 为什么重写HashCode和equals

ArrayList源码

总:

**ArrayList**底层是使用名为 **elementDataObject动态数组进行实现,与Java中的数组相比,她的容量能够动态的进行增长。在我们更新元素的时候,我们会通过ensureCapacityInternal()方法来确保我们的容量够用,如果容量不够则调用grow()**方法对我们的数组进行扩容为1.5倍,然后将我们原来的的数组复制过去。

ArrayList 和 Vector 的区别?

  • ArrayListList 的主要实现类,底层使用 Object[]存储,适用于频繁的查找工作,线程不安全 。
  • VectorList 的古老实现类,底层使用Object[] 存储,线程安全。

ArrayList 可以添加 null 值吗?

ArrayList 中可以存储任何类型的对象,包括 null 值。不过,不建议向ArrayList 中添加 null 值, null 值无意义,会让代码难以维护比如忘记做判空处理就会导致空指针异常。

Arraylist 与 LinkedList 区别?

Arraylist 和 LinkedList都是实现了 List接口,都是线程不安全的。

不同点:

  • Arraylist底层是通过object数组进行实现,而LinkedList底层是通过双向链表进行实现
  • AL实现了随机读取接口,能够随机进行读取,用来查询的效率高,LL不能进行随机读取,只能遍历进行读取,但是她的插入效率高
  • 内存空间的占用上,AL的预留空间会占用一定的位置,而LL会占用更多的空间,因为要保存其他的一些位置信息

构造方法

Arraylist有有参构造方法也有无参构造方法,有参的构造方法会将我们的容器大小设置为设置的大小

无参构造方法先是将提前创建好的空数组给他,后续使用的时候在进行扩容操作

扩容机制

这里每次add操作的时候都会使用ensure CapacityInternal()方法进行容量判断,如果不足则会使用grow进行扩容

在这里插入图片描述

private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

这里的calculate Capacity方法主要是判断容器是否是已经初始化过

private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;
}

这里计算所需的最小容量是否大于当前的容器容量

private void ensureExplicitCapacity(int minCapacity) {// 用来记录遍历的时候时候,集合有没有进行改变modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);
}

这里先将数组扩容为原来的1.5倍,判断是否够用,或者有没有超过最大的容量

private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);
}

hugeCapacity()方法 当我们容量超过了当前容器设置的最大值的时候执行

    private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?	// 这里的MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;Integer.MAX_VALUE :MAX_ARRAY_SIZE;}

如果不够的话,也只能最多再添加8个了

remove

Arraylist 集合移除元素时候执行的函数,然后使用 **System.arraycopy()**方法将数组进行复制再将原来位置设置为null方便进行 gc

public E remove(int index) {rangeCheck(index); // 判断移除的元素是否越界modCount++;  // 判断当前的集合是否被修改E oldValue = elementData(index);int numMoved = size - index - 1;	// 确定移除位置if (numMoved > 0)	System.arraycopy(elementData, index+1, elementData, index, numMoved);  elementData[--size] = null; // clear to let GC do its workreturn oldValue;
}

HashMap

总:

HashMap在 jdk1.8之前是由Node数组+链表组成的,在jdk1.8以后更改了解决hash冲突的方式,是由Node数组+链表或者红黑树实现。元素添加是通过**putVal()方法添加,HashMap通过扰动函数处理得到Hash值,如果发生hash冲突的时候就会先判断当前节点的链表大小如果超过8,然后调用treeifBin()**如果hashMap总的容量大于64则会转为红黑树进行存储。

为什么不使用多路平衡二叉树?

  1. 红黑树是一种平衡二叉树,多路平衡二叉树需要存储更多的节点信息,空间上会使用更多的空间
  2. 操作的复杂性,红黑树不是严格的平衡二叉树,两边子节点的高度差没有完全的差1,插入的时候更好的处理
  3. 红黑树的查询表现已经可以了,不需要多路平衡二叉树的B+树的范围查询了
构造方法

构造方法有三个,关键就是携带参数的问题,有没有 **initialCapacity初始化容量**和 loadFactor扩容阈值

细节问题

默认的大小是16,AL是10,扩容每次为一倍,AL每次扩容1.5倍

putVal()方法

在这里插入图片描述

resize()方法

计算出新的**Capacity**和 threshold的值,然后创建一个新的数组,将我们原来的数组的值复制进去

**threshold**的值是通过 Capacity的值与设置的阈值0.75相乘得出来的

Hash值

hashCode()方法返回的是int整数类型,其范围为


-(2 ^ 31)~(2 ^ 31 - 1),而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;

那怎么解决呢?

HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
在保证数组长度为2的幂次方的时候,使用hash()运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题;

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap常见问题

为什么线程不安全

因为可能会造成数据丢失的问题,假如两个线程同时进行插入,并且产生了Hash碰撞,那么线程1判断完成以后插入之前挂起,同时线程2进行插入,这时线程1插入的将会覆盖线程2

ConcurrentHashMap

总:

ConcurrentHashMap是一个并发容器,能够解决多个线程使用HashMap造成的线程安全问题,底层的**putVal函数**是通过 Cas操作和Synchronized操作来保证线程的安全性。其他的结构和HashMap一样。

putVal()方法

和HashMap的过程基本一致

只是如果计算出hash值原位置没有的话,直接使用cas操作进行插入

如果后续碰撞则使用synchronized进行插入

自己的测试

自己使用了四个线程,分别进行100万次随机位置的写入,查看每个线程的完成时间,平均每个线程的完成时间将会降低百分之30左右。

为什么重写HashCode和equals

因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。

思考:重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题。

总结

  • equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
  • 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。

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

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

相关文章

C++template之类模版进一步了解

目录 一、类模板实例化 1.非类型模版参数 2.函数模板的特化 3.类模板特化 3.1全特化 3.2偏特化 3.2.1部分特化 3.2.2对参数进一步限制 二、注意事项 1.类模板的定义和声明要在同一个文件&#xff0c;不然容易出错 前言&#xff1a;这一篇是在我的上一篇文章的基础上&am…

网络安全接入认证-802.1X接入说明

介绍 802.1X是一个网络访问控制协议&#xff0c;它可以通过认证和授权来控制网络访问。它的基本原理是在网络交换机和认证服务器之间建立一个安全的通道&#xff0c;并要求客户端提供身份验证凭据。如果客户端提供的凭据是有效的&#xff0c;交换机将开启端口并允许访问。否则&…

“转行做程序员”很难?这里有4个建议

近几年来&#xff0c;传统行业多处于经济下行&#xff0c;加上互联网行业的赚钱效应&#xff0c;想要转行到这一行的人越来越多&#xff0c;其中程序员这个行业更是很多人梦寐以求的。 但另一方面&#xff0c;我们也发现&#xff0c;这些想要转行的同学们往往会遇到很多困扰。…

使用Kaggle API快速下载Kaggle数据集

前言 在使用Kaggle网站下载数据集时&#xff0c;直接在网页上点击下载可能会很慢&#xff0c;甚至会出现下载失败的情况。本文将介绍如何使用Kaggle API快速下载数据集。 具体步骤 安装Kaggle API包 在终端中输入以下命令来安装Kaggle API相关的包&#xff1a; pip install…

linux bypy 定时备份到百度网盘

安装 # 先卸载安装的python-pip sudo yum remove python-pip# 下载get-pip.py文件 wget https://bootstrap.pypa.io/pip/2.7/get-pip.py sudo python get-pip.py直接访问这个地址下载文件,再导入linux更快! https://bootstrap.pypa.io/pip/2.7/get-pip.py 连接 复制上面的连…

Prometheus+grafana环境搭建mysql(docker+二进制两种方式安装)(三)

由于所有组件写一篇幅过长&#xff0c;所以每个组件分一篇方便查看&#xff0c;前两篇 Prometheusgrafana环境搭建方法及流程两种方式(docker和源码包)(一)-CSDN博客 Prometheusgrafana环境搭建rabbitmq(docker二进制两种方式安装)(二)-CSDN博客 1.监控mysql 1.1官方地址:…

Unity TrailRenderer的基本了解

在Unity中&#xff0c;TrailRenderer组件用于在对象移动时创建轨迹效果。通常用于增强游戏中的动态物体&#xff0c;比如子弹、飞行道具或者角色移动时的拖尾效果。 下面来了解下它的基本信息。 1、创建 法1&#xff1a;通过代码创建 using UnityEngine;public class Trail…

linux系统命令chkconfig详解,管理系统服务的工具-查看、启用、禁用和设置系统服务的启动级别

目录 一、chkconfig命令介绍 二、命令的主要作用 1、管理服务的启动和停止&#xff1a; 2、配置运行级别&#xff1a; 3、简化系统管理&#xff1a; 4、查看服务状态&#xff1a; 三、命令语法 1、基本语法 2、运行级别 四、获取帮助 1、通过help获取 2、通过man获…

Eclipse+Java+Swing实现斗地主游戏

一. 视频演示效果 java斗地主源码演示 ​ 二.项目结构 代码十分简洁&#xff0c;只有简单的7个类&#xff0c;实现了人机对战 素材为若干的gif图片 三.项目实现 启动类为Main类&#xff0c;继承之JFrame&#xff0c;JFrame 是 Java Swing 库中的一个类&#xff0c;用于创建窗…

软考 系统架构设计师系列知识点之云原生架构设计理论与实践(8)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之云原生架构设计理论与实践&#xff08;7&#xff09; 所属章节&#xff1a; 第14章. 云原生架构设计理论与实践 第2节 云原生架构内涵 14.2 云原生架构内涵 关于云原生的定义有众多版本&#xff0c;对于云原生架构的…

Java学习之类和对象、内存底层

目录 表格结构和类结构 表格的动作和类的方法 与面向过程的区别 具体实现 对象和类的详解 类的定义 属性&#xff08;field 成员变量&#xff09; 方法 示例--编写简单的学生类 简单内存分析(理解面向对象) 构造方法(构造器 constructor) 声明格式&#xff1a; 四…

探索父进程和子进程

文章目录 通过系统调用查看进程PID父进程、子进程 通过系统调用创建进程-fork初识为什么fork给父进程返回子进程的PID&#xff0c;给子进程返回0fork函数如何做到返回两个值一个变量为什么同时会有两个返回值&#xff1f;bash总结 通过系统调用查看进程PID getpid()函数可以获…

大屏可视化项目示例--基于Vue3+vite2+echart+mock+axios+dataV

图例&#xff1a; 项目环境&#xff1a; Vite、Echarts、Npm、Node、axios、mock、vue3、dataV。 项目地址&#xff1a; IofTV-Screen-Vue3: &#x1f525;(IofTV-Screen Vue3版本)一个基于 vue3、vite、Echart 框架的物联网可视化&#xff08;大屏展示&#xff09;模板&…

多尺度变换(Multidimensional Scaling ,MDS)详解

一、基本思想 MDS&#xff08;Multidimensional Scaling &#xff0c;MDS多维尺度变换&#xff09;是一种经典的降维算法&#xff0c;其基本思想是通过保持数据点之间的距离关系&#xff0c;将高维数据映射到低维空间中。 具体来说&#xff0c;MDS算法的基本步骤如下&#xff…

Cocos2dx-lua ScrollView[三]高级篇

一.概述 本文缩写说明&#xff1a;sv ScrollView, cell代表ScrollView的一个子节点 本文介绍sv的一种封装类库&#xff0c;来实现快速创建sv&#xff0c;有如下几个优点&#xff1a; 1.item的位置通过参数控制&#xff0c;提高开发效率 2.免去了调用sv的API&#xff0c;提…

Leetcode 322. 零钱兑换

心路历程&#xff1a; 这道题和上一道完全平方数的和基本上一摸一样&#xff0c;甚至比上一道题还简单&#xff0c;基于dp的建模&#xff1a; 状态&#xff1a;当前的目标总金额 动作&#xff1a;选哪一个硬币 返回值&#xff1a;凑成该目标总金额的最少硬币个数 这道题如果硬…

计算机网络数据链路层知识总结

物理层知识总结传送门 计算机网络物理层知识点总结-CSDN博客 功能 功能概述 一些基本概念 结点:主机、路由器链路﹔网络中两个结点之间的物理通道&#xff0c;链路的传输介质主要有双绞线、光纤和微波。分为有线链路、无线链路。数据链路︰网络中两个结点之间的逻辑通道&a…

红米手机Redmi 不会自动弹出USB调试选项,如何处理?(红米小米均适用)

参考&#xff1a; 红米手机Redmi 不会自动弹出USB调试选项&#xff0c;如何处理&#xff1f;&#xff08;红米小米均适用&#xff09; - 知乎 以红米9A为例&#xff1b; 【设置】菜单进入后&#xff0c;找到【我的设备】&#xff0c; 选择【全部参数】&#xff0c; 对准miui版…

shell脚本发布docker springboot项目示例

docker、git、Maven、jdk8安装略过。 使git pull或者git push不需要输入密码操作方法 约定&#xff1a; 路径&#xff1a;/opt/springbootdemo&#xff0c; 项目&#xff1a;springbootdemo&#xff0c; 打包&#xff1a;springbootdemo.jar&#xff0c; docker容器名字&#x…

LeetCode_33_中等_搜索旋转排序数组

文章目录 1. 题目2. 思路及代码实现详解&#xff08;Python&#xff09;2.1 二分查找 1. 题目 整数数组 n u m s nums nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c; n u m s nums nums 在预先未知的某个下标 k &#xff08; 0 < k…