概述
List是有序集合(这里的有序指元素存入和取出的顺序相同,而不是按照元素的特性排序),可以存储重复的元素,可以存入多个null值。List集合是工作中最常用的类,相比数组,集合的长度可变,更加方便开发。List主要有两个实现类ArrayList、LilnkedList。
常用List集合的实现类
ArrayList
ArrayList数组是底层由数组实现的有序集合,与Array数组不同的是,Array可以包含基本类型和对象类型的数据,大小是固定的。但ArrayList只能包含对象类型,大小是动态变化的,相比Array有更多的内置方法,如addAll(),removeAll()。针对基本数据类型,ArrayList使用自动装箱来减少编码的工作量,但是如果这个基本类型的数据大小固定,使用Array更加高效。
ArrayList不保证线程安全,可以插入null值,初始容量是10,每次扩容后都是原来的1.5倍,所以存在一定的空间浪费。由于底层是数组,所以插入和删除元素的时间复杂度会收到位置的影响,而读取元素的时候可以直接根据下标查找到元素,不受位置影响,所以更加适用于多读少增删的场景。
ArrayList的扩容机制
ArraList调用无参构造方法时创建的是一个长度为0的空数组,当调用add()方法添加元素时才会触发扩容机制。扩容时先将旧容量右移一位(位运算),加上旧容量就得到了新容量,右移一位相当于除以2,在此基础上加上旧容量就等价于新容量=旧容量*1.5,因此我们说ArrayList每次扩容都是原来的1.5倍。扩容后调用Arrays.copyOf()方法进行拷贝,并将引用指向新数组,此时旧数组因为没有引用指向,很快会被垃圾回收器回收。
LilnkedList
LinkedList底层是通过双线链表来实现的,随着元素的增加不断的向链表中增加节点,每次顺序插入元素时都会new一个对象。存储元素的时候要存放直接后继,直接前去以及数据,所以空间消耗比ArrayLlist大。LinkedList插入和删除元素不受位置的影响,如果要在指定位置插入元素的时间复杂度为O(n),因为插入元素前需要先找到指定的位置。由于底层是双线链表,每个节点都拥有指向前后节点的引用,增加了元素检索的效率,因此适用于多增删,少读写的场景。
Vector
vector相当于具有同步机制的ArrayList,通过Synchronized来实现线程同步,是线程安全的,但也因为加了同步锁,性能方面反而不如ArrayList。扩容时每次都是旧容量的两倍。
List集合的常用方法
add(index,obj):在指定位置添加元素
remove(index):删除元素并返回
set(index,obj):将指定位置的元素更改为指定值并返回修改前的值
indexOf(obj):返回指定元素在集合中第一次出现的位置
get(index):返回指定位置的元素
subList(fromIdex,toIndex):从指定位置截取元素,左闭右开
LinkedList的常用方法:
addFirst():往头部添加元素
addLast():往尾部添加元素
removeFirst():在头部删除元素
removelast():在尾部删除元素
getFirst():获取头部元素
getLast() :获取尾部元素
List集合储存元素的方式和遍历
常见元素在内存中的存储方式主要有两种:
1、顺序存储:相邻的数据元素在内存中的位置也是相邻的,可以根据元素的位置读取元素(如ArrayList的下标)。
2、链式存储:每个元素都包含它下一个元素的内存地址,在内存中不要求相邻,如LinkedList。
List集合的主要遍历方式:
1、for循环:遍历者在集合外部维护一个计数器,依次读取每一个位置的元素;
2、Iterator遍历:基于顺序存储的集合迭代器可以按照位置直接访问数据,基于链式存储的集合,迭代器需要先保存当前遍历的位置,然后根据当前位置来向前或向后移动指针。
3、foreach遍历:foreach内部也是采用Iterator的方式实现的,但是使用的时候不需要显示声明Iterator。
Java集合框架提供了RandomAccess接口,这个接口没有方法,只是一个标记,通常情况下用它来标记list的实现是否支持RandomAccess。所以遍历的时候,可以通过这个标记来判断选用哪种遍历方式,如果(list instanceof RandomAccess)支持就选用for循环遍历,不支持的情况下建议使用迭代器或foreach循环遍历。