【常见集合】Java 常见集合重点解析

Java 常见集合重点解析

1. 什么是算法时间复杂度?

在这里插入图片描述

  • 时间复杂度表示了算法的 执行时间数据规模 之间的增长关系;

什么是算法的空间复杂度?

  • 表示了算法占用的额外 存储空间数据规模 之间的增长关系;

常见的复杂度:

在这里插入图片描述

2. ArrayList 底层的数据结构是什么?

推荐博客

ArrayList 的底层是动态数组(Array),是一种用 连续的内存空间 存储 相同数据类型 数据的现象数据结构(单列),并且会随着数据的增加而扩容;

数组下标为什么从 0 开始?

  • 数组通过下标访问的原理就是通过数组的首元素地址与下标去计算对应下标元素的地址,从而访问对应的元素;
  • 寻址公式:baseAddress + i * dataTypeSize,计算下标的内存地址效率较高;
  • 如果是从 1 开始:寻址公式则为 baseAddress + (i - 1) * dataTypeSize,这样计算下标的时候还多了一步减法;
    • 而这个高频的操作,“日积月累”,如 几亿次的操作,这个减法也挺消耗 CPU 资源的,要尽可能的提高效率;

查找的时间复杂度?

  • 随机查询(通过下标)的时间复杂度是 O(1);
  • 查找元素(未知下标的值查询)的时间复杂度是 O(n);
  • 查找元素(未知下标但是已排序)通过二分的时间复杂度是 O(logn);

插入、修改、删除的时间复杂度?

  • 修改的时候,通过下标访问并修改即可,所以是O(1);
  • 按下标插入和删除的时候,为了保证数组的内存连续性,需要挪动数组元素,平均时间复杂度是 O(n);
    • 插入的时候甚至可能会扩容:O(n);

3. ArrayList 源码(底层原理)有了解过吗?

构造方法:

在这里插入图片描述

扩容机制:

  1. 空集合但是容量为0,即空数组;
    • 第一次添加元素的时候,如果 需要的空间大小 minCapacity 小于默认容量,则扩容至默认容量;
  2. 空集合但是容量不为0,即数组元素都为空;
    • 第一次添加元素的时候,与默认容量无关,按正常的情况走;
  • 添加之前要判断本此次添加 需要的空间大小 minCapacity 是否小于当前的容量,是则无需扩容,否则需要扩容;

    • 原数组扩容为原来的 1.5 倍(如果还是达不到 minCapacity,则取 minCapacity);

    • 如果扩容后的空间大于最大的数组大小,则调用一个方法计算

      在这里插入图片描述

      private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
      

      根据 minCapacity 计算出一个合理的巨大容量;

    • 然后 elementData[size++] = e,在有效数组尾添加节点;

在这里插入图片描述

ArrayList list = new ArrayList(10) 中 ArrayList 扩容了几次?

  • 0 次,调用构造方法,只是实例了一个指定容量的 ArrayList,未扩容;

4. 如何实现数组和 List 之间的转化?

在这里插入图片描述

  1. 数组 => List

使用 JDK 中 Arrays 数组工具类的 asList 方法(可变参数),将其转化为 List;

在这里插入图片描述

不难看出,如果修改原数组指向的元素,是会影响 List 的,当然,触发了扩容之后 List 就换了个数组,就不受影响;

如果要不受影响的话,就用遍历或者 stream 流去转化 List;

  1. List => 数组

使用 List 对象的 toArray 方法转化为数组:

  • 无参的返回的是 Object 数组;

在这里插入图片描述

  • 有参的则是指定类型的数组,并且如果数组足够大,数据会拷贝到数组里,反正以返回值为主就行了;

在这里插入图片描述

不难看出,每次转化数组都是拷贝一份 List 中的数据,所以修改 List 中的数据不会影响转化后的数组;

5. 链表操作数据的时间复杂度是多少?

推荐博客

单向链表只有一个方向(只有头节点),每个节点有一个后继节点 next;

在这里插入图片描述

  • 头插法,O(1)
  • 指定节点的后面插入,O(1)
  • 指定节点的前面插入,O(n)
  • 指定节点的删除,O(n)
  • 指定下标的增删查改,O(n)
  • 查找指定值,O(n)

双向链表则有两个方向(有头节点和尾节点),每个节点有一个后继节点 next 还有一个 前驱节点 prev;

  • 支持双向遍历,提高灵活性;

