Java_模拟实现 LinkedList(内存图分析+底层代码实现)

模拟实现 LinkedList

在实现之前,我们需要知道有关链表的知识,有兴趣的朋友可以去查阅一下资料。
我会一步一步带着自己和大家一起实现,仅供参考。
链表分为:单向链表,双向链表和循环链表
如下图所示,就是一个双向链表图
Java_模拟实现 LinkedList(内存图分析+底层代码实现)_第1张图片
需求:我们将实现的功能主要有四个:
1)add(String element) 添加数据
2) get(int index) 根据索引获取数据
3) remove(int index) 根据索引删除数据
4)add(String element, int index) 根据索引插入元素

第一个功能:添加功能

另外附一张添加的内存图
Java_模拟实现 LinkedList(内存图分析+底层代码实现)_第2张图片

public class LinkedList {
	/*
	 * 	永远指向链表的首节点
	 */
	private Node firstNode;
	/*
	 * 	永远指向链表的尾节点
	 */
	private Node lastNode;
	
	/*
	 * 存放节点元素实际个数
	 */
	private int size;
	public int size() {
		return size;
	}
	
	/**
	 * 添加数据(在链表末尾追加)
	 * @param element 添加的数据
	 */
	public void add(String element) {
		// 1.如果链表不存在的情况
		if(null == firstNode) { // 当firstNode不存在,则证明链表不存在
			// 1.1把添加的元素封装为节点对象
			Node node = new Node(null, element, null);
			// 1.2把node设置为链表的首节点和尾节点
			firstNode = node;
			lastNode = node;
			// 1.3更新size实际存放元素的个数
			size++;
		}
		// 2.链表已经存在的情况
		else { 
			// 2.1把添加的元素封装为节点对象
			Node node = new Node(null, element, null);
			// 2.2让lastNode的next指向新添加的节点
			lastNode.next = node;
			// 2.3让node的prev指向lastNode
			node.prev = lastNode;
			// 2.4跟新链表的尾节点
			lastNode = node;
			// 2.5更新size实际存放元素的个数
			size++;
		}
	}
	/*
	 * 链表的节点类
	 */
	private class Node {
		/*
		 * 指向上一个节点
		 */
		private Node prev;
		/*
		 * 节点存储的数据
		 */
		private String data;
		/*
		 * 指向下一个节点
		 */
		private Node next;
		// 无参构造方法
		public Node() {}
		// 有参构造方法
		public Node(Node prev, String data, Node next) {
			this.prev = prev;
			this.data = data;
			this.next = next;
		}
	}
}

主方法测试类

public class Test01 {
	public static void main(String[] args) {
		LinkedList list1 = new LinkedList();
		list1.add("aa");
		list1.add("bb");
		list1.add("cc");
		list1.add("dd");
		// 遍历数组
		
	}
}

说明:由于还未添加展示功能,可通过Debug模式来看里面的元素是否添加进去了

第二个功能:根据索引查看当前元素

public class LinkedList {
	/*
	 * 	永远指向链表的首节点
	 */
	private Node firstNode;
	/*
	 * 	永远指向链表的尾节点
	 */
	private Node lastNode;
	
	/*
	 * 存放节点元素实际个数
	 */
	private int size;
	public int size() {
		return size;
	}
	
	/**
	 * 添加数据(在链表末尾追加)
	 * @param element 添加的数据
	 */
	public void add(String element) {
		// 1.如果链表不存在的情况
		if(null == firstNode) { // 当firstNode不存在,则证明链表不存在
			// 1.1把添加的元素封装为节点对象
			Node node = new Node(null, element, null);
			// 1.2把node设置为链表的首节点和尾节点
			firstNode = node;
			lastNode = node;
			// 1.3更新size实际存放元素的个数
			size++;
		}
		// 2.链表已经存在的情况
		else { 
			// 2.1把添加的元素封装为节点对象
			Node node = new Node(null, element, null);
			// 2.2让lastNode的next指向新添加的节点
			lastNode.next = node;
			// 2.3让node的prev指向lastNode
			node.prev = lastNode;
			// 2.4跟新链表的尾节点
			lastNode = node;
			// 2.5更新size实际存放元素的个数
			size++;
		}
	}
	
