java集合类面试题

本文深入解析Java集合框架,包括各种集合如List、Set、Map的特性与区别,探讨HashMap、HashSet的工作原理,以及ArrayList与LinkedList的性能对比。同时,文章还讲解了线程安全集合的实现,如Vector和ConcurrentHashMap,并分析了红黑树在TreeMap中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. java 容器都有哪些?(说说常见的集合有哪些?)

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

    Collection接口的子接口包括set和list接口。

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

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

    map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap等。

根据各人对集合的理解程度作答即可。

2.Collection 和 Collections 有什么区别?

  Collection是集合的上级接口,

  Collections是针对集合的一个工具类,提供了一系列静态方法实现对集合的搜索、排序、线程安全化等操作。

3.List、Set、Map 之间的区别是什么?

  它们都是存储数据的容器,list和set来自于collection体系,而map不是,它可以存储键值对数据。

list:

   存储的对象可以重复且是有序的,可以插入多个null值。

    常用实现类:ArrayList、linkedList和Vector。ArrayList底层是数组实现,查询较快。而LinkedList增删较快。

set:无序,对象不可以重复,只允许一个null值。

map:存储键值对数据,map中可以有多个null值,但只能最多只能有一个null键。

4.HashMap 和 Hashtable 有什么区别?

        1.HashMap是非线程安全的,HashTable使用了synchronized关键字,是线程安全的。

        2.HashTable中,key和value都不允许出现null,在HashMap中,null可以作为键,这样的键只有一个,可以有一个或者多个键所对应的值为null。

        3.因为线程安全的问题,HashMap效率比HashTable的要高。

        4.默认容量不同 (HashMap:16  HashTable:11)

        HashTable和HashMap内部实现方式的数组初始大小和扩容方式不同。HashTable中hash数组的默认大小为11,增加方式为old*2+1。HashMap中hash数组的默认大小为16,而且一定是2的倍数。

       5. HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类

5.如何决定使用 HashMap 还是 TreeMap?

  TreeMap<K,V>的Key值是要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照Key值升序排列的;TreeMap的实现也是基于红黑树结构。而HashMap<K,V>的Key值实现散列hashCode(),分布是散列的均匀的,不支持排序,数据结构主要是桶(数组),链表或红黑树。

所以,查询的时候使用HashMap,增加、快速创建的时候使用TreeMap。

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

      hashMap基于hashing原理,我们通过put()和get()方法存储和获取对象。当我们将键值对传给put()方法时;它调用键对象的hashCode()方法来计算hashCode,然后找到bucket位置来存值对象。当获取对象时,通过键值对的equals()方法来找到正确的键值对。然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞时,对象会存储在链表的下一个节点。hashMap在每个链表的阶段存储键值对对象。

      当两个不同的键对象hashCode相同时会发生什么?他们会存储在同一个bucket位置的链表中。建对象的equals()方法用来找到键值对。

7.说一下 HashSet 的实现原理?

     HashSet是基于HashMap实现的,HashSet 底层使用HashMap来保存所有元素,
因此HashSet 的实现比较简单,相关HashSet 的操作,基本上都是直接调用底层HashMap的相关方法来完成,HashSet不允许有重复的值,并且元素是无序的。

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

    1. ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于双向链表的数据结构

    2. 对于随机访问,ArrayList要优于LinkedList,因为LinkedList要移动指针

    3. 对于插入和删除,LinkedList较占优势,ArrayList要移动数据。

    4. ArrayList和LinkedList都是非线程安全的容器

9.如何实现数组和 List 之间的转换?

   数组转list,可以使用Arrays.asList(数组),

​
     String[] str= {"a","b","c","d"};
		List<String> list = Arrays.asList(str);
		//注意:当数组转成list之后,list是不能执行add,remove等操作
	    //  list.add("ccc");//运行会报错
	    //如果想操作,则需要
	    List<String> list2=new ArrayList<String>(list);
		 for (String s : list) {
				System.out.println(s);
			}

​

  list转数组,使用list.toArray(),

        List<String> list=new ArrayList<String>();
		list.add("a");
		list.add("b");
		list.add("c");
		String[] array = list.toArray(new String[list.size()]);
		for (String string : array) {
			System.out.println(string);
		}

10.ArrayList 和 Vector 的区别是什么?

  相同点:

        两者都是基于存储元素的数组来实现的,它们会在内存中开辟块连续的空间来存储,由于数据存储是连续的,它们支持用序号(下标)来访问元素,但是插入和删除是要移动容器中的元素,所以执行较慢。两者都有一个初始化的容量的大小,为10;当里面存储的元素超过这个大小时,就会动态的进行扩容。Vector默认扩充为原来的2倍,ArrayList默认扩充为原来的1.5倍。

区别:

     二者最大的区别在与synchronization(同步)的使用。在ArrayList中没有一个方法是同步的,而在Vector中,绝大部分方法都是同步的。所以Vector是线程安全的,而ArrayList不是线程安全的。由于Vector提供同步,所以性能上较低于ArrayList。

11.Array 和 ArrayList 有何区别?

   Array是数组,存储类型可以为基本类型和引用数据类型,其长度运行时固定不可变。

  ArrayList是list集合的实现类。只可以存储引用类型数据,长度可以动态改变,即可以扩容。而且提供了更多是方法和特性。

12. ArrayList集合加入1万条数据,应该怎么提高效率

      ArrayList的默认初始容量为10,要插入大量数据的时候需要不断扩容,而扩容是非常影响性能的。因此,现在明确了10万条数据了,我们可以直接在初始化的时候就设置ArrayList的容量!这样就可以提高效率了~

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

     Vector:就比Arraylist多了个同步化机制(线程安全)。

     Hashtable:就比Hashmap多了个线程安全。

     ConcurrentHashMap:是一种高效但是线程安全的集合。

     Stack:栈,也是线程安全的,继承于Vector。

