[集合]ArrayList源码和时间复杂度
前言:
1.ArrayList的复杂度?
2.源码分析
2.1 ArrayList的继承关系
2.2 属性
2.2.1 关于序列化
2.2.2 什么是序列化,为什么要序列化?
2.3 构造函数
为什么elementData.getClass() != Object[].class做这个判断呢?
2.4 添加元素
2.4.1 在末尾增加元素add(E e)
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 删除元素
2.5.1 根据index删除元素remove(int index)
2.5.2 删除指定元素remove(Object o)
2.5.3 removeAll(Collection c)
2.5.4 clear()
2.6 查找元素
2.6.1 get(int index)
2.7 contains
2.7.1 contains(Object o)
2.8 其他方法
2.8.1 缩衣节食,调整大小trimToSize()
2.8.2 clone
2.8.3 toArray()
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 extends AbstractList
implements List, 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 true 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的方法
- 先把它转成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 true 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
* i such that
* (o==null ? get(i)==null : o.equals(get(i)))
* (if such an element exists). Returns true 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 true 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©;
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 true if this list contains the specified element.
* More formally, returns true if and only if this list contains
* at least one element e such that
* (onull ? enull : o.equals(e)).
*
* @param o element whose presence in this list is to be tested
* @return true 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 ArrayList instance to be the
* list’s current size. An application can use this operation to minimize
* the storage of an ArrayList 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 ArrayList instance. (The
* elements themselves are not copied.)
*
* @return a clone of this ArrayList 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).
*
*
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.
*
*
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 ArrayList instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the ArrayList
* instance is emitted (int), followed by all of its elements
* (each an Object) 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 来序列化。
- 小结
ArrayList是线程不安全的。
ArrayList扩容是1.5倍扩容。
删除元素不会缩小数组空间,可以使用trimToSize()来缩小空间。
最好指定大小初始化,可以防止扩容带来的内存消耗。
感谢:
关于序列化:https://zhuanlan.zhihu.com/p/69440847
ArrayList:https://www.jianshu.com/p/4a403049a4a2
————————————————
版权声明:本文为优快云博主「pmdream」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/pmdream/article/details/106995088
LinkedList源码和时间复杂度
目录
前言:
1.LinkedList的复杂度?
2.源码分析
2.1 LinkedList的继承关系?
2.2 属性
2.2.1 Node结构
2.3 构造函数
2.3.1 addAll(重磅)
2.4 添加元素
2.4.1 add(E e)
2.4.2 addAll(Collection c)
2.4.3 addFirst(E e)
2.4.4 addLast(E e)
2.4.5 void add(int index, E element)
2.5 删除元素
2.5.1 remove()
2.5.2 remove(Object o)
2.6 get
2.6.1 get(int index)
2.6.2 getFirst()
2.6.3 getLast()
2.7 contains
2.8 set 设置对应index的节点的值
2.9 队列相关
2.9.1 peek
2.9.2 poll
2.9.3 offer
2.9.4 push
2.9.5 pop
2.10 序列化
2.10.1 writeObject
2.10.2 readObject
前言:
基于jdk1.8.
同上一篇ArrayList的源码。
两个半天撸完= =。DK加油
上一篇:https://blog.youkuaiyun.com/pmdream/article/details/106995088
1.LinkedList的复杂度?
get() 获取第几个元素,依次遍历,复杂度O(n)
add(E) 添加到末尾,复杂度O(1)
add(index, E) 添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n) (这个比较容易想错)
remove()删除元素,直接指针指向操作,复杂度O(1)
2.源码分析
2.1 LinkedList的继承关系?
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
{
//…
}
对比ArrayList:
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
{
//…
}
没有了快速访问,多了Deque接口。
因为实现了队列接口,所以可以让LinkedList作为双向队列。
和ArrayList不同的地方是,LinkedList继承的是AbstractSequentialList,AbstractSequentialList继承AbstractList,而ArrayList直接继承AbstractList。
2.2 属性
2.2.1 Node结构
因为不同于ArrayList,对于LinkedList每个元素都有着Node节点(这是一个内部类,有这前后指针):
双向链表,那么顺序访问的效率很高,随机访问的效率低。因为通过索引访问,会比较索引值和链表长度的1/2,如果索引大,那么会从链表尾开始找,否则是头。所以才有了下面的first和last节点。
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
2.3 构造函数
无参构造函数:
/**
* Constructs an empty list.
*/
public LinkedList() {
}
有参构造函数:(因为链表形式不需要初始化空间给它)
/**
* 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 LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
2.3.1 addAll(重磅)
private static class Node {
E item;
Node next;
Node prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
/**
* 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. (Note that this will occur if the specified collection is
* this list, and it's nonempty.)
*
* @param c collection containing elements to be added to this list
* @return {@code true} 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) {
return addAll(size, 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 {@code true} 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) {
//检查index是否合法
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//succ:待添加节点的位置。
//pred:待添加节点的前一个节点。
//新添加的元素的位置位于LinkedList最后一个元素的后面。
//如果index==size;说明此时需要添加LinkedList中的集合中的每一个元素都是在LinkedList
//最后面。所以把succ设置为空,pred指向尾节点。
//否则的话succ指向插入待插入位置的节点。pred指向succ节点的前一个节点。
Node<E> pred, succ;
if (index == size) {
//其实如果没有元素,也会进入第一个if判断
//说明想要插入的是在链表的末尾
succ = null;
pred = last;
} else {
//这种情况返回要插入的是哪个节点那里,但是肯定是插在succ之前,pred之后,所以接下来要操作pred
succ = node(index);
pred = succ.prev;
}
//循环a 每个节点存储着数组a的值,该节点的prev用来存储pred节点,next设置为空。接着判断一下该节点的前一个节点是否为
//空,如果为空的话,则把当前节点设置为头节点。否则的话就把当前节点的前一个节点的next值
//设置为当前节点。最后把pred指向当前节点,以便后续新节点的添加。
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
//说明之前都是空的
first = newNode;
else
//那么如果pred不是空的了,把当前节点的前一个节点pred,的next指向新的内容
pred.next = newNode;
//pred指向newNode
pred = newNode;
}
if (succ == null) {
//新添加的节点位于LinkedList集合的最后一个元素的后面
//pred指向的是LinkedList中的最后一个元素
//所以把last指向pred
last = pred;
} else {
//中间位置插入的,相当于把一块内容插了进去,然后把前后指针都重新指向
pred.next = succ;
succ.prev = pred;
}
//加上新的集合大小
size += numNew;
//相当于整体链表修改的次数增加
modCount++;
return true;
}
//检查Pos是否合法,其实就是检查是否大于0并且在总大小内
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Tells if the argument is the index of a valid position for an
* iterator or an add operation.
*/
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
/**
* Returns the (non-null) Node at the specified element index.
* 最后返回一个
*/
Node<E> node(int index) {
// assert isElementIndex(index);
//与数组大小的一半进行比较
if (index < (size >> 1)) {
//如果index小于总大小的一半
//将first给x
Node<E> x = first;
//从头指针往后找
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
//从尾指针开始往前找
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
读了蛮久,总结就是有succ指针,指向当前index节点,pred是succ之前的节点,
那么想插入到index的时候,那么就得放到succ之前,pred之后,曹总pred指针呗~
最后再处理一下这两个的前后指针。
2.4 添加元素
2.4.1 add(E e)
/**
* Appends the specified element to the end of this list.
*
*
This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
当然连接到尾部了。
*/
void linkLast(E e) {
//这个last是内部属性,代表尾部节点指针
final Node l = last;
//声明一个新的newNode,next指针指向null,前置指针指向之前的last
final Node newNode = new Node<>(l, e, null);
last = newNode;
//这个书名之前的last是null,那么是相当于初始化,得吧first也得指向新的newNode
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
2.4.2 addAll(Collection<? extends E> c)
见上面构造函数。
2.4.3 addFirst(E e)
/**
* Inserts the specified element at the beginning of this list.
*
* @param e the element to add
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* Links e as first element.
*/
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
基本思想一样,就是更改头部指针。
2.4.4 addLast(E e)
/**
* Appends the specified element to the end of this list.
*
*
This method is equivalent to {@link #add}.
*
* @param e the element to add
*/
public void addLast(E e) {
linkLast(e);
}
这个等同于add,只是没有返回添加称没成功的boolean
2.4.5 void 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) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
//这种已经确保了,不会为null,因为如果链表长度只有1,那你却想插入5的index,就会报错的,运行期会报错
linkBefore(element, node(index));
}
这里面需要注意linkBefore这个方法。node(index)找到了对应的要插入的那个节点的succ,当前index指向的节点。
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
//为什么会有判断pred == null??接下来讲
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
在上面代码中,我不太明白为什么pred == null 有这个判断?
首先,进入这个方法的,肯定是有大小的。因为链表长度为1,但是比如我要插入5,就会报错。
public static void main(String[] args) {
//这样会报异常
LinkedList linkedList = new LinkedList();
linkedList.add("hh");
linkedList.add(5,"ss");
System.out.println(linkedList);
}
那么怎么进入pred==null这个判断呢?
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add("hh");
linkedList.add(0,"ss");
System.out.println(linkedList);
}
这种情况下,pred 就是空的。因为只有一个节点的时候,他的前后指针都是空的。
只不过我们会给first指针和last指针,但这不等于 node中的前后指针。
在进行这个操作之后,我们发现第一个节点有next,第二个节点有prev~
2.5 删除元素
2.5.1 remove()
/**
* Retrieves and removes the head (first element) of this list.
*
* @return the head of this list
* @throws NoSuchElementException if this list is empty
* @since 1.5
*/
public E remove() {
return removeFirst();
}
/**
* Removes and returns the first element from this list.
*
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
这个默认会删除掉头部的元素。
这边有一个点很可借鉴,当我们不用了一个元素的时候,需要把它的前后置镇 对于头结点就是next指针置为null,
这样可以帮助GC回收元素,因为就少了引用了。否则可能会持续的引用
2.5.2 remove(Object o)
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If this list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* (o==null ? get(i)null : o.equals(get(i)))
* (if such an element exists). Returns {@code true} 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 {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (Node x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
如果是null直接用 来判断相等,如果是对象类型使用equals。
unlink:
/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
//是头结点
first = next;
} else {
//否则,把之前节点的next指向next;并且释放x的前街店指针
prev.next = next;
x.prev = null;
}
if (next == null) {
//是尾结点
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
2.6 get
2.6.1 get(int index)
/**
* 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) {
//验证index合法性
checkElementIndex(index);
//使用node方法
return node(index).item;
}
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
2.6.2 getFirst()
/**
* Returns the first element in this list.
*
* @return the first element in this list
* @throws NoSuchElementException if this list is empty
*/
public E getFirst() {
final Node f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
可以用属性中的first指针。
2.6.3 getLast()
/**
* Returns the last element in this list.
*
* @return the last element in this list
* @throws NoSuchElementException if this list is empty
/
public E getLast() {
final Node l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
2.7 contains
/*
* Returns {@code true} if this list contains the specified element.
* More formally, returns {@code true} if and only if this list contains
* at least one element {@code e} such that
* (onull ? enull : o.equals(e)).
*
* @param o element whose presence in this list is to be tested
* @return {@code true} if this list contains the specified element
*/
public boolean contains(Object o) {
return indexOf(o) != -1;
}
/**
* 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 {@code i} such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*
* @param o element to search for
* @return the index of the first occurrence of the specified element in
* this list, or -1 if this list does not contain the element
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
做了一遍循环。
2.8 set 设置对应index的节点的值
/**
* 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) {
checkElementIndex(index);
Node x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
返回oldVal
2.9 队列相关
2.9.1 peek
/**
* Retrieves, but does not remove, the head (first element) of this list.
*
* @return the head of this list, or {@code null} if this list is empty
* @since 1.5
*/
public E peek() {
final Node f = first;
return (f == null) ? null : f.item;
}
返回头结点的值。(头结点为空,直接返回空)
2.9.2 poll
/**
* Retrieves and removes the head (first element) of this list.
*
* @return the head of this list, or {@code null} if this list is empty
* @since 1.5
*/
public E poll() {
final Node f = first;
return (f == null) ? null : unlinkFirst(f);
}
移除头结点,并返回移除的头结点的值。
2.9.3 offer
/**
* Adds the specified element as the tail (last element) of this list.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Queue#offer})
* @since 1.5
*/
public boolean offer(E e) {
return add(e);
}
在链表的头部添加一个新的元素
2.9.4 push
/**
* Pushes an element onto the stack represented by this list. In other
* words, inserts the element at the front of this list.
*
*
This method is equivalent to {@link #addFirst}.
*
* @param e the element to push
* @since 1.6
*/
public void push(E e) {
addFirst(e);
}
Push的话是添加头结点
2.9.5 pop
/**
* Pops an element from the stack represented by this list. In other
* words, removes and returns the first element of this list.
*
* <p>This method is equivalent to {@link #removeFirst()}.
*
* @return the element at the front of this list (which is the top
* of the stack represented by this list)
* @throws NoSuchElementException if this list is empty
* @since 1.6
*/
public E pop() {
return removeFirst();
}
2.10 序列化
2.10.1 writeObject
/**
* Saves the state of this {@code LinkedList} instance to a stream
* (that is, serializes it).
*
* @serialData The size of the list (the number of elements it
* contains) is emitted (int), followed by all of its
* elements (each an Object) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
和ArrayList很像。
2.10.2 readObject
/**
* Reconstitutes this {@code LinkedList} instance from a stream
* (that is, deserializes it).
*/
@SuppressWarnings(“unchecked”)
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
————————————————
版权声明:本文为优快云博主「pmdream」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/pmdream/article
三.总结
ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下:
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
3.LinkedList不支持高效的随机元素访问。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
get(index) 根据下标查询,顺序存储知道首个元素的地址,其他的位置很快就能确定,时间复杂度为O(1)
链式存储,从首个元素开始查找,直到查找到第 i个位置,时间复杂度为O(n)
add(E) 直接尾部添加,时间复杂度O(1)
add(index,E) 顺序存储需要查找到元素然后执行插入或删除,时间复杂度为O(1)+O(n)=O(n);
链式存储同样需要先查找到元素然后在插入或删除,时间复杂度为O(n)+O(1)=O(n)
remove(E) 顺序存储删除指定元素,后面元素要向前移动,时间复杂度O(n)
链式存储,直接 指针操作(找到前驱节点,再删除),时间复杂度O(1)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.youkuaiyun.com/weixin_43864727/article/details/88384142
原作者删帖 不实内容删帖 广告或垃圾
tips:
ArrayList 是线性表(数组)
get() 直接读取第几个下标,复杂度 O(1)
add(E) 添加元素,直接在后面添加,复杂度O(1)
add(index, E) 添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度O(n)
remove()删除元素,后面的元素需要逐个移动,复杂度O(n)
LinkedList 是链表的操作
get() 获取第几个元素,依次遍历,复杂度O(n)
add(E) 添加到末尾,复杂度O(1)
add(index, E) 添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n)
remove()删除元素,直接指针指向操作,复杂度O(1)/details/107016062