Java集合
集合类存放于 Java.util 包中,主要有 3 种:set(集)、list(列表包含 Queue)和 map(映射)。
- Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
- Iterator:迭代器,可以通过迭代器遍历集合中的数据
- Map:是映射表的基础接口
先来说说Collection接口中的集合
List(对付顺序的好帮手)
Java 的 List 是非常常用的数据类型。List 是有序的 Collection。Java List 最常见的实现类:
分别是 ArrayList、Vector 和 LinkedList。
特点:存储的元素是有序的、可重复的。
ArrayList(数组)
ArrayList是最常用的List实现类。
-
线程安全:ArrayList是不同步的,因此线程不安全。
-
底层数据结构:使用Object数组实现。
-
插入和删除是否受元素位置影响:ArrayList采用数组存储数据,做插入和删除元素操作时受元素位置的影响。
例如:数组长度为n,如果在数组i的位置做插入或删除操作时,需要将插入i位置之后的元素都向后位移一位,或者将删除的元素i之后的元素都向前位移一位。
-
随机访问:支持对元素进行快速随机访问。
-
内存占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留⼀定的容量空间。
ArrayList的扩容机制
了解一下ArrayList的内部构成
//举出一部分扩容的重点
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//默认容量位10
private static final int DEFAULT_CAPACITY = 10;
//定一个空数组
private static final Object[] EMPTY_ELEMENTDATA = {
};
//默认实例化一个空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
//存放在数组中的元素,不参与序列化
transient Object[] elementData;
//数组的长度,此参数是数组中实际的参数,区别于elementData.length
private int size;
//ArrayList中一共有三个构造函数
//默认无参构造函数,定义出的数组是一个空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//用户指定数组大小的构造函数
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//创建数组大小为initialCapacity的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//创建一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//包含特定集合元素的构造函数
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)
//传入的集合转换为数组,然后通过Arrays.copyOf方法把集合中的元素拷贝到elementData中
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 创建空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
}
从创建一个ArrayList数组开始
List<Integer> integers = new ArrayList<>();
//此时创建的数组的长度为0,也就是一个空数组
integers.add(1);
//当向integers数组中加入第一个元素时,此时ArrayList的扩容就已经开始了。
机制:
//ArrayList中调用add方法的源码
public boolean add(E e) {
//在向数组中加入新元素时,先判断这个数组的容量够不够装下下一个,因此调用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//此时加入第一个元素1到数组中,此时size = 0 ,size+1 =1 ,也就是minCapacity = 1.
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//此时minCapacity = 1,而DEFAULT_CAPACITY =10,因此最后minCapacity = 10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//此时elementData 为空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//通过比较默认容量(10)和最小扩容量,选取较大的一方,作为扩容的标准
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//判断是否需要扩容
//此时,minCapacity = 10,数组还是空数组,因此elementData.length = 0,需要进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//判断是否需要进行扩容
if (minCapacity - elementData.length > 0)
//扩容的方法(核心)
grow(minCapacity);
}
//扩容方法
private void grow(int minCapacity) {
//oldCapacity :旧容量
int oldCapacity = elementData.length;
//newCapacity:新容量
//oldCapacity >> 1:算数右移,右移一位相当于除2操作,右移n位相当于除2的n次方,(如果该数为正,则高位补0,若为负数,则高位补1;)
//十进制转成二进制进行位运算要比普通十进制除法运算要快的多.
//这也是为什么ArrayList每次都是扩容1.5倍的关键。
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新的扩容容量比最小需求容量还要小
if (newCapacity - minCapacity < 0)
//就把最小需求容量赋给新容量
newCapacity = minCapacity;
//判断新容量是否大于集合的最大容量(一般大不了)
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将扩容好的数组重新赋值
elementData = Arrays.copyOf(elementData, newCapacity);
}
//此时,minCapacity = 0 ,oldCapacity =elementData.length = 0
//因此newCapacity = 0 ,所以 newCapacity = minCapacity = 10;
//完成了ArrayList的一次扩容,从0扩容到10 。
//一值不断向数组加入新的元素,当加入第11个元素时,ArrayList就需要进行扩容,
//minCapacity = 11 ,oldCapacity =elementData.length = 10,
//newCapacity = 10 +(10 >> 1) = 10 + 5 = 15;
//最后完成扩容的数组的容量从10变成了15,满足了下一次的新数组的加入
LinkList(链表)
-
线程安全:LinkList是不同步的,因此线程不安全。
-
底层数据结构:使用双向链表 数据结构实现。(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
-
插入和删除是否受元素位置影响:对于 add(E e) ⽅法的插⼊,删除元素时间复杂度不受元素