面试官问:你熟悉哪些HashMap的封装扩展类?

2023-11-14

我习惯了无所谓,却不是真的什么都不在乎。                 请关注:源码猎人

目录

简介

LinkedHashMap 源码解读

LinkedHashMap属性

LinkedHashMap构造函数

LinkedHashMap 方法

LinkedHashMap 内部类

LinkedHashMap.Entry,v>

HashSet 类源码解读

HashSet 属性

HashSet 构造函数

HashSet 方法

LinkedHashSet 类源码解读

LinkedHashSet 构造函数

常见面试题


简介

LinkedHashMap、HashSet、LinkedHashSet都扩展了HashMap。它们是HashMap的二次封装,LinkedHashMap加入了一个单独链表存所有数据,并且完整保留HashMap结构。 HashSet 实际上只用到了HashMap的键,值为常量;LinkedHashSet 继承HashSet,并且和LinkedHashMap一样内部维护一个链表。

LinkedHashMap 源码解读

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

LinkedHashMap属性

// 链表头结点
transient LinkedHashMap.Entry<K,V> head;
// 链表尾结点
transient LinkedHashMap.Entry<K,V> tail;
// LRU算法相关  false 基于插入顺序,true 基于访问顺序 
final boolean accessOrder;

LinkedHashMap构造函数

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    // 默认使用插入顺序
    accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    // 默认使用插入顺序
    accessOrder = false;
}
public LinkedHashMap() {
    super();
    // 默认使用插入顺序
    accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    // 默认使用插入顺序
    putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    // 自定义顺序
    this.accessOrder = accessOrder;
}

从构造函数中可以看出,LinkedHashMap几乎完全使用HashMap的构造方法初始化(参照HashMap类篇),至于初始长度和加载因子也是延用HashMap,LinkedHashMap类自身只关注链表结构,数字加链表结构交给父类去维护

LinkedHashMap 方法

根据键取值

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

实际上就是调用HashMap的getNode方法, afterNodeAccess(e)方法只是为了维护顺序,除了这个方法LinkedHashMap没有添加删除方法,那么他是怎么维护链表结构的呢?是否还记得在HashMap篇有几个方法要放在LinkedHashMap讲

void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

这几个方法出现在HashMap元素增加或减少之后,就是为了给hashMap子类使用

父类元素变动后方法

// HashMap删除节点后
void afterNodeRemoval(Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    p.before = p.after = null;
    if (b == null)
        // 如果是头节点,则把头节的下一个节点设为头节点
        head = a;
    else
        // 否则,把前一个节点的下一个节点指向当前下一个节点
        b.after = a;
    if (a == null)
        // 如果是尾节点,设置当前节点前一个节点为尾节点
        tail = b;
    else
        // 否则把后面节点的前面执行当前节点的前面
        a.before = b;
}

HashMap删除元素成功后会调用此方法afterNodeRemoval

// 插入成功删除头节点
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

afterNodeInsertion方法是在哈希表中插入了一个新节点时调用的,它会把链表的头节点删除掉,删除的方式是通过调用HashMap的removeNode方法。我们要使用此功能必须重写removeEldestEntry方法

// accessOrder为true时将节点移到最后
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

当accessOrder设置为true时,把当前节点e移至链表的尾部,在HashMap的putVal方法中,会调用此方法

LinkedHashMap 内部类

LinkedHashMap.Entry<K,V>

