数组实现的,其封装的各种方法:Add、remove、get、set等,其本质就是对数组的基本操作。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList继承自AbstractList类,实现了RandomAccess、Cloneable、Serializable接口。其实查看RandomAccess接口:
public interface RandomAccess {
}
标识,并不是说因为ArrayList实现了随机访问接口才具有随机访问功能。
较为重要的两个属性:
//用于存储元素
transient Object[] elementData;
//ArrayList的大小
private int size;
ArrayList就是Object数组,与一般的数组的区别就是:
1. ArrayList存储对象可以是任意类型的元素,例如一个类的对象;而一般的数组,如 int[] a,只能是存储int类型的元素。
2. 一般的数组大小固定,而ArrayList支持动态扩展,可以在Add 元素的过程中自动增加。
ArrayList的三个构造方法:
//指定元素个数的构造
//指定大小若为0,集合为空
//指定大小若小于0,则抛出异常
//否则正常构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//无参构造,默认集合大小为10,元素为空
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//Collection的构造,以集合c为基础构造ArrayList对象,将c中的元素复制给新的ArrayList
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)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
一些常调用的方法:
Add(往list中添加元素):
//将新元素添加在末尾
public boolean add(E e) {
//判断数组大小是否充足,不够的话进行扩充
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//将新元素添加到指定位置
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//进行大量元素移动操作适合使用System.arraycopy,其底层调用C语言的元素移动的函数
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
与一般数组添加元素操作时一样,先判断一下数组大小是否足够,然后插入到相应位置。如若插入到数组末尾,只需将元素放入后,size+1;如若插入到指定位置,需要将其后到元素都往后挪一个位置后再插入当前元素。由此可以知道,ArrayList做插入、删除时,时间复杂度为O(n),不如LinkedList,这就是基本的数组与链表之间的优缺点。
remove(删除元素):
//删除指定下标元素并返回
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;
}
// 删除指定对象元素
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//自我调用方法,快速删除
private void fastRemove(int index) {
modCount++;
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
}
类比于一般数组,要删除一个指定下标元素时,只需将其后的元素依次往前移动一个位置,让最后一个元素为空;要删除指定元素时,需要从头到尾便利一遍,删除与之相等到元素,删除操作也是将其后元素依次往前移动一个位置。这里还写了一个fastRemove,与remove相比只是少了越界判断,因为在调用fastRemove方法时已经确保了一定存在,不会发生越界异常。
其他方法类比于一般数组思考即可!!!
动态增长容量的函数:
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);
}
newCapacity = oldCapacity + (oldCapacity >> 1); 将空间扩展到原大小的1.5倍。其扩展原理就是重新开辟一块newCapacity大小的存储空间,将原elementData中的元素复制到新的空间中,elementData指向新的空间地址。这和它底层数据结构有关,我们都知道数组是顺序存储的,首先要确保有足够的连续存储单元。此处和链表就不同了,链表不要求顺序存储,只是需要一个指针指向下一个存储单元,当然,由于指针的存储,链表存储所浪费的存储单元就多了。
一些常见问题:
Arraylist 与 LinkedList 的区别:
其本质就是数据结构中 顺序存储 和 链式存储 之间的关系。
1. 底层数据结构:ArrayList底层是Object数组;LinkedList 底层是双向链表数据结构。
2. 插入、删除时间复杂度:ArrayList底层是数组,插入、删除操作时间复杂度受元素位置的影响,如若插入到末尾add(E e)时间复杂度为O(1),如若插入到指定位置add(int index, E element)时间复杂度为O(n),因为需要大量的移动元素; 而LinkedList底层是链表,所以插入、删除时间复杂度不受元素位置的影响,时间复杂度为O(1)。
3. ArrayList支持随机访问,通过下标就能访问元素,时间复杂度为O(1);而LinkedList需要从头到尾遍历一遍链表才能找到元素位置,时间复杂度为O(n)。
4. ArrayList空间不足时支持动态扩展,扩大大原来的1.5倍;而LinkedList没有扩展的概念。
5. 内存空间使用情况:ArrayList会在末尾预留一定的空间,造成空间浪费;而LinkedList每个节点都需要存储其前趋和后继指针,所浪费的空间多于ArrayList。
6. 同: Arraylist 和 LinkedList 都不保证线程安全。(线程安全:学过操作系统都知道,多线程同步可能会造成死锁,Arraylist和LinkedList并没有提供避免死锁的机制。)
ArrayList 与 Vector 区别 :
1. ArrayList和Vector其实质都是Object数组,空间不足时都支持动态增长,ArrayList增长为原来的1.5倍,而Vector增长为原来的2倍。
2. ArrayList不保证线程安全;而Vector提供同步机制,保证线程安全,但时间开销大,因为每次加锁解锁需要耗费时间。
3. Vector现在已经不常用了。
ArrayList常用三种遍历方法:
1. 普通的for循环。调用ArrayList.get方法,for循环依次读出。
2. foreach方法。
//没有用泛型,需要进行强制类型转化
//通过for each方法访问集合元素
//Object取出遍历对象
public void testForEach() {
for(Object obj : studentList) {
Student stu = (Student) obj;
System.out.println(stu.id+":"+stu.name);
}
}
//泛型 For Each写法:
public void testForEach() {
for(Student stu :students) {
System.out.println(stu.id+":"+ stu.name);
}
}
3.迭代器访问方法。
通过迭代器来遍历List
public void testIterator() {
Iterator it = studentList.iterator();
while (it.hasNext()) {
Student stu = (Student) it.next();
System.out.println(stu.id+":"+stu.name);
}
}
- 实现RandomAccess接口的list,优先选择普通的for循环,其次是foreach。
- 未实现RandomAccess接口的list,优先选择iterator遍历。