Java集合高频(背诵版)

weixin公众号:Java工程师八股文;

jvm虚拟机(背诵版)

mysql(背诵版)全

java基础(背诵版)(重点)

计算机网络(背诵版)

1.集合

  • Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collecton接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。对于Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue。

2.常用的集合类有哪些?***

Map接口和Collection接口是所有集合框架的父接口:

  • Collection接口的子接口包括:Set接口和List接口
  • Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  • Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  • List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

集合框架底层数据结构
Collection

List

  • Arraylist: Object数组
  • Vector: Object数组
  • LinkedList: 双向循环链表

Set

  • HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
  • LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

Map

  • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
  • LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
  • HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap: 红黑树(自平衡的排序二叉树)

哪些集合类是线程安全的?

**

  • vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
  • statck:堆栈类,先进后出。
  • hashtable:就比hashmap多了个线程安全。
  • enumeration:枚举,相当于迭代器。

**

Java集合的快速失败机制 “fail-fast”?

  • 是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。

  • 例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

  • 原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

  • 解决办法:

    在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。

3.Array 和 ArrayList 有何区别? ***

  • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
  • Array大小是固定的,ArrayList的大小是动态变化的。(ArrayList的扩容是个常见面试题)
  • 相比于Array,ArrayList有着更多的内置方法,如addAll(),removeAll()。
  • 对于基本类型数据,ArrayList使用自动装箱来减少编码工作量;而当处理固定大小的基本数据类型的时候,这种方式相对比较慢,这时候应该使用Array。

如何实现数组和 List 之间的转换?(编程题用得到)

  • 数组转 List:使用 Arrays. asList(array) 进行转换。
  • List 转数组:使用 List 自带的 toArray() 方法。

4.comparable 和 comparator的区别? **

  • comparable接口出自java.lang包,可以理解为一个内比较器,因为实现了Comparable接口的类可以和自己比较,要和其他实现了Comparable接口类比较,可以使用compareTo(Object obj)方法

  • comparator接口出自java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序

很多包装类都实现了comparable接口,像Integer、String等,所以直接调用Collections.sort()直接可以使用。如果对类里面自带的自然排序不满意,而又不能修改其源代码的情况下,使用Comparator就比较合适。

5.遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?**

  • for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。

  • 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。

  • foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。

6.ArrayList 和 Vector 的区别是什么?***

这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合

  • 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
  • 性能:ArrayList 在性能方面要优于 Vector。
  • 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

ArrayList的扩容机制 ***

ArrayList的初始容量为10,扩容时对是旧的容量值加上旧的容量数值进行右移一位(位运算,相当于除以2,位运算的效率更高),所以每次扩容都是旧的容量的1.5倍。

7.ArrayList、Vector、LinkedList 的存储性能和特性? ***

  • ArrayList底层数据结构为数组,对元素的读取速度快,而增删数据慢,线程不安全。
  • LinkedList底层为双向链表,对元素的增删数据快,读取慢,线程不安全。
  • Vector的底层数据结构为数组,用Synchronized来保证线程安全,性能较差,但线程安全。

8.List 和 Set 的区别***

  • List , Set 都是继承自Collection 接口

  • List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。

  • Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

  • List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。

9.Set接口

说一下 HashSet 的实现原理?

  • HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

HashSet如何检查重复?HashSet是如何保证数据不可重复的?***

  • 向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。

  • HashSet 中的add ()方***使用HashMap 的put()方法。

  • HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。

hashCode()与equals()的相关规定:

  • 如果两个对象相等,则hashcode一定也是相同的
  • 两个对象相等,对两个equals方法返回true
  • 两个对象有相同的hashcode值,它们也不一定是相等的
  • 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖

hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
==与equals的区别

==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
==是指对内存地址进行比较 equals()是对字符串的内容进行比较3.==指引用是否相同 equals()指的是值是否相同

HashSet与HashMap的区别 ***

10.HashMap在JDK1.7和JDK1.8中有哪些不同点:***

11.HashMap 的实现原理?***

