链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。
链表可分为单向链表和双向链表。
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。
一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。
Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。
与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。
以下情况使用 ArrayList :
- 频繁访问列表中的某一个元素。
- 只需要在列表末尾进行添加和删除元素操作。
以下情况使用 LinkedList :
- 你需要通过循环迭代来访问列表中的某些元素。
- 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。
LinkedList 继承了 AbstractSequentialList 类。
{AbstractSequentialList 继承于 AbstractList ,提供了对数据元素的链式访问而不是随机访问。}
LinkedList 实现了 Queue 接口(Queue接口与List、Set同一级别,都是继承了Collection接口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用),可作为队列使用。
{
队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进
行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为
空队列。
队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个
元素就是说,队列以一种先进先出的方式管理数据,如果你试图向一个 已经满了的阻塞队列中添
加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞.在多线程进行合作时,
阻塞队列是很有用的工具。工作者线程可 以定期地把中间结果存到阻塞队列中而其他工作者线线
程把中间结果取出并在将来修改它们。队列会自动平衡负载。如果第一个线程集运行得比第二个
慢,则第二个 线程集在等待结果时就会阻塞。如果第一个线程集运行得快,那么它将等待第二个
线程集赶上来。下表显示了jdk1.5中的阻塞队列的操作:
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞
remove、element、offer 、poll、peek 其实是属于Queue接口。
Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,
使用poll()来获取并移出元素。它们的优点是通过返回值可以判断成功与否,add()和remove()方法
在失败的时候会抛出异常。 如果要使用前端而不移出该元素,使用element()或者peek()方法。
值得注意的是LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。
}
LinkedList 实现了 List 接口,可进行列表的相关操作。
{
List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索
引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许
有相同的元素。
List 接口存储一组不唯一,有序(插入顺序)的对象。
}
LinkedList 实现了 Deque 接口,可作为队列使用。
{
Deque(java.util.Deque)接口代表着双向队列,意思就是可以从队列的两端增加或者删除元素,
Deque就是双向Queue的意思。
}
LinkedList 实现了 Cloneable 接口,可实现克隆。
{
Cloneable接口是Java开发中常用的一个接口, 它的作用是使一个类的实例能够将自身拷贝到另一
个新的实例中,注意,这里所说的“拷贝”拷的是对象实例,而不是类的定义,进一步说,拷贝的是
一个类的实例中各字段的值。
在开发过程中,拷贝实例是常见的一种操作,如果一个类中的字段较多,而我们又采用在客户端中
逐字段复制的方法进行拷贝操作的话,将不可避免的造成客户端代码繁杂冗长,而且也无法对类中
的私有成员进行复制,而如果让需要具备拷贝功能的类实现Cloneable接口,并重写clone()方法,
就可以通过调用clone()方法的方式简洁地实现实例拷贝功能。
}
LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。
{
Serializable接口概述
Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口。
Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义(用于标识可序列
化的VO类)。实现Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通
过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件
中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重
新创建对象。
序列化:把原本在内存中的对象状态 变成可存储或传输的过程称之为序列化。序列化之后,就可
以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。序列化前的对象和反序列化后得
到的对象,内容是一样的(且对象中包含的引用也相同),但两个对象的地址不同。换句话说,序列
化操作可以实现对任何可Serializable对象的”深度复制(deep copy)"。
通俗理解序列化与反序列化:以搬桌子为例,桌子太大了不能通过比较小的门,我们要把它拆了再
运进去,这个拆桌子的过程就是序列化。同理,反序列化就是等我们需要用桌子的时候再把它组合
起来,这个过程就是反序列化。
什么情况下需要序列化?
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中,以便可以在以后重新创建精确
的副本;
b)当你想用套接字在网络上传送对象的时候(从一个应用程序域发送到另一个应用程序域中);
c)当你想通过RMI传输对象的时候;
注意事项:
a)序列化时,只对对象的状态进行保存,而不管对象的方法;
b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
d)并非所有的对象都可以序列化。
e) 序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,不能序列化。添加了static、transient关键字后的变量不能序列化。
}
LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下:
// 引入 LinkedList 类 import java.util.LinkedList; LinkedList<E> list = new LinkedList<E>(); // 普通创建方法 或者 LinkedList<E> list = new LinkedList(Collection<? extends E> c); // 使用集合创建链表
{
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针
链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态
生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指
针域。
链表与数组的区别
数组,是相同数据类型的元素按一定顺序排列的集合。根据概念我们可以知道数组在内存中连续,
链表不连续;由于不同的存储方式导致数组静态分配内存,链表动态分配内存,数组元素在栈区,
链表元素在堆区;由于数组在内存中连续,我们可以利用下标定位,时间复杂度为O(1),链表定位
元素时间复杂度O(n);但是由于数组的连续性数组插入或删除元素的时间复杂度O(n),链表的时间
复杂度O(1)。
总结一下,数组和链表的区别如下
1.数组静态分配内存,链表动态分配内存
2.数组在内存中连续,链表不连续
3.数组元素在栈区,链表元素在堆区
4.数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
5.数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
存储数据主要是线性结构、链式结构。
线性结构也叫顺序结构,他是把相邻的节点存储在物理位置相邻的存储单元里,通常借助于程序设
计中的数组方式来具体实现,所以对应的,数组存储的优缺点也是线性结构存储的优缺点。下面再
详细说说两种存储方式的优缺点。
链式结构,它不要求逻辑上相邻的节点在物理位置上也相邻,节点间的逻辑关系是由附加的指针字
段表示的,通常借助于程序设计中的指针结构来实现。
线性结构的优点是可以实现随机读取,时间复杂度为O(1),空间利用率高,缺点是进行插入和删除
操作时比较麻烦,时间复杂度为O(n),同时容量受限制,需要事先确定容量大小,容量过大,浪费
空间资源,过小不能满足使用要求,会产生溢出问题。
链式存储结构的优点主要是插入和删除非常简单,前提条件是知道操作位置,时间复杂度是O(1),
但如果不知道造作位置则要定位元素,时间复杂度为O(n),没有容量的限制,可以使用过程中动态
分配的分配内存空间,不用担心溢出问题,但是它并不能实现随机读取,同时空间利用率不高。
}
常用方法
方法 | 描述 |
---|---|
public boolean add(E e) | 链表末尾添加元素,返回是否成功,成功为 true,失败为 false。 |
public void add(int index, E element) | 向指定位置插入元素。 |
public boolean addAll(Collection c) | 将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。 |
public boolean addAll(int index, Collection c) | 将一个集合的所有元素添加到链表的指定位置后面,返回是否成功,成功为 true,失败为 false。 |
public void addFirst(E e) | 元素添加到头部。 |
public void addLast(E e) | 元素添加到尾部。 |
public boolean offer(E e) | 向链表末尾添加元素,返回是否成功,成功为 true,失败为 false。 |
public boolean offerFirst(E e) | 头部插入元素,返回是否成功,成功为 true,失败为 false。 |
public boolean offerLast(E e) | 尾部插入元素,返回是否成功,成功为 true,失败为 false。 |
public void clear() | 清空链表。 |
public E removeFirst() | 删除并返回第一个元素。 |
public E removeLast() | 删除并返回最后一个元素。 |
public boolean remove(Object o) | 删除某一元素,返回是否成功,成功为 true,失败为 false。 |
public E remove(int index) | 删除指定位置的元素。 |
public E poll() | 删除并返回第一个元素。 |
public E remove() | 删除并返回第一个元素。 |
public boolean contains(Object o) | 判断是否含有某一元素。 |
public E get(int index) | 返回指定位置的元素。 |
public E getFirst() | 返回第一个元素。 |
public E getLast() | 返回最后一个元素。 |
public int indexOf(Object o) | 查找指定元素从前往后第一次出现的索引。 |
public int lastIndexOf(Object o) | 查找指定元素最后一次出现的索引。 |
public E peek() | 返回第一个元素。 |
public E element() | 返回第一个元素。 |
public E peekFirst() | 返回头部元素。 |
public E peekLast() | 返回尾部元素。 |
public E set(int index, E element) | 设置指定位置的元素。 |
public Object clone() | 克隆该列表。 |
public Iterator descendingIterator() | 返回倒序迭代器。 |
public int size() | 返回链表元素个数。 |
public ListIterator listIterator(int index) | 返回从指定位置开始到末尾的迭代器。 |
public Object[] toArray() | 返回一个由链表元素组成的数组。 |
public T[] toArray(T[] a) | 返回一个由链表元素转换类型而成的数组。 |