Java 类库中其实是提供了链表的实现类的,但是如果自己来实现会不会很有成就感呢?

我们知道,Java 官方是没有指针的概念的,当然我们可以把对象的引用理解为指针,虽然与 C 或 C++ 中的指针概念不尽相同。想要自己实现链表,最重要的一步就是怎么表示一个链表中的结点。在 Java中,我们可以定义一个专门表示结点的类,最好是内部类,确保类的封装性与完整性。此结点类可定义如下:

class Node {
	public Node(String name, int age) {
		this.name = name;
		this.age = age;
		this.next = null;
	}
	
	private String name;
	private int age;
	private Node next;
}

此处结点的数据类型可以固定写在该类中,毕竟结点中存放的数据一般都是相同类型的。

确定了结点的表示方法,余下的关键问题就是链表的表示方法。我们可以定义一个头结点,初始化为null。然后对于链表增删改查操作都要在此基础上进行。

链表源代码如下:

/**
 * 实现自己的链表类
 * 
 * @author Wll
 *
 */
public class LinkedListDemo {
	public static void main(String[] args) {
		LinkedListDemo list = new LinkedListDemo();

		Node tom = new Node("Tom", 20);
		Node jack = new Node("Jack", 21);
		Node michael = new Node("Michael", 18);
		Node lisa = new Node("Lisa", 20);
		Node kitty = new Node("Kitty", 19);

		list.add(tom);
		list.add(jack);
		list.add(michael);
		list.add(lisa);
		list.add(kitty);

		System.out.println("+++++链表长度+++++");
		System.out.println(list.size());

		System.out.println("\n+++++遍历链表结果+++++");
		list.traverse();

		System.out.println("\n+++++删除第 5个结点+++++");
		try {
			System.out.println(list.delete(4));
			list.traverse();
		} catch (MyIndexOutOfBoundsException e) {
			System.out.println(e.getMessage());
		}

		System.out.println("\n+++++修改第 3 个结点+++++");
		try {
			System.out.println(list.modify(2, "Modified", 100));
			list.traverse();
		} catch (MyIndexOutOfBoundsException e) {
			System.out.println(e.getMessage());
		}

		System.out.println("\n+++++查找第 6 个结点+++++");
		try {
			System.out.println(list.indexOf(5));
		} catch (MyIndexOutOfBoundsException e) {
			System.out.println(e.getMessage());
		}

		System.out.println("\n+++++插入第 3 个结点+++++");
		Node lucy = new Node("Lucy", 22);
		try {
			System.out.println(list.insertAt(2, lucy));
			list.traverse();
		} catch (MyIndexOutOfBoundsException e) {
			System.out.println(e.getMessage());
		}

		System.out.println("\n+++++重置(清空)链表+++++");
		System.out.println(list.reset());
		list.traverse();
	}

	/**
	 * 在最后一个结点后面添加一个新结点
	 * 
	 * @param node
	 *            待添加的新结点
	 * @return 若插入成功返回 true,否则 false
	 */
	public boolean add(Node newNode) {
		Node p = head;

		if (head == null) { // 添加到头部
			head = newNode;
			return true;
		} else { // 添加到末尾结点后面
			// 这里是 p.next,因为如果是 p, 则 while 循环结束后 p 必定为 null,p.next 就会空指针异常
			while (p.next != null) {
				p = p.next;
			}
			p.next = newNode;
			return true;
		}
	}

	/**
	 * 在任意位置插入一个新结点
	 * 
	 * @param index
	 *            插入的位置
	 * @param newNode
	 *            插入的结点
	 * @return 若插入成功返回 true,否则 false
	 * @throws IndexOutOfBoundsException 
	 */
	public boolean insertAt(int index, Node newNode) throws MyIndexOutOfBoundsException {
		Node p = head;
		int count = 1;

		if (index < 0 || index > this.size()) {
			throw new MyIndexOutOfBoundsException("插入异常,请检查下标范围!");
		}

		if (index == 0) {
			newNode.next = head;
			head = newNode;
			return true;
		} else {
			while (p != null) {
				if (count++ == index) {
					newNode.next = p.next;
					p.next = newNode;
					return true;
				}
				p = p.next;
			}
			return false;
		}
	}