HashMap 基于 Hash 算法实现的

  • 当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
  • 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
  • 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
  • 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

12.HashMap的put方法的具体流程?***

  • ①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

  • ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

  • ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

  • ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

  • ⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

  • ⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

13.HashMap的扩容操作是怎么实现的?***

  • ①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;
  • ②.每次扩展的时候,都是扩展2倍;
  • ③.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。

14.HashMap是怎么解决哈希冲突的? ***

HashMap中的哈希冲突解决方式可以主要从三方面考虑

  • 拉链法

    HasMap中的数据结构为数组+链表/红黑树,当不同的key计算出的hash值相同时,就用链表的形式将Node结点(冲突的key及key对应的value)挂在数组后面。

  • hash函数

    key的hash值经过两次扰动,key的hashCode值与key的hashCode值的右移16位进行异或,然后对数组的长度取余(实际为了提高性能用的是位运算,但目的和取余一样),这样做可以让hashCode取值出的高位也参与运算,进一步降低hash冲突的概率,使得数据分布更平均。

  • 红黑树

    在拉链法中,如果hash冲突特别严重,则会导致数组上挂的链表长度过长,性能变差,因此在链表长度大于8时,将链表转化为红黑树,可以提高遍历链表的速度。

15.HashMap 的长度为什么是2的幂次方**

为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。
采用%取余的操作来实现。取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;) 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。

16.HashMap 与 HashTable 有什么区别?***

  • 线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
  • 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
  • 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。
  • **初始容量大小和每次扩充容量大小的不同 **: ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
  • 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

17.能否使用任何类作为 Map 的 key? ***

可以,但要注意以下两点:

  • 如果类重写了equals()方法,也应该重写hashCode()方法。
  • 最好定义key类是不可变的,这样key对应的hashCode()值可以被缓存起来,性能更好,这也是为什么String特别适合作为HashMap的key。

18.HashMap 和 ConcurrentHashMap 的区别

  • ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
  • HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

19.ConcurrentHashMap 和 Hashtable 的区别?**

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。

  • 底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
  • 实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

20.ConcurrentHashMap 底层具体实现知道吗? ***

  • JDK1.7

    在JDK1.7中,ConcurrentHashMap采用Segment数组 +HashEntry数组的方式进行实现。Segment实现了ReentrantLock,所以Segment有锁的性质,HashEntry用于存储键值对。一个ConcurrentHashMap包含着一个Segment数组,一个Segment包含着一个HashEntry数组,HashEntry是一个链表结构,如果要获取HashEntry中的元素,要先获得Segment的锁。

  • JDK1.8

    在JDK1.8中,不在是Segment+HashEntry的结构了,而是和HashMap类似的结构,Node数组+[链表](/jump/super-jump/word?word=%E9%93%BE%E8%A1%A8)/[红黑树](/jump/super-jump/word?word=%E7%BA%A2%E9%BB%91%E6%A0%91),采用CAS+synchronized来保证线程安全。当[链表](/jump/super-jump/word?word=%E9%93%BE%E8%A1%A8)长度大于8,[链表](/jump/super-jump/word?word=%E9%93%BE%E8%A1%A8)转化为[红黑树](/jump/super-jump/word?word=%E7%BA%A2%E9%BB%91%E6%A0%91)。在JDK1.8中synchronized只锁[链表](/jump/super-jump/word?word=%E9%93%BE%E8%A1%A8)或[红 黑 树](/jump/super-jump/word?word=%E7%BA%A2%E9%BB%91%E6%A0%91)的头节点,是一种相比于segment更为细粒度的锁,锁的竞争变小,所以效率更高。

21.HashTable的底层实现知道吗?*

HashTable的底层数据结构是数组+链表链表主要是为了解决哈希冲突,并且整个数组都是synchronized修饰的,所以HashTable是线程安全的.

22.TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?*

  • TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。
  • TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。

Collections 工具类的 sort 方法有两种重载的形式,

  • 第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;

  • 第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。

#2021届秋招进度交流##Java##学习路径#
全部评论

相关推荐

7 83 评论
分享
牛客网
牛客企业服务