static class Entry<K,V> extends HashMap.Node<K,V> {
    // 前一个结点,后一个节点
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

此内部类在HashMap中也用到,HashMap的TreeNode就是继承此类(是不是很绕)

HashSet 类源码解读

public class HashSet<E> extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable

跟ArraryList相比没有实现RandomAccess接口,接下来看成员变量和构造函数

HashSet 属性

// 底层是HashMap实现的   
private transient HashMap<E,Object> map;
// PRESENT是一个假的value值,帮助用HashMap实现HashSet。
private static final Object PRESENT = new Object();

HashSet 构造函数

public HashSet() {
    map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

通过构造函数可以看出HashSet的初始化时内部构建了一个HashMap对象

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

此构造函数安全级别为默认,只有自己或同包子类可以调用。HashSet并没有调用它,它是给子类使用的,跟上面构造函数唯一的区别就是内部Map换成LinkedHashMap<>对象

HashSet 方法

// 获取长度
public int size() {
    return map.size();
}
// 是否为空
public boolean isEmpty() {
    return map.isEmpty();
}
// 是否包含
public boolean contains(Object o) {
    return map.containsKey(o);
}
// 添加元素
public boolean add(E e) {
    // 添加元素时,元素为键,值为固定常量存入Map
    return map.put(e, PRESENT)==null;
}
// 删除元素
public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}
// 清空
public void clear() {
    map.clear();
}

HashSet只是用了HashMap的key,而值是一个固定的常量,所以HashMap的key拥有哪些特性HashSet就拥有哪些特性。

LinkedHashSet 类源码解读

public class LinkedHashSet<E> extends HashSet<E>
        implements Set<E>, Cloneable, java.io.Serializable

LinkedHashSet完成继承HashSet,内部只有构造函数

LinkedHashSet 构造函数

public LinkedHashSet(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) {
    super(initialCapacity, .75f, true);
}
public LinkedHashSet() {
    super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) {
    super(Math.max(2*c.size(), 11), .75f, true);
    addAll(c);
}

LinkedHashSet 所有构造函数都调用HashSet的HashSet(int initialCapacity, float loadFactor, boolean dummy)构造函数,内部使用LinkedHashMap对象

常见面试题

1.说一下LinkedHashMap的数据结构

LinkedHashMap继承自HashMap,并维持了一个双向链表。插入节点时,将节点追加到双向链表尾部,从而实现按照插入顺序的有序访问。也可以在初始化LinkedHashMap对象时设定为按照访问顺序排序,此时每当访问一个节点,afternodeaccess方法就会将该节点放到双向链表的尾部,从而实现按照访问顺序的有序遍历访问。

2、请说说HashSet原理

HashSet在存元素时,会调用对象的hashCode方法计算出存储位置,然后和该位置上所有的元素进行equals比较,
如果该位置没有其他元素或者比较的结果都为false就存进去,否则就不存。这样的原理注定了元素是按照哈希值来找存储位置,所有无序,而且可以保证无重复元素,我们在往HashSet集合存储元素时,对象应该正确重写Object类的hashCode和equals方法
正因为这样的原理,HashSet集合是非常高效的。

3、为什么HashMap中String、Integer这样的包装类适合作为K?

String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率

  • 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
  • 内部已重写了equals()hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况

如果Object作为键,那么需要重写hashCode()equals()方法

  • 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞
  • 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性

4、HashMap 和 LinkedHashMap 有什么区别?

  • LinkedHashMap 拥有与 HashMap 相同的底层哈希表结构,即数组 + 单链表 + 红黑树,也拥有相同的扩容机制。
  • LinkedHashMap 相比 HashMap 的拉链式存储结构,内部额外通过 Entry 维护了一个双向链表。
  • HashMap 元素的遍历顺序不一定与元素的插入顺序相同,而 LinkedHashMap 则通过遍历双向链表来获取元素,所以遍历顺序在一定条件下等于插入顺序。

5、ArrayList 与 LinkedList 有什么区别 ?

  • 存储结构上 ArrayList 底层使用数组进行元素的存储,LinkedList 使用双向链表作为存储结构。
  • 两者均与允许存储 null 也允许存储重复元素。
  • 在性能上 ArrayList 在存储大量元素时候的增删效率 平均低于 LinkedList,因为 ArrayList 在增删的是需要拷贝元素到新的数组,而 LinkedList 只需要将节点前后指针指向改变。
  • 在根据角标获取元素的时间效率上ArrayList优于 LinkedList,因为数组本身有存储连续,有 index 角标,而 LinkedList 存储元素离散,需要遍历链表。
  • 不要使用 for 循环去遍历 LinkedList 因为效率很低。
  • 两者都是线程不安全的,都可以使用 Collections.synchronizedList(List<E> list) 方法生成一个线程安全的 List。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

面试官问:你熟悉哪些HashMap的封装扩展类? 的相关文章

