Java集合知识面试题(收录全网总结最全面的面试题)

83 阅读7分钟

1.集合和数组的区别

数组的长度是固定的,集合的不是

数组可以存储基本数据类型和引用数据类型,集合只能存储引用数据类型

数组存储的元素必须是同一种数据类型,集合存储的对象可以是不同的数据类型

2.集合的常用类

java集合常用类.webp

  • 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

3.集合框架底层数据结构

  • Collection

    1. List

      • Arraylist: Object数组
      • Vector: Object数组
      • LinkedList: 双向循环链表
    2. 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: 红黑树(自平衡的排序二叉树)

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

Vector:就比Arraylist多了个 synchronized (线程安全)

HashTable:就比hashMap多了个synchronized (线程安全)

ConcurrentHashMap:是Java5中支持高并发、高吞吐量的线程安全HashMap实现

5.List和Map的遍历和删除

        List<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        //使用foi循环
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //使用iterator
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //使用for each
        list.forEach(s -> {
            System.out.println(s);
        });
        Map<String,String> map=new HashMap<>();
        map.put("A","a");
        map.put("B","b");
        map.put("C","c");
      for (Map.Entry<String, String> mp: map.entrySet()){
          System.out.println(mp.getKey());
          System.out.println(mp.getValue());
      }
      for (String key:map.keySet()){
          System.out.println(key);
      }
      for (String value:map.values()){
          System.out.println(value);
      }
       //使用foi删除
      for (int i = list.size()-1; i >= 0; i--) {
          list.remove(i);
      }
      

6.集合使用comparator排序

        List<User> users=new ArrayList<>();
        User user=new User("张三",18);
        User user1=new User("李四",16);
        User user2=new User("王五",25);
        users.add(user);
        users.add(user1);
        users.add(user2);
        //使用Comparator排序
//        Collections.sort(users, new Comparator<User>() {
//            @Override
//            public int compare(User o1, User o2) {
//                return o1.getAge()-o2.getAge();
//            }
//        });
        //使用Stream流排序
        users=users.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
        users.forEach(user3 -> {
            System.out.println(user3);
        });

7.集合、数组、字符串之间的相互转换

        List<String> list = new ArrayList();
        list.add("a");
        list.add("b");
        list.add("c");
        //使用list.toArray将集合转换成数组
        String[] array = list.toArray(new String[list.size()]);
        //使用Arrays.asList将数组转换成集合
        List<String> strings = Arrays.asList(array);
        //使用Arrays.toString将数组转换成字符串
        String arrayString = Arrays.toString(array);
        //使用split将字符串分割成数组
        String s="a,b,c";
        String[] split = s.split(",");
        //将字符串转换成集合
        List<String> strings1 = Arrays.asList(s.split(","));
        //将集合转换成,分割的字符串
        String join = String.join(",", list);

8.ArrayList和LinkedList的区别

ArrayList是动态数组,LinkedList是双向链表

从查询的角度:ArrayList查询更快,链表是从前往后依次查找,数组是根据索引进行查找

从添加和删除的角度:LinkedList添加和删除更快,链表改变指针的指向,数组要改变数组的元素下标

9.List和Set区别

1.都继承Collection接口

2.List是有序可重复的,Set是无序不可重复的

3.List可以使用for循环也可以使用迭代器,而Set因为无序无法确定下标只能使用迭代器循环

10.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 transient HashMap<E,Object> map;

public HashSet() {
    map = new HashMap<>();
}

public boolean add(E e) {
    // 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
	return map.put(e, PRESENT)==null;
}

11.HashMap实现原理

HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

HashMap 基于 Hash 算法实现的

  1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标

  2. 存储时,如果出现hash值相同的key,此时有两种情况。

    (1)如果key相同,则覆盖原始值;

    (2)如果key不同(出现冲突),则将当前的key-value放入链表中

  3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

  4. 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

JDK1.8主要解决或优化了一下问题:

  1. resize 扩容优化
  2. 引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考
  3. 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。

hashmap不同jdk版本比较.png

12.HashMap的key可以为null吗,key为null的时候对应的hash值为多少

可以为null,对应的hash值是0

13.HashMap为什么快?

是因为使用了唯一键来获取对象

14.ConcurrentHashMap的实现原理

oncurrentHashMapHashMap一样,是一个存放键值对的容器。ConcurrentHashMap是线程安全的,ConcurrentHashMap并非锁住整个方法,而是通过原子操作和局部加锁的方法保证了多线程的线程安全,且尽可能减少了性能损耗。

15.TreeMap实现原理

TreeMap基于红黑树(Red-Black tree)实现。映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。