	 /**
	 * 根据索引获取对应的元素
	 * @param index 索引
	 * @return 返回索引对应的节点
	 */
	public String get(int index) {
		// 判断索引是否越界
		if(index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("索引越界异常");
		}
		// 2.1如果索引小于size/2,证明查找的元素在上半区
		if(index < size>>1) {
			// 从前往后找
			Node node = firstNode;
		for (int i = 0; i < index; i++) {
				node = node.next;
			}
		// 返回索引对应的节点
		return node.data;
		}
		// 2.2如果索引大于等于size/2,证明查找的元素在下半区
		else {
			// 从后往前找
			Node node = lastNode;
			for (int i = size - 1; i > index; i--) {
				node = node.prev;
			}
			// 返回索引对应的节点
			return node.data;
		}
	}
	
	private class Node {
		/*
		 * 指向上一个节点
		 */
		private Node prev;
		/*
		 * 节点存储的数据
		 */
		private String data;
		/*
		 * 指向下一个节点
		 */
		private Node next;
		// 无参构造方法
		public Node() {}
		// 有参构造方法
		public Node(Node prev, String data, Node next) {
			this.prev = prev;
			this.data = data;
			this.next = next;
		}
	}
}

主方法测试数据

public class Test01 {
	public static void main(String[] args) {
		LinkedList list1 = new LinkedList();
		list1.add("aa");
		list1.add("bb");
		list1.add("cc");
		list1.add("dd");
		// 遍历数组
		for (int i = 0; i < list1.size(); i++) {
			System.out.println(list1.get(i));
		}
	}
}

循环遍历输出:
aa
bb
cc
dd

因为我们后面还需要使用“根据索引获取对应的元素”,因此在下一个功能中我把它抽成方法

第三个功能 根据索引删除元素

附上一张内存图
Java_模拟实现 LinkedList(内存图分析+底层代码实现)_第3张图片

public class LinkedList {
	/**
	 * 永远指向链表的首节点
	 */
	private Node firstNode;
	/**
	 * 永远指向链表的尾节点
	 */
	private Node lastNode;
	/**
	 * 用于保存实际存放元素的个数
	 */
	private int size;
	/**
	 * 获取链表实际存放元素的个数
	 */
	public int size() {
		return size;
	}
	
	/**
	 * 添加数据(在链表末尾追加)
	 * @param element 添加的数据
	 */
	public void add(String element) {
		// 1.如果链表不存在的情况
		if(null == firstNode) { // 当firstNode不存在,则证明链表不存在
			// 1.1把添加的元素封装为节点对象
			Node node = new Node(null, element, null);
			// 1.2把node设置为链表的首节点和尾节点
			firstNode = node;
			lastNode = node;
			// 1.3更新size实际存放元素的个数
			size++;
		}
		// 2.链表已经存在的情况
		else { 
			// 2.1把添加的元素封装为节点对象
			Node node = new Node(null, element, null);
			// 2.2让lastNode的next指向新添加的节点
			lastNode.next = node;
			// 2.3让node的prev指向lastNode
			node.prev = lastNode;
			// 2.4跟新链表的尾节点
			lastNode = node;
			// 2.5更新size实际存放元素的个数
			size++;
		}
	}
	/**
	 * 根据索引获取元素值
	 * @param index 索引值
	 * @return 索引对应的元素值
	 */
	public String get(int index) {
		// 1.判断索引是否合法
		if(index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("索引越界异常:" + index);
		}
		// 2.根据索引获取节点
		Node node = node(index);
		// 3.返回节点对应数据
		return node.data;
	}
	/**
	 * 根据索引删除元素
	 * @param index 索引值
	 */
	public void remove(int index) {
		// 1.判断索引是否合法
		if(index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("索引越界异常:" + index);
		}
		// 2.获取index对应的节点对象
		Node node = node(index);
		// 3.判断node是否为首节点
		if(node == firstNode)
		{
			// 3.1把node的next节点设置为首节点
			firstNode = node.next;
			// 3.2把node的next节点设置为尾节点
			firstNode.prev = null;
		}
		// 4.判断node是否为尾节点
		else if (node == lastNode) {
			// 4.1 把node的pre设置为尾节点
			lastNode = node.prev;
			// 4.2 把lastNode的next设置为空
			lastNode.next = null;
		}
		// 5.执行到此处,证明node既不是首节点,也不是尾节点
		else {
			// 5.1 获取node节点的上一个节点和下一个节点
			Node prevNode = node.prev;
			Node nextNode = node.next;
			// 5.2.更改prevNode和nextNode的指向关系
			prevNode.next = nextNode;
			nextNode.prev = prevNode;
		}
		
		// 6.更新size实际存放元素的个数
		size--;
	}
	
