3.2.7.1 请用ArrayList实现Stack以及Queue的功能。
public class ArrayListStack extends ArrayList implements Stack {
ArrayList arrayList =new ArrayList<>() ;
public void push(T obj) {
arrayList.add(obj);
}
public T pop() {
return arrayList.remove(arrayList.size()-1);
}
public int size(){
return arrayList.size();
}
}
3.2.7.2 如果让你实现Java的ArrayList,你需要考虑哪些要素?
ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。
每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection extends E> c)、addAll(int index, Collection extends E> c)这些添加元素的方法。
每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
Fail-Fast机制:
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
3.2.7.3 请通过Iterator对象访问LinkedList对象,并说明这种访问方式的好处。
LinkedList list = new LinkedList();
//省略赋值
ListIterator it = list.listIterator();
while(it.hasNext()) {
System.out.println(it.next().toString()); //使用
}
好处是,能用统一的方式来访问集合对象,这也是迭代器模式的好处。
3.2.7.4 你有没有读过ArrayList部分的底层实现源代码?如果有,请说明下其中的add方法是如何实现的?尤其请考虑动态扩展的情况。
如下是扩容的底层方法
/**
* 增加ArrayList容量。
*
* @param minCapacity 想要的最小容量
*/
public void ensureCapacity(int minCapacity) {
// 如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,最小扩容量为DEFAULT_CAPACITY,否则为0
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? 0: DEFAULT_CAPACITY;
//如果想要的最小容量大于最小扩容量,则使用想要的最小容量。
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
/**
* 数组容量检查,不够时则进行扩容,只供类内部使用。
*
* @param minCapacity 想要的最小容量
*/
private void ensureCapacityInternal(int minCapacity) {
// 若elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则取minCapacity为DEFAULT_CAPACITY和参数minCapacity 之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/**
* 数组容量检查,不够时则进行扩容,只供类内部使用
*
* @param minCapacity 想要的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 确保指定的最小容量 > 数组缓冲区当前的长度
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
/**
* 分派给arrays的最大容量
* 为什么要减去8呢?
* 因为某些VM会在数组中保留一些头字,尝试分配这个最大存储容量,可能会导致array容量大于VM的limit,最终导致OutOfMemoryError。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 扩容,保证ArrayList至少能存储minCapacity个元素
* 第一次扩容,逻辑为newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基础上增加一半。第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。
*
* @param minCapacity 想要的最小容量
*/
private void grow(int minCapacity) {
// 获取当前数组的容量
int oldCapacity = elementData.length;
// 扩容。新的容量=当前容量+当前容量/2.即将当前容量增加一半。
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的容量还是小于想要的最小容量
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);
}
/**
* 进行大容量分配
*/
private static int hugeCapacity(int minCapacity) {
//如果minCapacity<0,抛出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果想要的容量大于MAX_ARRAY_SIZE,则分配Integer.MAX_VALUE,否则分配MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}
3.2.7.5 请说下Collection和Collections的差别以及各自的用途。
Collections 是一个集合的一个类,其中包含有一些和集合操作相关的静态多态方法。Jave集合里则有另外一个和它非常相似的接口
Collection(不带s),它是线性表类集合的父接口,List和Set等接口都是通过实现这个接口来实现的。
3.2.7.6 我们知道Set对象里不能有重复的元素,请说下是用什么方法来判断是否重复?是通过equals方法吗?
请参与本书3.2.3 Set集合是如何判断重复,里面有详细的描述