java集合之List
写在前面
集合类是java语言对数据结构
的实现
List接口介绍
java.util.List是有序的 collection(也称为序列),继承自Collection接口,是单列集合的一个重要分支。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
特点
- 元素有序,即存入的顺序和取出的顺序一致。
- 通过索引可以精确操作集合中的元素。
- 允许重复。
List接口中常用方法
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:
public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
public E get(int index):返回集合中指定位置的元素。
public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
List的子类
List的子类常用的由三个
- ArrayList
- LinkedList
- Vector
1 . ArrayList
java.util.ArrayList
是List 接口的大小可变数组的实现,换言之它是数组结构的集合类。
因此它具有数组的特点,即 查询快,增删慢。
//ArrayList的底层就是一个Object[]数组。
transient Object[] elementData;
它实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
(ArrayList类大致上等同于 Vector 类,除了它是不同步的(多线程)。)
由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
许多程序员开发时非常随意地使用ArrayList完成任何需求,这很不严谨,也很不专业。
2. LinkedList
java.util.LinkedList
是List 接口的链接列表实现,换言之它是链表结构的集合类。因此它具有链表的特点,即查询慢,增删快。
java.util.LinkedList
是不同步的(多线程)。
java.util.LinkedList
是一个双向链表,结构如下:
LinkedList
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。
public void addFirst(E e):将指定元素插入此列表的开头。
public void addLast(E e):将指定元素添加到此列表的结尾。
public E getFirst():返回此列表的第一个元素。
public E getLast():返回此列表的最后一个元素。
public E removeFirst():移除并返回此列表的第一个元素。
public E removeLast():移除并返回此列表的最后一个元素。
public E pop():从此列表所表示的堆栈处弹出一个元素。
public void push(E e):将元素推入此列表所表示的堆栈。此方法等效于addFirst(E e)
public boolean isEmpty():如果列表不包含元素,则返回true。此方法等效于addLast(E e)
3. Vector
和ArrayList相似,唯一的区别是,Vector类是同步的(单线程)。
面试题
ArrayList与LinkedList的区别?
- ArrayList的底层是基于数组实现的,LinkedList的基于双向链表实现的。它们都是线程不安全的。
- LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个指针域,分别指向前一个元素和后一个元素。
- ArrayList查询快,增删慢。
- LinkedList查询慢,增删快。
补充
- ArrayList
public E get(int index)
: 获取指定索引位置的元素,时间复杂度 O(1)public boolean add(E e)
:先将原数组数据复制到容量+1的数组中,然后在尾部追加e,时间复杂度 O(1)public E remove(int index)
:底层需要复制数组,时间复杂度 O(n)public void add(int index, E element)
:底层需要复制数组,时间复杂度 O(n)
//移除指定索引处的元素
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
ArrayList的add()方法,如果只是添加到尾部,效率也是非常高的。但是如果要是向指定索引位置添加,那么效率就会下降,因为它会调用System.arraycopy()方法。
源码如下:
//向尾部添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
//向指定位置添加元素
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
//调用数组的复制方法
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
- LinkedList
public E getFirst()/getLast
:获取头尾,时间复杂度 O(1)public void addFirst(E e)/addLast(E e)
:添加到头尾,时间复杂度 O(1)public boolean add(E e)
:底层源码和addLast(E e)相同,时间复杂度 O(1)public E removeFirst()/removeLast()
:时间复杂度 O(1)public E remove()
:实际调用的是removeFirst(),删除第一个元素,时间复杂度 O(1)public E get(int index)
:获取指定索引位置的元素,底层采用二分查找,虽然效率有所提升,但是还是很慢,时间复杂度 O(n/2)public boolean remove(Object o)
:删除指定元素,需要遍历链表,时间复杂度 O(n)public E remove(int index)
:删除指定索引位置的元素,底层采用二分查找,时间复杂度 O(n/2)
数组Array和集合的区别?
- 数组是静态的,容量固定。无法动态扩容
补充:
-
有些博客里说:数组声明了它容纳的元素的类型,而集合不声明,这句话是错误的。
集合如果不声明容纳的元素类型,就等同于它申明了Object类型。 -
数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种,这句话也是错误的。如果new一个Object数组,那么它既能存放基本类型,也能存放引用类型。
public class Demo02 {
public static void main(String[] args) {
Object[] o = new Object[10];
o[1] = new Layman("layman", 18);
o[2] = "ABC";
o[3] = 1;
o[3] = new Object[3];
o[4] = 12.22;
o[5] = LocalDateTime.now();
}
}
@Data
@AllArgsConstructor
class Layman{
private String name;
private Integer age;
}
因为数组不能动态扩容(早期通过链表实现,非常复杂),所以引入集合类。
集合类是java语言对数据结构
的实现