在这里插入图片描述

  • 头插法、尾插法,O(1)
  • 指定节点的前面/后面插入,指定节点的删除,O(1)
  • 指定下标的增删查改,O(n)
  • 查找指定值,O(n)

在这里插入图片描述

6. ArrayList 和 LinkedList 的区别是什么?

6.1 底层数据结构

ArrayList 是动态数组,LinkedList 是双写链表;

6.2 操作数据效率

  1. 下标访问

ArrayList 按照下标查询的时间复杂度是 O(1),LinkedList 则不支持下标查询,如果从逻辑上的下标查询的话,则时间复杂度是 O(n);

  1. 值查询

ArrayList 和 LinkedList 都是 O(n);

  1. 新增/删除

ArrayList 从尾部插入/删除是 O(1),但是其他部分增删需要挪动数组,甚至触发扩容,时间复杂度是 O(n);

  • 只有给定下标的 O(n);

LinkedList 从头尾节点插入都是 O(1),其他都需要遍历链表,没有扩容的情况,时间复杂度为 O(n);

  • 给定下标是需要遍历的 O(n),但是给定节点则不需要遍历的 O(1);

6.3 内存空间占用

ArrayList 底层是数组,内存连续,节省空间;

LinkedList 底层是双向链表,除了 value 还存了两个指针,更占用内存;

6.4 线程安全

ArrayList 和 LinkedList 都不是线程安全的;

要保证线程安全两种方案:

  1. 在方法内使用,局部变量是线程安全的(让多线程不同时修改同一个对象);

  2. 套壳加锁返回线程安全的 List 实例;

    在这里插入图片描述

7. 什么是二叉树?什么是二叉搜索树?什么是红黑树?

二叉树

  • 二叉搜索树(Binary Search Tree,BST),又叫二叉查找树,有序二叉树;

  • 在树中的任意一个节点,其左子树中的每个节点都小于这个节点,右子树则都大于;

  • 没有键相等的节点;

  • 通常情况下二叉搜索树的增删查改,时间复杂度是 O(logn);

    恶劣情况的二叉搜索树会退化成链表(时间复杂度退化为 O(n)):

    在这里插入图片描述

而我们需要的是平衡度高的二叉搜索树,才能保证查找效率,AVL树和红黑树都是自平衡二叉搜索树。

AVL树通过旋转操作来保持平衡,并且在平衡性上有更严格的要求。

红黑树则使用颜色标记和旋转操作来维护平衡(维持一些规则),相对而言更灵活一些。

在AVL树中,任意节点的左右子树的高度差不超过1,而红黑树的规则则是:

在这里插入图片描述

  • 红黑树(Red Black Tree):也是一种自平衡的二叉搜索树;
  • 所有的红黑规则都是希望红黑树能够保证平;
  • 红黑树的时间复杂度:增删查改都是 O(logn);
    • 旋转调整是 O(1);

8. 什么是散列表(哈希表)?

推荐博客

什么是散列表?

  • 散列表(Hash Table)又名哈希表 / Hash 表;
  • 根据键直接访问内存存储位置的数据结构;
  • 由数组演化而来,利用了数组支持按照下标进行随机访问数据的性质;
  • 数组的每个位置不一定都有值,存储的比较松散,一般会保留一定的空位置;

在这里插入图片描述

散列冲突是什么?

  • 散列冲突又叫哈希冲突、哈希碰撞;
  • 指的是多个 key 映射到同一个数组的下标位置;

散列冲突其实无法完全避免,即使是 md5 这样的算法,也无法绝对的让 key 和 hashValue 一对一,更何况我们没有那么大的数组来存;

  • 要预防或者减少哈希冲突,可以用一些均匀化的算法,让哈希冲突不要集中在某个下标,适当扩充数组…

散列冲突,如何数组同个下标的存储数据?

  • 开散列的方式:
    • 数组的每个下标称之为一个哈希桶(bucket)或者哈希槽(slot);
    • 每个哈希桶会对应一个链表/一颗红黑树;
    • 散列冲突后的元素都放到对应哈希桶内的链表/红黑树中;

9. 说一下 HashMap 的实现原理?

  • 底层使用 Hash 表数据结构,即数组 + 链表/红黑树;
  • 添加数据时,计算 key 的值确定元素在数组中的下标;
    • key 相同则替换;
    • 不同则存入链表或红黑树;
  • 获取数据通过 key 的 hash 计算下标,查询链表/红黑树下标元素,一般是可以看成是 O(1) 的;

