集合面试题(上)

简介: 集合和数组的区别数组是固定长度的;集合可变长度的。数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。数据结构:就是容器中存储数据的方式。

640.png

集合和数组的区别

数组是固定长度的;集合可变长度的。数组可以存储基本数据类型,也可以存储引用数据类型集合只能存储引用数据类型。数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。数据结构:就是容器中存储数据的方式。

Collection接口的子接口包括:Set接口Queue接口和List接口

Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等

List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

Map接口的实现类主要有:HashMap、TreeMap、Hashtable、 ConcurrentHashMap以及Properties等

1、List,Set,Map三者的区别?

Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、 List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是 collection的子接口。

Collection集合主要有List和Set两大接口

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

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

Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。 Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap


2、集合框架底层数据结构

Arraylist: Object数组

Vector: Object数组

LinkedList: 双向循环链表

1.HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素

2.LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。

3.TreeSet(有序,唯一)红黑树(自平衡的排序二叉树。) Map

4.HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主 体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间

5.LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是 基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。

6.HashTable数组+链表组成的,数组是 HashMap 的主体链表则是主要为了解决哈希冲突而存在的

7.TreeMap 红黑树(自平衡的排序二叉树)

 

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

vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。

statck:堆栈类,先进后出。

hashtable:就比hashmap多了个线程安全。

enumeration:枚举,相当于迭代器。

Java中有HashTable、Collections.synchronizedMap、以及ConcurrentHashMap可以实现线程安全的Map。

HashTable是直接在操作方法上加synchronized关键字,锁住整个数组,粒度比较大Collections.synchronizedMap是使用Collections集合工具的内部类,通过传入Map封装出一个SynchronizedMap对象,内部定义了一个对象锁,方法内通过对象锁实现;ConcurrentHashMap使用分段锁,降低了锁粒度,让并发度大大提高。

4、ArrayList线程安全,安全方案

1、使用synchronized关键字

2.使用Collections.synchronizedList();使用方法如下:

假如你创建的代码如下:List data=new ArrayList();

那么为了解决这个线程安全问题你可以这么使用Collections.synchronizedList(),如List

data=Collections.synchronizedList(new ArrayList());


5、插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性?

ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。

Vector中的方法由于加了synchronized 修饰,因此Vector是线程安全容器,但性能上较ArrayList差。

LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以LinkedList插入速度较快。

6、Iterator和ListIterator有什么区别?

Iterator可以遍历Set和List集合,而ListIterator只能遍历List

Iterator只能单向遍历,而ListIterator可以双向遍历(向前/后遍历)。

ListIterator实现Iterator接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

7、遍历方式有以下几种:

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

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

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

最佳实践:Java Collections 框架中提供了一个RandomAccess接口,用来标记 List 实现是否支持 Random Access。

如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。

如果没有实现该接口,表示不支持Random Access,如LinkedList。推荐的做法就是,支持Random Access的列表可用for循环遍历,否则建议用Iterator或foreach 遍历。

8、comparable和comparator的区别?

comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序

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

一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或 compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort()

9、Collection和Collections有什么区别?

java.util.Collection是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了大化的统一操作方式,其直接继承接口有List与Set

Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

10、如何实现数组和 List 之间的转换?

数组转 List:使用 Arrays. asList(array) 进行转换

List 转数组:使用 List 自带的 toArray() 方法

代码示例:

// list to arrayList 转数组
Listlist = new ArrayList();
list.add("123");
list.add("456");
list.toArray();

// array to list数组转 List
String[] array = new String[]{"123","456"};
Arrays.asList(array);

11、HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,

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

12、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 )。

以下是HashSet 部分源码:

  private static final Object PRESENT = new Object();
  private transient HashMapmap;
  public HashSet() {<>
  map = new HashMap ();
  }
  public boolean add(E e) {
  // 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
  return map.put(e, PRESENT)==null;
  }

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

如果两个对象相等,则hashcode一定也是相同的

两个对象相等,对两个equals方法返回true

两个对象有相同的hashcode值,它们也不一定是相等的

综上,equals方法被覆盖过,则hashCode方法也必须被覆盖

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

==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同

==是指对内存地址进行比较 equals()是对字符串的内容进行比较3.== 指引用是否相同 equals()指的是值是否相同

13、HashSet与HashMap的区别

HashMap

HashSet

实现了Map接口

实现了Set接口

存储键值对

仅存储对象

调用 put()向 map中添加元素

调用 add() 方法向Set 中添加元素

HashMap 使用键 (Key)计算 Hashcode

HashSet 使用成员对象来计 算 hashcode 值,对于两个对象 来说 hashcode 可能相 同,所以 equals()方法用来判断对象的相等性,如果两个对象不同的话,那 么返回 false

HashMap 相对于 HashSet 较快,因为它是使用唯一的键获取对象

HashSet 较 HashMap 来说比较慢

显示详细信息


14、ArrayList 和 LinkedList 的区别是什么?

