文章目录
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
线程安全,效率低,不建议使用。