深入剖析Java集合——涵盖高频面试题

1.引言

文章篇幅有些长,主要讲了关于集合的一些底层原理,基本上包含了现在八股文中的高频面试题,希望大家有耐心看完,可以评论区交流。

2.数组与集合的区别

声明数组:

//数据类型[] 数组名
int[] array = {
    
    1,2,3,4};
int[] array = new int[4];

此处我们声明一个int型数组,那么里面的数据元素,就固定是int型,而且固定了数组大小。

创建集合:我们拿List举例

List list = new ArrayList();
list.add(1);
list.add("abc");

可以看到,集合是可以容纳不同数据类型的元素,而且没有固定容量的大小。

总结:数组必须声明数据类型,一个数组只能存放相同的数据类型,并且数组的容量是固定的,一旦声明就不能改变。集合可以存放不同数据类型的数据,而且会自动扩容。

3.集合的两大阵营

集合分为Collection单列集合和Map双列集合,其中Collection中又分为List(有序可重复)和Set(无序不可重复)。
在这里插入图片描述

4.Collection中的常用方法

  • boolean add(E e) 添加元素
  • boolean addAll(Collection<? extends E> c) 将一个集合添加进去
  • void clear() 删除所有元素
  • boolean contains(Object o) 判断是否包含元素
  • boolean isEmpty() 判断是否为空
  • Iterator iterator() 迭代器遍历集合
  • boolean remove(Object o) 移除指定元素
  • int size() 元素个数
  • Object[] toArray() 集合转为数组

4.1 List家族

List集合特点是有序,可以存放重复数据

4.1.1 ArrayList源码剖析及面试重点

ArrayList:底层是动态数组,线程不安全,支持快速随机访问,但是他的删除和插入效率低。对比数组,ArrayList可以进行扩容,可以存放不同类型的数据。

  • 空参构造函数初始化:new ArrayList();

jdk1.6源码分析:
在这里插入图片描述
在这里插入图片描述
再看添加元素的源码:
在这里插入图片描述
我们看一下确保容量的方法,里面有面试常问的扩容机制!
在这里插入图片描述

jdk1.6源码分析总结:无参构造函数,初始化容量为10,当容量不够时,按1.5倍+1进行扩容,扩容会将旧数组复制给新数组,新数组的容量为扩容后的容量。

jdk1.8源码分析
在这里插入图片描述
再看一下添加元素的方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

jdk1.8源码分析总结:无参构造函数初始化一个空数组,没有指定容量,当第一次添加元素时,给容量为10,之后如果进行扩容,按1.5倍进行扩容。

总结:ArrayList 1.8与1.6的区别有两个,一个是初始化时没有指定初始容量,第二个事扩容不再是1.5倍+1,而是1.5倍。

4.1.2 常用方法

  • boolean add(E e)
  • add(int index, E element) 在指定位置添加元素
  • forEach(Consumer<? super E> action) 遍历
  • get(int index) 根据下标获取元素
  • indexOf(Object o) 获取指定元素的下标
  • isEmpty() 如果此列表不包含元素,则返回 true
  • iterator() 遍历
  • lastIndexOf(Object o) 获取指定元素最后一次出现的位置
  • toArray() 转成数组
  • size() 集合中的元素个数

4.1.3 ArrayList与LinkedList的区别

ArrayList底层是动态数组,查询效率相比LinkedList高,LinkedList底层是双向链表,在中间插入和删除元素的效率比ArrayList高。

4.2 Set家族

Set集合的特点:无序,不可重复

4.2.1 HashSet

底层是哈希表(数组+链表+红黑树),它存储的元素是无序并且不可重复的,可以存储一个唯一的null值。

  • 为什么是无序的?

HashSet添加元素时,会根据所添加元素的hashcode,计算出所存放的位置,所以他不是根据你存入的顺序存储,而是按hashcode去存储。

  • 那为什么是不可重复的呢?

当元素具有相同的hashcode,第二个元素势必会和相同的元素在同一个位置,此时调用equals方法,如果也相同,则会覆盖相同的元素。如果不同,则在该节点的链表结构上增加一个节点。
所以,在使用HashSet时,一定对存入的元素进行hashcode和equals的重写。

4.2.2 TreeSet

底层是基于TreeMap实现,他的特点是有序,不可重复。

  • 自然排序:实现Comparable接口,重写compareTo方法
  • 定制排序:在创建TreeMap对象时,传入一个Comparator接口,并实现里面的compare方法
public class TreeSetTest {
    
    
    public static void main(String[] args) {
    
    
        TreeSet<User> set = new TreeSet<>(new Comparator<User>() {
    
    
            @Override
            public int compare(User o1, User o2) {
    
    
                int i = o1.getName().compareTo(o2.getName());
                if(i==0){
    
    
                    return o1.getAge()-o2.getAge();
                }
                return i;
            }
        });

        set.add(new User("eason",23));
        set.add(new User("bill",24));
        set.add(new User("jame",17));
        set.add(new User("jame",18));
        Iterator<User> iterator = set.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }
    }
}

5.Map家族

5.1 HashMap

首先hashmap是线程不安全的,他的key和value都可以为null,但是只允许有一个null键。

5.1.1 数据结构

  • jdk1.8之前
    底层数据结构为数组+链表,数组是HashMap的主体,链表则是主要为了解决哈希碰撞(两个对象调用的hashCode方法计算的哈希值一致导致计算的数组索引值相同)而存在的(“拉链法”解决冲突)。
  • jdk1.8
    底层数据结构为数组+链表+红黑树,当链表长度大于8,并且当前数组的长度大于64时,此时此索引位置上的所有数据改为使用红黑树存储,目的是提高效率。
    如果链表长度大于8,但是数组长度小于64,则会对数组进行扩容。

5.1.2 存储数据过程

5.1.3 扩容机制

5.2 HashTable

线程安全,效率低,不建议使用。

5.3 TreeMap

5.4 ConcurrentHashMap

猜你喜欢

转载自blog.csdn.net/zhang0305/article/details/126722124