14.迭代器 Iterator 是什么?

   Iterator接口提供遍历任何COllection的接口。可以从一个Collection中使用迭代器方法来获取迭代器实例。它允许调用者在迭代过程中移除元素。

15.Iterator 怎么使用?有什么特点?

 Iterator 接口源码中的方法

  • java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象
  • next() 方法获得集合中的下一个元素
  • hasNext() 检查集合中是否还有元素
  • remove() 方法将迭代器新返回的元素删除
  • forEachRemaining(Consumer<? super E> action) 方法,遍历所有元素

 注意事项

  • 在迭代过程中调用集合的 remove(Object o) 可能会报 java.util.ConcurrentModificationException 异常
  • forEachRemaining 方法中 调用Iterator 的 remove 方法会报 java.lang.IllegalStateException 异常

16.Iterator 和 ListIterator 有什么区别?

   1. Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。

   2. Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。

   3. ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

17.怎么确保一个集合不能被修改?

可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

示例代码如下:

        //怎么确保一个集合不能被修改?
		List<String> list = new ArrayList<>();
		list. add("a");
		Collection<String> unmlist = Collections. unmodifiableCollection(list);
		unmlist. add("b"); // 运行时此行报错java.lang.UnsupportedOperationException
		System. out. println(list.size());

18.ConcurrentHashMap应用场景?

        1. ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全,而同步的HashMap和HashMap的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器,只需要锁住对应的Segment就好了,所以可以保证高并发同步访问,提升了效率。

        2. 可以多线程写。

19. 说说你对红黑树的见解

     每个节点非红即黑

     根节点总是黑色的

     如果节点是红色的,则它的子节点必须是黑色的(反之不一定)

    每个叶子节点都是黑色的空节点(NIL节点)

    从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

20.Collection与iterator的remove方法的区别?

    1. 性能方面

     Collection的remove方法是有参的  必须首先找出要被删除的项,找到该项的位置采用的是单链表结构查询,单链表查询效率比较低,需要从集合中一个一个遍历才能找到该对象;

   collection的remove是每隔一行执行一次删除操作,意为着有的元素不会经过删除的操作.
  2. 容错方面

    在使用Iterator遍历时,如果使用Collection的remove则会报异常,会出现ConcurrentModificationException,因为集合中对象的个数会改变而Iterator内部对象的个数不会,不一致则会出现该异常。

  在使用Iterator遍历时,不会报错,因为iterator内部的对象个数和原来集合中对象的个数会保持一致

  结论:所有涉及到集合删除元素的操作时,使用迭代器去完成

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

     什么是哈希

       Hash翻译为"散列",就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值)。散列值的空间通常远小于输入的空间,不同的输入可能散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。散列函数的基本特征:根据统一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据统一散列函数计算出的散列值如果相同,输入值不一定相同。

什么是哈希冲突

      当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,称为碰撞(哈希碰撞)。

HashMap数据结构

     在java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做链地址法的方式可以解决哈希冲突,

这样我们就可以将拥有相同哈希值的对象组织成一个链表放在hash值所对应的bucket下,但相比于hashCode返回的int类型,我们HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要远小于int类型的范围,所以我们如果只是单纯的用hashCode取余来获取对应的bucket这将会大大增加哈希碰撞的概率,并且最坏情况下还会将HashMap变成一个单链表,所以我们还需要对hashCode作一定的优化

hash()函数

上面提到的问题,主要是因为如果使用hashCode取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动,在JDK 1.8中的hash()函数如下:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与自己右移16位进行异或运算(高低位异或)
}

这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动);

JDK1.8新增红黑树

通过链地址法(使用散列表)扰动函数我们成功让我们的数据分布更平均,哈希碰撞减少,但是当我们的HashMap中存在大量数据时,加入我们某个bucket下对应的链表有n个元素,那么遍历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使得遍历复杂度降低至O(logn);

简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:

     1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;

     2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;

     3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;

为什么是两次扰动呢?

      这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;

22. HashMap在JDK1.7和JDK1.8中有哪些不同?

不同JDK 1.7JDK 1.8
存储结构数组 + 链表数组 + 链表 + 红黑树
初始化方式单独函数:inflateTable()直接集成到了扩容函数resize()
hash值计算方式扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
存放数据的规则无冲突时,存放数组;冲突时,存放链表无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树
插入数据方式头插法(先讲原位置的数据移到后1位,再插入数据到该位置)尾插法(直接插入到链表尾部/红黑树)
扩容后存储位置的计算方式全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1))按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)

23.为什么HashMap中String、Integer这样的包装类适合作为key?

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

       1. 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况

       2. 内部已重写了equals()hashCode()等方法,遵守了HashMap内部的规范,不容易出现Hash值计算错误的情况;

如果想要让自己的Object作为K应该怎么办呢?

    重写hashCode()equals()方法

  1. 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;

  2. 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性

24. hashSet和hashMap的区别

HashSetHashMap
HashSet实现了set接口HashMap实现了map接口
HashSet仅仅存储对象HashMap存储键值对
使用add方法将元素放入set中使用put()方法将元素放入map中
HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回falseHashMap中使用键对象来计算hashcode值
HashSet较HashMap来说比较慢HashMap比较快,因为是使用唯一的键来获取对象

25. ConcurrentHashMap和Hashtable的区别?

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

在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现。Segment 用来封装映射表的键值对,HashEntry用来充当锁的角色。

在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现。



 


 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值