数据结构实现:ArrayList 是动态数组的数据结构实现,实现了 RandomAccess 接口,因此查找的时候非常快。而 LinkedList 是双向链表的数据结构实现。

随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找ArrayList 比较适合顺序添加、随机访问的场景。

增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要因为 ArrayList 增删操作要影响数组内的其他数据的下标ArrayList 在顺序添加一个元素的时候非常方便

内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全

综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList

补充:数据结构基础之双向链表

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

15、ArrayList 和 Vector 的区别是什么?

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

线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList非线程安全的。

性能:ArrayList 在性能方面要优于 Vector

扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%

Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。

16、HashMapTreeMap的区别

对于在Map中插入、删除和定位元素这类操作,HashMap是 好的选择。然

而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历

17、HashMap 和 ConcurrentHashMap 的区别

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

HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

18、HashtableConcurrentHashMap的区别?

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,竞争会越来越激烈效率越低。

两者的对比图:

HashTable:


640.png

JDK1.7的ConcurrentHashMap:

640.png

JDK1.8的ConcurrentHashMap(TreeBi(img)n: 红黑二叉树节点 Node: 链表节点):

640.png

答:ConcurrentHashMap 结合了 Hash(img)Map 和 HashTable 二者的优势。 HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。

19、HashMap和Hashtable的异同。

1.两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全。

2.key、value都是对象,但是不能拥有重复key值,value值可以重复出现。

1.Hashtable中,key和value都不允许出现null值。

2.HashMap允许null值(key和value都可以),因为在HashMap中null可以作为健,而它对应的值可以有  多个null。

3.Hashtable 是线程安全的,每个方法都要阻塞其他线程,所以 Hashtable 性能较差,HashMap 性能较好,使用更广。

4.Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类

20、ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

640.png

JDK1.7中的ConcurrentHashMap

内部主要是一个Segment数组,而数组的每一项又是一个HashEntry数组,元素都存在HashEntry数组里。因为每次锁定的是Segment对象,也就是整个HashEntry数组,所以又叫分段锁。

JDK1.8中的ConcurrentHashMap

舍弃了分段锁的实现方式,元素都存在Node数组中,每次锁住的是一个Node对象,而不是某一段数  组,所以支持的写的并发度更高。

再者它引入了红黑树,在hash冲突严重时,读操作的效率更高。

JDK1.7

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。

在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实

现,结构如下:

一个ConcurrentHashMap里包含一个Segment数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个Segment包含一个HashEntry数组,每个 HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得对应的Segment的锁。

该类包含两个静态内部类 HashE(img)ntry和Segment;前者用来封装映射表的键值对,后者用来充当锁的角色;

Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment锁。

JDK1.8

在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N 倍。

看插入元素过程(建议去看看源码):

如果相应位置的Node还没有初始化,则调用CAS插入相应的数据;

else if ((f = tabAt(tab, i = (n ‐ 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node(hash, key, value, null)))
break; // no lock when adding to empty bin
}

如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁,如果该节点的hash不小于0,则遍历链表更新节点或插入新节点;

if (fh >= 0) {
binCount = 1;
for (Nodee = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Nodepred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key, value, null);
break;
}
}
}

1.如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过 putTreeVal方法往红黑树中插入节点;如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树,如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值

2.如果插入的是一个新节点,则执行addCount()方法尝试更新元素个数 baseCount

目录
相关文章
|
3月前
|
安全 Java 容器
【Java集合类面试二十七】、谈谈CopyOnWriteArrayList的原理
CopyOnWriteArrayList是一种线程安全的ArrayList,通过在写操作时复制新数组来保证线程安全,适用于读多写少的场景,但可能因内存占用和无法保证实时性而有性能问题。
|
3月前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
3月前
|
Java
【Java集合类面试二十八】、说一说TreeSet和HashSet的区别
HashSet基于哈希表实现,无序且可以有一个null元素;TreeSet基于红黑树实现,支持排序,不允许null元素。
|
3月前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。
|
3月前
|
Java
【Java集合类面试二十六】、介绍一下ArrayList的数据结构?
ArrayList是基于可动态扩展的数组实现的,支持快速随机访问,但在插入和删除操作时可能需要数组复制而性能较差。
|
3月前
|
存储 Java 索引
【Java集合类面试二十四】、ArrayList和LinkedList有什么区别?
ArrayList基于动态数组实现,支持快速随机访问;LinkedList基于双向链表实现,插入和删除操作更高效,但占用更多内存。
|
1月前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
83 1
Java面试题之Java集合面试题 50道(带答案)
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
3月前
|
存储 安全 Java
Java集合类面试十七】、介绍一下ConcurrentHashMap是怎么实现的?
ConcurrentHashMap在JDK 1.7中通过分段锁实现线程安全,在JDK 1.8中则采用Node数组配合链表和红黑树,并使用Synchronized和CAS操作提高并发性能。
Java集合类面试十七】、介绍一下ConcurrentHashMap是怎么实现的?
|
3月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。