Java高频面试题--String和集合相关问题的整理和原理介绍,全网最详细的了

摘要:
本篇博客主要记录了Java面试的高频提问问题的原理和答案,包括了StringBuffer类和StringBuilder类、Java集合相关、List接口相关、Set接口相关、Map接口相关,具有比较详细的原理介绍。

String相关

1、StringBuffer类

该类代表可变的字符序列,jdk1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。
很多方法与string相同。
作为参数传递时,方法内部可以改变值。

StringBuffer类和String类不同,StringBuffer类必须使用构造器生成。主要有三个构造器:
StringBuffer():初始容量为16的字符串缓冲区;
StringBuffer(int size):构造指定容量的字符串缓冲区;
StringBuffer(String str):将内容初始化为指定字符串内容。

2、StringBuilder类

StringBuilder和StringBufffer非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样。
面试题:
对比String、StringBuffer、StringBuilder
String(jdk1.0):不可变字符序列;
StringBuffer(jdk1.0):可变字符序列,效率低、线程安全
StringBuilder(jdk5.0):可变字符序列、效率高、线程不安全
为什么线程安全/不安全?
StringBuffer 是通过使用 synchronized 来保证线程安全的。synchronized 关键字可以确保在同一时刻只有一个线程可以访问 StringBuffer 的方法。
注意:
作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值

集合相关

1、Java集合概述

Java集合可以分为Collection和Map两种关系
Colleciton接口:单列数据,定义了存取一组对象的方法的集合
List:元素有序、可重复的集合
Set:元素无序、不可重复的集合
Map接口:
双列数据,保存具有映射关系"key-velue"的集合。

2、List接口概述

List集合类种元素有序、且可重复,集合中的每个元素都有其对应的顺序索引;
List容器中的元素都对应一个整数型序号记载其在容器中的位置,可以根据序号存取容器中的元素;
jdk api中List接口的实现类常有:ArrayList、LinkedList和Vector。

(1)ArrayList

ArrayList是List接口的典型实现类、主要实现类;
本质上ArrayList是对象引用的一个变长数组;
ArrayList的jdk1.8之前与之后的实现区别:
jdk1.7:ArrayList像饿汉式,直接创建初始容量为10的数组;
jdk1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个初始容量为10的数组。

(2)LinkedList

对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高。
底层原理:
LinkedList是双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。

(3)Vector

Vector是一个古老的集合,jdk1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。

(4)面试题

请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层 是什么?扩容机制?Vector和ArrayList的最大区别?
ArrayList和LinkedList的异同 二者都线程不安全,相对线程安全的Vector,执行效率高。 此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于双向链表的数据结构。对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
ArrayList和Vector的区别 Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用 ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

3、Set接口概述

Set接口是Collection的子接口,Set接口没有提供额外的方法;
Set集合不允许包含相同的元素,如果试着把两个相同元素加入同一个Set集合中,则添加操作失败;
Set判断两个对象是否相同不是使用==运算符,而是根据¥equals()方法。

4、Set实现类

(1)HashSet

HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet具有以下特点:
不能保证元素的排列顺序;
HashSet不是线程安全的;
集合元素可以时null。
HashSet集合判断两个元素相等的标准:
两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

向HashSet中添加元素的过程:
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法 来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象 在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在 数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布, 该散列函数设计的越好)
如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果 为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了, 那么会通过链表的方式继续链接。
注意:
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相 等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
底层原理:
HashSet的底层也是数组,初始容量为16,当使用率超过0.75(16*0.75),就会扩大容量为原来的2倍。(16扩容后为32,依次为64,128…等)

重写hashCode()方法的基本准则:
在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值;
当两个对象的equals()方法比较返回true时,这两个对象的hashCode()方法的返回值也应相等;
对象中用作equals()方法比较的Field,都应该用来计算hashCode。

重写equals()方法的基本原则:
以自定义的Customer类为例,何时需要重写equals()?
当一个类有自己特有的“逻辑相等”,当改写equals()的时候,总是要改写hashCode(),根据Object.hashCode()方法,它们仅仅是两个对象。
因此违反了“相等的对象必须具有相等的散列码”。
综上所述:
复写equals()方法的时候一帮都需要同事复写hashCode()方法。通常参与计算hashCode()的对象的属性也应该参与到equals()中进行计算。
**为什么用Eclipse/IDEA复写hashCode方法,有31这个数字? **
选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的 “冲突”就越少,查找起来效率也会提高。(减少冲突)
并且31只占用5bits,相乘造成数据溢出的概率较小。
31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效 率)
31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结 果只能被素数本身和被乘数还有1来整除!(减少冲突)

(2)LinkedHashSet

LinkedHashSet是HashSet的子类;
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能;
LinkedHashSet不允许集合元素重复。

(3)TreeSet

TreeSet时SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet底层使用红黑树结构存储数据。
TreeSet两种排序方法:
自然排序和定制排序。
默认情况下,TreeSet采用自然排序。
TreeSet和TreeMap底层都是采用红黑树的存储结构。
特点:
有序,查询速度比List快。
红黑树参考链接
排序相关请网上查找!!!

5、Map接口

(1)Map接口概述

Map中的Key和value都可以是任何引用类型的数据;
Map中的key用Set来存放,不允许重复,即同一个Map对象所对应的类,须重写hashCode()和equals()方法;
常用String类作为Map的“键”;
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HasMap是Map接口使用频率最高的实现类。

(2)HashMap

HashMap是Map接口使用频率最高的实现类;
允许使用null键和null键,与HashSet一样,不保证映射的顺序;
所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals();
一个key-value构成一个entry;
所有的entry构成的集合是Set:无序的、不可重复的;
HashMap判断两个key相等的标准是:
两个key通过equals()方法返回true,hashCode值也相等;
HashMap判断两个value相等的标准是:
两个value通过equals()方法返回true。