  • HashMap不写入数据库

    我尝试在我的数据库中写入 但只写入发件人和消息 我不明白为什么会发生这种情况 我认为问题出在我使用 sendMessage 的地方 我认为问题是我没有什么可以做的读 写其他用户的主键 我在数据库中写入消息的活动 public class M
  • 无法解析类型为 xxx 的任何 bean;限定符:[@javax.enterprise.inject.Any()]

    我有一个 LoginProvider 接口 public interface LoginProvider boolean login String username String password 以及两种不同的实现 public clas
  • 使用 GWT CellTableBuilder 构建树表

    Is it possible to build a tree table like this http www sencha com examples ExamplePlace basictreegrid with the new Cell
  • Android 2.2 SDK - Droid X 相机活动无法正常完成

    我注意到我在 Droid X 上调用的默认相机活动与我的 Droid 和 Nexus One 上的默认相机活动看起来不同 在 Droid 和 Nexus One 上选择 确定 后 活动将完成 Droid X 有一个 完成 按钮 它将带您返回
  • 添加动态数量的监听器(Spring JMS)

    我需要添加多个侦听器 如中所述application properties文件 就像下面这样 InTopics Sample QUT4 Sample T05 Sample T01 Sample JT7 注意 这个数字可以多一些 也可以少一些
  • Grails 2.3.0 自动重新加载不起作用

    我最近将我们的项目升级到 grails 2 3 0 一切工作正常 除了每当我更改代码时自动重新加载都无法工作的问题 这包括所有项目工件 控制器 域 服务 gsps css 和 javascript 文件 我的旧版本 grails 可以正常工
  • Condition 接口中的 signalAll 与对象中的 notificationAll

    1 昨天我才问过这个问题条件与等待通知机制 https stackoverflow com questions 10395571 condition vs wait notify mechanism 2 我想编辑相同的内容并在我的问题中添加
  • Java套接字:在连接被拒绝异常时重试的最佳方法?

    现在我正在这样做 while true try SocketAddress sockaddr new InetSocketAddress ivDestIP ivDestPort downloadSock new Socket downloa
  • 主线程如何在该线程之前运行?

    我有以下代码 public class Derived implements Runnable private int num public synchronized void setA int num try Thread sleep 1
  • 记录骆驼路线

    我的项目中有几个 Camel 上下文 如果可能的话 我想以逆向工程方式记录路线 因为我们希望保持与上下文相关的文档最新 最好的方法是什么 我们倾向于预先实际设计路线 并使用来自EIP book http www eaipatterns co
  • Spring Security OAuth2简单配置

    我有一个简单的项目 需要以下简单的配置 我有一个 密码 grant type 这意味着我可以提交用户名 密码 用户在登录表单中输入 并在成功时获得 access token 有了该 access token 我就可以请求 API 并获取用户
  • 如何配置 WebService 返回 ArrayList 而不是 Array?

    我有一个在 jax ws 上实现的 java Web 服务 此 Web 服务返回用户的通用列表 它运行得很好 Stateless name AdminToolSessionEJB RemoteBinding jndiBinding Admi
  • 为什么java中的for-each循环中需要声明变量

    for 每个循环的通常形式是这样的 for Foo bar bars bar doThings 但如果我想保留 bar 直到循环结束 我可以not使用 foreach 循环 Foo bar null Syntax error on toke
  • 解决错误javax.mail.AuthenticationFailedException

    我不熟悉java中发送邮件的这个功能 我在发送电子邮件重置密码时遇到错误 希望你能给我一个解决方案 下面是我的代码 public synchronized static boolean sendMailAdvance String emai
  • Java:拆箱整数时出现空指针异常?

    此代码导致空指针异常 我不知道为什么 private void setSiblings PhylogenyTree node Color color throws InvalidCellNumberException PhylogenyTr
  • Java的-XX:+UseMembar参数是什么

