一、ArrayList
首先上两张图给大家看一下:
ArrayList是一个普通的类。
1.1 ArrayList初始化第一步做了什么?
个人代码部分:
List<String> list1 = new ArrayList<String>();
源码解析:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
我们看看Array List的构造方法,给elementData初始化了一个参数,elementData是什么?DEFAULTCAPACITY_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.
* 翻译:共享空数组实例,用于默认大小的空实例。我们
* 将其与EMPTY_ELEMENTDATA区分开来,以了解何时膨胀多少
* 添加第一个元素。
*/
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.
* 翻译:存储ArrayList元素的数组缓冲区。
* ArrayList的容量是这个数组缓冲区的长度。任何
* 使用elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA清空ArrayList
* 将在添加第一个元素时扩展为DEFAULT_CAPACITY。
*
*/
transient Object[] elementData; // non-private to simplify nested class access
DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空Object数组
我们用有道翻译来看看这断代码注释是什么意思,总结一下:
- 存储ArrayList元素的数组缓冲区
- ArrayList的容量是这个数组缓冲区的长度
- 添加第一个元素时扩展为DEFAULT_CAPACITY
DEFAULT_CAPACITY ,我们来看看这个常量是多少,往下看
/**
* Default initial capacity.
* 翻译:默认初始容量。
*/
private static final int DEFAULT_CAPACITY = 10;
综上,我们可以得出一个结论,Array List初始化就是创建了一个空的Object数组,并且当你第一次添加元素的时候,会给这个数组长度赋值为10
注意:这里还有一个想说的,就是elementData是一个成员变量,虽然是一个Object类型的数组,但是并没有一开始就赋值,而是通过另一个常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,赋值给elementData。为什么要这样呢?最最根本的原因,个人认为,是因为Java中数组一旦创建长度就已经确定了,不允许更改。这样做还是为了节省内存空间,赋值只是更换个引用而已。
1.2 Array List 中add() 之上集
第一步我们知道Array List初试化第一次添加元素的时候会创建一个长度为10的Object数组,那我们来验证一下,它创建了没有?它是怎么创建的?
个人代码:
List<String> list1 = new ArrayList<String>();
list1.add("第一个元素");
源码片段:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 翻译:修改次数
elementData[size++] = e;
return true;
}
我们一行一行代码来看:
ensureCapacityInternal(size + 1);
/**
* The size of the ArrayList (the number of elements it contains).
* 翻译:ArrayList的大小(它包含的元素的数量)。
*
* @serial
*/
private int size;
下面进去这个ensureCapacityInternal()看看:
private void ensureCapacityInternal(int minCapacity) {//minCapacity = 1
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
哟这里面又调用了两个方法?别慌我们慢慢消化。继续看calculateCapacity()
private static int calculateCapacity(Object[] elementData, int minCapacity) {//elementData = {},minCapacity = 1
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {},elementData = {},等式成立,进去if()
return Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY = 10,minCapacity = 1 ,返回大数值10
}
return minCapacity;
}
calculateCapacity()返回 10 ,下面我们再看ensureExplicitCapacity()
private void ensureExplicitCapacity(int minCapacity) {//minCapacity = 10
modCount++;//modCount = 0,modCount++ = 1
// overflow-conscious code
if (minCapacity - elementData.length > 0)//minCapacity = 10,elementData.length = 0,表达式成立,进入if()
grow(minCapacity);//minCapacity = 10
}
modCount是什么?
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
*/
protected transient int modCount = 0;
这断注释有点长啊,你只要知道modCount就是记录修改次数就行了
有点个多啊?怎么又有个grow(),源码就这样哦,要有耐心哦,我们继续看grow()
/**
* 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) {//minCapacity = 10
// overflow-conscious code
int oldCapacity = elementData.length;//elementData.length = 0,oldCapacity = 0
int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity = 0
if (newCapacity - minCapacity < 0)//等式成立,进入if()
newCapacity = minCapacity;//newCapacity = 10
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);//elementData = {},newCapacity = 10
}
看一下方法,copyOf()
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());//original 泛型数组,newLength = 10
}
草,又调了一次方法,此刻我们已经有些不赖烦了!!!带代码还是要分析
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {//newLength = 10
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)//等式成立,执行 new Object[newLength]
? (T[]) new Object[newLength]//newLength = 10 ,创建一个长度为10的Object数组
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
刚说完又来了?还调?操操操,含着泪…
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);//src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,destPos表示目标数组位置,length表示要复制的长度。
这是一个native方法,是由其它语言来实现的,一般是(C或C++),所以这儿没有实现代码。这是一个数组拷贝方法,大家还在写for循环拷贝数组吗?以后多用这个方法吧,简单又方便还能获得得更好的性能。感觉发现了一个宝贝吧?我们的幸苦还是有点用的。
public boolean add(E e) {//e = “第一个元素”
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;//size = 0 ,
return true;
}
终于走完了ensureCapacityInternal()我们elementData现在是一个长度为10的Objec类型的数组。 知道下面我们再看看后面的,后面就比较简单了。
elementData[size++] = e 等价于 elementData[0] = e
就是往这个长度为10的数组 下标为0的位置赋值e ,e是我们调用add()传进来的值。
总结,原来第一次add()真的会创建一个长度为10的Object数组,还有就是size此时为1。
1.3 Array List 中add() 之下集
通过上一篇文章,我们知道了第一次调用 add()Array List底层会创建一个长度为10的Object类型的数组,那你想过没有?如果我添加的元素超过10个怎么办?
个人代码:
List<String> list1 = new ArrayList<String>();
list1.add("第一个元素");
list1.add("第2个元素");
list1.add("第3个元素");
list1.add("第4个元素");
list1.add("第5个元素");
list1.add("第6个元素");
list1.add("第7个元素");
list1.add("第8个元素");
list1.add("第9个元素");
list1.add("第10个元素");
list1.add("第11个元素");
源码部分:
private void ensureExplicitCapacity(int minCapacity) {//minCapacity = 11
modCount++;//modCount = 11
// overflow-conscious code
if (minCapacity - elementData.length > 0)//minCapacity = 11,elementData.length = 10,等式成立,进入if()
grow(minCapacity);
}
我们再来看看grow()
/**
* 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) {//minCapacity = 11
// overflow-conscious code
int oldCapacity = elementData.length;//oldCapacity = 10
int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity = 15
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);//elementData = {“第一个元素”,“第2个元素”,....,“第10个元素”},newCapacity = 15
}
下面又调用了Arrays.copyOf(),这个方法我前面说过了,将原来的数组内容,赋值到新的数组里面,它的作用就是数组扩容。
这个grow方法里面有一个最最重要的步骤就是这句话 int newCapacity = oldCapacity + (oldCapacity >> 1);
1.3.1 题外话:位运算
怕有些同学忘了位运算,给大家补充一下知识。
: >> 表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
正数:r = 10 >> 110的二进制补码:0000 1010
向右移动1位后:0000 0101
结果:r = 5
总结,ArrayList 当底层数组不够存储元素时,就会扩容,扩多大呢?在原来的基础上扩大1.5倍。
1.4 Array List 中 remove()之上集
我们看到remove有两个重载方法,一个需要int 参数,一个需要Object参数
我们先看传int参数的方法,int其实就是数组下标索引
/**
* 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) {//index = 1
rangeCheck(index);//范围审核,判断下标是否越界,index = 1
modCount++;//记录修改次数
E oldValue = elementData(index);//根据下标返回数组元素,oldValue = “第2个元素”
int numMoved = size - index - 1;//计算数组需要拷贝的元素个数,size = 10,index = 1,numMoved = 8
if (numMoved > 0)//等式成立,进入if()
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//elementData = {“第一个元素”,“第2个元素”,...,“第10个元素”},index = 1,numMoved = 8
elementData[--size] = null; // clear to let GC do its work 翻译:清除,让GC完成它的工作
return oldValue;
}
我们又看到 System.arraycopy(elementData, index+1, elementData, index,numMoved);,看来这玩意是Array List核心方法啊?
这句话就是把elementData数组,下标为2的元素开始,拷贝8个(拷贝下标2-9的所有元素),到原数组(elementData数组),从下标1开始复制。
下面画个图给你们讲一下这个方法(System.arraycopy(elementData, index+1, elementData, index,numMoved))实际干了什么?
看着有点乱?希望各位同学谅解哈,这个画图操作不太熟练,画画技术不行(丢人啊,以前高中还是美术班出生呢)
分析下:这拷贝前和拷贝后,有三个需要关注的地方
- 拷贝后,从下标1-8 所有的元素指向都发生了改变
- 拷贝后,第2个元素不再有下标指向
- 拷贝后,最后一个元素还是指向了 “第10个元素”
由于最后一个元素还指向它原来的引用,所以就有了下一步骤:把下标9的引用置空。等待GC清除。
// size = 9
elementData[--size] = null; // clear to let GC do its work 翻译:清除,让GC完成它的工作
1.5 Array List 中 remove()之下集
废话不说了,直接看源码吧,节约时间
public boolean remove(Object o) {//0 = "第五个元素"
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {//进入else代码块
for (int index = 0; index < size; index++)//size = 9 (因为我们之前删除了一个元素了)
if (o.equals(elementData[index])) {//循环比较,如果存在“第5个元素”,表达式成立,进入if()
fastRemove(index);
return true;
}
}
return false;
}
老规矩,我们继续看 fastRemove(int index)
private void fastRemove(int index) {//index = 3
modCount++;//记录修改次数
int numMoved = size - index - 1;//size =9 ,index = 3,numMoved = 5
if (numMoved > 0)//表达式成立
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
这里我就不多说了,跟上面 remove(int index) 逻辑一样。
但是,这里有个最最重要的事,需要说一下,就是如果你Array List存的是对象,那么当你想通过remove(Object o)删除某个对象的时候,你就要注意了,可能需要重写equals()和 hashCode()。 感兴趣的同学可以试试啊。我这里就不写代码给你们看了。
1.6 ArrayList 之时间复杂度
我们之前讲解了add(),remove()一些源码。但是我们最常用的get()方法还没说,没说是因为太简单了,下面我们还是来看一下吧
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
就两步骤,审核和返回元素。你就这样看也知道get()这个方法的效率是不是很高呢?当然咯,直接通过数组的下标索引返回元素,时间复杂度为O(1),还有一个set()
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
两个参数,索引和元素。就是你告诉它在哪里添加什么元素,一次完成,时间复杂度也是O(1)。
但是我们知道add()和remove()方法都可能触发底层数组复制,时间复杂度为O(n)。
以上说的,两个时间复杂度有不理解的吗?不理解没关系,给大家再画个图,打个比喻。
如图:现在有一个小游戏,需要两个人来完成。桌子上有九个连续排放小盒子,每个盒子上面有一个数字,从0开始,从左到右依次递增。盒子里面的月饼(中秋节快到了)都是你亲自放的,每块月饼的花纹都不一样,你现在有两种方式让另一个人帮你从盒子里面拿,第一种方式:告诉另一个人你需要的月饼在哪个数字的盒子里面。这种方式最快,直接奔着数字的盒子过去拿给你。一次完成,所以时间复杂度为O(1)。第二种方式:告诉另一个人你想要的月饼花纹的图案是什么,另一个人需要从左往右,依此拿出来看一眼。假设你需要的月饼正好在第九个盒子里面,那么另一个人需要找九次才能拿到你需要的月饼。如果盒子的数量是n,那么就需要找n次。所以时间复杂度为O(n)。这个小游戏只是告诉大家时间复杂度的概念,让大家有个了解,什么叫时间复杂度,它往往是用来衡量计算机算法的效率。生活中的例子就是一件事的复杂程度,是否耗时。
你只要知道,在数组ArrayList中读取和存储(get/set)的性能非常高,为O(1),但插入(add(int index, E element))和删除(remove(int index))却花费了O(N)时间,效率并不高。
1.7 Arraylist与Vector的区别
首先我们给出标准答案:
1、Vector是线程安全的,ArrayList不是线程安全的。
2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
随便看了几个方法,都是加了synchronized 关键字,来保证线程的安全性。当执行synchronized修饰的方法前,系统会对该方法加一把锁,方法执行完成后释放锁,加锁和释放锁的这个过程,在系统中是有开销的,因此,在单线程的环境中,Vector效率要差很多。
和ArrayList和Vector一样,同样的类似关系的类还有HashMap和HashTable,StringBuilder和StringBuffer,后者是前者线程安全版本的实现。
二、LinkedList
今天我们来看Java中的另一种List即LinkedList,LinkedList是基于双向链表来实现的。
这几张图大家先看看,了解了解。我们之前学习的ArraList属于什么?没错,ArrayList属于线性表–顺序表
2.1 LinkedList 之 add()
个人代码:
public static void main(String[] args) {
List<String> linked = new LinkedList();
linked.add("元素1");
linked.add("元素2");
linked.add("元素3");
linked.add("元素4");
System.out.println(linked.size());
}
老规矩,我们来看看LinkedList的构造方法干了什么?
public LinkedList() {
}
啥也没干?没错,构造方法确实啥也没干…
那我只能看看有没有代码块啊?成员变量都有什么啊?
发现了三个成员变量
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;
这里说明一下,transient这个关键字告诉我们该对象在序列化的时候请忽略这个元素。 不影响我们看代码,不懂序列化的同学可以忽略这个字段。
size就不多说了,大家猜一下就知道是LinkedList的逻辑长度,初始化为0,并持有两个Node引用,first看名字一猜就是第一个,last看名字就是最后一个。
Node是什么?之前好像都没遇到过是吧?
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;
}
}
Node是一个私有的静态内部类。
- item --> 就是一个泛型变量
- next --> Node类型的,见名知意,next 就是下一个
- prev --> Node类型的,见名知意,prev 就是前一个
还是有点蒙蔽,感觉一头雾水啊?反正这个Node类肯定是核心了,这个毋庸置疑。下面我们debug看一下,这个Node到底存了什么?
public boolean add(E e) {//e:"元素1"
linkLast(e);//e:"元素1"
return true;
}
进入linkLast(e)方法
/**
* Links e as last element.
*/
void linkLast(E e) {//e:"元素1"
final Node<E> l = last;//l:null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
进入 Node有参构造方法
private static class Node<E> {
E item;//item:"元素1"
Node<E> next;//next:null
Node<E> prev;//prev:null
Node(Node<E> prev, E element, Node<E> next) {//prev:null element:"元素1" next:null
this.item = element;//element:"元素1"
this.next = next;//next:null
this.prev = prev;//prev:null
}
}
继续下面的,现在newNode对象有引用,last也指向了newNode的引用
/**
* Links e as last element.
*/
void linkLast(E e) {//e:"元素1"
final Node<E> l = last;//l:null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;//newNode:有引用 last :有引用
if (l == null)//等成成立
first = newNode;//first:有引用
else
l.next = newNode;
size++;//size:1
modCount++;//modCount:1
}
代码是走完了,元素1也添加进去了,感觉还是不知道在干嘛啊?别慌,我又来画图了(哈哈,绝对画好看点)
下面我们继续添加:“元素2”
void linkLast(E e) {//e:"元素2"
final Node<E> l = last;//l:{item="元素1",next = null,prev:null}
final Node<E> newNode = new Node<>(l, e, null);//newNode:{item="元素2",next=null,prev=l}
last = newNode;//last : newNode
if (l == null)
first = newNode;
else
l.next = newNode;//l.next : newNode
size++;//size:2
modCount++;//modCount:2
}
执行完了,看懂了没?没看到的来看我的画图哦(●’◡’●)
继续哈,添加 “元素3”、“元素4”。
void linkLast(E e) {//e:"元素4"
final Node<E> l = last;//l:{item="元素3",next = null,prev:{...}}
final Node<E> newNode = new Node<>(l, e, null);//newNode:{item="元素4",next=null,prev=l}
last = newNode;//last : newNode
if (l == null)
first = newNode;
else
l.next = newNode;//l.next : newNode
size++;//size:4
modCount++;//modCount:4
}
最终的效果图给大家重新画一下。是不是清晰了许多?
2.2 LinkedList 之 remove(int index)
个人代码:
public static void main(String[] args) {
List<String> linked = new LinkedList();
linked.add("元素1");
linked.add("元素2");
linked.add("元素3");
linked.add("元素4");
linked.remove(2);
linked.remove("元素4");
System.out.println(linked.size());
}
源码部分:
public E remove(int index) {
checkElementIndex(index);//审核元素索引,这个没什么好说的。
return unlink(node(index));
}
老规矩,一点点分析
unlink(node(index));
node(index),进去看看
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {//size >> 1这里等价于size/2
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;
}
}
很明显这里用到一个二分查找算法。然后再循环找到索引对应的元素。
我们回过头再看看 unlink(Node x)
E unlink(Node<E> x) {//x:{item:"元素3",prev:{item:"元素2"...},next:{item:"元素4"...}}
// assert x != null;
final E element = x.item;//element :"元素3"
final Node<E> next = x.next;//next:next:{item:"元素4"...}
final Node<E> prev = x.prev;//prev:{item:"元素2"...}
if (prev == null) {//等式不成立,进入else代码块
first = next;
} else {
prev.next = next;//把元素2的prev指向了元素4
x.prev = null;//把元素3的prev置空
}
if (next == null) {//等式不成立,进入else代码块
last = prev;
} else {
next.prev = prev;把元素4的prev指向了元素2
x.next = null;//把元素3的next 置空
}
x.item = null;//把元素3的item 置空
size--;//size:3
modCount++;//modCount:5
return element;//返回元素3
}
源码看懂没?没看到来看图。看懂的就不用看图了。
这就是效果图。
2.3 LinkedList 之 remove(int index)
源码部分
public boolean remove(Object o) {//o:"元素4"
if (o == null) {//等式不成立,进入else代码块
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {//循环遍历Node对象,从first指向的第一Node开始
if (o.equals(x.item)) {//比较元素是否相等
unlink(x);//清除这个Node对象的引用
return true;
}
}
}
return false;
}
unlink(x); 这个方法刚才带大家看过了,再看一遍,上次我们是删除双向链表中间的节点,这次删除的是最后一个节点,看看他又做了什么?
E unlink(Node<E> x) {//x:{item:"元素4"...}
// assert x != null;
final E element = x.item;//element :"元素4"
final Node<E> next = x.next;//next :null
final Node<E> prev = x.prev;//prev :{item:"元素2"...}
if (prev == null) {//等式不成立,进入else代码块
first = next;
} else {
prev.next = next;//把元素2的next置空
x.prev = null;//把元素4的prev 置空
}
if (next == null) {//等式成立
last = prev;//把LinkedList对象的last指向元素2
} else {
next.prev = prev;
x.next = null;
}
x.item = null;//把元素4的item置空
size--;//size:2
modCount++;//modCount:6
return element;
}
老规矩,看不懂源码的看图。
2.4 LinkedList 之 get()和set()
ArrayList源码部分:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
LinkedList源码部分:
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;
}
}
为什么LinkedList读取和存储性能慢?
上面上这断源码你就应该知道了,它是根据判断你要查的索引靠近first还是last,然后从头部或尾部开始一个个的匹配。假设你要查的这个元素的索引在中间值,那么很消耗性能。 和ArrayList源码对比一下,ArrayList直接根据数组下标获取元素,速度相当的快。
三、ArrayList和LinkedList性能对比
其实带大家看完源码,你们应该都知道Array List 的插入和删除都可能触发底层的数组复制,可以性能上可能会降低很多。
给大家做个Array List和LinkedList性能的对比。
个人代码:
/*
* Copyright (C), 2013-2019, 天津大海云科技有限公司
*/
package com.jikang.myArrayList;
import java.util.ArrayList;
import java.util.List;
/**
* @author yangjikang
* @date 2019/7/22 14:42
* @modified By yangjikang
*/
public class TestList01 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<String>();
int maxTestCount=50000;
//测试添加
long start =System.currentTimeMillis();
for(int i =0;i<maxTestCount;i++){
list.add(0,String.valueOf(i));
}
long end =System.currentTimeMillis();
System.out.println("ArrayList add cost time :"+(end-start));
//测试查询
start =System.currentTimeMillis();
for(int i =0;i<maxTestCount;i++){
list.get(i);
}
end =System.currentTimeMillis();
System.out.println("ArrayList get cost time :"+(end-start));
//测试删除
start =System.currentTimeMillis();
for(int i =maxTestCount;i>0;i--){
list.remove(0);
}
end =System.currentTimeMillis();
System.out.println("ArrayList remove cost time :"+(end-start));
}
}
控制台效果图:
个人代码:
/*
* Copyright (C), 2013-2019, 天津大海云科技有限公司
*/
package com.jikang.myArrayList;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* @author yangjikang
* @date 2019/7/22 14:42
* @modified By yangjikang
*/
public class TestList01 {
public static void main(String[] args) {
LinkedList<String> list=new LinkedList<String>();
int maxTestCount=50000;
//测试添加
long start =System.currentTimeMillis();
for(int i =0;i<maxTestCount;i++){
list.add(0,String.valueOf(i));
}
long end =System.currentTimeMillis();
System.out.println("LinkedList add cost time :"+(end-start));
//测试查询
start =System.currentTimeMillis();
for(int i =0;i<maxTestCount;i++){
list.get(i);
}
end =System.currentTimeMillis();
System.out.println("LinkedList get cost time :"+(end-start));
//测试查询
start =System.currentTimeMillis();
for(int i =maxTestCount;i>0;i--){
list.remove(0);
}
end =System.currentTimeMillis();
System.out.println("LinkedList remove cost time :"+(end-start));
}
}
控制台效果图