1、MyList 接口
/**
* 用于 LinkedList 继承
*
* @param <E>
*/
public interface MyList<E> {
/**
* 将指定节点追加到此列表的末尾
*
* @param element : 节点中存储的数据
* @return : 返回是否插入成功 boolean
*/
public boolean add(E element);
/**
* 在此列表中的指定位置插入指定节点。
* 当前处于移动中的节点(如果有)和右方的任何后续节点(向其索引添加节点)移动。
*
* @param index : 要添加的节点位置
* @param element : 节点中存储的数据
*/
public void add(int index, E element);
/**
* 获取指定索引位置的节点
*
* @param index : 要获取的节点位置
* @return : 返回获取的节点数据 element
*/
public E get(int index);
/**
* 替换指定索引位置的节点
*
* @param index : 要替换的节点位置
* @param element : 要替换的节点数据 element
* @return : 返回旧的节点数据 element
*/
public E set(int index, E element);
/**
* 移除指定索引位置的节点
*
* @param index : 要移除的节点位置
* @return : 返回被移除的节点数据 element
*/
public E remove(int index);
/**
* 获取 LinkedList 大小, 即 index + 1
*
* @return
*/
public int size();
/**
* 清空链表
*/
public void clear();
}
2、MyLinkedList 实现类
public class MyLinkedList<E> implements MyList<E> {
/**
* 0.01 链表头节点
*/
private Node head;
/**
* 0.02 链表尾节点
*/
private Node tail;
/**
* 0.03 MyLinkedList 的大小(它包含的Node数)
*/
private int size;
/**
* 0.04 初始化双向链表结构
*/
public MyLinkedList() {
}
/**
* 1.00 将 element 作为最后一个节点进行连接
*
* @param element
*/
void linkLast(E element) {
//① 获取链表尾节点
Node<E> t = tail;
//② 新建一个 Node 节点, 并将 element 数据放入其中,
// 该 Node 节点的上一个链接指向旧的 “tail 链表尾节点”,
// 该 Node 节点的下一个链接指向 null 节点
Node<E> newNode = new Node<>(t, element, null);
//③ 将 ”新的 Node 节点“ 作为 “tail 链表尾节点”
tail = newNode;
//④ 判断 旧的 “tail 链表尾节点” 是否存在
if (t == null) {
//⑤ 不存在, 将 ”新的 Node 节点“ 作为 “head 链表头节点”
head = newNode;
} else {
//⑥ 存在, 将 ”① 获取链表尾节点 t“ 的下一个链接指向 ”新的 Node 节点“
t.next = newNode;
}
//⑦ MyLinkedList 的大小(它包含的Node数)+ 1
size++;
}
/**
* 2.01 判断 add() 方法的 index 是否合法
*
* @param index : 要处理的索引值
*/
private void rangCheckForAdd(int index) {
// 判断 index 是否合法
if (index < 0 || index > size) {
// 抛出 数组下标越界异常.
throw new IndexOutOfBoundsException();
}
}
/**
* 2.02 判断 index 是否合法
*
* @param index : 要处理的索引值
*/
private void rangCheck(int index) {
// 判断 index 是否合法
if (index < 0 || index >= size) {
// 抛出 数组下标越界异常.
throw new IndexOutOfBoundsException();
}
}
/**
* 2.03 在指定节点索引处返回(非空)节点, 如 : Node<E> succ.
*
* @param index : 要添加的节点位置
* @return : 返回位置为 index 的 Node 节点
*/
private Node<E> node(int index) {
// assert isElementIndex(index);
//① 要返回的 Node 节点
Node<E> x;
//② 假定传入的 index 小于 size/2
if (index < (size >> 1)) {
//③ 要返回的 Node 节点 = ”链表头“ 节点
x = head;
//④ 循环遍历, 直到找到位置为 index 的 Node 节点
for (int i = 0; i < index; i++) {
x = x.next;
}
} else {
//⑤ 要返回的 Node 节点 = ”链表尾“ 节点
x = tail;
//⑥ 循环遍历, 直到找到位置为 index 的 Node 节点
for (int i = size - 1; i > index; i--) {
x = x.previous;
}
}
//返回位置为 index 的 Node 节点
return x;
}
/**
* 2.03 在非空节点 succ 之前插入节点数据 element
*
* @param element : 要插入的节点数据 element
* @param succ : 在 Node<E> succ 前插入节点
*/
private void linkBefore(E element, Node<E> succ) {
//① 获取 ”非空节点 succ“ 链接指向的上一个节点
// assert succ != null;
Node<E> prev = succ.previous;
//② 新建一个 Node 节点, 并将 element 数据放入其中,
// 该 Node 节点的上一个链接指向 ”非空节点 succ “ 链接指向的上一个节点,
// 该 Node 节点的下一个链接指向 succ 节点
Node<E> newNode = new Node<>(prev, element, succ);
//③ ”非空节点 succ“ 的上一个链接指向 ”新的 Node 节点“
succ.previous = newNode;
//④ 判断 ”①非空节点 succ “ 链接指向的上一个节点 是否存在
if (prev == null) {
//⑤ 不存在, 将 ”新的 Node 节点“ 作为 “head 链表头” 节点
head = newNode;
} else {
//⑥ 存在, 将 “ ”①非空节点 succ“ 连接的上一个节点的下一个节点链接” 指向 ”新的 Node 节点“
prev.next = newNode;
}
//⑦ MyLinkedList 的大小(它包含的Node数)+ 1
size++;
}
/**
* 2.04 取消链接非空节点 x
*
* @param x : 要移除的 Node<E> x 节点
* @return : 被移除的 Node<E> x 节点的 element 数据
*/
private E unlink(Node<E> x) {
// assert x != null;
//① 获取要移除的 Node<E> x 节点的 element 数据
E element = x.element;
//② 获取要移除的 Node<E> x 节点链接指向的 下一个节点
Node<E> next = x.next;
//③ 获取要移除的 Node<E> x 节点链接指向的 上一个节点
Node<E> prev = x.previous;
//④ 如果 Node<E> x 节点链接指向的上一个节点为空节点, 即: 为头节点
if (prev == null) {
//⑤ 将头节点设为 Node<E> x 节点链接指向 的下一个节点, 即: 当前节点的下一个节点
head = next;
} else { //⑥ 否则 Node<E> x 节点链接指向的上一个节点不是空节点
//⑦ 将要移除的 Node<E> x 节点链接指向的 “上一个节点” 的下一个节点
// 指向要移除的 Node<E> x 节点链接指向的 ”下一个节点“
prev.next = next;
//⑧ 将要移除的 Node<E> x 节点链接指向指向的 上一个节点链接 置为 null
x.previous = null;
}
//④ 如果 Node<E> x 节点指向的下一个节点为空节点, 即: 为尾节点
if (next == null) {
//⑤ 将尾节点设为 Node<E> x 节点链接指向 的上一个节点, 即: 当前节点的上一个节点
tail = prev;
} else { //⑥ 否则 Node<E> x 节点链接指向的下一个节点不是空节点
//⑦ 将要移除的 Node<E> x 节点链接指向的 “下一个节点” 的上一个节点
// 指向要移除的 Node<E> x 节点链接指向的 ”上一个节点“
next.previous = prev;
//⑧ 将要移除的 Node<E> x 节点链接指向的 下一个节点链接 置为 null
x.next = null;
}
//⑨ 将将要移除的 Node<E> x 节点的 element 置为 null
x.element = null;
//⑩ MyLinkedList 的大小(它包含的Node数)-1
size--;
// 返回被移除的 Node<E> x 节点的 element
return element;
}
/**
* 1.10 将指定节点追加到此列表的末尾
*
* @param element : 数据
* @return
*/
@Override
public boolean add(E element) {
//将 element 作为最后一个节点进行连接
linkLast(element);
return true;
}
/**
* 2.10 在此列表中的指定位置插入指定节点。
* 将当前位于该位置的节点(如果有)和右下的任何后续节点(向其索引添加一个节点)移位。
*
* @param index : 要添加的节点位置
* @param element : 节点存储的 element 数据
*/
@Override
public void add(int index, E element) {
//① 判断 index 是否合法
rangCheckForAdd(index);
//② 如果 index 等于链表长度
if (index == size) {
//③ 直接追加到此列表的末尾
linkLast(element);
} else {
//⑤ 在此列表中的指定位置插入
linkBefore(element, node(index));
}
}
/**
* 2.11 获取指定索引位置的节点存储的 element 数据
*
* @param index : 要获取的节点位置
* @return : 返回 Node 节点中存储的 element 数据
*/
@Override
public E get(int index) {
//① 判断 index 是否合法
rangCheck(index);
//② 返回 Node 节点中存储的 element 数据
return node(index).element;
}
/**
* 2.12 替换指定索引位置的节点
*
* @param index : 要替换的节点位置
* @param element : 要替换的 节点数据
* @return : 返回 被替换的 Node 节点中的 element 数据
*/
@Override
public E set(int index, E element) {
//① 判断 index 是否合法
rangCheck(index);
Node<E> x = node(index);
//② 获取指定索引位置的 Node 节点中的 element 数据
E oldValue = x.element;
//③ 用 ”传入的 element“ 替换对应索引中的 Node 节点中的 element 数据
x.element = element;
//⑤ 返回被替换的 Node 节点中的 element 数据
return oldValue;
}
/**
* 2.13 移除指定索引位置的节点
*
* @param index : 要移除的节点位置
* @return : 返回 被移除的 Node 节点中的 element 数据
*/
@Override
public E remove(int index) {
//① 判断 index 是否合法
rangCheck(index);
//② 移除指定节点, 并返回被移除节点的 element 数据
return unlink(node(index));
}
/**
* 2.14 MyLinkedList 的大小(它包含的 Node 数)
*
* @return : 返回包含的 Node 数
*/
@Override
public int size() {
return size;
}
/**
* 2.15 清空 链表
*/
@Override
public void clear() {
// 清除节点之间的所有链接是"不必要的",但:
// 如果丢弃的节点居住在多代,则帮助生成 GC
// 一定要释放内存,即使有一个可访问的迭代器
//① 让 x = head 链表头, 只要 x 不为 null 就一直遍历
for (Node<E> x = head; x != null; ) {
//② 取出当前节点链接指向的 下一个节点
Node<E> next = x.next;
//③ 将当前节点的 element 数据置为空
x.element = null;
//④ 将 当前节点与下一节点的链接 置为空
x.next = null;
//⑤ 将 当前节点与上一节点的链接 置为空
x.previous = null;
//⑤ 将当前节点替换为 下一节点
x = next;
}
//⑥ 将 头节点和尾节点 都置为 null
head = tail = null;
//⑦ 将 MyLinkedList 长度置为 0
size = 0;
}
/**
* 0.00 链表就是一系列存储数据元素的单元通过指针串接起来形成的.
* <p>
* 双向链表中的每个节点除了要保存它的下一个节点对象的引用以外,
* 还会保存一个它前一个节点对象的引用,这样就可以实现双向查找数据.
*
* @param <E>
*/
private static class Node<E> {
/**
* 0.00 用于数据元素的存储
*/
private E element;
/**
* 0.00 上一个节点对象的链接
*/
private Node<E> previous;
/**
* 0.0 下一个节点对象的链接
*/
private Node<E> next;
Node(Node<E> previous, E element, Node<E> next) {
this.previous = previous;
this.element = element;
this.next = next;
}
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer("Size = " + size + ", Data = ").append("[");
// 拼接第一个元素
sb.append(this.head.element);
Node node = this.head.next;
// 获取当前节点链接指向的下一个节点
for (int i = 0; i < size - 1; i++) {
sb.append(", " + node.element);
node = node.next;
}
sb.append("]");
return sb.toString();
}
}
3、Student 测试实体类
public class Student {
private int id;
private String name;
private String address;
public Student(int id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
@Override
protected void finalize() {
System.out.println("This desctory!");
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", Address='" + address + '\'' +
'}';
}
}
4、测试类
public class Test {
public static void main(String[] args) {
MyLinkedList<Student> list = new MyLinkedList<>();
list.add(new Student(1, "梨花1", "龙门客栈1"));
list.add(new Student(2, "梨花2", "龙门客栈2"));
list.add(new Student(3, "梨花3", "龙门客栈3"));
list.add(new Student(4, "梨花4", "龙门客栈4"));
System.out.println("添加元素" + list);
list.add(2, new Student(10, "梨花10", "龙门客栈10"));
System.out.println("插入元素" + list);
System.out.println("获取指定索引位置的元素" + list.get(2));
System.out.println("被替换的元素" + list.set(2, new Student(1000, "梨花1000", "龙门客栈1000")));
System.out.println("替换完成的LinkedList" + list);
System.out.println("被移除的元素" + list.remove(2));
System.out.println("移除元素完成的LinkedList" + list);
list.clear();
}
}