	/**
	 * 删除一个已有结点
	 * 
	 * @param index
	 *            该节点的索引,以 0 开始。第 2 个结点索引就是 1
	 * @return 若删除成功返回 true,否则返回 false
	 * @throws IndexOutOfBoundsException 
	 */
	public boolean delete(int index) throws MyIndexOutOfBoundsException {
		int count = 1;
		Node p = head;

		if (index < 0 || index > this.size() - 1) {
			throw new MyIndexOutOfBoundsException("删除异常,请检查下标范围!");
		}

		if (index == 0) {
			head = p.next;
			return true;
		} else {
			while (p != null) {
				if (count++ == index) {
					p.next = p.next.next;
					return true;
				}
				p = p.next;
			}
			return false;
		}
	}

	/**
	 * 修改结点内容
	 * 
	 * @param index
	 *            结点所在索引
	 * @param name
	 *            结点 name 属性
	 * @param age
	 *            结点 age 属性
	 * @return 若修改成功返回 true,否则返回 false
	 * @throws IndexOutOfBoundsException 
	 */
	public boolean modify(int index, String name, int age) throws MyIndexOutOfBoundsException {
		Node p = head;
		int count = 0;

		if (index < 0 || index > this.size() - 1) {
			throw new MyIndexOutOfBoundsException("修改异常,请检查下标范围!");
		}

		while (p != null) {
			if (count++ == index) {
				p.name = name;
				p.age = age;
				return true;
			}
			p = p.next;
		}

		return false;
	}

	/**
	 * 查找指定索引处的结点
	 * 
	 * @param index
	 *            结点索引
	 * @return 索引处结点
	 * @throws IndexOutOfBoundsException
	 */
	public Node indexOf(int index) throws MyIndexOutOfBoundsException {
		Node p = head;
		int count = 0;

		if (index < 0 || index > this.size() - 1) {
			throw new MyIndexOutOfBoundsException("查询异常,请检查下标范围!");
		}

		while (p != null) {
			if (count++ == index) {
				return p;
			}
			p = p.next;
		}

		return null;
	}

	/**
	 * 获得链表的大小
	 * 
	 * @return 链表长度
	 */
	public int size() {
		int count = 0;
		Node p = head;

		while (p != null) {
			count++;
			p = p.next;
		}

		return count;
	}

	/**
	 * 遍历链表
	 */
	public void traverse() {
		Node p = head;
		int count = 0;

		while (p != null) {
			System.out.println(p);
			count++;
			p = p.next;
		}

		if (count == 0) {
			System.out.println("链表为空!");
		}
	}

	/**
	 * 重置链表
	 */
	public boolean reset() {
		head = null;
		return true;
	}

	private Node head = null;

	/**
	 * 内部结点类
	 * 
	 * @author Wll
	 *
	 */
	private static class Node {
		public Node(String name, int age) {
			this.name = name;
			this.age = age;
			this.next = null;
		}

		@Override
		public String toString() {
			return this.getClass().getName() + "[Name=" + this.name + ", Age=" + this.age + "]";
		}

		private String name;
		private int age;
		private Node next;
	}
}

class MyIndexOutOfBoundsException extends Exception {
	private static final long serialVersionUID = 1L;

	public MyIndexOutOfBoundsException(String msg) {
		this.msg = msg;
	}

	@Override
	public String getMessage() {
		return this.msg;
	}

	private String msg;
}

虽然上边的代码可以实现链表的常用操作,但是可以想象当链表中的结点数太大时性能会有所下降。下面是对于提升上面代码执行效率的一点思考。

首先,每一次在末尾添加结点都会遍历整个链表。其次,对于链表长度的获取也需要遍历整个链表。这两个操作都会多耗费很多时间。所以更好的解决办法是定义一个变量记录链表长度,每次添加或删除结点的时候就将此变量加 1 或者减 1。除此之外,再定义一个指向末尾结点的引用,每次添加或删除结点时对应将此引用修改,这样在末尾添加结点时就可以节省遍历链表的时间。