HashMap 的 jdk1.7 和 jdk1.8 的区别:

  • jdk1.8 之前,数组 + 链表(头插);
  • jdk 1.8 及之后,数组 + 链表(尾插) + 红黑树,链表长度和数组长度各达到一定值(链表长于 8 并且数组长于 64)则会从链表转化尾红黑树;

10. HashMap 的 put 方法的具体流程?

在这里插入图片描述

哈希表被无参实例化的时候,数组没有被实例化,第一次 put 的时候,再 resize 为 16;
(因为可以由其他构造方法去构造 HashMap,所以第一次 put 时数组不为 null,可能数组长度不会以 16 开始,但是数组的长度一定是 2 的倍数)

在这里插入图片描述

  1. 判断键值对数组 table 是否为空或为 null,否则执行 resize() 进行扩容(初始化)

  2. 根据键值 key 计算 hash 值得到数组索引

  3. 判断 table[i] == null,条件成立,直接新建节点添加

  4. 如果 table[i] == null,不成立

    4.1 判断 table[i] 的首个元素是否和 key 一样,如果相同直接覆盖 value

    4.2 判断 table[i] 是否为 treeNode,即 table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对

    4.3 遍历 table[i],链表的尾部插入数据,然后判断链表长度是否大于 8,大于 8 的话把链表转换为红黑树,在红黑树中执行插入操 作,遍历过程中若发现 key 已经存在直接覆盖 value

  5. 插入成功后,判断实际存在的键值对数量 size 是否超多了最大容量 threshold(数组长度* 0.75),如果超过,进行扩容。

11. HashMap 的扩容机制?

在这里插入图片描述

参考回答:

  • 在添加元素或初始化的时候需要调用 resize 方法进行扩容,第一次添加数据初始化数组长度为 16,以后每次每次扩容都是达到了扩容阈值(数组长度 * 0.75)

  • 每次扩容的时候,都是扩容之前容量的 2 倍;

  • 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中

    • 没有hash冲突的节点,则直接使用 e.hash & (newCap - 1) 计算新数组的索引位置
    • 如果是红黑树,走红黑树的添加
    • 如果是链表,则需要遍历链表,可能需要拆分链表,判断 (e.hash & oldCap) 是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上

在这里插入图片描述

12 HashMap 的寻址方式?

在这里插入图片描述

(h = key.hashCode()) ^ (h >>> 16) ,称为 二次哈希

  1. 首先获取 key 的 hashCode 值;
  2. 然后右移 16 位异或运算原来的 hashCode 值
  • 主要作用就是使原来的 hash 值更加均匀,减少 hash 冲突,例如 17 或者 33 模 16 都等于 1,二次哈希后再取模大概率就不哈希冲突了;

(n - 1) & hash,代替取模,性能更好一些,n 即数组长度,必须是 2 的整数倍才等价于取模;

  • 例如 16 => 15 的二进制 1111,按位与就是保留后面四个比特位,就是取模;

为何 HashMap 的数组长度一定是 2 的整数倍?

  1. 计算索引时效率更高:(n - 1) & hash,代替取模,n 即数组长度,必须是 2 的整数倍才等价于取模;
  2. 扩容时重新计算索引效率更高:hash & oldCap == 0 的元素留在原地,否则 newPos = oldPos + oldCap
    • 例如 16 => 32:
    • 两次计算索引的结果一样 <=> hash 的后 4 位 和 后 5 位 相等 <=> 那么就是从右到左第 5 位必须是 0
      • 也就是 10000(即原数组长度) 按位与 hash,得知其是否为 0;
    • 所以,hash & oldCap == 0,并且在**数组为 2 的整数倍的情况下**是可以推理出来两次计算索引的结果一样(充要条件);

13. HashMap 在 jdk1.7 下,多线程死循环问题

单线程情况下:

在这里插入图片描述

多线程的情况下(两个线程同时让一个 HashMap 扩容):

在这里插入图片描述

线程 1 完成扩容:

在这里插入图片描述

线程 2 执行:

  • 现在 cur2 指向 3,会将 3 头插到扩容数组中,由于这里节点 3 跟线程 1 扩容后的节点 3 是同一个,所以就会出现这种情况:

在这里插入图片描述

  • 所以这样就会导致成环不断循环下去!

问题原因:

  1. 头插法,导致插入的顺序和原来链表的顺序相反的;
  2. table 是共享的,table 里面的元素也是共享的,while 循环都直接修改 table 里面的元素的 next 指向,导致指向混乱;