	/**
	 * 根据索引获取对应的节点
	 * @param index 索引
	 * @return 返回索引对应的节点
	 */
	private Node node(int index) {
		// 2.1如果索引小于size/2,证明查找的元素在上半区
		if (index < size >> 1) {
			// 从前往后找
			Node node = firstNode;
			for (int i = 0; i < index; i++) {
				node = node.next;
			}
			// 返回索引对应的节点
			return node;
		}
		// 2.2如果索引大于等于size/2,证明查找的元素在下半区
		else {
			// 从后往前找
			Node node = lastNode;
			for (int i = size - 1; i > index; i--) {
				node = node.prev;
			}
			// 返回索引对应的节点
			return node;
		}
	}
	
	/**
	 * 链表的节点类(只能存储字符串)
	 */
	private class Node {
		/**
		 * 指向上一个节点
		 */
		private Node prev;
		/**
		 * 节点存储的数据
		 */
		private String data;
		/**
		 * 指向下一个节点
		 */
		private Node next;
		/**
		 * 无参构造方法
		 */
		public Node() {}
		/**
		 * 有参构造方法
		 * @param prev
		 * @param data
		 * @param next
		 */
		public Node(Node prev, String data, Node next) {
			this.prev = prev;
			this.data = data;
			this.next = next;
		}
	}
}

主方法,测试数据

public class Test01 {
	public static void main(String[] args) {
		LinkedList list = new LinkedList();
		// 添加元素
		list.add("aa");
		list.add("bb");
		list.add("cc");
		list.add("dd");
		list.add("ee");
		list.add("ff");
		// 删除元素
		list.remove(5);
		// 遍历元素
		for(int i = 0; i < list.size(); i++) {
			System.out.println(list.get(i));
		}
	}
}

输出:
aa
bb
cc
dd
ee

第四个功能 根据索引插入元素

附上一张内存图
Java_模拟实现 LinkedList(内存图分析+底层代码实现)_第4张图片

/**
 * add(String element) 添加数据
 * get(int index)      根据索引获取数据
 * remove(int index)   根据索引删除数据
 * add(String element, int index) 根据索引插入元素
 */
public class LinkedList {
	/**
	 * 永远指向链表的首节点
	 */
	private Node firstNode;
	/**
	 * 永远指向链表的尾节点
	 */
	private Node lastNode;
	/**
	 * 用于保存实际存放元素的个数
	 */
	private int size;
	/**
	 * 获取链表实际存放元素的个数
	 */
	public int size() {
		return size;
	}
	
