1、List(线性结构)
ArrayList Object[] 数组实现,默认大小为 10 ,支持随机访问,连续内存空间,
插入末尾时间复杂度 o(1),插入第 i 个位置时间复杂度 o(n - i)。
扩容,大小变为 1.5 倍,Arrays.copyOf(底层 System.ArrayCopy),复制到新数组,指针指向新数组。
Vector 类似 ArrayList,线程安全,扩容默认增长为原来的 2 倍,还可以指定增长空间长度。
LinkedList 基于链表实现,1.7 为双向链表,1.6 为双向循环链表,取消循环更能分清头尾。
1.2、遍历一个list有哪些方式?
1.3、ArrayList
1.3.1、arraylist是线程安全的么,怎么让他线程安全
不是,
1、使用synchronized关键字;
2.使用Collections.synchronizedList();
1.3.2、Arraylist的扩容机制
1.3.3 Arraylist的源码分析
先上测试代码:
public static void main(String[] args) {
ArrayList objects = new ArrayList<>();// 第一步
objects.add("1号选手"); //第二步
System.out.println(objects);
}
//1、首先可以看到ArrayList实现了List接口;
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
//2、默认容量 DEFAULT_CAPACITY = 10
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
//3、一个空数组,用于实例化后进行复制
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//4、elementData为存储元素的数组
transient Object[] elementData; // non-private to simplify nested class access
//5、实际数组长度
private int size;
//当测试代码进入第一步时,实例化一个ArrayList时,就进入这样一个无参构造方法中,当然还有几个有参的,这里用的是无参的
public ArrayList() {
//将elementData赋值为一个空数组,此时的Size为0
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//当测试代码进入第二步时,进行add方法时
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //上一个语句到这一步中间隔了很多,到这里elementData的空间始终是够的,直接增加
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//注意这个在第一次添加第一个元素时,add方法会进入到这里,显然第一次if为true,此时return 10,所以此时的数组实际长度瞬间变为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//add方法的最后操作就是进入扩展方法之中
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //相当于oldCapacity+(oldCapacity/2) 就是1.5倍
if (newCapacity - minCapacity < 0) //这里判断有没有必要扩容,当minCapacity大时,则会舍去前面的扩容操作
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //MAX_ARRAY_SIZE这个数很大,一般不会进入这里
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity); //将elementData复制到新空间中
}
1.4、LinkedList
1.4.1、LinkedList的使用
存储结构:双向链表
1.4.2、LinkedList的源码分析
//1、首先可以看到的是Linked List实现了List接口
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//链表的长度,或节点的个数
transient int size = 0;
//头节点
transient Node<E> first;
//尾节点
transient Node<E> last;
//注意Linkedlist的数据结构是双向链表
private static class Node<E> {
E item; //元素值
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
//当调用add方法时
public boolean add(E e) {
linkLast(e); //进入linkLast方法
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode; //如果是第一次加入,则设置其为头节点
else
l.next = newNode; //否则在尾节点后面插入
size++;
modCount++;
}
1.5、ArrayList、Vector 和 LinkedList 有什么共同点与区别?
-
ArrayList、Vector 和 LinkedList 都是可伸缩的数组,即可以动态改变长度的数组。
-
ArrayList 和 Vector 都是基于存储元素的 Object[] array 来实现的,它们会在内存中开辟一块连续的空间来存储,支持下标、索引访问。但在涉及插入元素时可能需要移动容器中的元素,插入效率较低。当存储元素超过容器的初始化容量大小,ArrayList 与 Vector 均会进行扩容。
-
Vector 是线程安全的,其大部分方法是直接或间接同步的。ArrayList 不是线程安全的,其方法不具有同步性质。LinkedList 也不是线程安全的。
-
LinkedList 采用双向列表实现,对数据索引需要从头开始遍历,因此随机访问效率较低,但在插入元素的时候不需要对数据进行移动,插入效率较高。
-
ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),**扩容机制(再补充详细些):**因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数据,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)
-
LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历;遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexOf对list进行了遍历,当结果为空时会遍历整个列表
1.6、如何实现数组和 List 之间的转换?
- List转换成为数组:调用ArrayList的toArray方法。
- 数组转换成为List:调用Arrays的asList方法。
1.7、Array 和 ArrayList 有何区别?
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。
ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。
Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。