1.我们知道数组是连续存储,这样一来会大大降低内存使用率,而如果采用链表的话,由于链表是采用分散存储,这样就大大提升了内存使用率,而且在数组中的元素叫做元素,而在链表中的元素叫做节点。而元素和节点的差距就在于——节点不仅包括节点内容,还包括了下一个节点的地址。如图所示
2.那么我想手搓一个链表的话,就直接看代码吧。
package com.example.main;
//首先创建一个链表类。
public class Node {
private int content;
private Node next;
public Node(int content, Node next) {
this.content = content;
this.next = next;
}
public int getContent() {
return content;
}
public void setContent(int content) {
this.content = content;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
以下就是主方法创建一个包含9,2,4三个数字的链表,并且返回链表中的第一个节点。
package com.example.main;
public class Main {
public static Node CreateLinkedList(int[] array){
//首先链表的根节点肯定是空的,所以先创建一个根节点。
Node root = null;
//然后就是从末尾元素依次开始创建Node节点。
for (int i = array.length - 1; i >= 0; i--) {
Node node = new Node(array[i],null);
//将链表连接。
node.setNext(root);
root = node;
}
return root;
}
public static void main(String[] args) {
int[] array = new int[]{9,2,4};
//打印第一个链表的节点。
System.out.println(CreateLinkedList(array).getContent());
//打印第二个,可以以此类推。
System.out.println(CreateLinkedList(array).getNext().getContent());
}
}
3.我们创建了链表后呢,就该实现链表的一些基本功能了,最常见的肯定就是读取和查找功能了。直接看代码吧:
package com.example.main;
public class Main {
Node root = null;
int size = 0;
public Main(int[] array){
this.root = CreateLinkedList(array);
this.size = array.length;
}
public static Node CreateLinkedList(int[] array){
//首先链表的根节点肯定是空的,所以先创建一个根节点。
Node root = null;
//然后就是从末尾元素依次开始创建Node节点。
for (int i = array.length - 1; i >= 0; i--) {
Node node = new Node(array[i],null);
//将链表连接。
node.setNext(root);
root = node;
}
return root;
}
//获取长度。
public int getSize(){
return this.size;
}
//获取某个索引节点内容
public int getContent(int index){
Node node = this.root;
for (int i = 0; i < index; i++) {
node = node.getNext();
}
return node.getContent();
}
//查找某个值的索引值,默认不存在为-1。
public int getIndex(int content){
if (this.root == null){
return -1;
}
Node node = this.root;
int index = 0;
while (index <= this.size - 1){
if (node.getContent() == content){
return index;
}
node = node.getNext();
index++;
}
return -1;
}
public static void main(String[] args) {
int[] array = {9, 2, 4};
Main linkedList = new Main(array);
System.out.println(linkedList.getIndex(2));
System.out.println(linkedList.getIndex(5));
System.out.println(linkedList.getContent(2));
}
}
4.进阶还要有一些插入和删除功能,废话不多说,咱们直接看代码:
package com.example.main;
public class Main {
private Node root = null;
int size = 0;
public Main(){}
public Main(int[] array){
this.root = CreateLinkedList(array);
this.size = array.length;
}
public static Node CreateLinkedList(int[] array){
//首先链表的根节点肯定是空的,所以先创建一个根节点。
Node root = null;
//然后就是从末尾元素依次开始创建Node节点。
for (int i = array.length - 1; i >= 0; i--) {
Node node = new Node(array[i],null);
//将链表连接。
node.setNext(root);
root = node;
}
return root;
}
//获取长度。
public int getSize(){
return this.size;
}
//获取某个索引节点内容
public int getContent(int index){
Node node = this.root;
for (int i = 0; i < index; i++) {
node = node.getNext();
}
return node.getContent();
}
//查找某个值的索引值,默认不存在为-1。
public int getIndex(int content){
if (this.root == null){
return -1;
}
Node node = this.root;
int index = 0;
while (index <= this.size - 1){
if (node.getContent() == content){
return index;
}
node = node.getNext();
index++;
}
return -1;
}
public boolean addLast(int value){
return add(value,this.size - 1);
}
//索引值为-1则代表在头部插入节点。
public boolean addFirst(int value){
return add(value,-1);
}
/*如果索引值为-1,则表示在头部添加节点。
如果索引值为0,则代表在第一位元节点后面添加。
而如果为n,则代表在第n位节点后面添加。
*/
public boolean add(int value,int index){
if (index < -1 || index > this.size - 1){
return false;
} else if (index == -1) {
if (this.root == null){
this.root = new Node(value,null);
} else {
this.root = new Node(value,this.root);
}
} else {
Node node = this.root;
while (index > 0){
if (node.getNext() == null) {
return false;
}
node = node.getNext();
index--;
}
if (node.getNext() == null){
node.setNext(new Node(value,null));
} else {
Node newNode = new Node(value,node.getNext());
node.setNext(newNode);
}
}
this.size++;
return true;
}
//删除最后一个元素
public boolean removeLast(){
return remove(this.size - 1);
}
//删除第一个元素
public boolean removeFirst(){
return remove(0);
}
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
/*
index值为0时,则代表要删除第1个链表节点,
index值为n时,则代表要删除第n+1个链表节点。
*/
public boolean remove(int index){
if (index < 0 || index > this.size - 1 || this.root == null){
return false;
} else if (index == 0) {
Node node = this.root;
this.root = node.getNext();
node.setNext(null);
} else {
Node node = this.root;
while (index > 1){
if (node.getNext() == null){
return false;
}
node = node.getNext();
index--;
}
if (node.getNext() == null){
return false;
}
Node newNode = node.getNext();
node.setNext(node.getNext().getNext());
newNode.setNext(null);
}
this.size--;
return true;
}
//此处重写toString方法就是为了能够在控制台打印出链表节点内容。
@Override
public String toString(){
if (this.root == null) {
return "";
}
Node node = this.root;
StringBuilder builder = new StringBuilder();
while (node != null) {
builder.append(node.getContent()).append(" ");
node = node.getNext();
}
return builder.toString();
}
public static void main(String[] args) {
Main main = new Main();
boolean result = main.addFirst(9);
boolean bag = main.addLast(2);
boolean re = main.remove(1);
System.out.println(main.toString());
}
}
这里有个知识点大家必须知道,就是我在里面重写了toString方法首先String类是final修饰的终极类,因此它不是基本数据类型,所以不能够修改,但是StringBuffer类和StringBuilder类可以修改或添加删除元素,但是前者是线程安全的,后者不是。因此,如果想要多线程安全的修改字符串,那么应该使用前者,而如果要单线程高效率的修改则需要调用后者。
这里的add()方法和remove()方法一些Java入门小白可能看的有点懵,我并没有修改this.root,那么这个插入还有用吗?其实,这里运用了JVM的栈堆知识,因为this.root是Node类型,即是一个对象,因此它的地址存放在栈里面,而内容存放在堆里面。我们创建一个Node类型的变量node,它和this.root指向的是同一个在堆里面的对象,即共用同一个对象。
5.那么编写完,我们需要知道我们写的是单向链表,这种是最简单的,但是jdk里面就已经提供了现成的链表API,那就是LinkedList类,它和ArrayList类十分相似,前者的增加和删除效率强于后者,但是查找和修改效率要弱于后者。另外,LinkedList是双向链表。直接看代码吧:
6.经过了上面的编写,大家应该能看出链表和数组之间的区别了,直接看图吧:
从上面的图可以明显地看出,只要是在
频繁插入和删除的情况下,链表明显更优于数组。