	/**
	 * 添加数据(在链表末尾追加)
	 * @param element 添加的数据
	 */
	public void add(String element) {
		// 1.如果链表不存在的情况
		if(null == firstNode) { // 当firstNode不存在,则证明链表不存在
			// 1.1把添加的元素封装为节点对象
			Node node = new Node(null, element, null);
			// 1.2把node设置为链表的首节点和尾节点
			firstNode = node;
			lastNode = node;
			// 1.3更新size实际存放元素的个数
			size++;
		}
		// 2.链表已经存在的情况
		else { 
			// 2.1把添加的元素封装为节点对象
			Node node = new Node(null, element, null);
			// 2.2让lastNode的next指向新添加的节点
			lastNode.next = node;
			// 2.3让node的prev指向lastNode
			node.prev = lastNode;
			// 2.4跟新链表的尾节点
			lastNode = node;
			// 2.5更新size实际存放元素的个数
			size++;
		}
	}
	/**
	 * 根据索引获取元素值
	 * @param index 索引值
	 * @return 索引对应的元素值
	 */
	public String get(int index) {
		// 1.判断索引是否合法
		if(index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("索引越界异常:" + index);
		}
		// 2.根据索引获取节点
		Node node = node(index);
		// 3.返回节点对应数据
		return node.data;
	}
	/**
	 * 根据索引删除元素
	 * @param index 索引值
	 */
	public void remove(int index) {
		// 1.判断索引是否合法
		if(index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("索引越界异常:" + index);
		}
		// 2.获取index对应的节点对象
		Node node = node(index);
		// 3.判断node是否为首节点
		if(node == firstNode)
		{
			// 3.1把node的next节点设置为首节点
			firstNode = node.next;
			// 3.2把node的next节点设置为尾节点
			firstNode.prev = null;
		}
		// 4.判断node是否为尾节点
		else if (node == lastNode) {
			// 4.1 把node的pre设置为尾节点
			lastNode = node.prev;
			// 4.2 把lastNode的next设置为空
			lastNode.next = null;
		}
		// 5.执行到此处,证明node既不是首节点,也不是尾节点
		else {
			// 5.1 获取node节点的上一个节点和下一个节点
			Node prevNode = node.prev;
			Node nextNode = node.next;
			// 5.2.更改prevNode和nextNode的指向关系
			prevNode.next = nextNode;
			nextNode.prev = prevNode;
		}
		
		// 6.更新size实际存放元素的个数
		size--;
	}
	/**
	 * 根据索引插入元素
	 * @param element 插入的元素
	 * @param index 插入位置的索引
	 */
	public void add(String element, int index) {
		// 1.判断索引是否合法
		if(index < 0 || index > size) {
			throw new IndexOutOfBoundsException("索引越界异常:" + index);
		}
		// 2.判断插入的元素是否在链表的末尾
		if(index == size) {
			add(element);
		}
		// 3.判断插入的元素是否在链表的开头
		else if(index == 0){
			// 3.1把需要插入的元素包装为一个节点
			Node node = new Node(null, element, null);
			// 3.2更改firstNode和node之间的连线
			firstNode.prev = node;
			node.next = firstNode;
			// 3.3更新链表的首节点
			firstNode = node;
			// 3.4更新size实际存放元素的个数
			size++;
		}
		// 4.把index插入指定的位置(排除末尾和开头)
		else {
			// 4.1获取index对应的元素节点
			Node nextNode = node(index);
			// 4.2获取index对用的上一个元素节点
			Node prevNode = nextNode.prev;
			// 4.3把需要插入的元素包装为一个节点
			Node newNode = new Node(null, element, null);
			// 4.4更改newNode和prevNode之间的连线
			newNode.prev = prevNode;
			prevNode.next = newNode;
			// 4.5更改newNode和nextNode之间的连线
			nextNode.prev = newNode;
			newNode.next = nextNode;
			// 4.6更新size实际存放元素的个数
			size++;
		}
	}
	
	/**
	 * 根据索引获取对应的节点
	 * @param index 索引
	 * @return 返回索引对应的节点
	 */
	private Node node(int index) {
		// 2.1如果索引小于size/2,证明查找的元素在上半区
		if (index < size >> 1) {
			// 从前往后找
			Node node = firstNode;
			for (int i = 0; i < index; i++) {
				node = node.next;
			}
			// 返回索引对应的节点
			return node;
		}
		// 2.2如果索引大于等于size/2,证明查找的元素在下半区
		else {
			// 从后往前找
			Node node = lastNode;
			for (int i = size - 1; i > index; i--) {
				node = node.prev;
			}
			// 返回索引对应的节点
			return node;
		}
	}
	
	/**
	 * 链表的节点类(只能存储字符串)
	 */
	private class Node {
		/**
		 * 指向上一个节点
		 */
		private Node prev;
		/**
		 * 节点存储的数据
		 */
		private String data;
		/**
		 * 指向下一个节点
		 */
		private Node next;
		/**
		 * 无参构造方法
		 */
		public Node() {}
		/**
		 * 有参构造方法
		 * @param prev
		 * @param data
		 * @param next
		 */
		public Node(Node prev, String data, Node next) {
			this.prev = prev;
			this.data = data;
			this.next = next;
		}
	}
}

主方法,测试数据

public class Test01 {
	public static void main(String[] args) {
		LinkedList list = new LinkedList();
		// 添加元素
		list.add("aa");
		list.add("bb");
		list.add("cc");
		list.add("dd");
		list.add("ee");
		list.add("ff");
		// 删除元素
		 list.remove(5);
		// 插入元素
		// list.add("00", 0);
		// list.add("00", list.size());
		list.add("00", 2);
		// 遍历元素
		for(int i = 0; i < list.size(); i++) {
			System.out.println(list.get(i));
		}
	}
}

输出:
aa
bb
00
cc
dd
ee

大功告成,所有功能已经实现,可对着内存图理解记忆

你可能感兴趣的:(Java,笔记,心得)