java集合-List简单介绍
Posted woshijinlei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java集合-List简单介绍相关的知识,希望对你有一定的参考价值。
List为一个接口,直接继承Collection接口,对比他们的接口变化:除了Collection接口中size(),isEmpty()等方法,其增加了基于下标index的一系列方法,摘抄部分接口方法:
get(int)
set(int, E)
add(int, E)
remove(int)
indexOf(Object)
lastIndexOf(Object)
subList(int, int)
我们简单看一下ArrayList,
首先看一下成员变量
private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; private int size;
elementData为内部实际存储元素的数组,即ArrayList是基于数组的存储结构,我们get(int),set(int,E)等方法,实质也是操作数组来实现;
看一下加入元素方法:
public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; 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++; if (minCapacity - elementData.length > 0) grow(minCapacity); } 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); }
关于容器扩容检查的几个方法,我们看一下主要流程,最终参数minCapacity的值为size+1,通过grow()方法,newCapacity其中一种尝试是赋值为elementData数组长度的1.5倍,并通过Arrays.copyOf()方法实现elementData数据最终扩容;
完成扩容检查之后,我们进行数组复制,因为此时elementData数组已经扩容,所以可以从index位置统一移动到index+1位置,预留出index位置;
最后我们把index位置赋值为我们add的元素,整个即完成来ArrayList在特定位置增加元素的过程;
我们在简单看一下LinkedList:
LinkedList除了具备List接口的所有特性,它还实现了Deque接口,可以先看List接口相关的;我们看一下成员变量:
transient int size = 0; transient Node<E> first; transient Node<E> last;
成员变量只有三个,主要存储了首尾Node元素,看一下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; } }
是一个私有数据存储类,包含实际存储的元素item以及指向前后Node的变量;是一种基于链表结构的容器;
我们也看一下加入元素方法:
public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); }
主要包括输入参数检查,如果是末尾添加linkList(),如果不是末尾添加linkBefore();
主要看一下linkBefore()方法,输入参数node(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; } }
从成员变量我们知道,LinkedList不像ArrayList那样维护一个内部数组,而是通过包装类Node持有存入的元素和在它之前和之后的Node引用;所以当我们想要找到某一个元素时,只能够从成员变量的first开始位置的Node或者结束位置last的Node变量逐个逐个查找;通过node()方法可以看到,分析当index小于容器长度一半时,是从first位置开始遍历,由第一个Node的next引用可以找到第二个Node,第二个Node的next引用找下一个,直到小于index时的Node;
回到linkBefore()方法,我们已经找到index位置的Node,在以此为基础add进入一个新的的Node,即add进新的元素;
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; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
我们简单看一下,主要目的是new一个新的Node对象newNode,newNode需要保存add进入容器的元素element,将prev和next分别赋值上一个Node和下一个Node,上一个Node即从传入的Node参数获取,下一个即传入参数Node本身,同时跟新传入Node的上一个引用指向新插入的newNode,最终完成了LinkedList指定位置add元素的整个流程;
从两种List集合的add方法的分析过程可以看出,删除和插入元素ArrayList涉及到内部数组的赋值移动,而LinkedList又涉及到内部遍历,效率都不是很好;如果我们想拿到具体位置的元素,ArrayList可以通过index索引直接得到,但是LinkedList需要从头部或者尾部逐个遍历才能拿到具体位置的元素;如果操作首尾元素,而linkedList则更具有效率;
以上是关于java集合-List简单介绍的主要内容,如果未能解决你的问题,请参考以下文章