List在数据结构中表现为是线性表的方式,其元素以线性方式存储,集合中允许存放重复的对象,List接口主要的实现类有
ArrayList
ArrayList其实就是一组长度可变的数组,当实例化了一个ArrayList,该数据也被实例化了,当向集合中添加对象时,数组的大小也随着改变,这样它所带来的有优点是快速的随机访问,即使访问每个元素所带来的性能问题也是很小的,但缺点就是想其中添加或删除对象速度慢,当你创建的数组是不确定其容量,所以当我们改变这个数组时就必须在内存中做很多的处理,如你想要数组中任意两个元素中间添加对象,那么在内存中数组要移动所有后面的对象。
LinkedList
LinkedList是通过节点的连接实现链表的数据结构,向linkedList中插入或删除元素的速度是特别快,而随机访问的速度相对较慢,这个是由于链表本身的性质造成的,在链表中,每个节点都包含了前一个节点的引用,后一个节点的引用和节点存储值,当一个新节点插入式,只需要修改其中相关的前后关系节点引用即可,删除节点也是一样。操作对象只需要改变节点的链接,新节点可以存放在内存的任何位置,但也就是因为如此LinkedList虽然存在get()方法,但是这个方法通过遍历节点来定位所以速度很慢。LinkedList还单独具addFrist(),addLast(),getFrist(),getLast(),removeFirst(),removeLast()方法,这些方法使得LinkedList可以作为堆栈,队列,和双队列来使用。
说白了,ArrayList和LinkedList就是数据结构中的顺序存储表和链式存储表。
ArrayList构造原理
上面已经清楚ArrayList和LinkedList就是数据结构的顺序表和链表(不清楚的翻翻数据结构的书),下面简单分析一下它们的实现方式。
下表是摘自sum提供的ArrayList的api文档
ArrayList()
构造一个初始容量为 10 的空列表。
ArrayList(Collection<? extends E> c)
构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
ArrayList(int initialCapacity)
构造一个具有指定初始容量的空列表。
第一个构造函数是没有默认构建了一个初始容量10的空列表,第二个构造函数是制定collection元素的列表,第三个构造函数是由用户指定构造的列表初始化容量多少,如果使用第一个构造函数则表示默认调用该参数为initialCapacity=10来构造一个列表对象。
ArrayList源码稍微进行分析
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
private transient Object[] elementData;
private int size;
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
this(10);
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
public int size() {
return size;
}
}
可以发现ArrayList中包含两个主要的属性
private transient Object[] elementData;
private int size;
其中elementData[]是列表的实现的核心数组,我们使用该数组来存放集合中的数据,而我们的构造函数所传递的initialCapacity参数是构建该数组的长度。
在看看size的实现形式,它的作用是返回size的属性值的大小,我们再看看另外一个构造函数public ArrayList(Collection<? extends E> c),该构造函数的作用是把另外一个容器对象中的元素放入当点的List对象中。首先是通过调用另外一个容器对象c的size()来设置当前List对象的size属性的长度大小。接下来就似乎对elementData[]数组进行初始化,最后通过Arrays.copyOf(U[] original, int newLength, Class<? extends T[]> newType)方法把当前容器中的对象都存放进新的数组elementData,主要就完成了一个列表的创建。
ArrayList容量扩充
还有一个问题就是我们所建立的ArrayList是使用数组来实现的,但数组的长度一旦被初始化就不能改变,而我们在给此列表对象添加元素时却没有受到长度的限制,所以,ArrayList的elementData属性一定是存在一个动态扩充容量的机制,下面把相关的部分源码贴出来再做研究
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
protected transient int modCount = 0;
/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
看看public boolean add(E e)方法,可以发现在添加一个元素到容器中的时候,我们会先通过ensureCapacity(size + 1)判断该数组是否需要扩充。
public void ensureCapacity(int minCapacity)这个方法是用来判断当前的数组是否需要扩充,并且该扩充多少。modCount++; 表示当前的对象对elementData数组进行了多少次扩充,清空和移除等操作,相当于是一个对当前List对象的一个操作记录数。
int oldCapacity = elementData.length; 初始化oldCapacity,表示为当前elementData数组的长度。
if (minCapacity > oldCapacity) 判断minCapacity和oldCapacity谁大,来决定是否需要扩充。
int newCapacity = (oldCapacity * 3)/2 + 1; 扩充的策列是判断(oldCapacity * 3)/2 + 1和minCapacity两者之间谁更大,取更大的数作为扩充后数组的initialCapacity值,然后使用数组拷贝的方式,把以前的数据转移到新的数组对象中
如果minCapacity 小于 oldCapacity 就不需要再扩充。
ArrayList删除元素
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
private void RangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
}
在看看ArrayList移除元素是怎么实现的,首先判断需要删除的index是否在elementData数组的下标内,如不存在则抛出IndexOutOfBoundsException。
modCount++; 与扩充元素一个,删除元素也记下来操作数。
E oldValue = (E) elementData[index]; 获取需要删除元素的对象。
int numMoved = size - index - 1; 获取需要被删除元素的下标,删除该元素后,数组需要在此元素下标后的所有对象进行内存的移动。
System.arraycopy(elementData, index+1, elementData, index,numMoved);对numMoved后面的所有对象通过copy的方式进行内存的移动重新构建数组。
说完ArrayList的实现,再说说linkedList
构建双链表(LinkedList)
LinkedList是类似于C语言的双链表,双链表比单链表多了一个域,这个双链表就有了三个域,一个域来用存储数据元素,一个用来指向后续节点,另一个是指向结点的直接前驱节点。
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;
public LinkedList() {
header.next = header.previous = header;
}
private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
}
在Entry类中,定义了三个属性,分别为E element 表示数据与,Entry<E> next为后续指针域,Entry<E> previous为前驱指针域。
在LinkedList中定义了一个重要的属性header,头结点,不会纳入链表的总元素,该节点的previous是指向最后节点,next是指向第一节点。
构造函数LinkedList() 构造一个空列表。将header的前驱指针域和后续指针域都指向了自己,看到这里可以发现,next和previous就是一个引用,其实也相等于C里面的指针,不过C不会处理空指针,直接放操作系统处理了,java就直接抛出NullPointerException,根本不让它破坏系统的机会。
LinkedList元素变动
上面说到了LinkedList的新增和删除的效率比ArrayList的高,实际上在 链表操作这些方法时,只需要改变2个节点各自的前驱指针和后续指针域,而ArrayList是需要移动很多的元素。
addBefore(e, header);
return true;
}
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}
相比ArrayList的add()方法,LinkedList实现起来非常简单,主要是两行代码:
newEntry.previous.next = newEntry;将上一节点的后续节点指向新增的节点
newEntry.next.previous = newEntry;头节点的前驱节点指向新增节点,size和modCount自增记录。
同样remove的实现也非常简单
e.previous.next = e.next;该节点的后一节点的后去节点指向该节点的后驱节点,
e.next.previous = e.previous;该节点的后一节点的前驱节点指向该节点的前驱节点。
e.next = e.previous = null;把该节点的前驱节点和后驱节点全部指向null。
e.element = null;把该节点的数据域设置为null。
随机访问
相比顺序表,链表的随机访问效率要低得多(理论说法,不是绝对),ArrayList可以根据索引号进行随机访问,而LinkedList则不要遍历访问。
return entry(index).element;
}
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
Entry<E> e = header;
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
上列的代码是对一个链表的遍历,其中包含了一个算法,如果给的索引号小于总节点数的一半,则在链表的前半部分第一个节点完进行遍历,如果给的索引号大于总节点数的一半,则从最后一个节点往前进行遍历直到索引号。
最后总结一下ArrayList和LinkedList的各自特点
1.ArrayList是基于线性表的顺序存储表,LinkedList是基本线性表的链表存储表。
2.对于新增和删除元素,LinkedList比较占有优势,只需要变前后2个节点,而ArrayList要移动数据。
3.对于随机访问来说,ArrayList比较占有优势,可以根据索引号快速访问,而LinkedList则需要遍历集合的元素来定位。
4.而对于迭代操作(iterate)和查找操作(indexOf),两者是差不多。
不过上面都是基于理论,具体问题还是要根据事实进行分析,如ArrayList删除的元素刚好是队列的最后一个元素,那么是无需要移动数据的,大体我们可以认为需要随机访问较多的那么比较适合用ArrayList,如果是插入和删除(如消息队列)较多的那么就需要考虑LinkedList。
上面主要是参考了jdk源码,数据结构和一些相关资料本着好记性不如烂博客的精神记录下来,希望朋友们如果发觉哪里不对请指出来,虚心请教
----------------------------------------
相关推荐
Java中的List接口是集合框架的重要组成部分,它定义了一组有序的元素序列,允许有重复的元素。ArrayList、Vector和LinkedList都是List接口的实现...在实际使用中,根据业务需求权衡性能和功能,选择最适合的数据结构。
本学习资料包"java数据结构--学习"聚焦于如何在Java环境下理解和应用各种数据结构,旨在提升开发者的技术水平,使其能够编写出更加高效和优化的代码。 1. **数组**:数组是最基本的数据结构,用于存储同类型元素的...
此java类实现了对数据表的分类递归树的实现,为本人倾力之作,后期,会发布js版,敬请期待!
《数据结构(Java版本)》这本书正是为此目的而编写,旨在将理论与实际编程相结合,通过Java语言来实现各种经典的数据结构。 首先,书中的基础部分会介绍数据结构的基本概念,如数组、链表、栈和队列。数组是最基本...
1. **Java集合框架**:Java集合框架是Java库中的核心部分,提供了一组高效且灵活的数据结构,如List、Set和Map。在这个通讯录项目中,最可能使用的是List接口,因为它允许我们保持元素的顺序,并可以有重复元素。 2...
在IT领域,尤其是在编程世界,数据结构是至关重要的一个概念,尤其对于Java开发者而言。数据结构是组织、管理和存储数据的方式,它允许我们高效地访问和修改数据。本资源包"java---数据结构"显然是针对Java程序员...
### Java基础复习笔记09数据结构-哈夫曼树 #### 1. 哈夫曼树概述 哈夫曼树是一种特殊的二叉树,它在计算机科学领域有着广泛的应用,尤其是在数据压缩方面。哈夫曼树也被称为最优二叉树,其构建原则是使得带权路径...
"java-data-struct.rar_数据结构 java_数据结构源码"这个压缩包文件包含了用Java实现的数据结构的相关代码,对于学习和理解数据结构的实现具有很高的参考价值。 1. **链表(LinkedList)**:链表是一种线性数据结构...
数据结构列表(List)介绍和Java示例代码 数据结构列表(List)是 Java 中的一种线性数据结构,用于存储有序的元素集合。它允许重复元素,并且每个元素都有一个对应的索引来访问和操作。列表可以动态增长或缩小,并且...
### Java数据结构与算法学习笔记知识点总结 #### 一、数据结构概述 数据结构是对数据的一种组织形式,它决定了数据的存储方式以及处理数据的方法。常见的数据结构包括但不限于数组、链表、栈、队列、二叉树、图等...
Java集合体系是Java编程中非常核心的部分,涵盖了用于存储和操作数据的各种数据结构。在Java中,集合主要分为三大接口:List、Set和Map。这些接口各有特点,适用于不同的应用场景。 一、List接口 List接口是单列...
【Java基础——List接口详解...在实际开发中,根据具体需求选择合适的数据结构,可以提高程序性能并降低复杂度。同时,掌握如何对List进行排序,无论是通过Comparable接口还是Comparator接口,都能使数据处理更加灵活。
根据提供的信息,“Java数据结构和算法中文第二版”这本书主要关注的是数据结构与算法的相关内容。下面将基于这些信息,详细介绍数据结构与算法的核心概念、重要性和应用领域,以及在Java编程环境中如何实现这些概念...
链表是一种常用的数据结构,它在计算机科学中扮演着重要的角色。...通过深入学习这些链表的Java实现,你可以更好地理解数据结构的内部工作原理,并将其应用到实际项目中,提升代码的效率和灵活性。
C、C++和Java都是广泛使用的编程语言,它们在处理数据结构和算法时各有特点。以下是对这三种语言在数据结构与算法方面的一些关键知识点的详细阐述: 1. **数据结构**: - **数组**:基本的数据结构,用于存储同...
"java数据结构总结" java数据结构是计算机科学中研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等等的学科。下面是java数据结构的知识点总结: 一、数据结构定义 数据结构是相互之间...
### Java常用数据结构详解 #### 一、线性表(顺序表) 线性表是数据结构中最基础的数据组织形式之一,通常分为顺序表和链表两种实现方式。本部分主要介绍顺序表,即通过数组来实现的一种线性表。 ##### 1. 顺序表...
《清华邓俊辉Java数据结构》是一门深入探讨数据结构及其在Java编程语言中实现的课程。这门课程由清华大学的邓俊辉教授主讲,旨在帮助学生掌握数据结构的基本概念,理解它们的工作原理,并能用Java语言进行实际操作。...
例如,在`list_SqList`中,我们可能会看到一个实现了顺序表操作的类,如`SequentialList`,它使用数组作为底层数据结构。在`list_linkList`中,可能有一个`SingleLinkedList`类,它实现了单链表的节点和操作。而在`...