容器的概念
在Java当中,如果有一个类专门用来存放其它类的对象,这个类就叫做容器,或者就叫做集合,集合就是将若干性质相同或相近的类对象组合在一起而形成的一个整体。
容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。
容器与数组的关系
之所以需要容器:
1、数组的长度难以扩充
2、数组中数据的类型必须相同
容器与数组的区别与联系:
1、容器不是数组,不能通过下标的方式访问容器中的元素
2、数组的所有功能通过Arraylist容器都可以实现,只是实现的方式不同
3、如果非要将容器当做一个数组来使用,通过toArray方法返回的就是一个数组
容器常用的几个方法
boolean add(Object obj):向容器中添加指定的元素
Iterator iterator():返回能够遍历当前集合中所有元素的迭代器
Object[] toArray():返回包含此容器中所有元素的数组。
Object get(int index):获取下标为index的那个元素
Object remove(int index):删除下标为index的那个元素
Object set(int index,Object element):将下标为index的那个元素置为element
Object add(int index,Object element):在下标为index的位置添加一个对象element
Object put(Object key,Object value):向容器中添加指定的元素
Object get(Object key):获取关键字为key的那个对象
int size():返回容器中的元素数
Iterable 接口
Iterable是一个超级接口,被Collection所继承。它只有一个方法: Iterator<T> iterator() //即返回一个迭代器
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为轻量级对象,因为创建它的代价小。
Java中的Iterator
功能比较简单,并且只能单向移动:
(1) 使用方法iterator()
要求容器返回一个Iterator
。第一次调用Iterator的next()
方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
(2) 使用next()
获得序列中的下一个元素。
(3) 使用hasNext()
检查序列中是否还有元素。
(4) 使用remove()
将迭代器新返回的元素删除。
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
//迭代器举例
Iterator iter = l.iterator();
while(iter.hasNext()){
System.out.println((String)iter.next());
}
Collection 接口
文档中写道,JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set 和 List)实现。也就是一般不会直接使用Collection
,而是会使用它的子类,如List或Set。
不同的Collection子类对于有序性、重复性、null、线程同步都有不同的策略。下面就其具体的子类来进行介绍。
List 接口
-
List是有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
-
用户插入的顺序或者指定的位置就是元素插入的位置。它与Set不同,List允许插入重复的值。
-
List 接口提供了特殊的迭代器,称为
ListIterator
,除了允许 Iterator
接口提供的正常操作外,该迭代器还允许元素插入和替换,以及双向访问。还提供了一个方法(如下)来获取从列表中指定位置开始的列表迭代器。
java ListIterator <E> listIterator(int index)
返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始 -
List 接口提供了两种搜索指定对象的方法。从性能的观点来看,应该小心使用这些方法。在很多实现中,它们将执行高开销的线性搜索。
-
List 接口提供了两种在列表的任意位置高效插入和移除多个元素的方法。
List的子类
List最流行的实现类有Vector、ArrayList、LinkedList。三者的区别将在下文提及。
1.1) ArrayList (类)
源码分析:
- 概览
因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess
接口标识着该类支持快速随机访问。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
数组的默认大小为 10。
private static final int DEFAULT_CAPACITY = 10;
- 扩容
添加元素时使用ensureCapacityInternal()
方法来保证容量足够,如果不够时,需要使用grow()
方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1),也就是旧容量的 1.5
倍。
扩容操作需要调用 Arrays.copyOf()
把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
- 删除元素
需要调用System.arraycopy()
将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
- Fail-Fast
modCount
用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException
。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
- 序列化
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
保存元素的数组 elementData
使用 transient
修饰,该关键字声明数组默认不会被序列化。
transient Object[] elementData; // non-private to simplify nested class access
ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
序列化时需要使用 ObjectOutputStream
的 writeObject()
将对象转换为字节流并输出。而 writeObject()
方法在传入的对象存在 writeObject()
的时候会去反射调用该对象的 writeObject()
来实现序列化。反序列化使用的是 ObjectInputStream
的 readObject()
方法,原理类似。
ArrayList list = new ArrayList();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject</