目录
为什么elementData.getClass() != Object[].class做这个判断呢?
2.4.2 指定位置增加元素add(int index, E element)
2.4.3 增添集合类型addAll(Collection c)
2.4.4 指定位置增添集合类型addAll(int index, Collection c)
2.4.5 对指定位置进行更新set(int index, E element)
2.5.1 根据index删除元素remove(int index)
2.9 序列化相关的writeObject和readObject?
前言:
认真自己照着源码写一遍意思。作为笔记和准备记录。ArrayList比HashMap简单很多读起来。下一篇读LinkedList。
1.ArrayList的复杂度?
如果我们不指定位置直接添加元素时(add(E element)),元素会默认会添加在最后,不会触发底层数组的复制,不考虑底层数组自动扩容的话,时间复杂度为O(1)
在指定位置添加元素(add(int index, E element)),需要复制底层数组,根据最坏打算,时间复杂度是O(n)。
总结:
ArrayList 是线性表(数组)
get() 直接读取第几个下标,复杂度 O(1)
add(E) 添加元素,直接在后面添加,复杂度O(1)
add(index, E) 添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度O(n)
remove()删除元素,后面的元素需要逐个移动,复杂度O(n)
2.源码分析
2.1 ArrayList的继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//...
}
ArrayList继承了AbstractList。
其中比较需要注意的是随机访问接口。证明能够通过序号就可以访问到元素。
2.2 属性
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.初始空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
不会被序列化,因为transient这个关键字的原因。因为序列化前会进行把元素write,
相当于把元素弄到一个合适的数组上面,有元素和大小,这样可以节省空间
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
* 保存当前数组的长度
* @serial
*/
private int size;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//继承的元素,标识修改的次数,顾名思义
protected transient int modCount = 0;
2.2.1 关于序列化
为什么要加上transient 关键字呢?
因为有的属性需要序列化,有的不需要。比如敏感信息不能序列化传输过去,就应该为空值,那么就可以加上这个关键字。
这样的话,就可以这个东西生命周期只在调用者的内存中。
另外有一种情况不起作用,如果是实现了Externalizable接口,哪一个属性被序列化使我们手动去指定的,即使是transient关键字修饰也不起作用。
另外静态变量也不会参与序列化,静态变量在全局区,本来流里面就没有写入静态变量,我打印静态变量当然会去全局区查找,而我们的序列化是写到磁盘上的,所以JVM查找这个静态变量的值,是从全局区查找的,而不是磁盘上。
静态变量是不会被序列化的,即使没有transient关键字修饰。
TODO:需要了解transient底层实现原理
2.2.2 什么是序列化,为什么要序列化?
序列化的本质就是把一种数据按照格式转成另一种数据形式。
序列化本身的目的是二进制序列和程序内部表示结构的转化。
当Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。
像数组和链表之类的得拍扁hhh,然后反序列化有点像充气。
2.3 构造函数
无参构造函数
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
使用默认空数组,elementData中的长度是0,size是0。
但是当进行第一次add的时候,elementDate会使用默认长度10.
有参构造函数(int)
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
有参构造函数(入参集合类)
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
//集合類可以直接toArray
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;
}
}
为什么elementData.getClass() != Object[].class做这个判断呢?
大概简单叙述是因为c.toArray();可能会让这个集合转成数组之后,不是Object[]这种类型,有可能重写了toArray这个方法。所以为了让这个Object[] elementData能够成功接收集合中的元素,所以才这么写的,防止出现classcast的异常。
因为 c.toArray(); 功能是将集合中的元素转换成数组,它在Collection中是这样子的,是接口中的方法,那么它就要被重写。
这个涉及到,因为子类重写父类方法的时候,在不修改返回值类型的前提下,子类返回了什么类型,具体得到的是子类的返回值类型,而不会上转成父类的返回值类型。
public static void main(String[] args) {
String [] a = new String[2];
Object [] b = new Object[2];
//print :[Ljava.lang.String;
System.out.println(a.getClass());
//print :[Ljava.lang.Object;
System.out.println(b.getClass());
}
上面代码证明了别的类型的的数组并不是Object[].
这里面会涉及到元素的拷贝。
2.4 添加元素
2.4.1 在末尾增加元素add(E e)
ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)这个五个方法来实现ArrayList增加。
一般来说,添加元素可以添加到末尾,不涉及到元素的移动,但是如果制定了index的话,就会涉及到了元素的移动。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
设想一下,元素添加,肯定要判断一下数组大小是否够,需不需要扩容。
size是作为当前数组的大小,所以可以直接对size进行操作。
ensureCapacityInternal
//
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);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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);
}
//如果比最大的大小都大的话,那么就进行huge 超大容量
//但是好像少没少8个大小真的有那么大关系吗
private static int hugeCapacity(int minCapacity) {
//这个地方为啥是minCapacity<0? 没有想太明白
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
在某些情况下>>1可以理解为除以2,所以如果是扩容,数组长度为原来的1.5倍。
总之:
1.判断长度+1之后是否会触发扩容(扩容1.5倍),如果是刚刚初始化的,会分配默认为10的空间。
2.如果扩容 修改次数modCount标识自增1(证明数组空间变化的次数)
3.将新元素添加到位于size++的位置上。
4.返回添加成功的布尔值。
2.4.2 指定位置增加元素add(int index, E element)
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//校验index的合理性
rangeCheckForAdd(index);
//和上面的add方法一样,进行判断是否需要扩容之类的
ensureCapacityInternal(size + 1); // Increments modCount!!
//为什么进行数组拷贝呢?是因为插入了index制定了位置之后,别的都需要进行移位
//对源数组进行复制处理(位移),从index + 1到size - index
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
其中:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
相当于原数组进行移位,那么吧index空出来,移动的元素是siez - index这个长度大小的。从index 移动到destPos
rangeCheckForAdd这个方法其实就是做一个check,并不重要。
/**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
//判断index是否合法
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Constructs an IndexOutOfBoundsException detail message.
* Of the many possible refactorings of the error handling code,
* this "outlining" performs best with both server and client VMs.
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
2.4.3 增添集合类型addAll(Collection<? extends E> c)
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the
* specified collection's Iterator. The behavior of this operation is
* undefined if the specified collection is modified while the operation
* is in progress. (This implies that the behavior of this call is
* undefined if the specified collection is this list, and this
* list is nonempty.)
*
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
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;
}
addAll的方法
1. 先把它转成Object[] 2. 是否需要扩容 3. 转换后的数组复制到列表尾部
2.4.4 指定位置增添集合类型addAll(int index, Collection<? extends E> c)
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element from the
* specified collection
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
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;
}
首先了解这个方法的含义,在指定的位置增加集合全部元素是什么意思呢?
就是把新的要插入集合中的元素,都留出空位来,然后插入进去。
2.4.5 对指定位置进行更新set(int index, E element)
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
//获取插入位置的当前元素
E oldValue = elementData(index);
//将新的元素替换当前插入位置的元素
elementData[index] = element;
//返回老的值
return oldValue;
}
set()是更新,更新指定下标位置的值。
2.5 删除元素
ArrayList提供了外界remove(int index)、remove(Object o)、removeAll(Collection<?> c)、clear()四个方法进行元素的删除。
2.5.1 根据index删除元素remove(int index)
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
}
最后的
elementData[--size] = null; // clear to let GC do its work
原因是因为都向前移动了元素之后,会留一个元素,那么把这个置为空。
2.5.2 删除指定元素remove(Object o)
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
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 remove method that skips bounds checking and does not
* return the value removed.
*/
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 根据index来进行删除操作,其实也就是先进行比较,然后做跟上面index删除一样的操作。
2.5.3 removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
//这里面入参是false batch是批量的意思
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.
//这边说明 为了确保没有出错,如果r!=size 那么就有可能是出错了,因为需要保证异常抛//出前的内容满足期望,把如果出错后面的元素弄到后面拼接上面去
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;
}
这里面比较有意思的是,使用到了Objects的判断方法。
Objects里面很多中工具方法。
final Object[] elementData = this.elementData;
这个修改的是一个对象吗?
答案是是的。
public class Test {
public String[] a = {"1","2","3"};
public void handle(){
final String[] b = a;
b[0] = "2";
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}
public static void main(String[] args) {
Test t = new Test();
t.handle();
}
}
输出结果是:
2
2
3
2.5.4 clear()
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
删除这个list的所有元素,
将当前数组大小设置为0,但是不减少数组容量。
2.6 查找元素
2.6.1 get(int index)
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
2.7 contains
contains方法会遍历ArrayList。
2.7.1 contains(Object o)
/**
* Returns <tt>true</tt> if this list contains the specified element.
* More formally, returns <tt>true</tt> if and only if this list contains
* at least one element <tt>e</tt> such that
* <tt>(o==null ? e==null : o.equals(e))</tt>.
*
* @param o element whose presence in this list is to be tested
* @return <tt>true</tt> if this list contains the specified element
*/
public boolean contains(Object o) {
//大于等于0则存在
return indexOf(o) >= 0;
}
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
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判断null和元素相等是不同的。
2.8 其他方法
2.8.1 缩衣节食,调整大小trimToSize()
/**
* Trims the capacity of this <tt>ArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>ArrayList</tt> instance.
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
如果当前ArrayList的实际长度小于列表的长度,将列表超过size后的空余的空间(包括null值)去除,调用Arrays.cppyof方法拷贝elementData,长度为size;把当前长度大小的拷贝。
Arrays.copyOf(elementData, size) 应该是会返回一个新的数组。
2.8.2 clone
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
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);
}
}
将元素都拷贝到v中。使用的弗雷德克隆方法。
2.8.3 toArray()
/**
* Returns an array containing all of the elements in this list
* in proper sequence (from first to last element).
*
* <p>The returned array will be "safe" in that no references to it are
* maintained by this list. (In other words, this method must allocate
* a new array). The caller is thus free to modify the returned array.
*
* <p>This method acts as bridge between array-based and collection-based
* APIs.
*
* @return an array containing all of the elements in this list in
* proper sequence
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
2.9 序列化相关的writeObject和readObject?
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
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();
}
}
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
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
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, 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();
}
}
}
writeObject是将元素写到输入流里面,
这里面做了个操作就是在写入流的时候,不能有空间大小的更改;
readObject是取数据到elementData中,一样是先读容量,再读数据
实现这两个方法是因为,
transient Object[] elementData;
这个属性是不会参与序列化的,因为有很多没有用到的null空间,这样会占用内存。
如果你重写了writeObject 和readObjec 这个两个方法,在实际序列化的时候,会利用反射最终调用到你重写的writeObject和readObject 来序列化。
3. 小结
ArrayList是线程不安全的。
ArrayList扩容是1.5倍扩容。
删除元素不会缩小数组空间,可以使用trimToSize()来缩小空间。
最好指定大小初始化,可以防止扩容带来的内存消耗。
感谢:
关于序列化:https://zhuanlan.zhihu.com/p/69440847
ArrayList:https://www.jianshu.com/p/4a403049a4a2