上节课我们自己手动实现了一个ArrayList,底层是用数组实现的。但是,这种实现的缺点显而易见,太浪费空间了。每次扩容直接扩充一倍,浪费的空间太多了,不太好。
这节课我们还是想要实现一个允许动态增删改查元素的容器,只不过这次我们换一种方式。
说到容器,我们可以想一下,我们能不能自己设计一个容器来存放对象而不是使用数组呢?
比如下面这种形式:
class Node{
Object elem;
}
我们的Node实际上就相当于一个容器,里面可以存放对象了。但是有个问题,如何通过这个容器可以访问同类型的其他容器?
很简单,我们再给它加一个成员,类型就是Node
class Node{
Object elem;
Node next; //指向下一个Node
}
之后我们再把必要的构造器和修饰符加上,
//类型转换太麻烦了,我们加个泛型 class Node<E>{ //这里干脆把属性声明成public public E elem; public Node<E> next; public Node(){ // elem = null; // next = null; //我们可以直接调用下面那个构造器 this(null); } public Node(E elem) { this.elem = elem; next = null; } }
OK,这样我们的存放元素的容器就做好了,接下来我们再来想一下:
Node<Integer> node = new Node<Integer>(1);
Node<Integer> node2 = new Node<Integer>(2);
Node<Integer> node3 = new Node<Integer>(3);
node.next = node2;
node2next = node3;
.
这个看起来是不是很像一条链?
这种结构我们就称之为链表(LinkdeList)
链表必须要有个头结点
Demo:
package helloworld; //类型转换太麻烦了,我们加个泛型 class Node<E>{ //这里干脆把属性声明成public public E elem; public Node<E> next; public Node(){ // elem = null; // next = null; //我们可以直接调用下面那个构造器 this(null); } public Node(E elem) { this.elem = elem; next = null; } } public class MyLinkedList<E> { private Node<E> head; //头结点 private Node<E> tail; //尾结点,这个可以通过代码获取,但为了方便我就直接定义一下了 int size = 0; public MyLinkedList() { head = null; tail = head; } //核心方法 public void add(E e) { //添加结点的步骤: //写完就可以发现问题,tail一开始指向的是head,而head一开始是null,所以要先做个判断 if (this.head == null) { this.head = new Node<>(e); this.tail = head; //这里必须要让tail指向head size++; return; } /** * 1. 将要添加的对象e封装到一个结点里 * * 2. 把新结点添加到尾结点的末尾 * * 3. 让尾结点指向新添加的结点 * * 4. 别忘了size++ */ Node<E> node = new Node<>(e); this.tail.next = node; this.tail = node; size++; } //将结点插入到想要的位置 public void insert(E e , int index) { //在ArrayList里也写过这个,首先肯定是对index进行判断 if (index < 0 || index > size) { return; } //如果head为null呢 //由于head为null,所以可以插入的位置只有0,即调用add方法即可 if (head == null) { add(e); return; //指向完add后直接return就行 } //现在有个问题,怎么插入? /** * 1. 找到想要插入位置的前一个结点 * * 2. 将e封装成结点 * * 3. 让e的next指向刚才找到结点的后一个结点 * * 4. 让刚才找到的结点的next指向e */ //因为是找前一个,所以是index - 1 Node<E> prevNode = getNode(index - 1); Node<E> node = new Node<>(e); node.next = prevNode.next; prevNode.next = node; //最后别忘了size++ size++; } //删除 public E delete(int index) { //和insert反着来就行 Node<E> prevNode = getNode(index - 1); if (prevNode.next == null) return null; Node<E> node = prevNode.next; if (node == tail) tail = prevNode; //让尾结点指向前一个结点 prevNode.next = node.next; node.next = null; //让node指向的对象彻底不在链表之中 //不要忘了size-- size--; //但还有个问题,如果删的是尾结点呢 return node.elem; } //取数据 public E get(int index) { Node<E> node = getNode(index); if (node != null) return node.elem; return null; } private Node<E> getNode(int index) { if (index < 0 || index >= size) return null; else { Node node = head; for (int i = 0; i < index; i++) { node = node.next; } return node; } } public void set(int index, E e) { Node<E> node = getNode(index); if (node != null) { node.elem = e; } } public Object[] toArray() { if (size == 0) return new Object[0]; Object[] arr = new Object[size]; Node<E> node = head; for (int i = 0; i < size; i++) { arr[i] = node.elem; node = node.next; } return arr; } public static void main(String[] args) { MyLinkedList<Integer> list = new MyLinkedList<>(); for (int i = 0; i < 10; i++) { list.add(i); } System.out.println(list.get(5)); list.insert(100,5); System.out.println(list.get(5)); //可以看到insert函数完成了 //接下来写删除 list.delete(5); System.out.println(list.get(5)); //可以看到删除函数也是正确的 list.set(5,30); System.out.println(list.get(5)); //修改函数也是正确的 //OK,增删改查都完成了,最后写个toArray就收工 } }