一、List接口
List接口直接继承Collection接口,代表有序的Collection。
public interface List extends Collection {}
是一个有序的允许重复的集合,可以通过索引查找list中的具体元素。在开发中常用实现类有:ArrayList、LinkedList、Vector:
ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。
LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率低。
Vector 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
1.常用的方法
add():在列表的最后添加元素;
add(index,obj):将指定的元素插入此列表中的指定位置;
size():返回当前列表的元素个数
get(int index):返回下标为index的元素;
set(index,obj):用新传入的对象,将指定位置的元素替换掉,返回被替换前的元素对象;
remove():传入一个下标,或者一个对象,删除指定元素;
如果删除指定对象,需要重写equals()方法,比对传入的对象是不是属于原列表,如果属于,结果返回true;
clear():清除列表中所有元素;
isEmpty():检测列表是否为空;返回true/false;
contains():传入一个对象,检测列表中是否含有该对象;
indexOf():传入一个对象,返回该对象在列表中首次出现的地址;
lastInsevOf():传入一个对象,返回该对象在列表中最后一次出现的地址;
subList( int fromIndex , int toIndex) :截取一个子列表返回List类型;
toArray():将列表转为数组,返回一个Object[]类型;
iterator():使用迭代器遍历。
2.List使用场景
应当根据实际操作场景和需要具体考虑选用List的实现类 (涉及“栈” 、“队列” 、“链表”等):
(1)需要快速插入、删除元素,应该使用LinkedList, LinkedList是有序的,增删快、查询慢
(2)对于需要快速随机访问元素,应使用ArrayList,ArrayList是无序的,查询块、增删慢
(3)对于“单线程环境” 或者:“多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类,如ArrayList
Vector是支持同步的,Stack继承于Vector。
二、ArrayList集合
ArrayList是一个动态数组,elementDate就是底层数组,其初始的容量为10 ,也就是未定义时数组的大小。随着数组中元素的不断增加,数组的容量的大小也会随
着增加。在每次向容器中增加元素的同时,会对ArrayList进行容量检查,当快溢出时,就会进行扩容操作。在定义数组时,一般要定义数组的初始值,减少因元素个素
超过数组初始大小时进行扩容操作而浪费时间、效率。
图解:
ArrayList的size()
、isEmpty()
、get(int index)
、set(int index, E element)
、iterator()
和 listIterator()
操作都是以固定时间运行。add()
操作以分摊
的固定时间运行,添加n个元素需要O(n)时间,要考虑到扩容,所以时间不仅仅只是添加元素分摊固定时间。
ArrayList类似于数组,存储的数据在内存中是连续的、成块的、查找的时候直接遍历内存就可以;而插入删除的时候,就要把修改的那个节点之后的所有数据都向
后移,或者向前移。
1.线程不安全
ArrayList 是基于动态数组的集合。ArrayList查询速度非常快,使得它在实际开发中被广泛使用,但它并不是线程安全的。
// 查询元素 public E get(int index) { rangeCheck(index); // 检查是否越界 return elementData(index); } // 顺序添加元素 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++; } // 从数组中删除元素 private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
由于ArrayList是非同步的,没有同步锁;在多线程的情况下,调用这个方法时,可能会有可能被多个线程拿到相同的size值去与ArrayList的容量做比较,而执行到
elemntData[size++] = e;时却是有序的,这时由于没有适当的扩大ArrayList的容量,从而导致插入数据的长度大于ArrayList的剩余容量,于是抛出了数组越界异常
(java.lang.ArrayIndexOutOfBoundsException)。
一个ArrayList,在添加一个元素的时候,它有可能会分为两步来完成:
(1)在Items[size]的位置存放此元素
(2)增大Size值
在单线线程运行的情况下,如果size=0,添加一个元素后,此元素在位置0,而且size=1;
而如果在多线程情况下,比如有两个线程,线程A先将元素存放在位置0。但是此时CPU调度线程A暂停,线程B得到运行的机会。线程B也向此ArrayList添加元素,
因为此时size仍然等于0(假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都
增加 Size 的值。现在看ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
关于ArrayList线程不安全更详细的可以参考: https://blog.csdn.net/qq_28081081/article/details/80413669
线程不安全的解决:
如果需要在多线程中使用,可以采用Collections.synchronizedList()方法
List
三、LinkedList集合
LinkedList是List接口的链表实现,首尾相接,可以作为栈、队列、双端队列数据结构使用,本质是双向链表,不能像ArrayList一样随意访问查询;包含两个重要的
成员:header 和 size。header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是
该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。size是双向链表中节点的个数。LinkedList非同步,线程不安全.
查询慢、增删快
LinkedList 其实是一个个的Node节点,每个Node节点首位相连
// 查询元素 public E get(int index) { checkElementIndex(index); // 检查是否越界 return node(index).item; } Nodenode(int index) { if (index < (size >> 1)) { // 类似二分法 Node x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } // 插入元素 public void add(int index, E element) { checkPositionIndex(index); // 检查是否越界 if (index == size) // 在链表末尾添加 linkLast(element); else // 在链表中间添加 linkBefore(element, node(index)); } void linkBefore(E e, Node succ) { final Node pred = succ.prev; final Node newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
当进行数组循环时,ArrayList以及数组,适合用for循环下标遍历,当用for大批量的循环LinkedList时,程序会卡死
LinkedList适合用foreach循环 (队列 : 先进先出 栈:先进后出)
四、Vector
Vector的数据结构和ArrayList相似,操作基本与ArrayList一致,但是其对数据操作的方法基本上都被synchronized关键字修饰,所以是线程安全的,所以相比较
于ArrayList而言,性能要低。若想要一个高性能,又是线程安全的ArrayList,可以使用Collections.synchronizedList(list),方法或者使用CopyOnWriteArrayList集合。