1.定义
1.集合(
Collection
)有时又称为容器,简单来说,是一个对象
,能够将多个性质相同的元素汇聚成一个整体。2.集合被用于
存储
、获取
、操纵
和传输
聚合的数据。3.集合框架(Collections Framework)是用来
表现和操纵集合
的一个统一的体系结构。4.所有的集合框架都包含以下内容:
接口
:是代表集合的抽象数据类型。
实现
:是集合接口的具体实现。本质上,它们是可重用的数据结构,是一些类。
算法
:是在实现了集合接口的对象上执行有用的计算的方法,如查找和排序。
2.分类
集合框架:
1.Collection接口
2.Iterator接口
3.List接口以及实现类
4.Set接口以及实现类
5.Map接口以及实现类
3.Collection接口
Collection接口的父接口是Iterable接口,Iterable接口有以下子接口:BlockingDeque、BlockingQueue、 Collection、Deque、List、Queue和Set等。Collection接口提供了集合接口的通用操作。
基本操作 | |
int size() | 返回集合元素的个数 |
boolean isEmpty() | 判断集合是否包含集合元素 |
boolean contains(Object o) | 判断集合中是否包含指定元素 |
boolean add(E element) | 向集合中添加指定元素 |
boolean remove(Object o) | 从集合中移除指定元素 |
Iterator iterator() | 返回在集合元素上进行迭代的迭代器 |
批量操作 | |
boolean containAll(Collection<?> c) | 集合中是否包含指定的所有集合元素 |
boolean addAll(Collection<? extendsE> c ) | 向集合中添加指定集合的所有元素 |
boolean removeAll(Collection<?> c) | 从集合中移除指定集合的包含的元素 |
void clear() | 移除集合中所有元素 |
boolean retainAll(Collection<?> c) | 保留集合中指定的元素 |
4.List接口
List接口是一个有序的集合,可以包含重复元素,除了从Collection继承来的操作外,List接口还提供了以下按序列进行操作的方法:
按序列操作的方法 | |
E get(int index) | 返回集合中指定位置的元素 |
int indexOf(Object o) | 返回指定对象在集合中的索引位置 |
List subList(int from ,int to) | 从集合中截取子集合 |
E remove(int index) | 移除集合中指定位置的元素 |
5.ArrayList
add(E e):往集合中中添加元素,其中E表示泛型,其实就是类型,当我们在声明一个集合的时候一般需要指定泛型,包裹在<>内,例如:ArrayList,指定的泛型代表我们存储在集合内元素的类型。
ArrayList list=new ArrayList();
//ArrayList可以存储任意类型元素,可以重复,可以为null,有序(存取顺序一致)
list.add("abc");
list.add(10);
list.add(10);
list.add(true);
list.add(null);
System.out.println(list.get(1));//10
//也可以指定存储类型
ArrayList<String> list=new ArrayList<>();
list.add("abc");
//源码分析
/*ArrayList的成员变量*/
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;//默认容量
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access 该数组用于存储元素
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
/*ArrayList的构造*/
public ArrayList(int initialCapacity) {//参数表示集合初始化容量
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];//初始化数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;//如果初始化容量为0,就赋给它一个空数组。
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//不指定初始化容量,就赋一个空数组给它。
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! size=0,此时还未初始化
elementData[size++] = e;//size:0--》1
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//elementData空数组,minCapacity:1
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//使用的是无参构造
return Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY:默认容量为10
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {//minCapacity:10
modCount++;//modCount:0--》1,modCount在它的父类AbstractList中,表示列表修改次数
// 在我们第一次添加元素后,elementData数组的长度(容量)已经变为10了,
//也就是在添加2-10个元素时,都不会再进行grow操作了。
if (minCapacity - elementData.length > 0)//elementData.length:0
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//0
//在添加第11个元素时,newCapacity=10+10/2=15,容量直接加5。
int newCapacity = oldCapacity + (oldCapacity >> 1);//0
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//10
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);//数组扩容为10
}
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
从上面的源码分析来看:ArrayList是以数组(elementData)来存储元素的,并且有一个默认容量(DEFAULT_CAPACITY),当我们使用无参构造时有用,这个默认容量会在我们第一次添加元素时赋给elementData,elementData会在我们不断添加元素的过程中进行扩容,赋予默认容量后,在添加2-10
元素时都不会再扩容,当添加第11个元素时会扩容到15,当我们添加第16个元素时才会进行再次扩容,扩容幅度和我们的当前容量有关。
将该 collection 中的所有元素添加到此列表的尾部
public static void main(String[] args) {
List<String> list1=new ArrayList<>();
list1.add("abc");
list1.add("def");
List<String> list2=new ArrayList<>();
list2.addAll(list1);
for (String s : list2) {
System.out.println(s);
}
}
//源码分析
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;//numNew:2
ensureCapacityInternal(size + numNew); // Increments modCount size:0,numNew=2
System.arraycopy(a, 0, elementData, size, numNew);//把a数组的元素复制到elementData中
size += numNew;//size:2
return numNew != 0;
}
public Object[] toArray() {
return Arrays.copyOf(elementData, size);//其实就是把elementData复制到其它数组再返回
}
从上面分析来看:添加过程首先将被添加集合转换为数组,其实就是把该集合中所有元素放到一个数组中,然后再将该数组中所有元素复制到当前集合的elementData中(从尾部开始添加)。
clear():移除此列表中的所有元素。
list.clear();
for (Object o : list) {
System.out.println(o);
}
//源码
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;//将每个元素都置为null
size = 0;//size置零
}
contains(Object o):如果此列表中包含指定的元素。
System.out.println(list.contains(10));//true
//源码
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
get(int index):返回此列表中指定位置上的元素。
System.out.println(list.get(2));//10
//源码
public E get(int index) {
rangeCheck(index);//index:2
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)//size:5
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];//elementData是Object数组,所以在这进行了向下转型
}
indexOf(Object o):返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1。
System.out.println(list.indexOf(10));//1
//源码
public int indexOf(Object o) {
if (o == null) {//如果传入的是null
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {//如果传入为非null
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
isEmpty():如果此列表中没有元素,则返回
true
System.out.println(list.isEmpty());//false
//源码
public boolean isEmpty() {
return size == 0;
}
remove(int index):移除此列表中指定位置上的元素。
list.remove(3);
System.out.println(list.get(3));//null
//源码
public E remove(int index) {//index:3
rangeCheck(index);
modCount++;
E oldValue = elementData(index);//oldValue:true
int numMoved = size - index - 1;//size:5 numMoved:1,其实就是被删除元素后面元素的数量
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//将被删除元素后面元素往前覆盖
elementData[--size] = null; // clear to let GC do its work size:4
return oldValue;//返回被删除的元素
}
remove(Object o):移除此列表中首次出现的指定元素(如果存在)。
//list.remove(10); 10会被当作索引而不是对象
list.remove("abc");
//源码
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
}
set(int index,E element):用指定的元素替代此列表中指定位置上的元素。
list.set(1, 20);
System.out.println(list.get(1));
//源码
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
size():返回此列表中的元素数。
System.out.println(list.size());
toArray():按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。
Object[] array = list.toArray();
for (Object o : array) {
System.out.println(o);
}
//源码
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public static <T> T[] copyOf(T[] original, int newLength) {//original:elementData newLength:5
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
add(int index,E element):在指定索引处插入指定元素。
list.add(1, "def");
System.out.println(list);//[abc, def, 10, 10, true, null]
//源码
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);//将下标为index以及他后面的元素整体后移,
elementData[index] = element;//后移后下标为index为null,这里把要插入的元素插到index处。
size++;//size加一
}
ArrayList使用数组来存储元素,内存空间分布是连续的,查询快,但是在插入或者移除时慢(因为要移动大量元素)。
6.LinkedList
add(E e):将指定元素添加到列表结尾。允许添加null,重复元素
LinkedList<String> list=new LinkedList<>();
//list.add(null);允许添加null
//list.add("a");允许添加重复元素
list.add("a");
list.add("d");
list.add("e");
list.add("b");
System.out.println(list);//[a, d, e, b] 存取顺序一致
//源码分析
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;//首节点
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;//尾节点
public boolean add(E e) {
linkLast(e);//e:"abc"
return true;
}
void linkLast(E e) {
final Node<E> l = last;//last:null
final Node<E> newNode = new Node<>(l, e, null);//l:null e:"abc"
last = newNode;//新节点变成尾节点
if (l == null)
first = newNode;//如果l为null,此时newNode既是首节点也是尾节点
else
l.next = newNode;//如果不为null,则newNode成为l的下一个节点
size++;//size加一
modCount++;
}
//内部类
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;
}
}
从上面源码来看:LinkedList内部是以Node(节点)来保存元素的,Node分为三部分:它的前一个节点、它所保存的内容和它的下一个节点。我们在添加元素时,首先会基于当前要添加的元素构建一个新节点,这个节点需要指定它的上一个节点,然后上一个节点的next要指向当前节点。
add(int index,E e):在列表的指定位置插入指定元素。
public static void main(String[] args) {
LinkedList<String> list=new LinkedList<>();
list.add("abc");
list.add("def");
list.add(1,"ccc");
System.out.println(list);//[abc, ccc, def]
}
//源码
public void add(int index, E element) {//index:1 element:“ccc”
checkPositionIndex(index);
if (index == size)//size:2
linkLast(element);
else
linkBefore(element, node(index));
}
Node<E> node(int index) {//index:1 该方法用于返回指定索引处的节点
// assert isElementIndex(index);
if (index < (size >> 1)) {//>>:右移一位,相当于除以2,也就是size的一半,这里是从第一个往后遍历
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {//这里是从后往前遍历
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
//被插入元素的前一个节点是原来在这的元素的前一个节点,而被插入元素的后一个节点则变成了原来在这的那个
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)//如果原本元素前一个节点为null,则证明它是首节点
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
从上面的源码分析来看:当我们在指定位置插入一个元素时,首先会查找当前在指定位置的元素节点,它的查找方式很有特点,不是单纯的从一而终,而是根据传入的索引将查找范围缩小到一半,虽然在节点数量十分庞大的时候依旧会有查找效率的问题,但这毫无疑问极大的减少了查找时间。查找到该节点后根据该节点以及该节点的上一个节点创建新节点,并改变它们的指向。
从上面的分析可以看出:LinkedList在插入元素的时候只需要改变原本节点以及它前一个节点的指向就行了,不需要像ArrayList一样大批量移动,所以LinkedList在插入、删除速度较快;但是通过node方法可以看出,在查询某个元素时需要通过遍历从前往后或者从后往前一个一个查,如果存储节点十分庞大的话,查询效率非常的低,不过ArrayList明显没有这方面的不足。
get(int index):查询指定位置的元素
System.out.println(list.get(1));
//源码
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
clear():清空所有元素。
//源码
public void clear() {
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
从上面的源码来看:清空元素的过程中,首先使用循环将每个节点保存的三个部分(内容和前后节点)全部置为null,然后将first和last置为null,size置为0
contains(Object o):判断是否包含指定元素。
System.out.println(list.contains("b"));
//源码分析
public boolean contains(Object o) {
return indexOf(o) != -1;
}
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
从上面的源码来看:contains方法通过indexOf的返回值来确定是否包含指定元素,indexOf方法内定义了一个index来记录当前的“位置”,通过for循环来判断节点保存的item是否和传入值相等,相等则返回index,否则index++。这种从头到尾的查询显然效率很低。
element():返回首节点保存的内容。
System.out.println(list.element());
//源码分析
public E element() {
return getFirst();
}
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
remove():删除第一个元素
System.out.println(list.remove());
//源码
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
set(int index,E element):修改指定位置的元素
System.out.println(list.set(1, "cc"));
//源码分析
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
sort(Comparator<? super E> comparator):根据指定比较器进行排序
//源码
//List接口
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
//LinkedList
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
//AbstractList
public ListIterator<E> listIterator() {
return listIterator(0);
}
//LinkedList
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
//ListItr
private Node<E> next;
private int nextIndex;
private Node<E> lastReturned;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
从上面的源码来看:我们知道,LinkedList是由一个一个的Node节点所组成的,在排序过程中,首先会通过toArray将所有节点中保存的item也就是内容给抽取出来形成一个数组,这个数组中保存了所有的节点内容,然后通过Arrays的sort方法对数组进行排序,然后再通过listIterator方法获取迭代器(ListItr对象),最后通过遍历数组对我们的所有节点内容item进行重新赋值,next()方法在这里只起到一个“后移”的作用,而set方法则是对当前节点的item进行赋值。
ArrayList和LinkedList对比
ArrayList | LinkedList | |
元素是否有序 | 有序,存取一致 | 有序、存取一致 |
底层 | 数组 | 链表节点 |
是否允许出现null、重复元素 | 是 | 是 |
查询效率 | 快 | 慢 |
插入、删除效率 | 慢 | 快 |