集合之ArrayList篇
简介:
ArrayList就是动态数组,实现了List,RandomAccess(快速随机访问),Cloneable(克隆),Serializable(序列化)接口。
public classArrayList<E>
extendsAbstractList<E>
implementsList<E>, RandomAccess, Cloneable, Serializable
ArrayList使用方法及源码解析
1. ArrayList的构造方法:
|
通过上面的构造方法可以很清楚的了解,默认情况下,ArrayList会生成一个Object类型的数组;也可以使用第一个构造方法来初始化数组的大小;还可以向ArrayList中传入一个Collection类型的容器,将容器数组化并赋值给这个ArrayList。
举例说明:
package com.boco;
import java.util.ArrayList;
public class Test12 {
public static void main(String[] args) {
ArrayList list1 = new ArrayList();
list1.add("abcd1");
ArrayList list2 = new ArrayList(12);
list2.add("abcd2");
ArrayList list3 = new ArrayList(list1);
list3.add("abcd3");
// 后面会讲这个forEach的用法,这里先不讲解
list3.forEach(System.out::println);
}
}
输出:
abcd1
abcd3
add方法
add方法主要有四种:
add(Object e):再末尾添加一个元素
add(int index,Object element):在指定位置添加一个元素
addAll(int index , Collection c):在指定位置添加Collection元素,原位置的元素后移
addAll( Collection c):将Collection元素添加到集合的尾部
2. add(Ee) 方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 增加 modCount!!
elementData[size++] = e;
return true;
}
首先,第一步进行增量操作,让后进入ensureCapacityInternal()方法,该方法主要是判断是否要扩容
|
再看calculateCapacity() 方法,返回值为DEFAULT_CAPACITY(长度是10)和size+1大的值;
private staticint calculateCapacity(Object[] elementData, int minCapacity) {
//如果说数组没有长度的话,需要进行长度赋值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
在回头看ensureExplicitCapacity()这个方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount++,修改次数加1,(主要作用是迭代器遍历初始时会得到这个数,如果遍历时候这个数改变了,就会抛异常,这就是快速失败策略),之后比较新增之后的长度和现有数组长度,当新增长度大的时候就应该进行扩容操作,咱们接着看grow()方法
|
首先,数组会扩容原长度的1.5倍,再比较扩容后的长度和上面返回的值(取默认长度10和size+1中大的)比较,取大的值①的代码;如果长度大于Integer.MAX_VALUE – 8 抛异常②处代码。之后进行数组拷贝,new一个新的长度的数组,赋值。由于数组扩容时候也会浪费一定的时间,所以如果可以确定数组长度可以直接赋值1.5倍的长度。
Arrays的copyOf()方法传回的数组是新的数组对象,所以您改变传回数组中的元素值,也不会影响原来的数组。第二个变量指定要建立的新数组长度。
3. add(intindex, E element)方法
public void add(intindex, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
首先,rangeCheckForAdd(index) 进行检查index是否大于数组长度或者小于0,是抛异常;
其次,ensureCapacityInternal(size+ 1);判断数组是否需要扩容;
最后,复制index位置之后的元素到index+1处,把element插入到index位置,数组长度加1。
4. addAll(intindex , Collection c)
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
这里面主要的两句
System.arraycopy(elementData, index,elementData, index + numNew,numMoved);
如果不是在末尾插入,先把原数组index位置之后的数据复制到index+Collection.size 长度之后。
System.arraycopy(a, 0, elementData, index,numNew);
把Collection里的元素复制到index位置
5. remove(intindex)
删除指定位置的元素
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;
}
先判断index是否小于0或大于集合长度;
之后判断是否是数组最后一位,不是,复制数组index之后的元素到index位置,最后一位置为null,返回删除元素的值;
6. remove(Objecto)
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;
}
原理:
判断传入的元素是否是null,是的话遍历数组,找到null删除,调用fastRemove方法复制index之后的元素到index位置;
不是的话遍历数组,找到和o相等的元素,删除方式同上,如果找不到返回false。
7. removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
首先对集合中的对象数组进行了一次复制,然后将数组中的元素依次与collection进行对比。try{}块的结果就是得到了一个数组,数组前w位元素或与collection相同(complement=true的时候)或不同(complement=false)。
下面来看finally,先来看第二个if{}代码块,它的作用就是把数组中w以后的元素全部变为null值,让gc来回收。
现在回到finally的第一个if中,看条件(r != size),似乎永远不会满足这个条件吧。上面的for循环一直r++啊,可是别忘了,c.contains(elementData[r])这句话是有可能抛出异常的,如果一旦类型不匹配,就会抛出异常 进入finally中。
这个方法,如果没有删除任何数据,那么将会返回false。
8. set(intindex ,Object element)
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
set方法比较简单,主要是修改数组某个长度的元素,代码也比较简单,这里不再多说。
9. get(intindex)
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
get方法查找集合某个位置的元素,也比较简单,直接定位到数组位置即可。
10. 其他的一些方法
indexoOf(Object o):返回列表中第一次出现指定元素的索引,如果此列表不包含元素,则返回-1;
原理:从0开始遍历数组元素。
lastIndexOf(Object o):返回列表中最后一次出现的指定元素*的索引,如果此列表不包含该元素,则返回-1。
原理:从后往前遍历,同上。
clone():克隆操作,它是浅复制。
toArray():以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组。
subList():该方法返回的原集合的视图,对返回后的值进行操作,原集合也会改变(后面我们在说这个);生成子列表之后,对原列表进行操作,子列表视图会报异常,所以最好把原列表通过Collections.unmodifiableList方法设成只读状态
11. 遍历ArrayList方式
主要有3种,for循环,foreach,Iterator方式
|
12. 其他注意事项
Arrays.asList(data) 返回值为Arrays工具类中的一个内部类,是一个静私有的内部类,没有添加,删除的方法,只能读操作。
Comparable接口可以作为实现类的默认排序法,Comparator接口是一个类的扩展排序工具。
前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。可以说前者属于 “静态绑定”,而后者可以 “动态绑定”
ArrayList线程不安全,可以使用Collections.synchronizedList(newArrayList());解决多线程并发访问集合时候线程安全问题。
利用Iterator遍历集合时候不要对集合进行修改,原因是集合中有个参数modCount,这个参数是记录集合操作次数的如修改,删除,当遍历时候会判断这个参数和最开始遍历时候的值是否相等,不相等直接抛异常(hashNext==true啥时候才进行判断)。
ArrayList实现了RandomAccess接口(随机存取接口),标志ArrayList是一个可以随机存取的列表,RandomAccess和Cloneable,Serializable一样,都是标志性接口,不需要任何的实现,只是表明具有某种特质。
Java中的foreach是Iterator的变形用法,对于ArrayList而言,需要创建一个迭代器容器,屏蔽内部细节,对外提供hasNext,next等方法 ,ArrayList实现RandomAccess接口,表明元素之间没有联系,但迭代器需要建立一个强制互相知晓的关系,如上一个元素可以判断是否还有下一个元素,这就是foreach遍历耗时原因,所以ArrayList直接用get(i)遍历最快,而LinkedList用foreach遍历较好。
总结:
1. ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
2. ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
3. ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。
4. 由于是数组结构,所以ArrayList具有增删慢,查找快的特点。
Java中ArrayList和LinkedList区别
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据
Serializable作用?
- 想把的内存中的对象状态保存到一个文件中或者数据库中时候
- 想把对象通过网络进行传播的时候