HashMap的存储结构:
jdk 7及以前版本:HashMap是数组+链表结构(即为链地址法)
jdk 8版本发布以后:HashMap是数组+链表+红黑树实现

** HashMap什么时候进行扩容和树形化呢? **
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有 达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成 树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后, 下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
**关于映射关系的key是否可以修改? **
不要修改。 映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算 每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关 系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。
** JDK1.8相较于之前的变化: **
1.HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
2.当首次调用map.put()时,再创建长度为16的数组
3.数组为Node类型,在jdk7中称为Entry类型
4.形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
5.当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置 上的所有key-value对使用红黑树进行存储。

面试题:
**谈谈你对HashMap中put/get方法的认识?如果了解再谈谈 HashMap的扩容机制?默认大小是多少?什么是负载因子( 或填充比)?什么是吞吐临界值(或阈值、threshold)? **
HashMap是Java中常用的一种数据结构,它是基于哈希表实现的。HashMap中put方法用于将键值对存储到哈希表中,get方法用于从哈希表中获取指定键对应的值。在HashMap中,每个键值对都会映射到一个桶(bucket)中,桶是哈希表中的一个槽,它包含了一组键值对。当我们调用put方法时,HashMap会根据键的哈希值找到对应的桶,然后将键值对存储到该桶中。当我们调用get方法时,HashMap也会根据键的哈希值找到对应的桶,然后在该桶中查找指定键对应的值。
HashMap的扩容机制是指在哈希表中存储的键值对数量达到一定阈值时,HashMap会自动扩容,以提高哈希表的性能。默认情况下,HashMap的初始容量为16,负载因子为0.75。负载因子是指哈希表中键值对数量与桶数量的比值。当HashMap中键值对数量达到容量与负载因子的乘积时,就会触发扩容操作。在扩容时,HashMap会将桶数量扩大一倍,并重新计算每个键值对的哈希值和所属桶的索引,然后将键值对重新分配到新的桶中。
扩容过程中,HashMap需要重新计算每个键值对的哈希值和所属桶的索引,这会消耗一定的时间和计算资源。因此,我们可以通过调整HashMap的初始容量和负载因子来平衡HashMap的性能和空间占用。如果我们预先知道HashMap中键值对的数量,可以通过设置合适的初始容量来减少扩容的次数,从而提高HashMap的性能。如果我们希望HashMap占用的空间更小,可以将负载因子设置为较小的值。
吞吐临界值(或阈值threshold)是HashMap中的一个重要参数,它表示哈希表中存储的键值对数量达到多少时需要进行扩容操作。默认情况下,吞吐临界值等于容量与负载因子的乘积。在扩容时,HashMap会重新计算吞吐临界值,以确保哈希表中存储的键值对数量不会超过负载因子所允许的最大值。
**负载因子值的大小,对HashMap有什么影响? **
负载因子是HashMap中一个重要的参数,它表示哈希表中键值对数量与桶数量的比值。负载因子值的大小对HashMap有以下几个影响:

  1. 决定了哈希表的填充程度:负载因子越大,哈希表中存储的键值对数量就越多,填充程度也就越高, 同时发生碰撞的几率越高,数组中的链表越容易长, 造成查询或插入时的比较次数增多,性能会下降  。反之亦然。如果负载因子过高,可能会导致哈希冲突增多,查找、插入和删除操作的性能下降。
  2. 决定了哈希表的空间利用率:负载因子越大,哈希表中存储的键值对数量就越多,空间利用率也就越高,反之亦然。如果负载因子过低,可能会导致哈希表占用过多的空间,浪费内存资源。
  3. 直接影响哈希表的性能:负载因子越小,在哈希表中查找、插入和删除键值对的平均时间复杂度就越小,性能也就越好。但是,负载因子过小会导致哈希表频繁扩容,增加了时间和空间的开销。

因此,选择合适的负载因子值非常重要。通常情况下,负载因子的推荐值为0.75。这个值既可以保证哈希表的填充程度不会过高,也可以保证哈希表的空间利用率不会过低,同时还能保证哈希表的性能达到较好的平衡。如果我们希望HashMap占用的空间更小,可以将负载因子设置为较小的值。如果我们预先知道HashMap中键值对的数量,可以通过设置合适的初始容量来减少扩容的次数,从而提高HashMap的性能。

(3)LinkedHashMap

LinkedHashMap是HashMap的子类;
在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
与LinkedHashSet类型,LinkedHashMap可以维护Map的迭代顺序,迭代顺序与key-value对的插入顺序一致

(4)TreeMap

TreeMap存储key-value对时,需要根据key-value对进行排序。TreeMap可以保证所有的key-value对处于有序状态;
TreeMap底层使用了红黑树结构存储数据;
TreeMap 的 Key 的排序:
自然排序:
TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
定制排序:
创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
TreeMap判断两个key相等的标准:
两个key通过compareTo()方法或 者compare()方法返回0。

(5)Hashtable

Hashtable是个古老的Map实现类,jdk 1.0就提供了。不同于HashMap,Hashtable是线程安全的;
Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用;
与HashMap不同,Hashtable不允许使用null作为key和value;
与HashMap一样,Hashtable也不能保证其中key-value对的顺序;
Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。

(6)Properties

Properties类是Hashtable的子类,该对象用于处理属性文件;
由于属性文件里的key、value都是字符串类型,所以Properties里的key和value都是字符串类型;
存取数据是,建议使用setProperty(String key,String value)方法和getProperty(String key)方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小雏菊的成长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值