面试题记录之--java集合(持续更新)

目录

数组和链表有什么区别?

ArrayList和Vector的区别?

HashMap、 TreeMap、 HashTable 的区别?

HashMap 的工作原理是什么?

List、 Set、 Map 的区别?

谈谈 ArrayList 的扩容机制?

HashMap 和 HashSet 的区别?

ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMap 线程安全的底层实现

HashMap 有哪几种场景的遍历方式?

HashSet 如何检查重复?

HashMap 的长度为什么是 2 的幂次方?

Comparable 和 Comparator 的区别

ArrayList 和 LinkedList 的区别

Collection 和 Collections 有什么区别?

如何决定使用 HashMap 还是 TreeMap?

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

在 Queue 中 poll()和 remove()有什么区别?

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

迭代器 Iterator 是什么?

Iterator的特点?

Iterator 和 ListIterator 有什么区别?

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


数组和链表有什么区别?

比较项

数组

链表

逻辑结构

数组在内存中是连续的,长度是固定的;增加元素可能会造成数组越界,减少元素可能会造成内存浪费,删除元素需要移动其它元素

链表在内存中是不连续的;支持动态增加或删除元素、长度不固定

访问效率

查找元素的效率高,直接通过下标访问元素

查找元素的效率低,需要通过遍历查找元素

增删效率

增删效率低,操作时往往需要移动其它元素

增删效率高,不需要移动其它元素

总结:

应用场景查找和遍历偏多时使用数组,增删操作较多时使用链表

ArrayList和Vector的区别?

比较项

ArrayList

Vector

线程安全问题

采用异步处理,线程不安全

采用同步处理,线程安全

性能问题

性能较高

性能较低

扩容

动态扩容时增加0.5倍

动态扩容时增加1倍

HashMap、 TreeMap、 HashTable 的区别?

比较项

HashMap

HashTable

TreeMap

线程安全问题

线程不安全

线程安全

线程不安全

key和value能否为null

可以为null,但是key中的null只能有一个

不允许为null

不允许为null

底层数据结构

哈希表(jdk1.8之后数组+链表+红黑树)

哈希表(jdk1.8之后数组+链表+红黑树)

红黑二叉树

是否有序

无序

无序

key有序排列

HashMap 的工作原理是什么?

版本1

HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。

版本2

在调用HashMap的无参构造器时,指定散列因子为0.75。当调用put方法添加数据时,先判断数组是否为空或长度为0,第一次判断时会调用resize方法进行扩容,数组扩容后的初始容量就是16。接着会根据key的hashCode方法计算出hash值,用此hash值对数组长度求余获取桶的下标。多个数据的hash值相同时即产生hash冲突,HashMap使用链表+红黑树来存储相同hash值的value。
根据计算出桶的位置,先判断桶中是否有元素,如果没有,则直接放在桶的第一个位置上。如果桶中有元素,先判断两者的hash和key是否同时相同,相同则覆盖该位置的元素。否则判断是否为红黑树节点,是的话就按照红黑树的算法进行存储。如果不是红黑树,则必然是链表,遍历链表判断是否存在相同的节点,存在则覆盖;否则插入到链表的最末端,并判断链表长度是否>=7,满足条件就将链表变为红黑树

List、 Set、 Map 的区别?

比较项

List

Set

Map

单值或双值存储

单值存储

单值存储

双值存储

能否使用Iterator

不能

元素是否有序

有序

除了TreeSet有序,其他无序

除了TreeMap的key有序,其他无序

是否允许元素重复

允许

不允许

key不允许,value允许

或者

谈谈 ArrayList 的扩容机制?

第一次调用无参构造器时,创建的是一个空数组,并没有初始化容量。在第一次调用add方法时,在后面的扩容算法中会赋予数组初始容量为10。后续添加数据时如果满足了数组长度=数组存储元素的个数,则进行扩容,扩容后是数组原来长度的1.5倍。扩容使用了位运算符">>"

HashMap 和 HashSet 的区别?

  • HashSet的底层是使用HashMap实现的

比较项

HashMap

HashSet

所属集合类型

Map(双值存储)

Set(单值存储)

是否能使用Iterator

不能

添加元素的方法

put()

add()

使用谁计算hashCode

使用key来计算

使用存储的对象计算

ConcurrentHashMap 和 Hashtable 的区别

HashTable:
线程安全,但是实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap对其做了相关优化

ConcurrentHashMap :
线程安全,底层采用分段的数组+链表实现(锁分段技术),对并发性能的提升较大

ConcurrentHashMap 线程安全的底层实现

