List接口的使用
首先List接口是从Collection接口那里继承而来,特点是有序有重复
有序:每个元素都有一个固定的下标,和数组类似
有重复:可以有多个重复的元素
List接口中与下标相关的对应方法
- add(int index, Object object),添加元素到指定序列
- get(int index)根据下标直接获取某个位置的元素值并返回
- set(int index,Object e)修改指定下标位置的元素值
- remove(int index) 根据下标删除指定位置的元素
注意:如果按照一个int类型的数据内容进行删除,需要手动进行包装,否则将参数理解为下标
ArrayList集合
ArrayList集合继承了List接口,重写了接口中的方法,在这些方法包含了能够对下标进行处理的方法。在ArrayList中还是用数组实现,所以可以近似把它当做数组运用。
ArrayList实现类的原理说明
ArrayList定义的常用属性
//默认初始数组容量
private static final int DEFAULT_CAPACITY = 10;
//储存集合中实例对象
private static final Object[] EMPTY_ELEMENTDATA = {};
//共享的空数组实例用于默认大小的空实例。将其与EMPTY ELEMENTDATA区分开来,
//以便知道添加第一个元素时要膨胀多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//数组列表的元素存储在数组缓冲区中。数组列表的容量就是这个数组缓冲区的长度。
//任何带有elementData == DEFAULTCAPACITY的*空ArrayList将在添加第一个元素时扩展为DEFAULTCAPACITY。
transient Object[] elementData; // non-private to simplify nested class access
//储存元素个数
private int size;
1. 在ArrayList中保存的数据都是保存在对象数组EMPTY_ELEMENTDATA中
//ArrayList中属性设置
private static final Object[] EMPTY_ELEMENTDATA = {};
2. 在ArrayList集合中提供了三种构造方法如下图
2.1 使用无参构造时提供了一个默认初始化容量为10,但是不立即创建对象,这其实是一种“延迟策略”,在客户正真要使用的时候才分配元素对应的空间
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.2 在使用构造器的时候传入一个整数,表示设置初始容量,但是在设置初始容量的时候尽量要防止扩容,因为扩容消耗内存,效率不高,所以一般推荐空参构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2.3 在使用构造器的过程中还可以传入一个实现collection接口的集合,供初始化
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
3. ArrayList添加元素过程
public boolean add(E e) {
ensureCapacityInternal(size + 1); //确定数组的容量是否足够,并传入添加当前元素需要的最小容量
elementData[size++] = e;//将新元素的值添加到内部数组的最后并对元素个数进行增
return true;//返回添加操作的状态
}
private void ensureCapacityInternal(int minCapacity) {
//如果数组为空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果是第一次添加就设置容量为10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果需要的长度比现有长度大,那么就执行grow方法
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 首先复制原有数组长度
int oldCapacity = elementData.length;
//将原有数组长度设置为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
ArrayList特点
数组属于线性表这种数据结果,查找和检索还有遍历的效率非常高 时间复杂度为O(1),但是增删的效率非常低O(n)
LinkedList实现类
LinkedList常用方法
- addFirst:添加到集合第一个元素
- addLast:添加到集合最后一个元素
- getFirst: 获取第一个集合元素
- getLast: 获取最后一个集合元素
- removeFirst:删除集合第一个元素
- removeLast:删除集合最后一个元素
LinkedList list=new LinkedList();
list.add("111");
list.add("222");
list.add("333");
list.addFirst("666");//在头部添加一个新元素
list.addLast("999");//在尾部添加一个新元素
Object data=list.removeFirst();//移除最前面的元素并返回
list.removeLast();//移除列表中最后一个元素并返回
list.getFirst();//获取头部元素并返回
list.getLast();//获取尾部元素并返回
System.out.println(data);
for (Object object : list) {
System.out.println(object);
}
LinkedList原理分析
LinkedList底层使用的双向数据链表,每个元素称为一个“节点”,每个节点由三个部分组成:左边存储上一个节点的地址,中间存储本身的数据,右边存储下一个节点的地址,每个节点在内存中不要求是连续的,相比于数组来说可以有效的利用一些分散的内存碎片
代码实现
//在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;
}
}
添加元素代码分析
void linkLast(E e) {
//记录之前最后一位元素
final Node<E> l = last;
//将此次添加元素prev指针指向之前最后一位元素
final Node<E> newNode = new Node<>(l, e, null);
//添加后那么此元素即为最后一位
last = newNode;
//如果为空那么说明此次添加为第一个元素
if (l == null)
first = newNode;
//否则将之前的最后一位元素naxt指针指向此元素
else
l.next = newNode;
size++;
}
Vector实现类
该集合的使用包括原理和ArrayList完全一样,不同的是大部分方法都有一个synchronized修饰,用来保证线程安全的,其余操作与ArrayList用法几乎一致。
之所以现在Vector集合运用的少,是因为现在处理线程问题很多不是非常需要synchronized来解决