JAVA基础面试题(第九篇)中! 集合与数据结构

JAVA集合和数据结构也是面试常考的点,内容也是比较多。

在看之前希望各位如果方便可以点赞收藏,给我点个关注,创作不易!

JAVA集合

11. HashMap 中 key 的存储索引是怎么计算的?

首先根据key的值计算出hashcode的值,然后根据hashcode计算出hash值,最后通过hash&(length-1)计算得到存储的位置。看看源码的实现:

// jdk1.7
方法一:
static int hash(int h) {
    int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

    h ^= k.hashCode(); // 为第一步:取hashCode值
    h ^= (h >>> 20) ^ (h >>> 12); 
    return h ^ (h >>> 7) ^ (h >>> 4);
}
方法二:
static int indexFor(int h, int length) {  //jdk1.7的源码,jdk1.8没有这个方法,但实现原理一样
     return h & (length-1);  //第三步:取模运算
}


// jdk1.8
static final int hash(Object key) {   
     int h;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    /* 
     h = key.hashCode() 为第一步:取hashCode值
     h ^ (h >>> 16)  为第二步:高位参与运算
    */
}

这里的 Hash 算法本质上就是三步:**取key的 hashCode 值、根据 hashcode 计算出hash值、通过取模计算下标。**其中,JDK1.7和1.8的不同之处,就在于第二步。我们来看下详细过程,以JDK1.8为例,n为table的长度。

在这里插入图片描述

12. HashMap 的put方法流程?

简要流程如下:

  • 首先根据 key 的值计算 hash 值,找到该元素在数组中存储的下标;

  • 如果数组是空的,则调用 resize 进行初始化;

  • 如果没有哈希冲突直接放在对应的数组下标里;

  • 如果冲突了,且 key 已经存在,就覆盖掉 value;

  • 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;

  • 如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,链表插入键值对,若 key 存在,就覆盖掉 value。

在这里插入图片描述

13. HashMap 的扩容方式?

HashMap 在容量超过负载因子所定义的容量之后,就会扩容。Java 里的数组是无法自动扩容的,方法是将 HashMap 的大小扩大为原来数组的两倍,并将原来的对象放入新的数组中。

那扩容的具体步骤是什么?让我们看看源码。

先来看下JDK1.7 的代码:

void resize(int newCapacity) {   //传入新的容量
        Entry[] oldTable = table;    //引用扩容前的Entry数组
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {  //扩容前的数组大小如果已经达到最大(2^30)了
            threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
            return;
        }

        Entry[] newTable = new Entry[newCapacity];  //初始化一个新的Entry数组
        transfer(newTable);                         //!!将数据转移到新的Entry数组里
        table = newTable;                           //HashMap的table属性引用新的Entry数组
        threshold = (int)(newCapacity * loadFactor);//修改阈值
    }
这里就是使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。

void transfer(Entry[] newTable) {
        Entry[] src = table;                   //src引用了旧的Entry数组
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
            Entry<K,V> e = src[j];             //取得旧Entry数组的每个元素
            if (e != null) {
                src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
                    e.next = newTable[i]; //标记[1]
                    newTable[i] = e;      //将元素放在数组上
                    e = next;             //访问下一个Entry链上的元素
                } while (e != null);
            }
        }
    }

newTable[i] 的引用赋给了 e.next ,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到 Entry 链的尾部(如果发生了 hash 冲突的话)。

14. 一般用什么作为HashMap的key?

一般用Integer、String 这种不可变类当 HashMapkey,而且 String 最为常用。

  • 因为字符串是不可变的,所以在它创建的时候 hashcode 就被缓存了,不需要重新计算。这就是 HashMap 中的键往往都使用字符串的原因。
  • 因为获取对象的时候要用到 equals() 和 hashCode() 方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的重写了 hashCode() 以及 equals() 方法。

15. HashMap为什么线程不安全?

在这里插入图片描述

  • 多线程下扩容死循环。JDK1.7中的 HashMap 使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此,JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题。
  • 多线程的put可能导致元素的丢失。多线程同时执行 put 操作,如果计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致元素的丢失。此问题在JDK 1.7和 JDK 1.8 中都存在。
  • put和get并发时,可能导致get为null。线程1执行put时,因为元素个数超出threshold而导致rehash,线程2此时执行get,有可能导致这个问题。此问题在JDK 1.7和 JDK 1.8 中都存在。

