讲解之前先讨论一个问题:容器类(集合类)可以存储多个数据,既然数组可以存储,为什么需要定义容器类?
数组的弊端:1)长度是不可变的,一旦数组初始化以后,长度就固定了;
2)若是在多个地方需要存储多个数据,都得特意编写数组的操作方法,如此以来就没有体现DRY原则(Don‘t repeat yourself),即为代码的重用性不高!
为了解决这一弊端,Collection接口就诞生出一个子接口-----List接口,封装一些通用的抽象方法来规范并模拟数组元素的基本操作。注意:任何集合框都包含了三大块内容:对外的接口、接口的实现以及对集合运算的算法(底层都对应某一种数据结构算法)
(一)ArrayList实现类的简单介绍:(由于使用过于频繁,不详解,简单介绍)
ArrayList接口是大小可变数组的实现。实现了所有可选列表操作,并允许包括null在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)
注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:
List list = Collections.synchronizedList(new ArrayList(...));
ArrayList类源代码分析:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
//长度为10的Object数组
private static final int DEFAULT_CAPACITY = 10;
//初始化,新建一个长度为0的Object数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA= {};
//本质上是一个Object数组
transient Object[] elementData;
//初始化创建一个长度为0的Object数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//新创建一个长度为10的Object数组
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//在进行第一次add()添加元素时,新创建一个长度为10的Object数组
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//数组的扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//增加为原来的1.5倍
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);
}
}
(二)Vector实现类详解:
(1)定义:Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector的大小可以根据需要增大或缩小,以适应创建Vector后进行添加或移除项的操作。与新 collection 实现不同,Vector 是同步的(ArrayList是不同步的)!
注意:Vector类底层其实是一个Object数组,Vector类中的方法是支持同步的!并且Vector数组所存储的元素个数大于Vector的容量,就需要扩容,复制操作,一旦扩容,性能就会降低。建议一般使用初始容量!
(2)Vector类存储原理:
查看源代码:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//底层实现也是封装了一个Object数组
protected Object[] elementData;
//数组元素个数
protected int elementCount;
//初始化Vector实例对象,默认创建一个长度为10的Object数组
public Vector() {
this(10);
}
//add()添加元素方法,由于每个方法都添加了synchronized修饰,只展示一个
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
//数组的扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
通过源代码分析,发现在Vector类中有一个Object[]类型的数组:
protected Object[] elementData;
1):表面上把数据存储到Vector对象中,其实底层实现依然是把数据存储到Object数组中的;
2):我们发现该数组中的元素类型是Object类型,意味着集合中只能存储任意类型的对象,集合中只能存储对象,不能存储基本数据类型;
3):集合类中存储的对象,都存储的是对象的引用,而不是对象本身!
Vector类中常用方法:
增加:
boolean add(Object a)//将指定元素添加到该集合的末尾,等价于addElement(Object a)方法;
void add(int index,Object Element)//在指定的位置添加指定的元素;
boolean addAll(Collection c)//把集合c中的元素逐个添加到当前集合中;
删除:
Object remove(int index)//删除指定索引位置的元素,并返回删除之后的元素对象;
boolean remove(Object o)//删除指定的元素;
boolean removeAll(Collection c)//从此集合中移除包含在指定集合c中的所有元素;
boolean retainAll(Collection c)//求两个集合的交集;
修改:
Object set(int index,Object element)//修改当前集合中指定索引位置的元素,返回旧元素;
查询:
int size()//返回集合中的元素个数;
boolean isEmpty()//判断当前集合中元素个数是否为0;
Object get(int index)//获取指定索引处的元素;
Object[] toArray()//将集合中的元素装换为Object数组,本质是提供集合的副本。
重点:ArrayList类和Vector类的区别:
1)在Java7之前,即使使用了new ArrayList()创建对象,一个元素不存在也会初始化一个长度为10的Object数组,浪费空间;并且Vector底层实现和ArrayList等同,一样初始化一个长度为10的Object数组;
2)从Java7开始,ArrayList类使用new ArrayList()创建对象,底层是一个空数组,只是在第一次调用add(Object o)时,才会初始化一个长度为10的Object数组,实现效果会更好一点,防止创建并没有使用,浪费内存空间!
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
3)Vector类中的所有方法都是synchronized关键字修饰的,即多个线程方法同一Vector实例对象是安全的,但是效率很低,速度极慢;
4)ArrayList类中的所有方法非synchronized关键字修饰,但是多线程访问不安全,效率高,速度快;
5)即使在多线程中访问,也不使用Vector类,Collections包装类提供了一个方法,返回一个可同步的线程安全的ArrayList对象:
ArrayList list = Collections.synchronizedList(new ArrayList());
(三)栈(Stack):数据结构的一种,存储特点:先进后出
查看栈的源代码:
public class Stack<E> extends Vector<E> {
//栈的初始化
public Stack() {
}
//向栈中添加元素
public E push(E item) {
//本质上调用Vector中的方法
addElement(item);
return item;
}
//从栈中弹出一个元素
public synchronized E pop() {
E obj;
int len = size();
//调用了该类中获取栈顶的方法
obj = peek();
//本质上调用Vector类中的删除方法
removeElementAt(len - 1);
return obj;
}
//弹出栈顶元素
public synchronized E peek() {
//获取元素总个数
int len = size();
if (len == 0)
throw new EmptyStackException();
//本质调用Vector获取指定索引处的元素
return elementAt(len - 1);
}
//判断栈是否为空
public boolean empty() {
return size() == 0;
}
}
(1)根据Stack的源代码,底层实现是Vector数组形式;
(2)Vector类中的add()等方法是实现了List接口统一规范提供的,而addElement()方法是Vector类特有的方法(原始方法),两者功能等同;
由于此篇幅过长,不易阅读,将在下一篇幅中讲解LinkedList接口,以及手动实现一个双向链表