    我在各种地方 论坛等 看到这个参数 并且常见的答案是它有助于高并发服务器 尽管如此 我还是找不到 sun 的官方文档来解释它的作用 另外 它是Java 6中添加的还是Java 5中存在的 顺便说一句 许多热点虚拟机参数的好地方是这一页 ht
  • Java:多线程内的 XA 事务传播

    我如何使用事务管理器 例如Bitronix http docs codehaus org display BTM Home JBoss TS http www jboss org jbosstm or Atomikos http www a
  • Hibernate 和可序列化实体

    有谁知道是否有一个框架能够从实体类中剥离 Hibernate 集合以使它们可序列化 我查看了 BeanLib 但它似乎只进行实体的深层复制 而不允许我为实体类中的集合类型指定实现映射 BeanLib 目前不适用于 Hibernate 3 5
  • JAXB - 列表<可序列化>?

    我使用 xjc 制作了一些课程 public class MyType XmlElementRefs XmlElementRef name MyInnerType type JAXBElement class required false
  • Android 和 Java 中绘制椭圆的区别

    在Java中由于某种原因Ellipse2D Double使用参数 height width x y 当我创建一个RectF在Android中参数是 left top right bottom 所以我对适应差异有点困惑 如果在 Java 中创

随机推荐

  • SpringBoot框架实现邮件发送(上)

    文章目录 前言 1 邮件发送类依赖导入 2 配置发件邮箱的信息 3 邮件接收类 4 邮件发送工具类 前言 实现登录注册功能的时候 一些软件总是要手机号验证码或者邮件验证码 手机号验证码功能的实现是需要付费使用的 而且也比较容易搭建 例如阿里
  • 【unity3d】Layers的控制/LayerMask的使用

    文章目录 unity Layers的控制 LayerMask的使用 Layers 概述 演示效果 Layers的设置 gameobject设置Layer 手动设置 代码设置 LayerMask的使用 Camera的culling mask的
  • TypeScript基础学习

    最终还是没有逃过ts的魔爪 哈哈哈也不能说魔爪 工作这段时间 感觉每天都在学习新的知识 最近在看源代码的时候看到了部分源码是用ts写的 之前没接触过 今天就来学习一下ts 文章参考 TypeScript超详细入门教程 TypeScript
  • python练习题3

    1 数列翻转 reverse 问题描述 编写程序对列表中的数据进行翻转转换 即将数组中第一个数和最后一个数交换 第二个数和倒数第二个数交换 依此类推 样例输入 4 100 200 300 400 样例输出 400 300 200 100 a
  • bpython bs4用哪个解释器好_Python之解BS4库如何安装与使用?正确方法教你

    Beautiful Soup 库一般被称为bs4库 支持Python3 是我们写爬虫非常好的第三方库 因用起来十分的简便流畅 所以也被人叫做 美味汤 目前bs4库的最新版本是4 60 下文会介绍该库的最基本的使用 具体详细的细节还是要看 官
  • Cocos2d-android游戏引擎

    什么是游戏引擎 游戏引擎是指一些已编写好的可编辑游戏系统或者一些交互式实时图像应用程序的核心组件 这些系统为游戏设计者提供各种编写游戏所需的各种工具 其目的在于让游戏设计者能容易和快速地做出游戏程式而不用由零开始 Cocos2d家族 coc
  • 数据结构小知识------时间与空间复杂度

    本章思维导图 一 时间复杂度 1 1时间复杂度的概念 什么是时间复杂度呢 时间复杂度其实就是一个程序运行时它的指令运行的次数 在这里 程序默认每条指令的运行时间是一样的 所以时间复杂度就可以理解为是程序内指令的运行次数 说一千道一万 不如来
  • Java 使用EasyExcel解析导入的Excel文件

    最近在做项目时 有遇到需要使用excel导入的场景 以前也有写过使用 Apache poi 来解析导入数据 但整体解析逻辑比较繁琐 封装成工具类后也不是很好用 这个可能是我个人技术原因 和poi无关 这次开发时 在网上找了个更加简洁的方式
  • Python循环控制语句

