#####1. Collection
Iterable 接口与 Map 接口在 Java 体系中占主要地位,基本上所有的数据结构都是基于这个两个接口,一个是线性可迭代的一个是哈希表。
通过上图看出,Collection 接口作为集合体系的父接口,Map 作为哈希表体系的接口。
最主要的集合 Collection 源码提供方法:
public interface Collection<E> extends Iterable<E> {
int size(); // 集合大小
boolean isEmpty(); // 是否为空
boolean contains(Object o); // 是否包含某个元素
Iterator<E> iterator(); // 返回迭代器
Object[] toArray(); // 返回 Object 数组
<T> T[] toArray(T[] a); // 返回指定泛型的数组
boolean add(E e); // 添加一个元素,返回是否添加成功
boolean remove(Object o); // 移除一个元素,返回是否添加成功
boolean containsAll(Collection<?> c); // 是否包含另一个集合
boolean addAll(Collection<? extends E> c); // 将另一个集合添加到当前集合
boolean removeAll(Collection<?> c); // 移除当前集合的元素,返回集合是否被修改过
default boolean removeIf(Predicate<? super E> filter); // 根据添加移除元素
boolean retainAll(Collection<?> c); // 仅保留存在于给定集合的元素,返回集合是否被修改过
void clear(); // 清空集合
boolean equals(Object o); // 判断是否相等
int hashCode(); // 返回hash值
}
#####2. List
- 存储的数据特点:存储有序的、可重复的数据。
- 常用的实现类:
- Collection接口:单列集合,用来存储一个一个的对象
- List接口:存储序的、可重复的数据。 -->“动态”数组,替换原的数组
- ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储,随着元素的增加而动态扩容
- LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储,随着元素的增加而不断向链表的后端增加结点
######2.1 List接口
#List方法
A:添加功能
boolean add(E e):向集合的尾部添加一个元素
* void add(int index, E element):在指定位置添加元素
boolean addAll(Collection<? extends E> c):在集合中尾部添加一个集合。
* boolean addAll(int index, Collection<? extends E> c);// 在集合指定位置添加一个集合。
B:删除功能
void clear():删除集合中的所有元素
* E remove(int index):删除集合指定位置的元素,并把删除的元素返回
boolean remove(Object o):删除集合中指定的元素
boolean removeAll(Collection<?> c):删除两个集合的交集 。
* default void replaceAll(UnaryOperator<E> operator) // 将集合中的元素特换成指定元素
C:修改功能
void clear(); // 清空集合元素
E set(int index, E element):把指定索引位置的元素修改为指定的值,返回修改前的值。
D:获取功能
* E get(int index):获取指定位置的元素
* int indexOf(Object o);// 获取元素在集合中首次出现的位置,获取不到返回 -1
* int lastIndexOf(Object o);// 获取元素在集合中最后出现的位置,获取不到返回 -1
* List<E> subList(int fromIndex, int toIndex);// 截取指定区间位置内的集合
E:迭代器
Iterator iterator():获取集合中每一个元素。
* ListIterator<E> listIterator(int index); // 获取集合从特定位置开始的每个元素
F:判断功能
boolean isEmpty():判断集合是否为空:size==0
boolean contains(Object o):判断集合中是否存在指定的元素。
boolean containsAll(Collection<?> c):判断集合C是否为当前集合的子集。
* boolean retainAll(Collection<?> c); // 求两个集合的交集,并将交集赋值给当前集合,若集合有变化则返回true,若两集合相等则返回false
boolean equals(Object o);// 判断两个集合是否相等:集合长度、元素、元素位置
G:数组功能
int hashCode();
int size():获取集合中的元素个数
* default void sort(Comparator<? super E> c)// 对集合进行特定排序
H:把集合转换成数组
Object[] toArray():把集合变成数组。
public <T> T[] toArray(T[] a) ;
2.2 ArrayList类
ArrayList是Java集合框架中使用最多的一个类,是一个数组队列,线程不安全集合。
它继承于AbstractList,实现了List, RandomAccess, Cloneable, Serializable接口。
- ArrayList实现List,得到了List集合框架基础功能;
- ArrayList实现RandomAccess,获得了快速随机访问存储元素的功能,RandomAccess是一个标记接口,没有任何方法;
- ArrayList实现Cloneable,得到了clone()方法,可以实现克隆功能;
- ArrayList实现Serializable,表示可以被序列化,通过序列化去传输,典型的应用就是hessian协议。
2.2.1 ArrayList类结构特点:
- 容量不固定,随着容量的增加而动态扩容(阈值基本不会达到)
- 有序集合(插入的顺序==输出的顺序)
- 插入的元素可以为null
- 增删改查效率更高(相对于LinkedList来说)
- 线程不安全
2.2.2 ArrayList类常用方法
ArrayList实现List集合框架基础功能,实现了所有方法的实现逻辑,无新增的公共方法,新增的私有方法也仅是助于处理逻辑。
- 1.ArrayList全局变量
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final int DEFAULT_CAPACITY = 10; // 默认初始化容量为10
private static final Object[] EMPTY_ELEMENTDATA = {}; // 创建一个空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 创建一个拥有默认容量为10的空数组
transient Object[] elementData; // ArrayList底层使用数组进行存储。设置为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的list,在首次调用add方法时,容量会扩容为10.
private int size; // ArrayList的大小,即元素的个数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // list的最大容量
}
- 2.ArrayList构造器
ArrayList有三个构造器:
- 无参:创建空数组,调用方法时默认创建数组的容量为10
- 指定list长度:创建指定常数的数组
- 指定元素:创建容量大小为集合长度且元素确定的数组
// 1. 创建空数组,调用方法时默认创建数组的容量为10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 2. 创建指定长度的list:底层创建指定长度的数组
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);
}
}
// 3. 创建一个包含指定元素的集合列表
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添加元素及扩容
ArrayList的add方法主要有两大类:
- 在list尾部添加元素或集合 :add(E e)、addAll(Collection<? extends E> c)
- 在list指定位置添加元素或集合:add(int index, E element)、addAll(int index, Collection<? extends E> c)
第2类方法中涉及到指定位置后元素的后移,源码中主要是调用System.arraycopy方法进行的元素后移和赋值操作;第1类的方法直接在尾部添加,不涉及元素的移动。
ArrayList调用add方法时首先会检查当前容量是否满足 size + 1(numNew)需求,如果满足则继续添加,如果不满足则进行扩容.
注意:ArrayList允许添加null,同时size+1;清空集合时元素置为null,size=0
// 1.在list尾部添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//2.将集合c元素添加到在list尾部
public boolean addAll(Collection<? extends E> c){
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
//3.在指定位置插入元素
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//4.在指定位置插入集合元素
public boolean addAll(int index, Collection<? extends E> c){
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
ArrayList扩容的逻辑:
- 最小容量计算:minCapacity= size + 1(numNew)
- 如果list为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则所需最小容量为:Math.max(DEFAULT_CAPACITY, minCapacity)
- 否则,所需最小容量为:minCapacity
- 当前容量与最小容量(minCapacity)判断:
- 如果当前容量>最小容量(minCapacity),则进行正常添加
- 如果当前容量<最小容量(minCapacity),则进行扩容
- 扩容:
- 将当前容量扩充为原来的1.5倍,得到新容量
- 如果新容量>最小容量(minCapacity),则新容量成立;如果新容量<最小容量(minCapacity),则新容量=最小容量(minCapacity)
- 新容量阈值判断:MAX_ARRAY_SIZE
- 如果新容量<MAX_ARRAY_SIZE,则新容量成立
- 如果新容量超过MAX_ARRAY_SIZE
- 若最小容量(minCapacity) <MAX_ARRAY_SIZE,则新容量=MAX_ARRAY_SIZE
- 若最小容量(minCapacity) >MAX_ARRAY_SIZE,则新容量=Integer.MAX_VALUE
注意:list扩容是将当前元素复制到新数组,返回新数组elementData = Arrays.copyOf(elementData, newCapacity)
// 与扩容相关的方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- 4.ArrayList交集、差集
removeAll和retainAll方法都涉及到元素的删除,底层均调用了batchRemove方法,只不过传递的参数不同
batchRemove方法的处理逻辑:- 将符合条件的元素重新放置在数组中(从下标0开始)
- 全部筛选完后,将剩余位置的元素设为null
// 删除两个集合的交集
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
// 保留两个集合的交集
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
// 集合操作过程
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
- 5.ArrayList元素移动
因为ArrayList是用连续的地址存储元素,所以当list在指定位置添加或删除元素(或集合)时,需要将指定位置后的所有元素向后或向前移动一位(或N位)。
ArrayList对应的方法有:
- add(int index, E element)、addAll(int index, Collection<? extends E> c)
- remove(int index)、removeRange(int fromIndex, int toIndex)、 remove(Object o)
此类方法底层均为调用 System.arraycopy方法实现元素的移动。
// 1.在指定位置添加元素
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
// 2.在指定元素添加集合元素
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
// 3.删除指定位置的元素
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; // clear to let GC do its work
return oldValue;
}
// 4.删除指定范围内的元素
protected void removeRange(int fromIndex, int toIndex){
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
// 5.删除指定元素
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 快速删除指定位置元素
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
}
- 6.ArrayList迭代器
ArrayList内置了两个迭代器:Itr、ListItr:
- Itr实现Iterator接口,主要针对集合操作,定义了hasNext、next、remove等方法的实现逻辑
- ListItr继承Itr类、实现了ListIterator接口,主要针对list 集合进行操作,定义了 nextIndex、previous、previousIndex、set、add等方法的实现逻辑
ArrayList内置迭代器方法的实现逻辑与AbstractList内置迭代器的方法逻辑相同。
ArrayList主要有三种方法调用迭代器:iterator()、listIterator()、listIterator(int index)
注意:在调用迭代器时,不能对原list进行修改操作;同时对set、add、remove方法只能操作一次。迭代器具体实现逻辑可见【集合迭代器】详解
// 1.调用Itr迭代器
public Iterator<E> iterator() {
return new Itr();
}
// 2.调用listIterator迭代器
public ListIterator<E> listIterator() {
return new ListItr(0);
}
// 3.指定位置开始调用listIterator迭代器
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
- 7.ArrayList常用一般方法
- ArrayList中注意区分元素个数(size)与数组容量
- trimToSize( )方法去除多余的容量,将元素位置与个数相等的新数组赋值给当前数组。
- clone()方法返回元素位置与个数相等的新集合
- clear()将元素全部置为null,size=0
- isEmpty()判断size==0
public boolean isEmpty() {
return size == 0;
}
* E elementData(int index) {
return (E) elementData[index];
}
public int indexOf(Object o);// 返回元素在集合中第一次出现的位置,元素O可以为null,查找不到返回-1
// 去掉预留元素位置:使数组容量=数组元素个数(size)
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
//克隆当前集合为新的集合对象
* public Object clone(){
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
#####3. Queue
######3.1.Queue接口
Queue接口继承Collection接口,在Collection接口基本操作的基础上,新增了添加、获取、删除方法,每种方法都有两种方式:1.如果操作失败抛出异常;2.如果操作失败返回null或false。
public interface Queue<E> extends Collection<E> {
#添加元素
boolean add(E e);// 添加成功返回true;如果因为容量有限添加失败,则抛出异常IllegalStateException
boolean offer(E e);// 添加成功返回true,否则返回false。
#删除头元素
E remove();// 删除头元素,如果队列为空,抛出异常NoSuchElementException
E poll();// 获取头元素,如果队列为空返回null
#查询头元素,并不删除头元素
E element();// 查询头元素,如果队列为空抛出异常NoSuchElementException
E peek();// 查询头元素,如果队列为空返回null
}
3.2.Deque接口
Deque接口中的方法可以分为:
- 针对双端队列:1.在队列的first、last进行add、remove、get的6大类方法;2.删除特定元素:removeFirstOccurrence(Object o)、removeLastOccurrence(Object o)。
- 针对队列:add、remove、get的3大类方法。
- 针对栈:push、pop的2个方法
- 针对集合:1.特定元素的获取或删除:contains(Object o)、remove(Object o);2.元素个数: size();3.迭代器:iterator()、descendingIterator()
Deque接口特点:
Deque接口继承Queue接口,所以涉及到add、remove、get操作提供了两种方式:1.操作失败抛异常;2.操作失败返回null或false。
Deque接口不支持下标访问元素。
public interface Deque<E> extends Queue<E> {
/** 双端队列的方法
*/
#添加元素至队列头部
void addFirst(E e);// 添加成功返回true,若因容量受限添加失败,抛出异常:IllegalStateException
boolean offerFirst(E e); // 添加成功返回true,否则返回false
#添加元素至队列尾部
void addLast(E e);// 添加成功返回true,若因容量受限添加失败,抛出异常:IllegalStateException
boolean offerLast(E e);// 添加成功返回true,否则返回false
#删除队列头元素
E removeFirst();// 删除头元素,如果队列为空,抛出异常:NoSuchElementException
E pollFirst();// 删除头元素,如果队列为空返回null
#删除队列尾元素
E removeLast();// 删除尾元素,如果队列为空,抛出异常:NoSuchElementException
E pollLast();// 删除尾元素,如果队列为空返回null
#获取队列头元素
E getFirst();// 获取头元素,如果队列为空,抛出异常:NoSuchElementException
E peekFirst();// 获取头元素,如果队列为空返回null
#获取队列尾元素
E getLast();// 获取尾元素,如果队列为空,抛出异常:NoSuchElementException
E peekLast();// 获取尾元素,如果队列为空返回null
#将队列首次出现的元素o删除
boolean removeFirstOccurrence(Object o);// 如果删除成功返回true
#将队列最后出现的元素o删除
boolean removeLastOccurrence(Object o);// 如果删除成功返回true
/** Queue接口方法
*/
#在队列尾部添加元素
boolean add(E e);// 添加成功返回true;如果因为容量有限添加失败,则抛出异常IllegalStateException
boolean offer(E e);// 添加成功返回true,否则返回false。
#删除队列尾部元素
E remove();// 如果队列为空,抛出异常NoSuchElementException
E poll();// 如果队列为空返回null
#查询队列头元素,并不删除头元素
E element();// 如果队列为空抛出异常NoSuchElementException
E peek();// 如果队列为空返回null
/** 栈Stack方法,只在栈顶进行元素的入栈和出栈
*/
#元素入栈
void push(E e);// 入栈成功返回true,如果因为容量受限,抛出异常:IllegalStateException
#元素出栈
E pop();// 如果队列为空,抛出异常:NoSuchElementException
/** 集合Collection方法
*/
# 删除队列首次出现的元素o
boolean remove(Object o);// 删除成功返回true。 方法等同于 removeFirstOccurrence(Object o) 方法
# 判断队列中是否包含元素o
boolean contains(Object o);// 如果包含返回true,否则返回false
# 队列元素个数
public int size();
#队列迭代器
Iterator<E> iterator();
#队列反转的迭代器
Iterator<E> descendingIterator();
}
3.3.LinkedList
LinkedList是一个双向链表,每一个节点都拥有指向前后节点的引用。相比于ArrayList来说,LinkedList的随机访问效率更低。
它继承AbstractSequentialList,实现了List, Deque, Cloneable, Serializable接口。
(1)LinkedList实现List,得到了List集合框架基础功能;
(2)LinkedList实现Deque,Deque 是一个双向队列,也就是既可以先入先出,又可以先入后出,说简单些就是既可以在头部添加元素,也可以在尾部添加元素;
(3)LinkedList实现Cloneable,得到了clone()方法,可以实现克隆功能;
(4)LinkedList实现Serializable,表示可以被序列化,通过序列化去传输,典型的应用就是hessian协议。
2.常用方法
3.源码分析
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了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;
}
}
######3.4. ArrayList和LinkedList比较
LinkedList和ArrayList之前的区别主要就是数组和链表的区别。
数组中查询和赋值比较快,因为可以直接通过数组下标访问指定位置。
链表中删除和增加比较快,因为可以直接通过修改链表的指针(Java中并无指针,这里可以简单理解为指针。其实是通过Node节点中的变量指定)进行元素的增删。
所以,LinkedList和ArrayList相比,增删的速度较快。但是查询和修改值的速度较慢。同时,LinkedList还实现了Queue接口,所以他还提供了offer(), peek(), poll()等方法。
https://www.jianshu.com/p/63b01b6379fb
#####4. Vector
Vector和ArrayList一样,都是通过数组实现的,但是Vector是线程安全的。和ArrayList相比,其中的很多方法都通过同步(synchronized)处理来保证线程安全。
如果你的程序不涉及到线程安全问题,那么使用ArrayList是更好的选择(因为Vector使用synchronized,必然会影响效率)。
二者之间还有一个区别,就是扩容策略不一样。在List被第一次创建的时候,会有一个初始大小,随着不断向List中增加元素,当List认为容量不够的时候就会进行扩容。Vector缺省情况下自动增长原来一倍的数组长度,ArrayList增长原来的50%。
#####5. 如何选择
如果涉及到多线程,那么就选择Vector(当然,你也可以使用ArrayList并自己实现同步)。
如果不涉及到多线程就从LinkedList、ArrayList中选。 LinkedList更适合从中间插入或者删除(链表的特性)。 ArrayList更适合检索和在末尾插入或删除(数组的特性)。
链接:https://www.jianshu.com/p/1149f1bf1736