而 jdk 8 扩容算法做出了调整,使用了尾插法还有红黑树;还可以用线程安全的 ConcurrentHashMap;

  • 其中还有 高低位拆分转移方式,可以查询资料去了解,在这里不讨论;

14. HashSet 有了解过吗?

  1. HashSet 实现了 Set 接口,仅存储对象;
  2. HashMap 实现了 Map 接口,存储的是键值对;
  3. HashSet 底层其实是用 HashMap 实现存储的;HashSet 封装了一系列 HashMap 的方法;依靠 HashMap 来存储元素值(利用 hashMap 的 key 键进行存储), 而 value 值默认为 Object 对象;
  4. 所以 HashSet 也不允许出现重复值,判断标准和 HashMap 判断标准相同, 两个元素的 hashCode 相等并且通过 equals() 方法返回 true;

15. HashTable 有了解过吗?

区别HashTableHashMap
数据结构数组 + 链表数组 + 链表 + 红黑树
是否可以为nullkey 和 value 都不能为 null可以为 null
hash算法key 的 hashCode()二次 hash
扩容方式当前容量翻倍 + 1当前容量翻倍
线程安全同步 (synchronized 加在方法上) 的,线程安全非线程安全

在实际开中不建议使用 HashTable,在多线程环境下可以使用 ConcurrentHashMap 类

  • 推荐文章

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

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

相关文章

超实用的公众号搭建教程分享,纯干货

微信公众号已经成为了企业、个人和品牌进行宣传和互动的重要平台。在这个拥有海量公众号的时代&#xff0c;如何让你的公众号脱颖而出&#xff0c;吸引更多的关注者&#xff0c;实现有效传播呢&#xff1f;接下来&#xff0c;伯乐网络传媒将为你详细解析公众号搭建教程&#xf…

便捷在线导入:完整Axure元件库集合,让你的设计更高效!

Axure元件库包含基本的工具组件&#xff0c;可以使原型绘制节省大量的重复工作&#xff0c;保持整个设计页面的一致性和标准化&#xff0c;同时显得专业。Axure元件库就像我们日常生活中的门把手、自行车踏板和桌子上的螺丝钉&#xff0c;需要组装才能使用。作为一名成熟的产品…

信息安全管理与评估DCST-6000B-Pro神州数码堡垒机沙箱连接教程

信息安全管理与评估DCST-6000B-Pro神州数码堡垒机沙箱连接教程 一、前言 在全国职业院校技能大赛-信息安全管理与评估赛项中&#xff0c;我们会用到DCST-6000B-Pro神州数码堡垒机沙箱&#xff0c;简称堡垒机&#xff0c; 很多院校并没有购买该设备&#xff0c;导致备赛学生可…

阿珊详解Vue Router的守卫机制

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

安全防御第七次实验

需求&#xff1a;在FW7和FW8之间建立一条IPSEC通道保证10.0.2.0/24网段可以正常访问到192.168.1.0/24 一、NAT配置 FW4&#xff1a; FW6&#xff1a; 二、在FW4上做服务器映射 三、配置IPSEC FW5&#xff1a; FW6&#xff1a; 四、防火墙上的安全策略 FW4&#xff1a; FW5:…

spring cloud 之 Netflix Eureka

1、Eureka 简介 Eureka是Spring Cloud Netflix 微服务套件中的一个服务发现组件&#xff0c;本质上是一个基于REST的服务&#xff0c;主要用于AWS云来定位服务以实现中间层服务的负载均衡和故障转移,它的设计理念就是“注册中心”。 你可以认为它是一个存储服务地址信息的大本…

Androidstudio实现登录按钮按下变色

在activity_main.xml中&#xff0c;写如下代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"androi…

禁止使用搜索引擎,你了解吗?

员工A&#xff1a;“我今天想搜索的时候&#xff0c;用不了浏览器了&#xff0c;你能用么&#xff1f;” 员工B&#xff1a;“不知道啊我试一下啊” “也不行” 员工C&#xff1a;“为什么啊&#xff1f;” 针对上述对话&#xff0c;我们不禁思考&#xff1a; 公司为什么禁…

2023最新群智能优化算法:巨型犰狳优化算法(Giant Armadillo Optimization,GAO)求解23个基准函数(提供MATLAB代码)

