前言:
集合简介
- 集合定义:
存储数据的容器,是对普通数据结构的封装。 - 集合和其他容器的比较:
数据库:数据可以长时间存储,从而可以使数据反复使用。
数组/集合:项目运行起来使存入数据,一旦项目结束数据就不存在了,起到暂存作用。 - 数组和集合的比较:
- 数组是静态的,集合是动态的。
- 数组是Java内置类型,效率更高,但集合功能更加全面。
- 数组需要指定类型,集合则可有可无。
- 数组既可以使用普通类型,也可以使用引用类型,而集合只能使用引用类型。
- 集合和数组的选择:
对元素的数量有明确判断后续不会有太多复杂操作的时候,选择数组。集合反之。
集合框架库概述(部分)
- Iterator:迭代器遍历集合,可以从前向后遍历。
- ListIterator:是Iterator的子接口,可以进行双向遍历。
- Collection:是存放一组单值的最顶层的接口。
- List:是Collection的子接口,对Collection进行了大量扩充,允许重复和空值,并且是有序的。
- Set:是Collection的子接口,没有对Collection进行扩充,不允许重复。SortedSet:单值的排序接口。
- Queue:是Collection的子接口,具有先入先出的特点。
- Map:是存放一对值得最顶层的接口,即接口中的每个元素都是以key—value的键值对形式保存到的。
- SortedMap:Map的子接口,存放键值对的排序接口。
所有的接口要么是Map接口下的,要么是Collection接口下的。因此要么存放单值,要么存放双值。
一、ArrayList
底层
采用数组结构
继承的接口
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- List:存放单值。允许重复和空值并且有序。
- RandomAccess:表示ArrayList可以被随机访问。
- Cloneable:集合可以使用clone方法。
- Serializable:可序列化,表示ArrayList可以进行序列化操作。
方法
public static void main(String[] args) {
ArrayList<Integer> arr1 = new ArrayList<>();
//增
arr1.add(1);
arr1.add(1,8);//下标,插入的值
//遍历
Iterator<Integer> iterator = arr1.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next()+" ");
}
//遍历
ListIterator<Integer> listIterator = arr1.listIterator();
while(listIterator.hasNext()){
System.out.print(listIterator.next()+" ");
}
//删
arr1.remove(1);//按下标
Integer inc = 3;
arr2.remove(inc);//按元素
//改
arr1.set(1,10);
//子集
List<Integer> arr1 = arr.subList(1,3);//[1,3)
//并集
arr4.addAll(arr3);//将arr3中的全部元素插入到arr4最后
arr4.addAll(1,arr3);//将arr3的所有元素插入到arr4的该下标后
//差集
arr4.removeAll(arr3);//将arr4中所有arr3中的元素删除
//求不重复并集
ArrayList<Integer> arr5 = new ArrayList<>();
arr5.add(1);arr5.add(2);arr5.add(3);
ArrayList<Integer> arr6 = new ArrayList<>();
arr6.add(3);arr6.add(4);arr6.add(5);
ArrayList<Integer> arr7 = new ArrayList<>();
arr7.addAll(arr5);arr7.addAll(arr6);//1 2 3 3 4 5
arr6.retainAll(arr5);//3 //交集
arr7.removeAll(arr6);//1 2 4 5 //差集
arr7.addAll(arr6);//1 2 4 5 3
for (Object a:arr7) {
System.out.print(a+" ");
}
}
源码分析
构造器
public ArrayList(int initialCapacity) {//有参构造
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {//无参构造,默认为空数组
super();
this.elementData = EMPTY_ELEMENTDATA;
}
grow方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length; //原数组大小
int newCapacity = oldCapacity + (oldCapacity >> 1);//新数组大小
if (newCapacity - minCapacity < 0) //第一次扩容时将数组大小定为10
newCapacity = minCapacity; //10
if (newCapacity - MAX_ARRAY_SIZE > 0)//数组容量非常庞大的时候判断新数组大小是否超过默认容量上限
newCapacity = hugeCapacity(minCapacity);//如果超过将最大容量返回回来
elementData = Arrays.copyOf(elementData, newCapacity);//扩容
}
总结
ArrayList在创建对象时默认为空数组,添加数据时才开始扩容。第一次扩容为10,其后1.5倍扩容。
优点:适合做随机访问。
缺点:不适合做随机的插入删除,会有大量的数据移动。
二、LinkedList
底层
采用双向链表结构
继承的接口
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- List:存放单值。允许重复和空值并且有序。
- Deque:queue的子接口,双端队列接口。
- Cloneable:集合可以使用clone方法。
- Serializable:可序列化,表示ArrayList可以进行序列化操作。
方法
public static void main(String[] args){
LinkedList<Integer> linkedList = new LinkedList();
//增
linkedList.add(1);//尾插
linkedList.add(1, 2)//下标,元素
linkedList.addFirst(3)//头插
linkedList.push(4)//当用链式结构实现栈时,进栈
//删
linkedList.removeFirst()//头删
linkedList.remove(1)//删除1元素
linkedList.remove(1)//下标
linkedList.pop()//当用链式结构实现栈时,出栈操作
//改
linkedList.set(1,10)//下标,元素
//查
linkedList.peekFirst()//查询第一个
linkedList.peekLast()//查询最后一个
linkedList.get(1)//查询1号位置的元素
}
源码分析
构造器
//无参构造:链表的添加不需要开辟空间
public LinkedList() {
}
//有参构造:将集合传入
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
删除操作
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) { //前驱为空
first = next;
} else {
prev.next = next;
x.prev = null;//gc
}
if (next == null) {//后继为空
last = prev;
} else {
next.prev = prev;
x.next = null;//gc
}
x.item = null;//gc
size--;
modCount++;//快速失败机制
return element;
}
总结
LinkedList没有容量这一说,更不需要扩容。
LinkedList的优点:插入删除操作不需要数据移动,适合时常需要数据更新的情况。
缺点:不适合做随机访问,需要遍历链表。
三、Vector
继承的接口
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
与ArrayList一致。
方法
Vector集合中增删改查,遍历方法,迭代器与ArrayList相同。
源码分析
构造器
//双惨构造函数 (增长因子)
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
//调用双参构造器,增长因子默认为0
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
//无参构造,默认初始容量为10
public Vector() {
this(10);
}
grow方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//增长因子大于0时,按照增长因子扩容,否则按照两倍扩容
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
//当初始容量为0,增长因子为-1时,为了防止扩容后容量还是0,所以添加的条件,新容量不能小于最小应该扩的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);//如果超过将最大容量返回回来
elementData = Arrays.copyOf(elementData, newCapacity);//扩容
}
总结
ArrayList 和LinkedList 的异同
- 从继承的接口来看:
ArrayList比 LinkedList多实现一个RandomAccess,用来表明其支持随机访问。LinkedList比ArrayList多实现一个Deque,具有队列先入先出的特点。此接口的子类可以实现队列操作。 - 底层实现上来看
- ArrayList底层用数组实现,LinkedList底层用双链表来实现。
因为数组的访问只需要给定下标,就可以立即访问,而链表需要遍历,遍历的次数即为下标值。 - ArrayList 的 get() ,set()方法相对于LinkedList效率要高,ArrayList的查询以及获取元素,效率更高。
- 数组随机添加一个元素,指定下标的元素以及后面的所有元素,都需要向后移动,开销很大,而链表只需要将指定位置下标的上一个的节点确定,改变引用即可,开销很小。linkedList的增删效率更高。
- 使用场景
如果需要增删频繁,则使用LinkedList;
如果需要查询操作频繁,则使用ArrayList;
ArrayList 和Vector的异同
- 从继承的接口来看:
实现的接口相同,特点相同 - 底层实现上来看
都是数组实现,调用方法没有区别 - 从源码上来看
- 构造方法
ArrayList的初始数组大小为空
Vector的初始数组大小为10 - 扩容方法:
ArrayList 以1.5倍扩容,如果目前元素时1000,再增加1个,则要1.5倍扩容,会造成499个空间浪费。
Vector默认2倍扩容,如果增长因子大于零,则扩容增长因子个大小。 - 线程安全问题:
Vector:方法都用了Synchronized 来修饰,是线程安全的。
ArrayList :没有加锁是线程不安全的。
- 使用场景
如果需要考虑线程安全问题,则选择Vector
如果不考虑线程安全问题,则选择ArrayList
因为加锁操作非常耗时,所以不考虑线程安全问题时不使用Vector。