Java的List接口有两个比较常用的实现类,分别是ArrayList和LinkedList。关于它们的区别主要是ArrayList使用动态数组存储数据,而LinkedList使用相互链接的节点存储数据。关于它们的性能比较,具体要看进行的是什么操作,此处不赘述。
相关知识:
1、链式结构是一种数据结构,它使用对象引用变量来创建对象之间的链接。
2、链表由一些对象构成,其中每个对象指向了链表的下一个对象。
3、链表会按需动态增长,因此在本质上,它没有容量限制。
4、访问链表的元素的唯一方式是,从第一个元素开始,顺着该链表往下进行。
5、改变引用顺序是维护链表的关键。
6、处理链表的首节点需要进行一些特殊处理。
7、存储在集合中的对象不应该含有基本数据结构的任何实现细节。
8、可以使用栈来模拟递归处理,以跟踪恰当的数据。
下面我们实现一个自己的MyLinkedList。
一、首先定义一个内部类,使用private修饰限定其只能在类内部使用,使用static修饰表示其是类成员无需依赖具体实例。
节点类有三个成员变量(LinkedList可以使用单向,在这里使用双向是为了以后扩展)
element:当前节点的元素
previous:指向当前节点的上一个节点
next:指向当前节点的下一个节点
// 链表的节点
private static class Node<E> {
// 节点元素
private E element;
// 节点上一个节点
private Node<E> previous;
// 节点下一个节点
private Node<E> next;
// 含参构造函数
public Node(E element, Node<E> previous, Node<E> next) {
this.element = element;
this.previous = previous;
this.next = next;
}
}
二、接着定义三个成员变量。
first:记录第一个元素,遍历(迭代)、删除元素时使用
last:记录最后一个元素,添加元素时使用
size:记录元素数量
// 第一个元素
private Node<E> first;
// 最后一个元素
private Node<E> last;
// 元素数量
private int size;
三、对外提供的API。
1、获取元素数量
/**
* 获取链表集合元素数量
* @return 数量
*/
public int size() {
return size;
}
2、判断链表是否为空
/**
* 判断是否为空:即元素个数是否为0
* @return 元素个数为0返回true,否则返回false
*/
public boolean isEmpty() {
return size == 0;
}
3、添加一个元素
先创建一个节点,然后判断last是否为空,是的话说明链表为空,该节点也是首节点,否的话把该节点设为last的下一节点,把该节点的上一节点设为last。接着将该元素设为last。最后将size加一。
/**
* 添加一个元素
* @param e
*/
public void add(E e) {
// 1、根据元素值创建一个节点node
Node<E> node = new Node<>(e, null, null);
// 2、定义一个变量l保存原来last节点
Node<E> l = last;
// 3、如果last为空,说明没有元素,将当前节点设为第一个元素
// 否则,将last的下一个元素指向node,并将node的上一个元素指向last
if (Objects.isNull(l)) {
first = node;
} else {
l.next = node;
node.previous = l;
}
// 4、设置node为最后一个节点
last = node;
// 5、数量+1
size++;
}
4、删除一个元素
将所有符合的元素都删掉,涉及到所删节点的前后节点重新链接。
/**
* 删除元素
* @param e
* @return 删除的个数
*/
public int remove(E e) {
// 1、从第一个节点first开始,遍历所有节点
Node<E> current;
int count = 0;
for (current = first; Objects.nonNull(current); current = current.next) {
// 将符合的元素都删掉,并且调整前面节点的下一个元素和后面节点的上一个元素
if (Objects.equals(e, current.element)) {
Node<E> p = current.previous;
Node<E> n = current.next;
if (Objects.nonNull(p)) p.next = n;
if (Objects.nonNull(n)) n.previous = p;
size--;
count++;
}
}
return count;
}
5、迭代所有元素
需要创建一个内部类迭代器并提供接口给外部创建该类实例。迭代器提供两个方法:判断是否还有元素和获取下一个元素。
创建迭代器时,需要把当前对象作为参数,并将current设置为链表的第一个节点。next方法需要将current指向下一节点。
/**
* 获取一个迭代器
* @return 迭代器
*/
public Iterator<E> iterator() {
return new Iterator<>(this);
}
/**
* 迭代器
* @author z_hh
* @date 2018年8月9日
*/
public static class Iterator<E> {
private MyLinkedList<E> linkedList;
private Node<E> current;
private boolean hasNext = false;
// 私有构造函数,传入当前链表集合对象
private Iterator(MyLinkedList<E> linkedList) {
this.linkedList = linkedList;
current = this.linkedList.first;
}
/**
* 是否还有元素
* @return 还有下一个元素返回true,否则返回false
*/
public boolean hasNext() {
hasNext = Objects.nonNull(current);
return hasNext;
}
/**
* 获取下一个元素,并把指针下移一位
* @return
*/
public E next() {
if (!hasNext) {
throw new UnsupportedOperationException("当前没有元素可以迭代");
}
Node<E> c = current;
current = c.next;
return c.element;
}
}
6、测试
/**
* 自定义链表测试
* @author z_hh
* @time 2018年8月11日
*/
public class LinkedTest {
public static void main(String[] args) {
MyLinkedList<String> linkedList = new MyLinkedList<>();
linkedList.add("里");
linkedList.add("奥");
linkedList.add("梅");
linkedList.add(null);
linkedList.add("西");
linkedList.add("煌");
linkedList.add(null);
linkedList.add("华");
System.out.println("元素数量:" + linkedList.size());
System.out.println("元素为:");
Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
System.out.println("删除元素:煌,共删除" + linkedList.remove("煌") + "个");;
System.out.println("删除元素:null,共删除" + linkedList.remove(null) + "个");;
System.out.println("元素数量:" + linkedList.size());
System.out.println("元素为:");
Iterator<String> iterator2 = linkedList.iterator();
while (iterator2.hasNext()) {
System.out.print(iterator2.next() + " ");
}
}
}
结果为