16. ConcurrentHashMap 的实现原理是什么?

ConcurrentHashMap 在 JDK1.7 和 JDK1.8 的实现方式是不同的。

先来看下JDK1.7

JDK1.7中的ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成,即ConcurrentHashMap 把哈希桶切分成小数组(Segment ),每个小数组有 nHashEntry 组成。

其中,Segment 继承了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色;HashEntry 用于存储键值对数据。

在这里插入图片描述
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问,能够实现真正的并发访问。

再来看下JDK1.8

在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用CAS + synchronized实现更加低粒度的锁。

将锁的级别控制在了更细粒度的哈希桶元素级别,也就是说只需要锁住这个链表头结点(红黑树的根节点),就不会影响其他的哈希桶元素的读写,大大提高了并发度。

在这里插入图片描述

17. ConcurrentHashMap 的 put 方法执行逻辑是什么?

先来看JDK1.7

首先,会尝试获取锁,如果获取失败,利用自旋获取锁;如果自旋重试的次数超过 64 次,则改为阻塞获取锁。

获取到锁后:

  • 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
    遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
  • 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
  • 释放 Segment 的锁。

再来看JDK1.8

大致可以分为以下步骤:

  • 根据 key 计算出 hash值。
  • 判断是否需要进行初始化。
  • 定位到 Node,拿到首节点 f,判断首节点 f:
  • 如果为 null ,则通过cas的方式尝试添加。
  • 如果为 f.hash = MOVED = -1 ,说明其他线程在扩容,参与一起扩容。
  • 如果都不满足 ,synchronized 锁住 f 节点,判断是链表还是红黑树,遍历插入。
  • 当在链表长度达到8的时候,数组扩容或者将链表转换为红黑树。

18. ConcurrentHashMap 的 get 方法是否要加锁,为什么?

get 方法不需要加锁。因为 Node 的元素 val 和指针 next 是用 volatile 修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。

这也是它比其他并发集合比如 Hashtable、用 Collections.synchronizedMap()包装的 HashMap 安全效率高的原因之一。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    //可以看到这些都用了volatile修饰
    volatile V val;
    volatile Node<K,V> next;
}

19. get方法不需要加锁与volatile修饰的哈希桶有关吗?

没有关系。哈希桶table用volatile修饰主要是保证在数组扩容的时候保证可见性。

static final class Segment<K,V> extends ReentrantLock implements Serializable {