ConcurrentHashMap是使用了锁分段技术来保证线程安全的。
首先将数据分成一段一段的存储,然后给每一段数据配一把锁。ConcurrentHashMap默认将hash表分为16个桶,一次锁住一个桶。当使用get、put、remove等常用操作时会锁住需要用到的桶,但是其他未被锁住的桶依然能够被访问。原本只能一个线程进入,现在缺能同时有16个写线程执行,并发性能的提升显而易见


关于HashMap、HashTable、ConcurrentHashMap的区别的文章:
https://zhuanlan.zhihu.com/p/50675732

HashMap 有哪几种场景的遍历方式?

  • 迭代器+EntrySet()
HashMap<Integer,String> map1=new HashMap<>();
        map1.put(1,"aa");
        map1.put(2,"bb");
        map1.put(3,"cc");
        map1.put(4,"dd");
        map1.put(5,"ee");
        Iterator<Map.Entry<Integer,String>> it = map1.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry<Integer, String> entry = it.next();
            System.out.println(entry.getKey() + entry.getValue());
        }
  • 迭代器+KeySet()
HashMap<Integer,String> map=new HashMap<>();
        map.putAll(map1);
        Iterator<Integer> it = map.keySet().iterator();
        while(it.hasNext()) {
            Integer key = it.next();
            System.out.println(key+ map.get(key));
        }
  • foreach+EntrySet()
HashMap<Integer,String> map3=new HashMap<>();
        map3.putAll(map2);
        for (Map.Entry<Integer, String> entry : map1.entrySet()) {
            System.out.println(entry.getKey() + entry.getValue());
        }
  • foreach+keySet()
HashMap<Integer,String> map4=new HashMap<>();
        map4.putAll(map3);
        for(Integer key: map1.keySet()){
            System.out.println(key+map4.get(key));
        }
  • lambda
    • 最便捷且常用
HashMap<Integer,String> map5=new HashMap<>();
        map5.putAll(map4);
        map5.forEach((k,v)->{
            System.out.println("key:"+k+",value:"+v)+;
        });

HashSet 如何检查重复?

通过判断两个对象的hashCode方法是否一致来判断对象是否相同。
传入的对象通常需要重写equals和hashCode方法

HashMap 的长度为什么是 2 的幂次方?

因为HashMap为了存取高效,必须要减少hash碰撞,尽量做到均匀分配数据,使得每个链表的长度大致相同。这个算法其实就是取模运算,令hash对数组长度length进行求余操作,在源码中使用了位运算符“&”进行了优化。

hash&(length-1)的前提是length是2的n次方

当length是2的n次方时,hash与length-1求余产生哈希冲突的概率较小

本题详解转自:

HashMap的长度为什么要是2的n次方_sidihuo的专栏-CSDN博客

  • length不为2的n次方时:例如取length=9,则length-1=8
    • 此时容易产生hash冲突

  •  length为2的n次方时:例如取length=8,则length-1=7
    • 此时不容易产生hash冲突

Comparable 和 Comparator 的区别

Comparable:强行对实现它的每个类的对象进行整体排序,这种排序称为类的自然排序。

类的compareTo方法被称为它的自然比较方法,只能在类中实现comparTo一次,不能经常修改类的代码来实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collection.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

Comparator:强行对某个对象进行整体排序,可以将Comparator传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序Set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序

ArrayList 和 LinkedList 的区别

比较项  ArrayList LinkedList
数据结构实现 由动态数组实现 由双向链表实现
随机访问效率 随机访问效率高,因为是线性的数据存储方式,可以通过下标直接获取数据 随机访问效率低,因为需要移动指针遍历链表查询
增加和删除效率 效率低,因为增加或删除数据,可能需要移动大量的数据,会影响其他数据的下标 效率高,增删操作仅仅是添加一个节点并修改指针,无需其他操作
  • 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList

Collection 和 Collections 有什么区别?

Collection是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如List、Set等

Collections是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供了排序方法:Collections.sort(list)

如何决定使用 HashMap 还是 TreeMap?

对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。

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

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

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

在 Queue 中 poll()和 remove()有什么区别?

相同点:都是返回第一个元素,并在队列中删除返回的对象。

不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。

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

Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现,它们也有了自己对应的线程安全类,比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。

迭代器 Iterator 是什么?

Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。

Iterator的特点?

Iterator 的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。

Iterator 和 ListIterator 有什么区别?

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

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

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

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

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

List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());

  • 后续遇到新的题,本文会持续更新。如果有错的地方,欢迎评论区留言指正,谢谢

おすすめ

転載: blog.csdn.net/future_god_qr/article/details/121317123