一、巨型犰狳优化算法 巨型犰狳优化算法&#xff08;Giant Armadillo Optimization&#xff0c;GAO&#xff09;由Omar Alsayyed等人于2023年提出&#xff0c;该算法模仿了巨型犰狳在野外的自然行为。GAO设计的基本灵感来自巨型犰狳向猎物位置移动和挖掘白蚁丘的狩猎策略。GAO…

Spring Boot中SQL语句报错

报错原因&#xff1a; You have an error in your SQL syntax 你的SQL语句出现错误 报错位置&#xff1a; check the manual that corresponds to your MySQL server version for the right syntax to use near :/sql/schema.sql.t_film at line 1 在:/sql/schema.sql附近使用…

前端使用Ant Design中的Modal框实现长按顶部拖动弹框需求

需求&#xff1a;需要Ant Design中的一个Modal弹框&#xff0c;并且可以让用户按住顶部实现拖动操作 实现&#xff1a;在Ant Design的Modal框的基础上&#xff0c;在title中添加一个onMouseDown去记录拖拽的坐标&#xff0c;然后将其赋值给Modal的style属性 代码部分&#xff…

20 卷积层里的填充和步幅【李沐动手学深度学习v2课程笔记】

1. 填充和步幅 在上下左右分别填充一些0 2. 代码实现 2.1 填充 我们创建一个高度和宽度为3的二维卷积层&#xff0c;并在所有侧边填充1个像素。给定高度和宽度为8的输入&#xff0c;则输出的高度和宽度也是8。 import torch from torch import nn# 为了方便起见&#xff0c;…

smplx pkl格式可视化

smplx pkl格式可视化 import glob import os import pickleimport torch import numpy as npfrom smplpytorch.pytorch.smpl_layer import SMPL_Layer from display_utils import display_model, display_model_continuousfrom matplotlib import pyplot as plt from matplotl…

详解float函数类型转换

函数描述 float([x]) 函数将数字或数字的字符串表示形式转换为与它等效的有符号浮点数。如果参数x是一个字符串&#xff08;十进制表示的数字串&#xff09;&#xff0c;数字前面可以添加符号来表示正数&#xff0c;或负数。符号和数字之间不能出现空格&#xff0c;但是符号前…

Spring学习 基础(二)Bean和AOP

3、Spring Bean Bean 代指的就是那些被 IoC 容器所管理的对象&#xff0c;我们需要告诉 IoC 容器帮助我们管理哪些对象&#xff0c;这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。 Bean的创建方式 1. XML 配置文件&#xff1a; 传统上&am…

python 截取字符串string.split

目录 作用语法只要第一个值获得第3个值遍历 作用 根据某个符号对数据进行截取 从而获得自己想要的内容 语法 使用’string.split’ 方法 对字符串’123/abc/BPYC’ 以 ‘/’ 进行截取 string "123/abc/BPYC" substring string.split("/") print(subs…

3、proxy、for...of、iterator遍历器以及原理

一、proxy&#xff1a; 1、proxy的作用&#xff1a;&#xff08;重点&#xff09; 代理解决跨域 2、proxy代理格式&#xff1a; // target:要代理的对象 property:对象里的方法 let proxy new Proxy(target, property);3、代理里面的方法 get(target,property) 创建代…

对simplex算法的时间复杂度进行分析

对于simplex算法,如果每进行一次pivot变换,目标函数所得到的结果都会有可能出现增加的情况,所以得到的结论中,可以肯定它的值是一定不会出现减少的情况的,每次从目标函数中找到一个系数大于0的变量,然后再在约束条件中选取能够让它的增值最少的那个来继续进行pivot变换。…

代码随想录day16 栈与队列:前 K 个高频元素(leetcode347)

题目要求&#xff1a;给定一个非空的整数数组&#xff0c;返回其中出现频率前 k 高的元素。 思路&#xff1a;我们需要使用map来统计整个数组中元素出现的频率&#xff0c;然后再根据统计好的频率去排序&#xff0c;取得频率前K高的元素。我们不必使用快排实际上我们使用优先级…

【Web前端】Vue核心基础

文章目录 1. Vue简介2. Vue官网使用指南3. 初识Vue3.1 搭建Vue开发环境3.2 HelloWorld案例3.3 el与data的两种写法3.4 MVVM模型3.5 模板语法 4. 数据绑定4.1 v-bind单向数据绑定4.2 v-model双向数据绑定 5. 事件处理5.1 v-on绑定事件5.2 事件修饰符5.3 键盘事件 6. 计算属性6.1…
最新文章