一、集合分类
Java中容器有单列集合(Collection)和双列集合(Map)两大类,其下又有很多子类,如下:
(1)单例集合:Collection
- Collection
- List
- ArrayList
- LinkList
- Vector
- Stack
- Set
- HashSet
- TreeSet
- List
(2)双列集合:Map
- Map
- HashMap
- LinkedHashMap
- TreeMap
- ConcurrentHashMap
- HashTable
- HashMap
二、Collection 接口
所有单列集合的父接口。
(1)Collection中常用的方法:
方法名 | 描述 |
add(E e) | 向集合中添加元素。 |
addAll(Collection<? extends E> c) | 将指定 Collection 中的所有元素都添加到此 Collection 中 |
clear() | 移除此 Collection 中的所有元素 |
contains(Object o) | 如果此 Collection 包含指定的元素,则返回true |
containsAll(Collection<?> c) | 如果此 Collection 包含指定 Collection 中的所有元素,则返回 true |
equals(Object o) | 比较此 Collection 与指定对象是否相等 |
isEmpty() | 如果此 Collection 不包含元素,则返回true |
iterator() | 返回在此 Collection 的元素上进行迭代的迭代器 |
remove(Object o) | 从此 Collection 中移除指定元素,如果存在。不存在时不会报异常 |
removeAll(Collection<?> c) | 移除此 collection 中那些也包含在指定 collection 中的所有元素 |
retainAll(Collection<?> c) | 仅保留此 collection 中那些也包含在指定 collection 的元素 |
size() | 返回此 collection 中的元素数 |
toArray() | 返回包含此 collection 中所有元素的数组 |
三、List
List 是 Collection 接口的子接口,集合中元素有序且可重复。
public interface List<E> extends Collection<E> {}
1、ListIterator
List接口中有一个特别的方法,listIterator() 得到一个 ListIterator 接口类型的,专门针对 List 集合设计的迭代器实例,该迭代器实现了双向遍历 List 集合,并能够在遍历过程中实现 集合元素的增删改。ListIterator 针对 List 集合提供了更强大的遍历功能。
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
ListIterator 接口源码:
package java.util;
public interface ListIterator<E> extends Iterator<E> {
// Query Operations
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
// Modification Operations
void remove();
void set(E e);
void add(E e);
}
四、ArrayList
ArrayList 集合继承了 AbstractList 抽象类,并实现了是 List 接口。同时还实现了 RandomAccess(随机存取,一个标记接口,类似的还有 SequenceAccess (顺序访问) )、Cloneable(克隆)、Serializable(序列化)接口。
ArrayList 类中还以私有的方式实现了 Iterator 和 ListIterator 接口。
ArrayList 的实现原理是数组,并允许包括null在内的所有元素。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess,
Cloneable, java.io.Serializable {}
3、ArrayList使用
package basis.CollectionStu;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class StuList {
public static void main(String[] args) {
//创建一个ArrayList集合对象
List<String> list_1 = new ArrayList<>();
List<String> list_2 = new ArrayList<>();
//向集合中添加元素,同一个集合中,如果不加泛型约束,可以添加任意类型。
list_1.add("张三");
list_1.add("李四");
list_1.add("王五");
list_2.add("1");
list_2.add("2");
list_2.add("3");
//把一个集合中的所有元素插入到另一个集合
list_2.addAll(list_1);
//移除集合中的所有元素
//list_1.clear();
//判断集合中是否包含指定元素
System.out.println(list_2.contains("张三"));
//判断集合中是否包含指定集合的所有元素
System.out.println(list_2.containsAll(list_1));
//判断两个集合是否相等
System.out.println(list_2.equals(list_1));
//判断集合是否为空
System.out.println(list_1.isEmpty());
//从集合中移除指定元素,元素不存在时,不会抛异常
list_2.remove("aaa");
//从当前集合中移除指定集合中的所有元素
list_2.removeAll(list_1);
//仅保留此collection 中的那些也包含在指定集合中的元素
list_2.retainAll(list_1);
//返回集合中元素的个数
System.out.println(list_2.size());
//把集合转成数组,需要为集合添加泛型
List<String> list_3 = new ArrayList<>();
list_3.add("suxing");
list_3.add("suxing");
list_3.add("suxing");
//转成Object类型的数组
Object[] obj = list_3.toArray();
//转成指定类型的数组
/*
new String[0],告诉程序集合中的元素应该转成什么类型的数组,
[0],表示数组的实际长度,该数值小于集合元素个数时,数组长度都等于集合元素个数;
如果该值大于集合中元素的个数,该值为数组的实际长度
*/
String[] str = list_3.toArray(new String[0]);
System.out.println(str.length);
//遍历集合:Iterator
Iterator<String> iterator = list_3.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
//可以再遍历中,使用Iteator 中的删除方法,进行元素的删除,不可以使用集合的remove()方法
iterator.remove();
}
//遍历集合:ListIterator
ListIterator listIterator = list_2.listIterator();
while (listIterator.hasNext()){
listIterator.hasPrevious();//是否存在前一个元素
listIterator.next();//下一个
listIterator.previous();//前一个
listIterator.nextIndex();//下一个元素的索引
listIterator.previousIndex();//前一个元素的索引
listIterator.add("aaa");//添加元素
listIterator.set("123");//修改元素
listIterator.remove();//删除元素
}
}
}
4、ArrayList 源码分析
(1)构造方法:
- ArrayList 集合的默认容量为 10。
private static final int DEFAULT_CAPACITY = 10;
- ArrayList 集合内部使用数组来实现
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ArrayList 有三个构造方法,分别是无参的、int类型参数的、和集合类型参数的。
- 无参构造:使用预定义的Object[]类型的数组存放数据
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- int类型参数的构造:使用传入的数字(不为0时),作为数组的初始容量
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);
}
}
- 集合类型参数的构造 :使用传入的集合中的数据初始化数组(传入集合不为空),同时保证数组必须是Object类型的。
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;
}
}
(2)扩容机制:
ArrayList扩容发生在add()方法调用的时候,下面是add()方法的源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
根据意思可以看出ensureCapacityInternal()是用来扩容的,形参为最小扩容量,进入此方法后:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
通过方法calculateCapacity(elementData, minCapacity)获取:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
ensureExplicitCapacity方法可以判断是否需要扩容:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果最小需要空间比elementData的内存空间要大,则需要扩容
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
接下来重点来了,ArrayList扩容的关键方法grow():
private void grow(int minCapacity) {
// overflow-conscious code
//获取到ArrayList中elementData数组的内存空间长度
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);
// 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
// 并将elementData的数据复制到新的内存空间
elementData = Arrays.copyOf(elementData, newCapacity);
}
数组在进行扩容时会进行大量的数组的复制操作,调用了 System.arraycopy() 方法,
System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
而,System.arraycopy() 是一个本地方法,因此,复制效率还是相当高的。
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
五、LinkList
通过链表实现的集合,存储结构:循环双向链表。
LinkedList链表由一系列表项(节点)连接而成。一个表项总是包含3个部分:元素内容,前驱表和后驱表,如图所示:
在下图展示了一个包含3个元素的 LinkedList 的各个表项间的连接关系。在 JDK 的实现中,无论 LikedList 是否为空,链表内部都有一个 header 表项,它既表示链表的开始,也表示链表的结尾。表项 header 的后驱表项便是链表中第一个元素,表项header 的前驱表项便是链表中最后一个元素。
LInkedList 类的声明:
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 继承了 AbstractSequentialList 抽象类,并实现了 List, Deque(双端队列), Cloneable, Serializable 接口。在LinkedList中有三个transient修饰的变量,分别表示该 LinkedList 的元素个数、头结点和尾节点。
transient关键字
java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。
1、常用方法
LinkedList 中的方法和 ArrayList 中的方法基本类似且用法相同。
2、源码分析
(1)无参构造:
public LinkedList() {
}
(2)有参构造:使用传入的集合的数据初始化此集合
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
(3)add() 方法
public boolean add(E e) {
linkLast(e);
return true;
}
add()方法中调用了 linklast() 方法,该方法在 将创建一个新的节点并将此节点插入到链表的末尾。
linklast()方法
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++;
}
该方法先获取链表的最后一个节点,然后创建一个新的节点,将这个新节点插入到链表中;把当前的最后一个节点作为新节点的前一个节点,把新节点的后一个节点设为 null 。然后更新最后一个节点为新插入的节点。
相似的,还有 linkfirst() 方法。
linkfirst() 方法
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
该方法首先获取当前链表的头结点,作为新创建的节点的下一个结点,并将新创建的节点加入到链表中,然后更新头结点。
六、ArrayList 和 LinkedList 比较
ArrayList 集合适用于 查找比较频繁的情况,因为 ArrayList 底层是用数组实现的,查找效率很高,不适用于增加和删除操作比较频繁的情况,因为数组在进行增加和删除时需要移动元素,效率不高。
LinkedList 不适用于查找比较频繁的情况,因为LinkedList 底层使用链表实现,链表的查询操作每次都需要从根结点开始逐个进行遍历;而适用于增加和删除操作比较频繁的情况,因为链表的增加和删除不需要移动元素,效率较高。