    Python循环控制语句 生活中循环的例子也很多 例如 听歌的时候进行循环等等 程序中循环的效果和生活中的循环效果相同 Python中的循环是往复的执行某一段代码 结构while循环 初始条件设置 通常是一个计数器 来控制条件表达式是否成立
  • OpenStack nova-compute 报TooOldComputeService版本过低问题

    项目场景 安装openstack的nova compute部分 问题描述 启动nova conductor时报错 查看nova conductor log 发现如下错误 Current Nova version does not suppo
  • android aosp,安卓源码AOSP下载使用的正确姿势

    安卓源码AOSP下载使用的正确姿势 从同步源码到编译完成 整个过程应至少准备200G空间 编译时需要的内存数与编译线程数相关 博主实测比较极限的配置是4核8G 超过这个范围将触发swap交换导致编译速度急剧下降 开始搞 注 以下 号所有内容
  • mac运行ps特别慢_PS CC 2019 太卡,运行特别慢?这几个优化提速技巧我再说一遍...

    只要设置好这几个选项 让你的 PS CC 2019 运行如飞 曾经写过关于PS优化提速的教程 但总有粉丝问我PS很卡很慢 怎么办 所以 这几个核心的 PS 优化提速技巧我再说一遍 先声明一下 我这里讲的优化提速是指你电脑配置足够的情况下PS
  • ​LeetCode刷题实战33:搜索旋转排序数组

    来源 https www cnblogs com techflow p 12441002 html 算法的重要性 我就不多说了吧 想去大厂 就必须要经过基础知识和业务逻辑面试 算法面试 所以 为了提高大家的算法能力 这个公众号后续每天带大家
  • 【Monkey】Android压力测试

    一 简单介绍一下Monkey Monkey工具直接运行在设备或模拟器的adb shell中 生成用户或系统的伪随机事件流 二 Monkey命令 1 adb shell monkey p package 事件数 50 随机完成50个事件 ad
  • Unity架构之域重新加载

    域重新加载 域重新加载将重置脚本状态 默认情况下会启用域重新加载 此功能为您提供了全新的脚本状态 并会在您每次进入运行模式时重置所有静态字段和已注册的处理程序 这意味着每次在 Unity Editor 中进入运行模式时 您的项目就会采用与在
  • pkpm字体库下载_pkpm字体库转到cad

    等级 文件 5MB 格式 rar 五层框架结构PKPM模型 CAD配筋图纸 建筑说明 本工程为唐山市市医院办公大楼 建筑面积约为 4000平方米 本建筑共五层 为框架结构 抗震烈度按8度设防 图纸包括 唐山医院建筑图 CAD配筋图纸以及pk
  • matlab实现以不同信噪比在干净语音信号中叠加噪声

    原理公式 信噪比计算公式 信号功率和噪声功率之比 也是信号幅度和噪声幅度的平方之比 一般情况下我们使用分贝的形式 即单位是dB 其值为对数信号与噪声功率比的十倍 matlab实现代码 function y noise add noise m
  • shopify 前端开发遇到的问题及解决(部分)

    问题 gallery不同部分的小li互相干扰 解决 修复了小li互相干扰的bug 原因 其实不单单需要修改小li的class 并且需要修改小li的控件 也就是是loopli 不然会互相干扰 shopify的section中jQuery能够拿
  • MongoDB 内置角色

    1 数据库用户角色 针对每一个数据库进行控制 read 提供了读取所有非系统集合 以及系统集合中的system indexes system js system namespacesreadWrite 包含了所有read权限 以及修改所有非
  • 面试官问:你熟悉哪些HashMap的封装扩展类?

    我习惯了无所谓 却不是真的什么都不在乎 请关注 源码猎人 目录 简介 LinkedHashMap 源码解读 LinkedHashMap属性 LinkedHashMap构造函数 LinkedHashMap 方法 LinkedHashMap 内