底层是数组实现的。其实它就是动态数组。当数组长度未知时使用它是较好的选择。实现了Collection和List接口。它的继承关系如下:
实现的接口如下:
内部的参数:
private static final int DEFAULT_CAPACITY = 10; //默认容量大小
private static final Object[] EMPTY_ELEMENTDATA = {}; //用于空实例的共享空数组实例。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //用于默认大小的空实例的共享空数组实例
/*
ArrayList的容量是此数组缓冲区的长度。 添加第一个元素时,任何带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList都将扩展为DEFAULT_CAPACITY。
*/ transient Object[] elementData; //存储ArrayList元素的数组缓冲区。
private int size; // ArrayList的大小(它包含的元素数)。
elementData用户存放实际的数据。它是transient类型的。
Transient是什么关键字呢?自己从来没用过诶。
将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。就是不进行序列化的标志字段。序列化后也不能被访问。
详细说明参考:https://www.cnblogs.com/lanxuezaipiao/p/3369962.html
其实静态变量也不能被序列化。
1) 那么为什么要将elementData[]定义为transient呢?
因为ArrayList 实际上是动态数组,每次在放满以后会扩容,如果数组扩容后,实际上只放了一个元素,那就会序列化很多null 元素,浪费空间。
2) 难道elementData数据序列化时就不保存了吗?
当然不是。对象实现java.io.Serializable 接口以后,序列化的动作不仅取决于对象本身,还取决于执行序列化的对象。
以ObjectOutputStream 为例,如果ArrayList 或自定义对象实现了writeObject(),readObject(),那么在序列化和反序列化的时候,就按照自己定义的方法来执行动作,所以ArrayList 就自定义了writeObject 和readObject 方法,然后在writeObject 方法内完成数组元素的自定义序列化动作,在readObject 方法内完成数组元素的自定义反序列化动作。可以看到它自己实现序列化elementData只取了size长度。
三种构造函数:
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);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
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;
}
}
可以看出来,如果事先指定已知的容量能减少扩容带来的开销。(对集合的添加是深克隆。?)
提供的方法包括:
Size,isEmpty,get,set,iterator,以及listIterator操作以恒定时间运行。add操作在分摊的常量时间中运行,即添加n个元素需要O(n)时间。 所有其他操作都以线性时间运行(粗略地说)。 与 LinkedList 实现相比,常数因子较低。
Add(i,value)//将后面的元素依次后移。时间复杂度为O(n)
set(i,value)//覆盖掉原来位置i的值
contains(value)//遍历元素,时间复杂度为O(n)因此大量的包含判断应考虑用HashSet。
扩容的函数:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
默认是扩容原来容量的一半及制定值得最大值。拷贝是浅拷贝
Remove方法:先找到要删除的元素,然后之后的元素依次向前移动。时间复杂度O(n)
排序:
@Test
public void testSort(){
List<Integer> list=new ArrayList<Integer>();
int n=1;
Random random=new Random();
while(++n<10){
list.add(random.nextInt(100));
}
System.out.println(list);
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});//传入自己实现的Comparator。
System.out.println(list);
}
1、 ArrayList和LinkedList区别在哪里?
1) ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2) 对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。
3) 对于新增和删除操作ad(I,value)和remove,LinedList比较占优势,因为ArrayList要移动数据。
4) ArrayList每次扩容会固定为之前的1.5倍,所以当你ArrayList达到一定量之后会是一种很大的浪费,并且每次扩容的过程是内部复制数组到新数组,耗时,耗空间;LinkedList的每一个元素都需要消耗一定的空间。
2、 ArrayList是线程不安全,多线程环境下可能出现什么问题?
1)Add(value)方法可能出现越界错误,或赋值失败,导致本该有值得某值为空。一下的测试代码多次执行可出现这两个问题。
public static int threadNum =10;
public static AtomicInteger aInteger=new AtomicInteger(0);
public static CountDownLatch countDownLatch = new CountDownLatch(threadNum);
/**
* 测试list多线程添加问题
*/
@Test
public static void testAddThreads() throws InterruptedException{
ArrayList list=new ArrayList();
int n=threadNum;
while(n-->0){
new Thread(new Runnable() {
public void run() {
int n=100;
while(n-->0){
list.add(aInteger.incrementAndGet());
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println(aInteger);
System.out.println(list.size());
for(int i=0;i" ");
}
System.out.println();
}
2)使用for循环进行删除,会出现:删除被覆盖,导致并不是所有想删除的数据都能被删除掉。数据尾出现空或者尾数数字重复,数据实际长度小于size等。
@Test
public void testRemoveThreads() throws InterruptedException{
ArrayList list=new ArrayList();
int n=40;
for(int i=0;iout.println(list);
System.out.print("每次移除的数据是:");
for(int i=0;inew Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}for(int i=0;i<2;i++){
System.out.print(list.remove(0)+" ");
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println();
System.out.println("长度:"+list.size());
System.out.println(list);
System.out.println(list.get(list.size()-1));
}