    // 存放数据的桶
    transient volatile HashEntry<K,V>[] table;

20. ConcurrentHashMap 不支持 key 或者 value 为 null 的原因?

我们先来说value 为什么不能为 null ,因为ConcurrentHashMap 是用于多线程的 ,如果map.get(key)得到了 null ,无法判断,是映射的value是 null ,还是没有找到对应的key而为 null ,这就有了二义性。

而用于单线程状态的HashMap却可以用containsKey(key) 去判断到底是否包含了这个 null 。

我们用反证法来推理:

假设ConcurrentHashMap 允许存放值为 null 的value,这时有A、B两个线程,线程A调用ConcurrentHashMap .get(key)方法,返回为 null ,我们不知道这个 null 是没有映射的 null ,还是存的值就是 null 。

假设此时,返回为 null 的真实情况是没有找到对应的key。那么,我们可以用ConcurrentHashMap .containsKey(key)来验证我们的假设是否成立,我们期望的结果是返回false。

但是在我们调用ConcurrentHashMap .get(key)方法之后,containsKey方法之前,线程B执行了ConcurrentHashMap .put(key, null )的操作。那么我们调用containsKey方法返回的就是true了,这就与我们的假设的真实情况不符合了,这就有了二义性。

总结:数据结构和集合这快的面试考点还是很多的,希望大家仔细看看,这只是一部分。我们分三部分来更新。

多谢大家支持!!!

如果没看过前几篇的文章的,可以点击链接去看看,如果方便可以点赞收藏,给我点个关注,创作不易!

JAVA基础面试题(第八篇)上! 集合与数据结构

JAVA基础面试题(第七篇)!异常

JAVA基础面试题(第六篇)!序列化与IO流

JAVA基础面试题(第五篇)!反射与泛型

JAVA基础面试题(第四篇)!equal、hashcode及String解析

JAVA基础面试题(第三篇)!面向对象

JAVA基础面试题(第二篇)!基础语法与关键字

JAVA基础面试题(第一篇)!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux 基于 TCP 协议的简单服务器-客户端应用

目录 一、相关函数 1、listen() 2、accept() 3、connect() 4、两种IP地址转换方式 5、TCP和UDP数据发送和接收函数对比 5、log.hpp自定义记录日志 二、udp_server.hpp单进程版本 三、tcp_server.cc 四、Telnet客户端&#xff08;代替tcp_client.cc&#xff09; 五…

XSS 检测神器:XSStrike 保姆级教程

一、介绍 XSStrike 是一款专门用于检测和利用跨站脚本&#xff08;XSS&#xff09;漏洞的工具&#xff0c;具有自动化、智能化的特点&#xff0c;它的主要功能包括&#xff1a; 自动检测&#xff1a; XSStrike 能够自动发现 Web 应用程序中的 XSS 漏洞&#xff0c;无需用户手动…

写一个uniapp的登录注册页面

目录 一、效果图 二、代码 1、登录 &#xff08;1&#xff09;页面布局代码 &#xff08;2&#xff09;逻辑实现代码 &#xff08;3&#xff09;css样式 2、注册 &#xff08;1&#xff09;页面布局代码 &#xff08;2&#xff09;逻辑实现代码 &#xff08;3&#x…

匿名对象 与 new delet初识

一.匿名对象 1.定义&#xff1a; 没有名称的临时创建的对象&#xff0c;通常用于临时操作或作为函数的实参或返回值。 2.声明周期与作用域&#xff1a; 仅仅在定义所在代码行中&#xff0c;执行完就销毁。 3.使用格式 类名(构造参数) 4.使用场景 临时调用成员函数 mid…

【InternLM 实战营第二期笔记06】Lagent AgentLego 智能体应用搭建

一、智能体的由来 为什么要有智能体呢&#xff1f;这主要源于大语言模型存在的局限性。尽管大语言模型在人工智能领域取得了显著的进步&#xff0c;但它们仍然面临着一些重要的问题。 智能体可以通过学习和优化算法&#xff0c;不断提升自身的性能。它们可以从历史数据中学习…

【前端面试3+1】15 CSS如隐藏元素、css块级元素和行内元素有哪些?两者有什么区别?、JavaScript中“==”与“===”的区别、【丢失的数字】

一、CSS如何隐藏元素&#xff1f; 1、使用 display: none; 这种方法会隐藏元素&#xff0c;并且不占据页面空间。元素会被完全移除&#xff0c;无法通过任何方式显示出来。 .hidden-element {display: none; }2、使用 visibility: hidden; 这种方法会隐藏元素&#xff0c;但仍然…

数字乡村创新实践推动农业现代化发展:科技赋能农业产业升级、提升农民收入水平与乡村治理效能

随着信息技术的迅猛发展和数字化转型的深入推进&#xff0c;数字乡村创新实践已成为推动农业现代化发展的重要引擎。数字技术的广泛应用不仅提升了农业生产的智能化水平&#xff0c;也带动了农民收入的增加和乡村治理的现代化。本文旨在探讨数字乡村创新实践如何科技赋能农业产…

Ubuntu24.04之软件源修改

注意事项 Ubuntu24.04的软件源从/etc/apt/sources.list改为/etc/apt/sources.list.d/ubuntu.sources 修改步骤 #备份软件源 sudo cp /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak #更换软件源&#xff08;更换为中科大源&#xff0…

使用 CentOS 搭建 Linux KVM 虚拟化平台

&#xff08;1&#xff09;上传镜像,可在这个链接上下载镜像&#xff1a;https://mirrors.aliyun.com/centos-vault/7.7.1908/isos/x86_64/CentOS-7-x86_64-Minimal-1908.iso?spma2c6h.25603864.0.0.4a41714fA9E9c0 &#xff08;2&#xff09;. 在 CentOS 图形界面打开虚拟系统…

LIN数据总线ESD保护方案

LIN总线&#xff08;Local Interconnect Network&#xff09;是一种用于车辆电子系统中的串行通信协议。LIN接口与其他外露的接口一样&#xff0c;也会受到静电放电 (ESD) 的影响。电子工程师需设计具有保护二极管的LIN 接口可为 LIN 收发器本身和相应的下游总线元件提供保护。…

【源码】基于I.MX6ull驱动移植ds18b20的实验详解

文章目录 前言一、硬件连接二、代码移植1.驱动代码2.编译程序 三、移植到开发板参考连接 前言 提示&#xff1a;基于I.MX6ull驱动移植ds18b20的实验&#xff1a; 实验平台&#xff1a;正点原子alpha开发板V2.2 传感器&#xff1a;ds18b20模块 一、硬件连接 ds18b20的VCC&…

在瑞芯微RV1126 Linux系统上调试WiFi的详细指南

目录标题 1. **系统和环境准备**2. **检查WiFi设备状态**3. **启用和禁用WiFi接口**4. **扫描可用的WiFi网络**5. **连接到WiFi网络**6. **查看当前的WiFi连接状态**7. **断开和重新连接WiFi**8. **管理WiFi网络配置**9. **使用iw工具进行高级WiFi调试**10. **故障排除和日志获…

Kafka安装Windows版

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Kafka 是一个由 LinkedIn 开发的分布式消息系统,它于2011年年初开源,现…

【JavaSE】异常

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 认识异常 异常分类 举例 栈溢出错误 空指针异常&#xff08;运行时异常&#xff09; 编译时异常 处理异常 抛出 异常 程序本身触发异常 手动抛出异常 举例 利用try ca…

1096 大美数

solution B被A整除&#xff0c;B是A的倍数。B / AA整除B A把B整除 &#xff22;被A整除 B / A >n可以整除不同的四个因数之和 等价于 (a b c d) % n 0 #include<iostream> #include<cmath> using namespace std; int main(){int k, n, t;scanf("%d&q…

【MATLAB】App 设计 (入门)

设计APP 主界面 函数方法 定时器 classdef MemoryMonitorAppExample < matlab.apps.AppBase% Properties that correspond to app componentsproperties (Access public)UIFigure matlab.ui.FigureStopButton matlab.ui.control.ButtonStartButton matlab.ui.cont…

openAI tts Java文本转语音完整前后端代码 html

Java后端代码 maven 仓库&#xff1a; <!--openAI 请求工具--> <dependency><groupId>com.unfbx</groupId><artifactId>chatgpt-java</artifactId><version>1.1.5</version> </dependency>maven 仓库官方 tts 使用案例…

【论文源码实战】轻量化MobileSAM,分割一切大模型出现,模型缩小60倍,速度提高40倍

前言 MobileSAM模型是在2023年发布的&#xff0c;其对之前的SAM分割一切大模型进行了轻量化的优化处理&#xff0c;模型整体体积缩小了60倍&#xff0c;运行速度提高40倍&#xff0c;但分割效果却依旧很好。 MobileSAM在使用方法上沿用了SAM模型的接口&#xff0c;因此可以与…

超越GPT-4V,苹果多模态大模型上新,神经形态计算加速MLLM(一)

4月8日&#xff0c;苹果发布了其最新的多模态大语言模型&#xff08;MLLM &#xff09;——Ferret-UI&#xff0c;能够更有效地理解和与屏幕信息进行交互&#xff0c;在所有基本UI任务上都超过了GPT-4V&#xff01; 苹果开发的多模态模型Ferret-UI增强了对屏幕的理解和交互&am…

【YOLOv8改进[Backbone]】使用MobileNetV3助力YOLOv8网络结构轻量化并助力涨点

目录 一 MobileNetV3 1 面向块搜索的平台感知NAS和NetAdapt 2 反向残差和线性瓶颈 二 使用MobileNetV3助力YOLOv8 1 整体修改 ① 添加MobileNetV3.py文件 ② 修改ultralytics/nn/tasks.py文件 ③ 修改ultralytics/utils/torch_utils.py文件 2 配置文件 3 训练 其他 …
最新文章