0.常用接口
0.1 Queue接口
0.1.1 注意事项
队列接口,且不允许null元素,因为队列使用poll()方法是否返回null来判断队列是否为空,但是LinkedList是允许null元素的,因此,当LinkedList作为队列实现时,不应该将null元素插入队列。
0.1.2 常用方法
boolean add(E e);
将对象e插入队列尾部,成功返回true,失败(没有空间)抛出异常IllegalStateException;
boolean offer(E e);
将对象e插入队列尾部,成功返回true,失败(没有空间)返回false;
E remove();
获取并移除队列头部元素,如果队列为空,抛出NoSuchElementException异常;
E poll();
获取并移除队列头部元素,如果队列为空,返回null;
E element();
获取但不移除队列头部元素,如果队列为空,抛出NoSuchElementException异常;
E peek();
获取但不移除队列头部元素,如果队列为空,返回null;
基于LinkedList实现的队列
package com.pichen.basis.col;
import java.util.LinkedList;
import java.util.Queue;
public class LinkListTest {
public static void main(String[] args) {
Queue linkedListQueue = new LinkedList();
//入队
linkedListQueue.offer(3);
linkedListQueue.offer(4);
linkedListQueue.offer(2);
linkedListQueue.offer(1);
//出队
Integer tmp;
while((tmp = linkedListQueue.poll()) != null){
System.out.println(tmp);
}
System.out.println(linkedListQueue.peek());
}
}
0.2 Deque接口
双端队列接口,继承队列接口,支持在队列两端进行入队和出队操作;
除了Collection接口Queue接口中定义的方法外,Deque还包括以下方法
void addFirst(E e);
将对象e插入到双端队列头部,容间不足时,抛出IllegalStateException异常;
void addLast(E e);
将对象e插入到双端队列尾部,容间不足时,抛出IllegalStateException异常;
boolean offerFirst(E e);
将对象e插入到双端队列头部
boolean offerLast(E e);
将对象e插入到双端队列尾部;
E removeFirst();
获取并移除队列第一个元素,队列为空,抛出NoSuchElementException异常;
E removeLast();
获取并移除队列最后一个元素,队列为空,抛出NoSuchElementException异常;
E pollFirst();
获取并移除队列第一个元素,队列为空,返回null;
E pollLast();
获取并移除队列最后一个元素,队列为空,返回null;
E getFirst();
获取队列第一个元素,但不移除,队列为空,抛出NoSuchElementException异常;
E getLast();
获取队列最后一个元素,但不移除,队列为空,抛出NoSuchElementException异常;
E peekFirst();
获取队列第一个元素,队列为空,返回null;
E peekLast();
获取队列最后一个元素,队列为空,返回null;
boolean removeFirstOccurrence(Object o);
移除第一个满足 (o==null ? e==null : o.equals(e)) 的元素
boolean removeLastOccurrence(Object o);
移除最后一个满足 (o==null ? e==null : o.equals(e)) 的元素
void push(E e);
将对象e插入到双端队列头部;
E pop();
移除并返回双端队列的第一个元素
Iterator descendingIterator();
双端队列尾部到头部的一个迭代器;
0.3 AbstractSequentialList类
基于迭代器利用索引index实现数据的随机访问;
public E get(int index)
public E set(int index, E element)
public void add(int index, E element)
public E remove(int index)
public boolean addAll(int index, Collection extends E> c)
1.List
1.1 ArrayList
ArrayList为动态数组,有如下优点:- 动态的增加和减少元素;
- 实现了ICollection和List接口
- 灵活的设置数组的大小
1.1.1 Array
1.1.1.1 Arrays.toString():将int数组转字符串数组
格式:Arrays.toString(数组名)
将数组转化成字符串,此时输出的结果是字符串类型。
int arr[] = {1,2,3,4,5,6};
String newArr = Arrays.toString(arr);
输出:[1, 2, 3, 4, 5, 6]
1.1.1.2 Arrays.copyOf(数组名,扩容后长度) 数组扩容
1.1.1.2.1 格式
格式:Arrays.copyOf(数组名,扩容后长度)
复制全部元素,并指定新数组长度
int[] arr = {3, 7, 2, 1};
int[] newArr = Arrays.copyOf(arr, 10);//指定新数组的长度
复制从n到m(但是不包括m)的元素
//只复制从索引[1]到索引[3]之间的元素,但是不包含索引[3]的元素
int[] newArr2 = Arrays.copyOfRange(arr, 1, 3);
输出:1 2 3 4 0 0 0 0
注意:此方法可以用于扩容,也可以用于缩容,改变其第二个参数即可。
1.1.1.2.2 Arrays.copyOf源码分析
Arrays.copyOf(U[] original, int neLength, Class extends T[]> newType) 根据class类型决定是new还是反射去构造一个泛型数组,同时利用native函数,批量赋值元素至新数组中。
public static T[] copyOf(U[] original, int newLength, Class extends T[]> newT){
@SupperessWarnings("unchecked")
//根据class类型来决定是new还是反射构造一个泛型数组
T[] copy = ((Object)newType == (Object)Object[].class) ? new Object[newLength] :(T[])Array.newInstance(newType.getComponentType(), newLength);
//利用native函数,批量赋值元素至新数组中
System.arrayCopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
1.1.1.3 System.arraycopy()
1.1.1.3.1 格式
格式:System.arraycopy(原数组,原数组起始位置,新数组,新数组起始位置,复制长度)
int arr[] = {1,2,3,4};
int[] arr1 = new int[6];
System.arraycopy(arr, 0, arr1, 1, 3);
输出:0 1 2 3 0 0
1.1.1.3.2 源码
@FastNative
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
1.1.1.4 Arrays.sort()排序
1.1.1.4.1 全数组排序
格式:Arrays.sort(数组名)
注意:只能做升序排序,不能做降序排序。
int arr[] = { 2, 5, -2, 6, -3, 8, 0, -7, -9, 4 };
Arrays.sort(arr);
1.1.1.4.2 部分数组排序
Arrays.sort(arr, 2, 5);
从索引[2]到索引[5]的元素进行排序,包括索引[5]
1.1.1.5 Arrays.fill()填充
格式:Arrays.fill(数组名 ,开始位置 , 结束位置, 填入的值)
int arr[] = {1,2,3,4,5,6,7,8,9,10};
Arrays.fill(arr, 3, 6, 50);
int array[] = new int[6];
Arrays.fill(array, 100);
结果为:
1 2 3 50 50 50 7 8 9 10
100 100 100 100 100 100
1.1.1.6 去重
使用set无序但是无重复项的特性
int[] arr11 = {1, 2, 3, 4,5,6,7,8,9,0,3,2,4,5,6,7,4,32,2,1,1,4,6,3};
Set set = new HashSet<>();
for(int i = 0; i < arr11.length; i++){
set.add(arr11[i]);
}
1.1.1.7 Arrays.equals(arr1, arr2)判定两个数组是否相同
boolean Arrays.equals(arr1, arr2);
1.1.1.8 数组转为list集合
String[] arr = {"1","2","3"};
List list = Arrays.asList(arr);
1.1.1.9 数组转为set
String[] arr = {"1","2","3"};
Set set = new HashSet(Arrays.asList(arr));
1.1.1.10 数组中是否包含一个值
Arrays.asList(arr).contains("1");
1.1.1.11 从array创建arrayList
ArrayList arrayList = new ArrayList(Arrays.asList(arr));
1.1.2 构造方法
public ArrayList(){
this(10);
//调用ArrayList(10) 默认初始化一个大小为10的object数组
}
public ArrayList(int initialCapacity){
if(initialCapacity < 0){
throw new IllegalArgumentException("IllegalCapacity:" + initialCapacity);
}
//如果用户初始化大小小于0抛异常,否则新建一个用户初始值大小的object数组
this.elementData = new Object[initialCapacity];
}
public ArrayList(Collection extends E> c){
elementData = c.toArray();
size = elementData.length;
// 当c.toArray返回的不是object类型的数组时,进行下面转化
if(elementData.getClass() != Object[].class){
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
}
由上面三种构造方法可知,默认情况下使用ArrayList会生成一个大小为10的Object类型的数组。也可以调用ArrayList(int initialCapacity) 来初始化Object数组的大小。并且用户可以往ArrayList中传入一个容器只要这个容器是Collection类型的。调用ArrayList(Collection extends E> c)接口的时候会将容器数组化处理并将这个数组值赋给Object数组。
1.1.3 ArrayList的线程安全性
1.1.3.1 线程不安全
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable{
/**
* 列表元素集合数组
* 如果新建ArrayList对象时没有指定大小,那么会将EMPTY_ELEMENTDATA赋值给elementData,
* 并在第一次添加元素时,将列表容量设置为DEFAULT_CAPACITY
*/
transient Object[] elementData;
//列表大小,elementData的存储的元素个数
private int size;
}
ArrayList的实现主要就是用了一个Object的数组,用来保存所有的元素,以及一个size变量用来保存当前数组中已经添加了多少元素。
public boolean add(E e){
/**
* 添加一个元素时,做了如下两步操作
* 1.判断列表的capacity容量是否足够,是否需要扩容
* 2.真正将元素放在列表的元素数组里面
*/
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
ensureCapacityInternal()的作用就是判断如果将当前的新元素加到列表后面,列表的elementData数组的大小是否满足,如果size + 1的这个需求长度大于了elementData这个数组的长度,那么就要对这个数组进行扩容。
由此看到add元素时,实际做了两个大的步骤: - 判断elementData数组容量是否满足需求 - 在elementData对应位置上设置值
这样也就出现了第一个导致线程不安全的隐患,在多个线程进行add操作时可能会导致elementData数组越界。具体逻辑如下:- 列表大小为9,即size=9
- 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。
- 线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。
- 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。
- 线程B也发现需求大小为10,也可以容纳,返回。
- 线程A开始进行设置值操作, elementData[size++] = e 操作。此时size变为10。
- 线程B也开始进行设置值操作,它尝试设置elementData[10] = e,而elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException.
另外第二步 elementData[size++] = e 设置值的操作同样会导致线程不安全。从这儿可以看出,这步操作也不是一个原子操作,它由如下两步操作构成:- elementData[size] = e;
- size = size + 1;
在单线程执行这两条代码时没有任何问题,但是当多线程环境下执行时,可能就会发生一个线程的值覆盖另一个线程添加的值,具体逻辑如下:- 列表大小为0,即size=0
- 线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。
- 接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。
- 线程A开始将size的值增加为1 - 线程B开始将size的值增加为2
这样线程AB执行完毕后,理想中情况为size为2,elementData下标0的位置为A,下标1的位置为B。而实际情况变成了size为2,elementData下标为0的位置变成了B,下标1的位置上什么都没有。并且后续除非使用set方法修改此位置的值,否则将一直为null,因为size为2,添加元素时会从下标为2的位置上开始。
1.1.3.2 如何实现线程安全
1.1.3.2.1 synchronized关键字
在程序上加锁
1.1.3.2.2 Collections.synchronizedList()方法
假设代码为
List> data = new ArrayList<>();
为了实现线程安全,可以使用
List> data = Collections.synchronizedList(new ArrayList<>());
返回的就是一个线程安全的ArrayList类;
1.1.3.2.3 CopyOnWriteArrayList类
CopyOnWriteArrayList类所有可变操作(add, set等)都是通过对底层数组进行一次新的复制来完成的,因为在遍历时不会做任何修改,因此绝对不会抛出ConcurrentModificationException异常。
eventbus的订阅方法subscribe()里面,就采用了线程较为安全的CopyOnWriteArrayList集合
private final Map, CopyOnWriteArrayList> subscriptionsByEventType;
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
1.1.4 源码
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
//默认的初始容量为10
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
// ArrayList中实际数据的数量
private int size;
public ArrayList(int initialCapacity) //带初始容量大小的构造函数
{
if (initialCapacity > 0) //初始容量大于0,实例化数组
{
this.elementData = new Object[initialCapacity];
}
else if (initialCapacity == 0) //初始化等于0,将空数组赋给elementData
{
this.elementData = EMPTY_ELEMENTDATA;
}
else //初始容量小于,抛异常
{
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
public ArrayList() //无参构造函数,默认容量为10
{
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection extends E> c) //创建一个包含collection的ArrayList
{
elementData = c.toArray(); //返回包含c所有元素的数组
if ((size = elementData.length) != 0)
{
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);//复制指定数组,使elementData具有指定长度
}
else
{
//c中没有元素
this.elementData = EMPTY_ELEMENTDATA;
}
}
//将当前容量值设为当前实际元素大小
public void trimToSize()
{
modCount++;
if (size < elementData.length)
{
elementData = (size == 0)? EMPTY_ELEMENTDATA:Arrays.copyOf(elementData, size);
}
}
//将集合的capacit增加minCapacity
public void ensureCapacity(int minCapacity)
{
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)?0:DEFAULT_CAPACITY;
if (minCapacity > minExpand)
{
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity)
{
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
{
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity)
{
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity)
{
int oldCapacity = elementData.length;
//注意此处扩充capacity的方式是将其向右一位再加上原来的数,实际上是扩充了1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity)
{
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
//返回ArrayList的大小
public int size()
{
return size;
}
//判断ArrayList是否为空
public boolean isEmpty() {
return size == 0;
}
//判断ArrayList中是否包含Object(o)
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//正向查找,返回ArrayList中元素Object(o)的索引位置
public int indexOf(Object o)
{
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
}
else
{
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//逆向查找,返回返回ArrayList中元素Object(o)的索引位置
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//返回此 ArrayList实例的浅拷贝。
public Object clone()
{
try
{
ArrayList> v = (ArrayList>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
}
catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
//返回一个包含ArrayList中所有元素的数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
@SuppressWarnings("unchecked")
public T[] toArray(T[] a) {
if (a.length < size)
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
//返回至指定索引的值
public E get(int index)
{
rangeCheck(index); //检查给定的索引值是否越界
return elementData(index);
}
//将指定索引上的值替换为新值,并返回旧值
public E set(int index, E element)
{
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
//将指定的元素添加到此列表的尾部
public boolean add(E e)
{
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
// 将element添加到ArrayList的指定位置
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
//从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
//arraycopy(被复制的数组, 从第几个元素开始复制, 要复制到的数组, 从第几个元素开始粘贴, 一共需要复制的元素个数)
//即在数组elementData从index位置开始,复制到index+1位置,共复制size-index个元素
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
//删除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; //将原数组最后一个位置置为null
return oldValue;
}
//移除ArrayList中首次出现的指定元素(如果存在)。
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;
}
//清空ArrayList,将全部的元素设为null
public void clear()
{
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
//按照c的迭代器所返回的元素顺序,将c中的所有元素添加到此列表的尾部
public boolean addAll(Collection extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
//从指定位置index开始,将指定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)
//先将ArrayList中从index开始的numMoved个元素移动到起始位置为index+numNew的后面去
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
//再将c中的numNew个元素复制到起始位置为index的存储空间中去
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
//删除fromIndex到toIndex之间的全部元素
protected void removeRange(int fromIndex, int toIndex)
{
modCount++;
//numMoved为删除索引后面的元素个数
int numMoved = size - toIndex;
//将删除索引后面的元素复制到以fromIndex为起始位置的存储空间中去
System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);
int newSize = size - (toIndex-fromIndex);
//将ArrayList后面(toIndex-fromIndex)个元素置为null
for (int i = newSize; i < size; i++)
{
elementData[i] = null;
}
size = newSize;
}
//检查索引是否越界
private void rangeCheck(int index)
{
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void rangeCheckForAdd(int index)
{
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
//删除ArrayList中包含在c中的元素
public boolean removeAll(Collection> c)
{
Objects.requireNonNull(c);
return batchRemove(c, false);
}
//删除ArrayList中除包含在c中的元素,和removeAll相反
public boolean retainAll(Collection> c)
{
Objects.requireNonNull(c); //检查指定对象是否为空
return batchRemove(c, true);
}
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) //判断c中是否有elementData[r]元素
elementData[w++] = elementData[r];
}
finally
{
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;
}
//将ArrayList的“容量,所有的元素值”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException
{
int expectedModCount = modCount;
s.defaultWriteObject();
//写入数组大小
s.writeInt(size);
//写入所有数组的元素
for (int i=0; i
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
//先将ArrayList的“大小”读出,然后将“所有的元素值”读出
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
s.defaultReadObject();
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
a[i] = s.readObject();
}
}
}
1.1.4.1 动态数组
ArrayList是一个动态数组,实现了List, RandomAccess, Cloneable, java.io.Serializable,并允许包括Null在内的元素;
1.1.4.1.1 访问速度O(1)
实现了RandomAccess接口标识着其支持随机快速访问,RandomAccess源码什么都没有定义.因为ArrayList底层是数组,那么随机快速访问是理所当然的,访问速度O(1).
1.1.4.1.2 可以被浅复制
实现了Cloneable接口,标识着可以被复制,ArrayList中的clone()复制为浅复制; - 浅复制:只复制一个对象,但新对象和老对象是同一个地址值; - 深复制:复制一个对象,新老对象的地址值都变了。
1.1.4.2 自动扩容机制
底层使用数组实现,初始容量为10;当超出后,自动扩容为原来的1.5倍,即自动扩容机制;
数组的扩容是新建一个大容量(原始数组大小+扩充容量)的数组,然后将原始数组拷贝到新数组,然后采用的是Arrays.copyOf浅复制新数组作为扩容之后的数组,因此代价很高,尽量减小该种操作。在开始阶段,直接指定大小最好。
1.1.4.2.1 开始阶段指定容量
如果在开始能够预知要保存的元素多少,要在构造ArrayList实例时,指定容量值,避免扩容;
1.1.4.2.2 ensureCapacity
根据实际需要,调用ensureCapacity方法来手动增加ArrayList实例的容量。
1.1.4.3 Fail-Fast机制
面对并发的修改时,迭代器很快就会完全失败,报异常concurrentModificationException(并发修改一次),而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
1.1.4.4 remove方法
remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC
ArrayList里面删除元素其实就是将该元素置位null
1.1.4.5 add(E e)
添加指定元素到末尾,如果增加的数据量很大,应使用ensureCapacity()方法,预先设置ArrayList的大小,提高初始化效率。
Object obj = new Object();
ArrayList list0 = new ArrayList();
long startTime0 = System.currentTimeMills();
for(int i = 0; i<=N; i++){
list0.add(obj);
}
long endTime0 = System.currentTimeMills();
Log.e("date", "111没有调用ensureCapacity()方法所用时间:" + (endTime0 - startTime0) + "ms");
ArrayList list1 = new ArrayList();
long startTime1 = System.currentTimeMillis();
list1.ensureCapacity(N);//预先设置list的大小
for(int i=0;i<=N;i++){
list1.add(obj);
}
long endTime1 = System.currentTimeMillis();
Log.e("date", "222调用ensureCapacity()方法所用时间:" + (endTime1 - startTime1) + "ms");
1.1.5 面试题
1.1.5.1 ArrayList的大小是如何自动增加的?你能分享一下你的代码吗?
当试图在ArrayList增加对象时,Java会检查arrayList,以确保存在的数组中有足够的容量来存储新的对象。如果没有足够容量,那么就会新建一个长度更长的数组,旧的数组就会使用Arrays.copyOf()方法被复制到新的数组中去,现有的数组引用指向了新的数组。
public boolean add(E e){
ensureCapacity(size+1);
elementData[size++] = e;
return true;
}
public void ensureCapacity(int minCapacity){
modCount++;
int oldCapacity = elementData.length;
if(minCapacity > oldCapacity){
Object oldData[] = elementData;
}
}
1.1.5.2 什么情况下你会使用ArrayList?什么时候你会选择LinkedList?
多数情况下,当访问元素比插入或者删除元素更加频繁的时候,应该使用ArrayList;
当在某个特别的索引中,插入或者删除元素更加频繁,或者不需要访问元素的时候,选择LinkedList。
原因:
ArrayList访问元素的最糟糕时间复杂度为1,而LinkedList可能就是n;在ArrayList中增加或者删除某个元素,通常会调用System.arraycpopy()方法,非常消耗资源,因此,LinkedList会更好一些。
1.1.5.3 当传递ArrayList到某个方法中,或者某个方法返回ArrayList,什么时候要考虑安全隐患?如何修复安全违规这个问题呢?
当array被当做参数传递到某个方法中,如果array在没有被复制的情况下直接被分配给了成员变量,那么就可能发生这种情况,即当原始的数组被调用的方法改变的时候,传递到这个方法中的数组也会改变。下面的这段代码展示的就是安全违规以及如何修复这个问题。
ArrayList被直接赋值给成员变量——安全隐患
public void setMyArray(String[] myArray){
this.myArray = myArray;
}
修复
public void setMyArray(String[] newMyArray){
if(newMyArray == null){
this.myArray = new String[0];
} else{
this.myArray = Arrays.copyOf(newMyArray, newMyArray.length);
}
}
1.1.5.4 如何复制某个ArrayList到另一个Arraylist中去?
1.使用clone()方法,如
ArrayList newArray = oldArray.clone();
2.使用ArrayList构造方法,比如
ArrayList myObject = new ArrayList(myTempObject);
3.使用Collection的copy方法
1.1.6 概述
1.ArrayList是List接口的可变数组非同步实现,并允许包括null在内的所有元素;
2.底层使用数组实现;
3.该集合是可变长度数组,数组扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量增长大约是其容量的1.5倍,这种操作的代价很高。
4.采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险
5.remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC